00001 #!/usr/bin/perl
00002 ## written by greg froese (g_froese@yahoo.com)
00003 ## install instructions by Robert Kulagowski (rkulagow@rocketmail.com)
00004 ##
00005 ## I had trouble maintaining my catalog of recordings when upgrading to
00006 ## cvs and from cvs to more recent cvs, so I wrote this.
00007 ##
00008 ##
00009 ## Here is what this program is supposed to do.
00010 ##
00011 ## It first scans through your myth database and displays all shows listed
00012 ## in the recorded table.
00013 ##
00014 ## It will then traverse the specified MythTV recordings directory
00015 ## set with --dir /YOURMYTHDIR) and find all files with
00016 ## video extensions (set with --ext) and check if they appear in the
00017 ## database. If no entry exists you will be prompted for identifying
00018 ## information and a recording entry will be created.
00019 ##
00020 ## See the help message below for options.
00021 ##
00022 ## Use at your own risk. Standard gnu warranty, or lack therof,
00023 ## applies.
00024
00025 ## To run:
00026 ## Ensure that the script is executable
00027 ## chmod a+x myth.rebuilddatabase.pl
00028 ## ./myth.rebuilddatabase.pl
00029
00030 ## Change log:
00031 ## 9-19-2003: (awithers@anduin.com)
00032 ## Anduin fights the urge to make code more readable (aka C like). Battle
00033 ## of urges ends in stalemate: code was reindented but not "changed" (much).
00034 ## To make it a little less useless a contribution also did:
00035 ## - added ability to grab title/subtitle/description from oldrecorded
00036 ## - support for multiple backends (via separation of host and dbhost
00037 ## and bothering to insert the host in the recorded table).
00038 ## - removed dependency on File::Find::Rule stuff
00039 ## - attempt to determine good default host name
00040 ## - provide default for --dir from DB (if not provided)
00041 ## - added --test_mode (for debugging, does everything except INSERT)
00042 ## - added --try_default (good for when you must specify a command
00043 ## line option but don't really need to)
00044 ## - added --quick_run for those occasions where you just don't have
00045 ## the sort of time to be sitting around hitting enter
00046 ## - changed all the DB calls to use parameters (avoids escape issues,
00047 ## and it looks better)
00048
00049 use strict;
00050 use DBI;
00051 use Getopt::Long;
00052 use Sys::Hostname;
00053 use File::Basename;
00054 use Date::Parse;
00055 use Time::Format qw(time_format);
00056
00057 my ($verbose, $dir);
00058
00059 my $show_existing = 0;
00060 my $test_mode = 0;
00061 my $quick_run = 0;
00062 my $try_default = 0;
00063
00064 my $host = hostname;
00065 my $dbhost = $host;
00066 my $database = "mythconverg";
00067 my $user = "mythtv";
00068 my $pass = "mythtv";
00069 my $ext = "{nuv,mpg,mpeg,avi}";
00070 my $file = "";
00071 my @answers;
00072 my $norename = 0;
00073 my $storagegroup = "Default";
00074
00075 my $date_regx = qr/(\d\d\d\d)(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/;
00076 my $db_date_regx = qr/(\d\d\d\d)-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)/;
00077 my $channel_regx = qr/(\d\d\d\d)/;
00078
00079 sub GetAnswer {
00080 my ($prompt, $default) = @_;
00081 print $prompt;
00082 if ($default) {
00083 print " [", $default, "]";
00084 }
00085 print ": ";
00086
00087 my $answer;
00088 if ($#answers >= 0) {
00089 $answer = shift @answers;
00090 print $answer, "\n";
00091 } else {
00092 chomp($answer = <STDIN>);
00093 $answer = $default if !$answer;
00094 }
00095
00096 return $answer;
00097 }
00098
00099 # there's a version of this in CPAN but I don't want to add another dependancy
00100 sub EscapeFilename {
00101 my $fn = $_[0];
00102 # escape everything that's possibly dangerous
00103 $fn =~ s{([^[:alnum:]])}{\\\1}g;
00104 # it's embarassing to escape / and . so put those back
00105 $fn =~ s{\\([/.])}{\1}g;
00106 return $fn;
00107 }
00108
00109 my $script_name = $0;
00110
00111 if ($0 =~ m/([^\/]+)$/) {
00112 $script_name = $1;
00113 }
00114
00115 my $script_version = "0.0.3";
00116
00117 ## get command line args
00118
00119 my $argc=@ARGV;
00120 if ($argc == 0) {
00121 print "$script_name Version $script_version
00122 usage: $script_name [options]
00123
00124 Where [options] is:
00125 --host - hostname of this backend (default: \"$host\")
00126 --dbhost - hostname or IP address of the mysql server
00127 (default: \"$dbhost\")
00128 --user - DBUSERNAME (default: \"$user\")
00129 --pass - DBPASSWORD (default: \"$pass\")
00130 --database - DATABASENAME (default: \"$database\")
00131 --show_existing - Dumps current recorded table.
00132 --dir - path to recordings
00133 --group - Storage Group to import as (default: \"Default\")
00134 --try_default - Try to just run with the defaults.
00135 --quick_run - don't prompt for title/subtitle/description just
00136 use the default
00137 --test_mode - do everything except update the database
00138 --ext - file extensions to scan. csh/File::Glob syntax
00139 is used (ie, --ext {mpg,avi,divx})
00140 --file - specific file to import
00141 --answer - command-line response to prompts (give as many
00142 answers as you like)
00143 --norename - don't rename file to myth convention
00144
00145 Example 1:
00146 Assumption: The script is run on DB/backend machine.
00147
00148 $script_name --try_default
00149
00150 Example 2:
00151 Assumption: The script is run on a backend other than the DB host.
00152
00153 $script_name --dbhost=mydbserver
00154
00155 Example 3:
00156 Import one specific file and supply first few answers.
00157
00158 $script_name --file MyVideo.avi --answer y \\
00159 --answer 1041 --answer \"My Video\"
00160
00161 The script chooses reasonable defaults for all values so it's possible
00162 to do a quick import of a single video by taking input from null:
00163
00164 $script_name --file MyVideo.avi < /dev/null
00165
00166 this also works with multiple videos but because record start time is
00167 synthesized from file modification time you have to be careful of
00168 possible collisions.
00169 ";
00170 exit(0);
00171 }
00172
00173 GetOptions('verbose+'=>\$verbose,
00174 'database=s'=>\$database,
00175 'dbhost=s'=>\$dbhost,
00176 'host=s'=>\$host,
00177 'user=s'=>\$user,
00178 'pass=s'=>\$pass,
00179 'dir=s'=>\$dir,
00180 'group=s'=>\$storagegroup,
00181 'show_existing|se'=>\$show_existing,
00182 'try_default|td'=>\$try_default,
00183 'quick_run|qr'=>\$quick_run,
00184 'test_mode|t|tm'=>\$test_mode,
00185 'ext=s'=>\$ext,
00186 'file=s'=>\$file,
00187 'answer=s'=>\@answers, # =s{,} would be nice but isn't implemented widely
00188 'norename'=>\$norename
00189 );
00190
00191 my $dbh = DBI->connect("dbi:mysql:database=$database:host=$dbhost",
00192 "$user","$pass") or die "Cannot connect to database ($!)\n";
00193
00194 my ($starttime, $endtime, $title, $subtitle, $channel, $description, $recgroup);
00195 my ($syear, $smonth, $sday, $shour, $sminute, $ssecond, $eyear, $emonth, $eday,
00196 $ehour, $eminute, $esecond);
00197
00198 my $q = "";
00199 my $sth;
00200
00201 if (!$dir) {
00202 print("Error: No recordings directory specified.\n");
00203 print(" You must use the --dir option to specify a recording directory to use.\n");
00204 exit 1;
00205 }
00206
00207 # remove trailing slash
00208 $dir =~ s/\/$//;
00209
00210 if ($show_existing) {
00211 $q = "select title, subtitle, starttime, endtime, chanid, recgroup from recorded order by starttime";
00212 $sth = $dbh->prepare($q);
00213 $sth->execute or die "Could not execute ($q)\n";
00214
00215 print "\nYour myth database ($database) is reporting the following programs as being recorded:\n\n";
00216
00217 while (my @row=$sth->fetchrow_array) {
00218 $title = $row[0];
00219 $subtitle = $row[1];
00220 $starttime = $row[2];
00221 $endtime = $row[3];
00222 $channel = $row[4];
00223 $recgroup = $row[5];
00224
00225 ## get the pieces of the time
00226 if ($starttime =~ m/$db_date_regx/) {
00227 ($syear, $smonth, $sday, $shour, $sminute, $ssecond) =
00228 ($1, $2, $3, $4, $5, $6);
00229 }
00230
00231 if ($endtime =~ m/$db_date_regx/) {
00232 ($eyear, $emonth, $eday, $ehour, $eminute, $esecond) =
00233 ($1, $2, $3, $4, $5, $6);
00234 }
00235
00236 ## print "Channel $channel\t$smonth/$sday/$syear $shour:$sminute:$ssecond - $ehour:$eminute:$esecond - $title ($subtitle)\n";
00237 print "Channel: $channel\n";
00238 print "Start time: $smonth/$sday/$syear - $shour:$sminute:$ssecond\n";
00239 print "End time: $emonth/$eday/$eyear - $ehour:$eminute:$esecond\n";
00240 print "Title: $title\n";
00241 print "Subtitle: $subtitle\n";
00242 print "RecGroup: $recgroup\n\n";
00243 }
00244 }
00245
00246 print "\nThese are the files stored in ($dir) and will be checked against\n";
00247 print "your database to see if the exist. If they do not, you will be prompted\n";
00248 print "for a title and subtitle of the entry, and a record will be created.\n\n";
00249
00250 my @files = $file ? ($dir . "/" . $file) : glob("$dir/*.$ext");
00251 print "@files\n";
00252
00253 foreach my $show (@files) {
00254 my $showBase = basename($show);
00255
00256 my $cnt = $dbh->selectrow_array("select count(*) from recorded where basename=(?)",
00257 undef, $showBase);
00258
00259 my $found_title;
00260
00261 if ($cnt gt 0) {
00262 $found_title = $dbh->selectrow_array("select title from recorded where basename=(?)",
00263 undef, $showBase);
00264 }
00265
00266 if ($found_title) {
00267 print("Found a match between file and database\n");
00268 print(" File: '$show'\n");
00269 print(" Title: '$found_title'\n");
00270
00271 # use this so the stuff below doesn't have to be indented
00272 next;
00273 }
00274
00275 print("Unknown file $show found.\n");
00276 next unless GetAnswer("Do you want to import?", "y") eq "y";
00277
00278
00279 # normal case: import file into the database
00280
00281 my ($channel, $syear, $smonth, $sday, $shour, $sminute, $ssecond,
00282 $eyear, $emonth, $eday, $ehour, $eminute, $esecond);
00283 my ($starttime, $duration, $endtime);
00284 my ($mythfile);
00285
00286 # filename varies depending on when the recording was
00287 # created. Gleam as much as possible from the name.
00288
00289 if ($showBase =~ m/$channel_regx\_/) {
00290 $channel = $1;
00291 } else {
00292 $channel = $dbh->selectrow_array("select min(chanid) from channel");
00293 }
00294
00295 if ($showBase =~ m/$channel_regx\_$date_regx\./) {
00296 ($syear, $smonth, $sday, $shour, $sminute, $ssecond) =
00297 ($2, $3, $4, $5, $6, $7);
00298 }
00299
00300 if ($showBase =~ m/$channel_regx\_$date_regx\_$date_regx/) {
00301 ($syear, $smonth, $sday, $shour, $sminute, $ssecond) =
00302 ($2, $3, $4, $5, $6, $7);
00303 ($eyear, $emonth, $eday, $ehour, $eminute, $esecond) =
00304 ($8, $9, $10, $11, $12, $13);
00305 }
00306
00307 my $guess_title = $showBase;
00308 $guess_title =~ s/[.][^\.]*$
00309 $guess_title =~ s/_/ /g;
00310
00311 my $guess_subtitle = "";
00312 my $guess_description = "Recovered file " . $showBase;
00313
00314 # have enough to look for an past recording?
00315 if ($ssecond) {
00316 print "Checking for a recording...\n";
00317 $starttime = "$syear$smonth$sday$shour$sminute$ssecond";
00318
00319 my $guess = "select title, subtitle, description from oldrecorded where chanid=(?) and starttime=(?)";
00320 $sth = $dbh->prepare($guess);
00321 $sth->execute($channel, $starttime)
00322 or die "Could not execute ($guess)\n";
00323
00324 if (my @row = $sth->fetchrow_array) {
00325 $guess_title = $row[0];
00326 $guess_subtitle = $row[1];
00327 $guess_description = $row[2];
00328 }
00329
00330 print "Found an orphaned file, initializing database record\n";
00331 print "Channel: $channel\n";
00332 print "Start time: $smonth/$sday/$syear - $shour:$sminute:$ssecond\n";
00333 print "End time: $emonth/$eday/$eyear - $ehour:$eminute:$esecond\n";
00334 }
00335
00336 # what about checking for guide data?
00337 if($guess_description =~ /^Recovered file/) {
00338 print "Checking for guide data...\n";
00339 my $guess = "select title, subtitle, description from program where " .
00340 "chanid='$channel' and " .
00341 "starttime='$syear-$smonth-$sday $shour:$sminute:$ssecond'";
00342 $sth = $dbh->prepare($guess);
00343 $sth->execute()
00344 or die "Could not execute ($guess)\n";
00345
00346 if (my @row = $sth->fetchrow_array) {
00347 $guess_title = $row[0];
00348 $guess_subtitle = $row[1];
00349 $guess_description = $row[2];
00350 print "Using guide data informaton for defaults\n";
00351 }
00352 }
00353
00354 my $newtitle = $guess_title;
00355 my $newsubtitle = $guess_subtitle;
00356 my $newdescription = $guess_description;
00357
00358 if (!$starttime) {
00359 # use file time if we can't infer time from name
00360 $starttime = time_format("yyyy-mm{on}-dd hh:mm{in}:ss",
00361 (stat($show))[9]);
00362 }
00363
00364 if ($quick_run) {
00365
00366 print("QuickRun defaults:\n");
00367 print(" title: '$newtitle'\n");
00368 print(" subtitle: '$newsubtitle'\n");
00369 print(" description: '$newdescription'\n");
00370
00371 $recgroup = "Default";
00372
00373 } else {
00374
00375 $channel = GetAnswer("Enter channel", $channel);
00376 $newtitle = GetAnswer("... title", $newtitle);
00377 $newsubtitle = GetAnswer("... subtitle", $newsubtitle);
00378 $newdescription = GetAnswer("Description", $newdescription);
00379 $starttime = GetAnswer("... start time (YYYY-MM-DD HH:MM:SS)", $starttime);
00380 $recgroup = GetAnswer("... Recording Group", "Default");
00381 }
00382
00383 if ($endtime) {
00384 $duration = (str2time($endtime) - str2time($starttime)) / 60;
00385 } else {
00386 $duration = "60";
00387 }
00388 $duration = GetAnswer("... duration (in minutes)", $duration);
00389 $endtime = time_format("yyyy-mm{on}-dd hh:mm{in}:ss", str2time($starttime) + $duration * 60);
00390
00391 if ($norename) {
00392 $mythfile = $showBase;
00393 } else {
00394 my ($ext) = $showBase =~ /([^\.]*)$/;
00395 my $time1 = $starttime;
00396 $time1 =~ s/[ \-:]
00397 $mythfile = sprintf("%s_%s.%s", $channel, $time1, $ext);
00398 }
00399
00400 my $sql = "insert into recorded (chanid, starttime, endtime, title, subtitle, description, hostname, basename, progstart, progend, storagegroup, recgroup) values ((?), (?), (?), (?), (?), (?), (?), (?), (?), (?), (?), (?))";
00401
00402 if ($test_mode) {
00403
00404 $sql =~ s/\(\?\)/"%s"/g;
00405 my $statement = sprintf($sql, $channel, $starttime, $endtime, $newtitle,
00406 $newsubtitle, $newdescription, $host, $mythfile,
00407 $starttime, $endtime, $storagegroup, $recgroup);
00408 print("Test mode: insert would have been been:\n");
00409 print($statement, ";\n");
00410
00411 } else {
00412
00413 $sth = $dbh->prepare($sql);
00414 $sth->execute($channel, $starttime, $endtime, $newtitle,
00415 $newsubtitle, $newdescription, $host, $mythfile,
00416 $starttime, $endtime, $storagegroup, $recgroup)
00417 or die "Could not execute ($sql)\n";
00418
00419 if ($mythfile ne $showBase) {
00420 rename($show, $dir. "/" . $mythfile);
00421 }
00422
00423 }
00424
00425
00426 print("Building a seek table should improve FF/RW and JUMP functions when watching this video\n");
00427
00428 if (GetAnswer("Do you want to build a seek table for this file?", "y") eq "y") {
00429 # mythcommflag takes --file for myth-originated files and
00430 # --video for everything else. We assume it came from myth
00431 # if it's a .nuv or if it's an mpeg where the name has that
00432 # chanid_startime format
00433 my $commflag = "mythcommflag --rebuild " .
00434 ($showBase =~ /[.]nuv$/ || ($showBase =~ /[.]mpg$/ && $ssecond)
00435 ? "--file" : "--video") .
00436 " " . EscapeFilename($dir . "/" . $mythfile);
00437 if (!$test_mode) {
00438 system($commflag);
00439 print "\n"; # cursor isn't always on a new line after commflagging
00440 } else {
00441 print("Test mode: exec would have done\n");
00442 print(" Exec: '", $commflag, "'\n");
00443 }
00444 }
00445
00446 } ## foreach loop
00447
00448 # vim:sw=4 ts=4 syn=off: