|
MythTV
0.26-pre
|
00001 #!/usr/bin/perl -w 00002 # 00003 # Creates symlinks to mythtv recordings using 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$ 00011 # @version $Revision$ 00012 # @author $Author$ 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, $rename, $maxlength); 00026 our ($chanid, $starttime, $filename); 00027 our ($dformat, $dseparator, $dreplacement, $separator, $replacement); 00028 our ($db_host, $db_user, $db_name, $db_pass, $video_dir, $verbose); 00029 our ($hostname, $dbh, $sh, $q, $count, $base_dir); 00030 00031 # Default filename format 00032 $dformat = '%T %- %Y-%m-%d, %g-%i %A %- %S'; 00033 # Default separator character 00034 $dseparator = '-'; 00035 # Default replacement character 00036 $dreplacement = '-'; 00037 00038 # Provide default values for GetOptions 00039 $format = $dformat; 00040 $separator = $dseparator; 00041 $replacement = $dreplacement; 00042 $maxlength = -1; 00043 00044 # Load the cli options 00045 GetOptions('link|destination|path:s' => \$dest, 00046 'chanid=s' => \$chanid, 00047 'starttime=s' => \$starttime, 00048 'filename=s' => \$filename, 00049 'format=s' => \$format, 00050 'live' => \$live, 00051 'separator=s' => \$separator, 00052 'replacement=s' => \$replacement, 00053 'rename' => \$rename, 00054 'maxlength=i' => \$maxlength, 00055 'usage|help' => \$usage, 00056 'underscores' => \$underscores, 00057 'verbose' => \$verbose 00058 ); 00059 00060 # Print usage 00061 if ($usage) { 00062 print <<EOF; 00063 $0 usage: 00064 00065 options: 00066 00067 --link [destination directory] 00068 --destination [destination directory] 00069 --path [destination directory] 00070 00071 Specify the directory for the links. If no pathname is given, links will 00072 be created in the show_names directory inside of the current mythtv data 00073 directory on this machine. eg: 00074 00075 /var/video/show_names/ 00076 00077 WARNING: ALL symlinks within the destination directory and its 00078 subdirectories (recursive) will be removed. 00079 00080 --chanid chanid 00081 00082 Create a link only for the specified recording file. Use with --starttime 00083 to specify a recording. This argument may be used with the event-driven 00084 notification system's "Recording started" event or in a post-recording 00085 user job. 00086 00087 --starttime starttime 00088 00089 Create a link only for the specified recording file. Use with --chanid 00090 to specify a recording. This argument may be used with the event-driven 00091 notification system's "Recording started" event or in a post-recording 00092 user job. 00093 00094 --filename absolute_filename 00095 00096 Create a link only for the specified recording file. This argument may be 00097 used with the event-driven notification system's "Recording started" event 00098 or in a post-recording user job. 00099 00100 --live 00101 00102 Include live tv recordings. 00103 00104 default: do not link live tv recordings 00105 00106 --format 00107 00108 default: $dformat 00109 00110 \%T = Title (show name) 00111 \%S = Subtitle (episode name) 00112 \%R = Description 00113 \%C = Category 00114 \%U = RecGroup 00115 \%hn = Hostname of the machine where the file resides 00116 \%c = Channel: MythTV chanid 00117 \%cn = Channel: channum 00118 \%cc = Channel: callsign 00119 \%cN = Channel: channel name 00120 \%y = Recording start time: year, 2 digits 00121 \%Y = Recording start time: year, 4 digits 00122 \%n = Recording start time: month 00123 \%m = Recording start time: month, leading zero 00124 \%j = Recording start time: day of month 00125 \%d = Recording start time: day of month, leading zero 00126 \%g = Recording start time: 12-hour hour 00127 \%G = Recording start time: 24-hour hour 00128 \%h = Recording start time: 12-hour hour, with leading zero 00129 \%H = Recording start time: 24-hour hour, with leading zero 00130 \%i = Recording start time: minutes 00131 \%s = Recording start time: seconds 00132 \%a = Recording start time: am/pm 00133 \%A = Recording start time: AM/PM 00134 \%ey = Recording end time: year, 2 digits 00135 \%eY = Recording end time: year, 4 digits 00136 \%en = Recording end time: month 00137 \%em = Recording end time: month, leading zero 00138 \%ej = Recording end time: day of month 00139 \%ed = Recording end time: day of month, leading zero 00140 \%eg = Recording end time: 12-hour hour 00141 \%eG = Recording end time: 24-hour hour 00142 \%eh = Recording end time: 12-hour hour, with leading zero 00143 \%eH = Recording end time: 24-hour hour, with leading zero 00144 \%ei = Recording end time: minutes 00145 \%es = Recording end time: seconds 00146 \%ea = Recording end time: am/pm 00147 \%eA = Recording end time: AM/PM 00148 \%py = Program start time: year, 2 digits 00149 \%pY = Program start time: year, 4 digits 00150 \%pn = Program start time: month 00151 \%pm = Program start time: month, leading zero 00152 \%pj = Program start time: day of month 00153 \%pd = Program start time: day of month, leading zero 00154 \%pg = Program start time: 12-hour hour 00155 \%pG = Program start time: 24-hour hour 00156 \%ph = Program start time: 12-hour hour, with leading zero 00157 \%pH = Program start time: 24-hour hour, with leading zero 00158 \%pi = Program start time: minutes 00159 \%ps = Program start time: seconds 00160 \%pa = Program start time: am/pm 00161 \%pA = Program start time: AM/PM 00162 \%pey = Program end time: year, 2 digits 00163 \%peY = Program end time: year, 4 digits 00164 \%pen = Program end time: month 00165 \%pem = Program end time: month, leading zero 00166 \%pej = Program end time: day of month 00167 \%ped = Program end time: day of month, leading zero 00168 \%peg = Program end time: 12-hour hour 00169 \%peG = Program end time: 24-hour hour 00170 \%peh = Program end time: 12-hour hour, with leading zero 00171 \%peH = Program end time: 24-hour hour, with leading zero 00172 \%pei = Program end time: minutes 00173 \%pes = Program end time: seconds 00174 \%pea = Program end time: am/pm 00175 \%peA = Program end time: AM/PM 00176 \%oy = Original Airdate: year, 2 digits 00177 \%oY = Original Airdate: year, 4 digits 00178 \%on = Original Airdate: month 00179 \%om = Original Airdate: month, leading zero 00180 \%oj = Original Airdate: day of month 00181 \%od = Original Airdate: day of month, leading zero 00182 \%% = a literal % character 00183 00184 * The program start time is the time from the listings data and is not 00185 affected by when the recording started. Therefore, using program start 00186 (or end) times may result in duplicate names. In that case, the script 00187 will append a "counter" value to the name. 00188 00189 * A suffix of .mpg or .nuv will be added where appropriate. 00190 00191 * To separate links into subdirectories, include the / format specifier 00192 between the appropriate fields. For example, "\%T/\%S" would create 00193 a directory for each title containing links for each recording named 00194 by subtitle. You may use any number of subdirectories in your format 00195 specifier. 00196 00197 --separator 00198 00199 The string used to separate sections of the link name. Specifying the 00200 separator allows trailing separators to be removed from the link name and 00201 multiple separators caused by missing data to be consolidated. Indicate the 00202 separator character in the format string using either a literal character 00203 or the \%- specifier. 00204 00205 default: '$dseparator' 00206 00207 --replacement 00208 00209 Characters in the link name which are not legal on some filesystems will 00210 be replaced with the given character 00211 00212 illegal characters: \\ : * ? < > | " 00213 00214 default: '$dreplacement' 00215 00216 --underscores 00217 00218 Replace whitespace in filenames with underscore characters. 00219 00220 default: No underscores 00221 00222 --rename 00223 00224 Rename the recording files back to their default names. If you had 00225 previously used mythrename.pl to rename files (rather than creating links 00226 to the files), use this option to restore the file names to their default 00227 format. 00228 00229 Renaming the recording files is no longer supported. Instead, use 00230 http://www.mythtv.org/wiki/mythfs.py to create a FUSE file system that 00231 represents recordings using human-readable file names or use mythlink.pl to 00232 create links with human-readable names to the recording files. 00233 00234 --maxlength length 00235 00236 Ensure the link name is length or fewer characters. If the link name is 00237 longer than length, truncate the name. Zero or any negative value for 00238 length disables length checking. 00239 00240 Note that this option does not take into account the path length, so on a 00241 file system used by applications with small path limitations (such as 00242 Windows Explorer and Windows Command Prompt), you should specify a length 00243 that takes into account characters used by the path to the dest directory. 00244 00245 default: Unlimited 00246 00247 --verbose 00248 00249 Print debug info. 00250 00251 default: No info printed to console 00252 00253 --help 00254 --usage 00255 00256 Show this help text. 00257 00258 EOF 00259 exit; 00260 } 00261 00262 # Ensure --chanid and --starttime were specified together, if at all 00263 if ((defined($chanid) or defined($starttime)) and 00264 !(defined($chanid) and defined($starttime))) { 00265 die "The arguments --chanid and --starttime must be used together.\n"; 00266 } 00267 00268 # Ensure --maxlength specifies a reasonable value (though filenames may 00269 # still be useless at such short lengths) 00270 if ($maxlength > 0 and $maxlength < 19) { 00271 die "The --maxlength must be 20 or higher.\n"; 00272 } 00273 00274 # Check the separator and replacement characters for illegal characters 00275 if ($separator =~ /(?:[\/\\:*?<>|"])/) { 00276 die "The separator cannot contain any of the following characters: /\\:*?<>|\"\n"; 00277 } 00278 elsif ($replacement =~ /(?:[\/\\:*?<>|"])/) { 00279 die "The replacement cannot contain any of the following characters: /\\:*?<>|\"\n"; 00280 } 00281 00282 # Escape where necessary 00283 our $safe_sep = $separator; 00284 $safe_sep =~ s/([^\w\s])/\\$1/sg; 00285 our $safe_rep = $replacement; 00286 $safe_rep =~ s/([^\w\s])/\\$1/sg; 00287 00288 # Get the hostname of this machine 00289 $hostname = `hostname`; 00290 chomp($hostname); 00291 00292 # Connect to mythbackend 00293 my $Myth = new MythTV(); 00294 00295 # Connect to the database 00296 $dbh = $Myth->{'dbh'}; 00297 END { 00298 $sh->finish if ($sh); 00299 } 00300 00301 my $sgroup = new MythTV::StorageGroup(); 00302 00303 # Only if we're renaming files back to "default" names 00304 if ($rename) { 00305 do_rename(); 00306 } 00307 00308 # Get our base location 00309 $base_dir = $sgroup->FindRecordingDir('show_names'); 00310 if ($base_dir eq '') { 00311 $base_dir = $sgroup->GetFirstStorageDir(); 00312 } 00313 00314 # Link destination 00315 # Double-check the destination 00316 $dest ||= "$base_dir/show_names"; 00317 # Alert the user 00318 vprint("Link destination directory: $dest"); 00319 # Create nonexistent paths 00320 unless (-e $dest) { 00321 mkpath($dest, 0, 0775) or die "Failed to create $dest: $!\n"; 00322 } 00323 # Bad path 00324 die "$dest is not a directory.\n" unless (-d $dest); 00325 # Delete old links/directories unless linking only one recording 00326 if (!defined($filename) and !defined($chanid)) { 00327 # Delete any old links 00328 find sub { if (-l $_) { 00329 unlink $_ or die "Couldn't remove old symlink $_: $!\n"; 00330 } 00331 }, $dest; 00332 # Delete empty directories (should this be an option?) 00333 # Let this fail silently for non-empty directories 00334 finddepth sub { rmdir $_; }, $dest; 00335 } 00336 00337 # Create symlinks for the files on this machine 00338 my %rows = (); 00339 if (defined($chanid)) { 00340 %rows = $Myth->backend_rows('QUERY_RECORDING TIMESLOT '. 00341 "$chanid $starttime"); 00342 00343 } 00344 else { 00345 %rows = $Myth->backend_rows('QUERY_RECORDINGS Descending'); 00346 } 00347 foreach my $row (@{$rows{'rows'}}) { 00348 my $show = new MythTV::Recording(@$row); 00349 # Skip LiveTV recordings? 00350 next unless (defined($live) || $show->{'recgroup'} ne 'LiveTV'); 00351 # File doesn't exist locally 00352 next unless (-e $show->{'local_path'}); 00353 # Check if this is the file to link if only linking one file 00354 if (defined($filename)) { 00355 next unless (($show->{'basename'} eq $filename) or 00356 ($show->{'local_path'} eq $filename)); 00357 } 00358 elsif (defined($chanid)) { 00359 next unless ($show->{'chanid'} eq $chanid); 00360 my $recstartts = unix_to_myth_time($show->{'recstartts'}); 00361 # Check starttime in MythTV time format (yyyy-MM-ddThh:mm:ss) 00362 if ($recstartts ne $starttime) { 00363 # Check starttime in ISO time format (yyyy-MM-dd hh:mm:ss) 00364 $recstartts =~ tr/T/ /; 00365 if ($recstartts ne $starttime) { 00366 # Check starttime in job queue time format (yyyyMMddhhmmss) 00367 $recstartts =~ s/[\- :]//g; 00368 next unless ($recstartts eq $starttime); 00369 } 00370 } 00371 } 00372 # Format the name 00373 my $name = $show->format_name($format,$separator,$replacement,$dest,$underscores); 00374 # 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) 00375 my $safe_file = $show->{'local_path'}; 00376 $safe_file =~ s/'/'\\''/sg; 00377 $safe_file = "'$safe_file'"; 00378 # Figure out the suffix 00379 my ($suffix) = ($show->{'basename'} =~ /(\.\w+)$/); 00380 # Check the link name's length 00381 $name = cut_down_name($name, $suffix); 00382 # Link destination 00383 # Check for duplicates 00384 if (($name) and -e "$dest/$name$suffix") { 00385 if ((!defined($filename) and !defined($chanid)) or 00386 (! -l "$dest/$name$suffix")) { 00387 $count = 2; 00388 $name = cut_down_name($name, ".$count$suffix"); 00389 while (($name) and -e "$dest/$name.$count$suffix") { 00390 $count++; 00391 $name = cut_down_name($name, ".$count$suffix"); 00392 } 00393 $name .= ".$count" if (($name)); 00394 } else { 00395 unlink "$dest/$name$suffix" or die "Couldn't remove ". 00396 "old symlink $dest/$name$suffix: $!\n"; 00397 } 00398 } 00399 if (!($name)) { 00400 vprint("Unable to represent recording; maxlength too small."); 00401 next; 00402 } 00403 $name .= $suffix; 00404 # Create the link 00405 my $directory = dirname("$dest/$name"); 00406 unless (-e $directory) { 00407 mkpath($directory, 0, 0775) 00408 or die "Failed to create $directory: $!\n"; 00409 } 00410 symlink $show->{'local_path'}, "$dest/$name" 00411 or die "Can't create symlink $dest/$name: $!\n"; 00412 vprint("$dest/$name"); 00413 } 00414 00415 # Check the length of the link name 00416 sub cut_down_name { 00417 my $name = shift; 00418 my $suffix = shift; 00419 if ($maxlength > 0) { 00420 my $charsavailable = $maxlength - length($suffix); 00421 if ($charsavailable > 0) { 00422 $name = substr($name, 0, $charsavailable); 00423 } 00424 else { 00425 $name = ''; 00426 } 00427 } 00428 return $name; 00429 } 00430 00431 # Print the message, but only if verbosity is enabled 00432 sub vprint { 00433 return unless (defined($verbose)); 00434 print join("\n", @_), "\n"; 00435 } 00436 00437 # Rename the file back to default format 00438 sub do_rename { 00439 $q = 'UPDATE recorded SET basename=? WHERE chanid=? AND starttime=FROM_UNIXTIME(?)'; 00440 $sh = $dbh->prepare($q); 00441 my %rows = $Myth->backend_rows('QUERY_RECORDINGS Descending'); 00442 foreach my $row (@{$rows{'rows'}}) { 00443 my $show = new MythTV::Recording(@$row); 00444 # File doesn't exist locally 00445 next unless (-e $show->{'local_path'}); 00446 # Format the name 00447 my $name = $show->format_name('%c_%Y%m%d%H%i%s'); 00448 # 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) 00449 my $safe_file = $show->{'local_path'}; 00450 $safe_file =~ s/'/'\\''/sg; 00451 $safe_file = "'$safe_file'"; 00452 # Figure out the suffix 00453 my ($suffix) = ($show->{'basename'} =~ /(\.\w+)$/); 00454 # Rename the file, but only if it's a real file 00455 if ($show->{'basename'} ne $name.$suffix) { 00456 # Check for duplicates 00457 $video_dir = $sgroup->FindRecordingDir($show->{'basename'}); 00458 if (-e "$video_dir/$name$suffix") { 00459 $count = 2; 00460 while (-e "$video_dir/$name.$count$suffix") { 00461 $count++; 00462 } 00463 $name .= ".$count"; 00464 } 00465 $name .= $suffix; 00466 # Update the database 00467 my $rows = $sh->execute($name, $show->{'chanid'}, $show->{'recstartts'}); 00468 die "Couldn't update basename in database for ".$show->{'basename'}.": ($q)\n" unless ($rows == 1); 00469 my $ret = rename $show->{'local_path'}, "$video_dir/$name"; 00470 # Rename failed -- Move the database back to how it was (man, do I miss transactions) 00471 if (!$ret) { 00472 $rows = $sh->execute($show->{'basename'}, $show->{'chanid'}, $show->{'recstartts'}); 00473 die "Couldn't restore original basename in database for ".$show->{'basename'}.": ($q)\n" unless ($rows == 1); 00474 } 00475 vprint($show->{'basename'}."\t-> $name"); 00476 # Rename previews 00477 opendir DIR, $video_dir; 00478 foreach my $thumb (grep /\.png$/, readdir DIR) { 00479 next unless ($thumb =~ /^$show->{'basename'}((?:\.\d+)?(?:\.\d+x\d+(?:x\d+)?)?)\.png$/); 00480 my $dim = $1; 00481 $ret = rename "$video_dir/$thumb", "$video_dir/$name$dim.png"; 00482 # If the rename fails, try to delete the preview from the 00483 # cache (it will automatically be re-created with the 00484 # proper name, when necessary) 00485 if (!$ret) { 00486 unlink "$video_dir/$thumb" 00487 or vprint("Unable to rename preview image: '$video_dir/$thumb'."); 00488 } 00489 } 00490 closedir DIR; 00491 } 00492 } 00493 exit 0; 00494 }
1.7.6.1