00001 #!/usr/bin/perl -w
00002 #
00003 # Renames mythtv recordings to more human-readable filenames.
00004 # See --help for instructions.
00005 #
00006 # Automatically detects database settings from mysql.txt, and loads
00007 # the mythtv recording directory from the database (code from nuvexport).
00008 #
00009 # @url $URL$
00010 # @date $Date: 2008-02-17 00:02:51 -0500 (Sun, 17 Feb 2008) $
00011 # @version $Revision: 16112 $
00012 # @author $Author: xris $
00013 # @license GPL
00014 #
00015
00016 # Includes
00017 use DBI;
00018 use Getopt::Long;
00019 use File::Path;
00020 use File::Basename;
00021 use File::Find;
00022 use MythTV;
00023
00024 # Some variables we'll use here
00025 our ($dest, $format, $usage, $underscores, $live);
00026 our ($dformat, $dseparator, $dreplacement, $separator, $replacement);
00027 our ($db_host, $db_user, $db_name, $db_pass, $video_dir, $verbose);
00028 our ($hostname, $dbh, $sh, $q, $count, $base_dir);
00029
00030 # Default filename format
00031 $dformat = '%T %- %Y-%m-%d, %g-%i %A %- %S';
00032 # Default separator character
00033 $dseparator = '-';
00034 # Default replacement character
00035 $dreplacement = '-';
00036
00037 # Provide default values for GetOptions
00038 $format = $dformat;
00039 $separator = $dseparator;
00040 $replacement = $dreplacement;
00041
00042 # Load the cli options
00043 GetOptions('link|dest|destination|path:s' => \$dest,
00044 'format=s' => \$format,
00045 'live' => \$live,
00046 'separator=s' => \$separator,
00047 'replacement=s' => \$replacement,
00048 'usage|help|h' => \$usage,
00049 'underscores' => \$underscores,
00050 'verbose' => \$verbose
00051 );
00052
00053 # Print usage
00054 if ($usage) {
00055 print <<EOF;
00056 $0 usage:
00057
00058 options:
00059
00060 --link [destination directory]
00061
00062 If you would like mythrename.pl to work like the old mythlink.pl, specify
00063 --link and an optional pathname. If no pathname is given, links will be
00064 created in the show_names directory inside of the current mythtv data
00065 directory on this machine. eg:
00066
00067 /var/video/show_names/
00068
00069 WARNING: ALL symlinks within the destination directory and its
00070 subdirectories (recursive) will be removed when using the --link option.
00071
00072 --live
00073
00074 Include live tv recordings, affects both linking and renaming.
00075
00076 default: do not link/rename live tv recordings
00077
00078 --format
00079
00080 default: $dformat
00081
00082 \%T = Title (show name)
00083 \%S = Subtitle (episode name)
00084 \%R = Description
00085 \%C = Category
00086 \%U = RecGroup
00087 \%hn = Hostname of the machine where the file resides
00088 \%c = Channel: MythTV chanid
00089 \%cn = Channel: channum
00090 \%cc = Channel: callsign
00091 \%cN = Channel: channel name
00092 \%y = Recording start time: year, 2 digits
00093 \%Y = Recording start time: year, 4 digits
00094 \%n = Recording start time: month
00095 \%m = Recording start time: month, leading zero
00096 \%j = Recording start time: day of month
00097 \%d = Recording start time: day of month, leading zero
00098 \%g = Recording start time: 12-hour hour
00099 \%G = Recording start time: 24-hour hour
00100 \%h = Recording start time: 12-hour hour, with leading zero
00101 \%H = Recording start time: 24-hour hour, with leading zero
00102 \%i = Recording start time: minutes
00103 \%s = Recording start time: seconds
00104 \%a = Recording start time: am/pm
00105 \%A = Recording start time: AM/PM
00106 \%ey = Recording end time: year, 2 digits
00107 \%eY = Recording end time: year, 4 digits
00108 \%en = Recording end time: month
00109 \%em = Recording end time: month, leading zero
00110 \%ej = Recording end time: day of month
00111 \%ed = Recording end time: day of month, leading zero
00112 \%eg = Recording end time: 12-hour hour
00113 \%eG = Recording end time: 24-hour hour
00114 \%eh = Recording end time: 12-hour hour, with leading zero
00115 \%eH = Recording end time: 24-hour hour, with leading zero
00116 \%ei = Recording end time: minutes
00117 \%es = Recording end time: seconds
00118 \%ea = Recording end time: am/pm
00119 \%eA = Recording end time: AM/PM
00120 \%py = Program start time: year, 2 digits
00121 \%pY = Program start time: year, 4 digits
00122 \%pn = Program start time: month
00123 \%pm = Program start time: month, leading zero
00124 \%pj = Program start time: day of month
00125 \%pd = Program start time: day of month, leading zero
00126 \%pg = Program start time: 12-hour hour
00127 \%pG = Program start time: 24-hour hour
00128 \%ph = Program start time: 12-hour hour, with leading zero
00129 \%pH = Program start time: 24-hour hour, with leading zero
00130 \%pi = Program start time: minutes
00131 \%ps = Program start time: seconds
00132 \%pa = Program start time: am/pm
00133 \%pA = Program start time: AM/PM
00134 \%pey = Program end time: year, 2 digits
00135 \%peY = Program end time: year, 4 digits
00136 \%pen = Program end time: month
00137 \%pem = Program end time: month, leading zero
00138 \%pej = Program end time: day of month
00139 \%ped = Program end time: day of month, leading zero
00140 \%peg = Program end time: 12-hour hour
00141 \%peG = Program end time: 24-hour hour
00142 \%peh = Program end time: 12-hour hour, with leading zero
00143 \%peH = Program end time: 24-hour hour, with leading zero
00144 \%pei = Program end time: minutes
00145 \%pes = Program end time: seconds
00146 \%pea = Program end time: am/pm
00147 \%peA = Program end time: AM/PM
00148 \%oy = Original Airdate: year, 2 digits
00149 \%oY = Original Airdate: year, 4 digits
00150 \%on = Original Airdate: month
00151 \%om = Original Airdate: month, leading zero
00152 \%oj = Original Airdate: day of month
00153 \%od = Original Airdate: day of month, leading zero
00154 \%% = a literal % character
00155
00156 * The program start time is the time from the listings data and is not
00157 affected by when the recording started. Therefore, using program start
00158 (or end) times may result in duplicate names. In that case, the script
00159 will append a "counter" value to the name.
00160
00161 * A suffix of .mpg or .nuv will be added where appropriate.
00162
00163 * To separate links into subdirectories, include the / format specifier
00164 between the appropriate fields. For example, "\%T/\%S" would create
00165 a directory for each title containing links for each recording named
00166 by subtitle. You may use any number of subdirectories in your format
00167 specifier. If used without the --link option, "/" will be replaced
00168 with the "\%-" separator character.
00169
00170 --separator
00171
00172 The string used to separate sections of the link name. Specifying the
00173 separator allows trailing separators to be removed from the link name and
00174 multiple separators caused by missing data to be consolidated. Indicate the
00175 separator character in the format string using either a literal character
00176 or the \%- specifier.
00177
00178 default: '$dseparator'
00179
00180 --replacement
00181
00182 Characters in the link name which are not legal on some filesystems will
00183 be replaced with the given character
00184
00185 illegal characters: \\ : * ? < > | "
00186
00187 default: '$dreplacement'
00188
00189 --underscores
00190
00191 Replace whitespace in filenames with underscore characters.
00192
00193 default: No underscores
00194
00195 --verbose
00196
00197 Print debug info.
00198
00199 default: No info printed to console
00200
00201 --help
00202
00203 Show this help text.
00204
00205 EOF
00206 exit;
00207 }
00208
00209 # Check the separator and replacement characters for illegal characters
00210 if ($separator =~ /(?:[\/\\:*?<>|"])/) {
00211 die "The separator cannot contain any of the following characters: /\\:*?<>|\"\n";
00212 }
00213 elsif ($replacement =~ /(?:[\/\\:*?<>|"])/) {
00214 die "The replacement cannot contain any of the following characters: /\\:*?<>|\"\n";
00215 }
00216
00217 # Escape where necessary
00218 our $safe_sep = $separator;
00219 $safe_sep =~ s/([^\w\s])/\\$1/sg;
00220 our $safe_rep = $replacement;
00221 $safe_rep =~ s/([^\w\s])/\\$1/sg;
00222
00223 # Get the hostname of this machine
00224 $hostname = `hostname`;
00225 chomp($hostname);
00226
00227 # Connect to mythbackend
00228 my $Myth = new MythTV();
00229
00230 # Connect to the database
00231 $dbh = $Myth->{'dbh'};
00232 END {
00233 $sh->finish if ($sh);
00234 }
00235
00236 my $sgroup = new MythTV::StorageGroup();
00237
00238 # Get our base location
00239 $base_dir = $sgroup->FindRecordingDir('show_names');
00240 if ($base_dir eq '') {
00241 $base_dir = $sgroup->GetFirstStorageDir();
00242 }
00243
00244 # Link destination
00245 if (defined($dest)) {
00246 # Double-check the destination
00247 $dest ||= "$base_dir/show_names";
00248 # Alert the user
00249 vprint("Link destination directory: $dest");
00250 # Create nonexistent paths
00251 unless (-e $dest) {
00252 mkpath($dest, 0, 0755) or die "Failed to create $dest: $!\n";
00253 }
00254 # Bad path
00255 die "$dest is not a directory.\n" unless (-d $dest);
00256 # Delete any old links
00257 find sub { if (-l $_) {
00258 unlink $_ or die "Couldn't remove old symlink $_: $!\n";
00259 }
00260 }, $dest;
00261 # Delete empty directories (should this be an option?)
00262 # Let this fail silently for non-empty directories
00263 finddepth sub { rmdir $_; }, $dest;
00264 }
00265
00266 # Only if we're renaming files
00267 unless ($dest) {
00268 $q = 'UPDATE recorded SET basename=? WHERE chanid=? AND starttime=FROM_UNIXTIME(?)';
00269 $sh = $dbh->prepare($q);
00270 }
00271
00272 # Create symlinks for the files on this machine
00273 my %rows = $Myth->backend_rows('QUERY_RECORDINGS Delete');
00274 foreach my $row (@{$rows{'rows'}}) {
00275 my $show = new MythTV::Recording(@$row);
00276 # Skip LiveTV recordings?
00277 next unless (defined($live) || $show->{'recgroup'} ne 'LiveTV');
00278 # File doesn't exist locally
00279 next unless (-e $show->{'local_path'});
00280 # Format the name
00281 my $name = $show->format_name($format,$separator,$replacement,$dest,$underscores);
00282 # Get a shell-safe version of the filename (yes, I know it's not needed in this case, but I'm anal about such things)
00283 my $safe_file = $show->{'local_path'};
00284 $safe_file =~ s/'/'\\''/sg;
00285 $safe_file = "'$safe_file'";
00286 # Figure out the suffix
00287 my ($suffix) = ($show->{'basename'} =~ /(\.\w+)$/);
00288 # Link destination
00289 if ($dest) {
00290 # Check for duplicates
00291 if (-e "$dest/$name$suffix") {
00292 $count = 2;
00293 while (-e "$dest/$name.$count$suffix") {
00294 $count++;
00295 }
00296 $name .= ".$count";
00297 }
00298 $name .= $suffix;
00299 # Create the link
00300 my $directory = dirname("$dest/$name");
00301 unless (-e $directory) {
00302 mkpath($directory, 0, 0755)
00303 or die "Failed to create $directory: $!\n";
00304 }
00305 symlink $show->{'local_path'}, "$dest/$name"
00306 or die "Can't create symlink $dest/$name: $!\n";
00307 vprint("$dest/$name");
00308 }
00309 # Rename the file, but only if it's a real file
00310 elsif (-f $show->{'local_path'}) {
00311 if ($show->{'basename'} ne $name.$suffix) {
00312 # Check for duplicates
00313 $video_dir = $sgroup->FindRecordingDir($show->{'basename'});
00314 if (-e "$video_dir/$name$suffix") {
00315 $count = 2;
00316 while (-e "$video_dir/$name.$count$suffix") {
00317 $count++;
00318 }
00319 $name .= ".$count";
00320 }
00321 $name .= $suffix;
00322 # Update the database
00323 my $rows = $sh->execute($name, $show->{'chanid'}, $show->{'recstartts'});
00324 die "Couldn't update basename in database for ".$show->{'basename'}.": ($q)\n" unless ($rows == 1);
00325 my $ret = rename $show->{'local_path'}, "$video_dir/$name";
00326 # Rename failed -- Move the database back to how it was (man, do I miss transactions)
00327 if (!$ret) {
00328 $rows = $sh->execute($show->{'basename'}, $show->{'chanid'}, $show->{'recstartts'});
00329 die "Couldn't restore original basename in database for ".$show->{'basename'}.": ($q)\n" unless ($rows == 1);
00330 }
00331 vprint($show->{'basename'}."\t-> $name");
00332 # Rename previews
00333 opendir DIR, $video_dir;
00334 foreach my $thumb (grep /\.png$/, readdir DIR) {
00335 next unless ($thumb =~ /^$show->{'basename'}((?:\.\d+)?(?:\.\d+x\d+(?:x\d+)?)?\.png)$/);
00336 my $dim = $1;
00337 $ret = rename "$video_dir/$thumb", "$video_dir/$name$dim.png";
00338 # If the rename fails, try to delete the preview from the
00339 # cache (it will automatically be re-created with the
00340 # proper name, when necessary)
00341 if (!$ret) {
00342 unlink "$video_dir/$thumb"
00343 or vprint("Unable to rename preview image: '$video_dir/$thumb'.");
00344 }
00345 }
00346 closedir DIR;
00347 }
00348 }
00349 }
00350
00351 $sh->finish if ($sh);
00352
00353 # Print the message, but only if verbosity is enabled
00354 sub vprint {
00355 return unless (defined($verbose));
00356 print join("\n", @_), "\n";
00357 }
00358