#!/usr/bin/perl # # This perl script is intended to perform TV Show data lookups based on # the popular www.tvrage.com website # # # Author: Steve Adeff (steve dot adeff At gmail com) # Based on Source by: Tim Harvey, Andrei Rjeousski and Jesse Anderson # With code contributed by: Mark Spieth, Christoph Holzbaur, Peter Kornhuld # v0.1 # - Using old tvgrabber script, transfered to grabbing information from TVRage.com # Due to my not knowing what was going on... the following features need to be checked # to make sure they still work!! # - Allow mysql.txt to be used. (Originally by Mark Spieth!) # - Allow multple file name input. (Originally by Mark Spieth!) # - Allow "SSxEE - NAME.avi". (Originally by Christoph Holzbaur!) # - Double episode (ie S02E13-14) support. (Originally by Peter Kornhuld!) # - Fix letter S in filename after "S"eason. (Originally by Christoph Holzbaur!) # - Use hostname instead of MythBox. (Originally by Mark Spieth!) # - Replace if Insert fails. May need to make an option to turn # this off. (Originally by Mark Spieth!) # - Option to create a symlink to to the recording. (Originally by Mark Spieth!) # - Four digit series/episode number support. (Originally by Mark Spieth!) # # # use DBI; use LWP::Simple; # libwww-perl providing simple HTML get actions use LWP::UserAgent; # libwww-perl providing simple HTML get actions use HTML::Entities; use URI::Escape; use XML::Simple; use Getopt::Long qw(:config permute ); use Sys::Hostname; use File::Spec; use POSIX; use Date::Manip; use IO::Socket::INET; # User settings my $recgroup = "Default"; my $playgroup = "Default"; my $host = `hostname`; my $dbhost = "192.168.1.151"; my $database = "mythconverg"; my $user = "mythtv"; my $pass = "mythtv"; my $RecordFilePrefix = "/var/mythtv/recordings"; my $MasterServerIP = "localhost"; my $MasterServerPort = 6543; chomp $host; # Script Info $title = "TVRage.com Show Info Grabber"; $version = "v0.1"; $author = "Steven Adeff"; my $hflag = ''; my $vflag = ''; my $dflag = ''; my $rflag = ''; my $mflag = ''; my $opt_v = -1; my $opt_d = 0; my $opt_l = 0; my $opt_m = 0; my $opt_t = 0; my $opt_i = -1; # my $hflag = ''; # my $vflag = ''; # my $dflag = ''; # my $rflag = ''; # my $mflag = ''; # my $opt_v = -1; # my $opt_d = 0; # my $opt_l = 0; # my $opt_m = 0; # my $opt_t = 0; # my $opt_i = -1; my $cfg_file = "$ENV{'HOME'}/.tvgrabber"; my $argc = @ARGV; if ($argc == 0) { help(); } GetOptions( "help"=> sub { help(); }, "debug"=>\$opt_d, "verbose:i"=>\$opt_v, "manual"=>\$opt_m, "test"=>\$opt_t, "import!"=>\$opt_i, "recgroup=s"=>\$recgroup, "playgroup=s"=>\$playgroup, "file=s"=>\$inputfile, "config=s"=>\$cfg_file, "link"=>\$opt_l #'<>' => \&main ); if ($inputfile) { main($inputfile); } else { main(@ARGV); } exit(0); sub main { open F, ") { /DBHostName=(\S+)/ and do { $dbhost = $1; }; /DBUserName=(\S+)/ and do { $user = $1; }; /DBName=(\S+)/ and do { $database = $1; }; /DBPassword=(\S+)/ and do { $pass = $1; }; } close F; }; getsettings(); #my ( $inputfile ) = @_; my @inputfiles = @_; $opt_v = $opt_v + 1; if ( $opt_v > 2 ) { $opt_v = 0; } if ( $opt_v == 1 ) { print "(V1) Verbose Level 1\n"; } if ( $opt_v == 2 ) { print "(V2) Verbose Level 2\n"; $opt_d = 1; } if ( $opt_d == 1 ) { print "(DD) Debugging On\n"; } if ( $opt_t == 1 ) { print "(TT) Test Mode On\n"; } if ( $opt_l == 1 ) { print "(V1) SymLink Mode On\n" if $opt_v; } if ( $opt_v ) { print "(V1) Input filename: $inputfile\n"; } my $laststate = -1; my $addedstuff = 0; while ($#inputfiles>=0) { my $inputfile = shift @inputfiles; my $filename; my $foldername = ""; if ( $inputfile =~ m/\//) { $strPos1 = rindex( $inputfile, "/" ); $foldername = substr($inputfile, 0, $strPos1 + 1); $filename = $inputfile; $filename =~ s/.*\///; if ($opt_v) { print "(V1) Folder: $foldername\n"; } } else { $filename = $inputfile; } if ($opt_v) { print "(V1) Filename: $filename\n"; } if ($opt_l) { #link file into recorded directory my $rfile = $RecordFilePrefix . "/" . $filename; #print "checking for existing link $rfile\n"; if (-l $rfile) { #print "unlinking $rfile\n"; unlink($rfile); } chomp(my $cwd = `pwd`); my $fullpath = $cwd . "/" . $inputfile; symlink $fullpath, $rfile; if ($opt_v) { print "(V1) Symlink in : $inputfile, $fullpath, $rfile\n"; } $inputfile = $filename; } @showinfo = search($filename); if ( $opt_i == -1 ) { print "Do you want to add this to the Myth Database? (y/n)\n"; chomp(my $answer = ); # $answer = ; if ($answer eq "y") { addrecord(@showinfo, $inputfile); #exit (-1); $laststate = -1; $addedstuff = 1; next; } #exit (-1); $laststate = -1; next; } if ($opt_i == 0 ) { #exit (0); $laststate = 0; next; } if ($opt_i == 1 ) { addrecord(@showinfo, $inputfile); $addedstuff = 1; $laststate = -1; #exit (-1); next; } } send_recordinglistchanged() if $addedstuff != 0; exit ($addedstuff); } sub search { my ($filename, $inputfile) = @_; if ( $opt_v ) { print "(V1) Parseing input filename: $filename\n"; } #get the show title, season, episode from filename @tempFileInfoArray = getShowDataFromName($filename); my $showname=$tempFileInfoArray[0]; my $season=$tempFileInfoArray[1]; my $episode=$tempFileInfoArray[2]; if ($showname eq "") { $filename =~ /([^\.]*)\./ and do { $showname = $1; }; } if ($opt_v) { printf("(V1) Show Name: %s, Season: %s Episode: %s,\n", $showname, $season, $episode);} my $seasonep = $season . "x" . $episode; if ($opt_v) { print "(V1) Searching for '$showname $seasonep'\n"; } #get the episode URL from the TVrage.com search my @tvrage_info = get_show($showname,"0",$seasonep); if ($opt_v) { print "(V1) TVRage URL= $tvrage_info[6]\n"; } my @showinfo; @showinfo = getTVRageData($tvrage_info[6], $tvrage_info[1]); return @showinfo; } sub version { print "$title ($version) by $author\n" } sub info { print "Query's www.tv.com for show information.\n"; } sub usage { print "usage: $0 -hvidrlF filename\n"; print "\tfilename format must be in:\n"; print "\tepisode.title.format.s01e01.blah.avi\t\n\tor\n"; print "\tepisode.title.format.101.blah.avi\n"; print "\n\t-h\tthis help screen\n"; print "\t-d\tturn on debugging\n"; print "\t-v\tturn on verbose\n"; print "\n"; # print "\t-m\tManually choose show from query list\n"; print "\t-F\tinput_filename\n"; print "\t-l\tcreate a symlink in the recordings directory first\n"; print "\t\tRequires absolute path!\n"; print "\n\tMythTV integration options\n\n"; print "\t-import/-noimport\tforce to import or not\n"; print "\t\t\t\tdefault is to ask\n"; print "\t-t\tturn on test mode, won't touch database\n"; exit (1); } sub help { version(); info(); usage(); } sub getShowDataFromName { my ($filename) = @_; if ($opt_d) { printf("(DD) Show Name: '%s'\n", $filename);} # if there are more than one Episode in one file (Serie.S02E13-14 or Serie.02x13-14) take the first one if ( $filename =~ /[0-9][0-9]-[0-9][0-9]/) { if ($opt_v) { print "(V1) File seems to have more than one show\n"; } $filename =~ s/(\d\d)-(\d\d)/\1/g; if ($opt_v) { print "(V1) New show name: $filename\n"; } } # Convert " - " to "." as Delimiter if ( $filename =~ / - /) { if ($opt_v) { print "(V1) Show name has \" - \" as delimiter\n"; } $filename =~ s/ - /\./g; if ($opt_v) { print "(V1) New show name: $filename\n"; } } # Convert 4 number format to S00E00 format if ( $filename =~ /\.[0-9][0-9][0-9][0-9]\./) { if ($opt_v) { print "(V1) Show name is four-number format\n"; } $filename =~ s/\.(\d\d)(\d\d)\./\.S\1E\2\./g; if ($opt_v) { print "(V1) New show name: $filename\n"; } } else { # Convert 3 number format to S00E00 format if ( $filename =~ /\.[0-9][0-9][0-9]\./) { if ($opt_v) { print "(V1) Show name is three-number format\n"; } $filename =~ s/\.(\d)(\d\d)\./\.S0\1E\2\./g; if ($opt_v) { print "(V1) New show name: $filename\n"; } } } # Convert 00x00 format to S00E00. format if ( $filename =~ /\.[0-9][0-9][xX][0-9][0-9]\./) { if ($opt_v) { print "(V1) Show name is 00x00 format\n"; } $filename =~ s/\.(\d\d)([xX])(\d\d)\./\.S\1E\3\./g; if ($opt_v) { print "(V1) New show name: $filename\n"; } } if ( $filename =~ /\.[sS][0-9][0-9][eE][0-9][0-9]\./ ) { if ($opt_v) { print "(V1) Show name is S00E00 format\n"; } $fileNameType=1 } if ($opt_d) { printf("(DD) File name is type: '%s'\n", $fileNameType);} my @returnData; if ( $fileNameType == 1 ) { #Show.Name.S01E01.Episode Name.avi $filename =~ s/[sS]([0-9]+)[eE]([0-9]+)/\.S\1E\2/g; #uppercase my $strPos1 = rindex( $filename, "..S" ); my $strPos2 = index( $filename, "E", $strPos1 + 1 ); my $strPos3 = index( $filename, ".", $strPos2 + 1 ); if ($opt_d) { print "(DD) Pos1 : $strPos1 pos2: $strPos2 pos3: $strPos3\n"; } # if ($filename =~ m/the.o.c./) {$filename =~ s/o.c./oc./; } # The OC check $filename =~ tr/./\ /; #Show Name $returnData[0] = substr( $filename, 0, $strPos1); #Season $returnData[1] = substr( $filename, $strPos1 + 3, $strPos2 - $strPos1 - 3 ); #Episode $returnData[2] = substr( $filename, $strPos2 + 1, $strPos3 - $strPos2 - 1 ); } else { # # example line in config file # '(Bold and the beautiful) (.*)\.' '"the ".$1' '-1' '$2' # matches "Bold and the beautiful April 13th 2006.avi" # print "reading tvgrabber config\n" if $opt_v; open CF, "< $cfg_file" and do { CFREAD: while () { chomp; next if /^\#/; print "got line $_\n" if $opt_v>1; /\'([^\']*)\'\s+\'([^\']*)\'\s+\'([^\']*)\'\s+\'([^\']*)\'/ and do { my $re = $1; my $nameexp = $2; my $seasonexp = $3; my $episodeexp = $4; print "got line $re $nameexp $seasonexp $episodeexp\n" if $opt_v>1; $filename =~ /$re/ and do { $returnData[0] = eval($nameexp); $returnData[1] = eval($seasonexp); $returnData[2] = eval($episodeexp); print "params are $returnData[0], $returnData[1], $returnData[2]\n" if $opt_v>1; last CFREAD; }; }; } } } $returnData[0] =~ s/\./ /g; if ($opt_d) { printf("(DD) Show Name: '%s', Season: '%s' Episode: '%s',\n", $returnData[0], $returnData[1], $returnData[2]);} return @returnData; } sub get_show { my ($show) = $_[0]; my ($exact ) = $_[1]; my ($episode) = $_[2]; my @ret = ""; my $premiered; if ( $show ne "" ) { if ($opt_d) { print "Search URL= http://www.tvrage.com/quickinfo.php?show=$show&ep=$episode&exact=$exact\n"; } my $site = get "http://www.tvrage.com/quickinfo.php?show=".$show."&ep=".$episode."&exact=".$exact; foreach $line (split("\n",$site) ) { my ($sec,$val) = split('\@',$line,2); if ($sec eq "Show Name" ) { $ret[0] = $val; my $showname = $val; } elsif ( $sec eq "Show URL" ) { $ret[1] = $val; } elsif ( $sec eq "Premiered" ) { $ret[2] = $val; } elsif ($sec eq "Country" ) { $ret[7] = $val; } elsif ( $sec eq "Status" ) { $ret[8] = $val; } elsif ( $sec eq "Classification" ) { $ret[9] = $val; } elsif ( $sec eq "Latest Episode" ) { my($ep,$title,$airdate) = split('\^',$val); $ret[3] = $ep.", \"".$title."\" aired on ".$airdate; } elsif ( $sec eq "Next Episode" ) { my($ep,$title,$airdate) = split('\^',$val); $ret[4] = $ep.", \"".$title."\" airs on ".$airdate; } elsif ( $sec eq "Episode Info" ) { my($ep,$title,$airdate) = split('\^',$val); $ret[5] = $ep.", \"".$title."\" aired on ".$airdate; print "Searched Episode= Ep: $ep Title: $title Airdate: $airdate\n"; } elsif ( $sec eq "Episode URL" ) { $ret[6] = $val; } } if ( $ret[0] ) { return @ret; } else { print ("Episode URL Not Found!"); exit (1); } } } sub getTVRageData { my ($episodeURL) = $_[0]; my ($showURL) = $_[1]; if ($opt_d) { print "(DD) Episode URL: $episodeURL\n"; } if ($opt_d) { print "(DD) Show URL: $showURL\n"; } my $episodeHTML = get $episodeURL; my $showHTML = get $showURL; #if ($$opt_v == 2) { print "(V2) Episodes Stripped:\n$episodeHTML\n"; } # parse title my $title = parseBetween( $episodeHTML, "Title: ", "Episode Number:"); $title = trim( $title ); #parse season my $season = parseBetween( $episodeHTML, "Season: ", "Season Episode"); #parse episode number my $episode_number = parseBetween( $episodeHTML, "Episode Number: ", "Season"); #parse season episode number my $s_episode = parseBetween( $episodeHTML, "Season Episode #: ", "Production Number"); #parse production number my $production_number = parseBetween( $episodeHTML, "Production Number: ", "Original Airdate"); #parse original airdate my $firstaired = parseBetween( $episodeHTML, "Original Airdate: ", ""); $strPoint2 = rindex( $firstaired, "/" ); $strPoint1 = index( $firstaired, "/" ); # parse year my $firstyear = substr( $firstaired, $strPoint2 + 1 ); # parse month my $firstday = substr( $firstaired, 0, $strPoint1 ); # parse day my $firstmonth = substr( $firstaired, $strPoint1 + 1, $strPoint2 - $strPoint1 - 1 ); #parse director my $director = parseBetween( $episodeHTML, "Director:
"); $director = stripParensFromName( parseBetween($director, ">", "<") ); #parse writer my $writer = parseBetween( $episodeHTML, "Writer:
"); $writer = parseBetween($writer, ">", "<") ; #multiple writers? #parse summary my $summary =parseBetween( $episodeHTML, "
", "
" ); #parse show name my $showname = parseBetween( $showHTML, "\"","\" Summary"); #parse category/genre my $category = parseBetween( $showHTML, "Show Genre: ", ""); while ($category =~ m/ \| / ) { $category =~ s/\ \|/,/; } #parse airtime my $runtime = parseBetween( $showHTML, "Runtime: ", " Minutes"); my $airtime = parseBetween( $showHTML, "Airs On: ", ""); $airtime = parseBetween( $airtime, "(", ")"); $strPoint1 = index ($airtime, ":" ); $hour = substr($airtime, 0, $strPoint1 ); $minute = substr($airtime, $strPoint1 + 1, 2 ); if ($airtime =~ m/p/) { $hour = $hour + 12 }; if (($airtime =~ m/a/) & ($hour == 12)) { $hour = 00 }; $airtime = $hour . ":" . $minute . ":00"; $endminute = $minute + $runtime; $endhour = $hour; while ($endminute >= 60) { $endminute = $endminute - 60; $endhour = $endhour + 1; } if ($endhour >= 24 ) { $endhour = $endhour - 24; } if ($endminute <10 ) { $endminute = "0" . $endminute; } $endtime = $endhour . ":" . $endminute . ":00"; #parse network my $network = parseBetween($showHTML, "Network: ", ""); print "Show: $showname\n"; print "Network: $network\n"; print "Season: $season\n"; print "Episode: $s_episode\n"; print "FakeEp: $episode_number\n"; print "Production Number: $production_number\n"; print "Title: $title\n"; print "First Aired: $firstyear / $firstmonth / $firstday\n"; print "Director: $director\n"; print "Plot:\n$summary\n"; print "Runtime: $runtime\n"; print "Airtime: $airtime\n"; print "Endtime: $endtime\n"; print "Writers: $writer\n"; print "Category: $category\n"; @showinfo = ($showname, $network, $season, $s_episode, $episode_number, $production_number, $title, $firstyear, $firstmonth, $firstday, $director, $summary, $runtime, $airtime, $endtime, $writer, $category ); return @showinfo; } # # # MythTV integration Functions # # sub commercialflag { my ($inputfile) = @_; print("Building a seek table should improve FF/RW and JUMP functions when watching this video\n"); print("Do you want to build a seek table for this file? (y/n): "); chomp(my $do_commflag = ); if ($do_commflag eq "y") { if (!$test_mode) { exec("mythcommflag --file $inputfile --rebuild"); } else { print("Test mode: exec would have done\n"); print(" Exec: 'mythcommflag --file $inputfile --rebuild'\n"); } } else { print("Skipping illegal file format: $show\n"); } } sub getsetting { my ($dbh, $ref, $val, $host ) = @_; my $sql = qq{ SELECT data FROM settings WHERE value = '$val' }; $sql .= " AND hostname = '$host'" if defined $host; my $sth = $dbh->prepare($sql); my $rv = $sth->execute(); my $data; if ($rv) { my @row = $sth->fetchrow_array; #print "num rows $rv,$#row, $row[0], $row[1], $row[2]\n"; # if $opt_v; $$ref = $row[0] if ($#row >= 0); } undef $sth; } sub getsettings { my $dbh = DBI->connect("dbi:mysql:database=$database:host=$dbhost","$user","$pass") or die "(EE) Cannot connect to database ($!)\n" . exit(-1); $dbh->{PrintError} = 0; #$dbh->{PrintWarn} = 1; #$dbh->{RaiseError} = 0; getsetting($dbh, \$RecordFilePrefix, "RecordFilePrefix", $host); getsetting($dbh, \$MasterServerIP, "MasterServerIP"); getsetting($dbh, \$MasterServerPort, "MasterServerPort"); $dbh->disconnect; } sub sendcmd { my ($sock, $cmd) = @_; my $l = length($cmd); $cmd = sprintf("%-8d",$l) . $cmd; #print "sending $cmd\n"; print $sock "$cmd"; $sock->flush(); $sock->recv($response,8,0); my $l = int $response; $sock->recv($response,$l,0); return $response; } sub send_recordinglistchanged { #print "notifying server of update\n"; my $sock = IO::Socket::INET->new( Proto => 'tcp', PeerAddr => $MasterServerIP, PeerPort => $MasterServerPort) or return 0; $sock->autoflush(1); binmode($sock,":utf8"); my $s = "MESSAGE[]:[]RECORDING_LIST_CHANGE"; my $response = sendcmd($sock, "ANN Playback tvgrabber 0"); #my $response = sendcmd($sock, "ANN SlaveBackend tvgrabber localhost"); #print "ANN resp was '$response'\n"; $response = sendcmd($sock, $s); #print "response was '$response'\n"; $response = sendcmd($sock, "DONE"); #print "DONE resp was '$response'\n"; undef $sock; } ## add records to db sub addrecord { my @showdata = @_; my ($showname, $network, $season, $s_episode, $episode_number, $production_number, $title, $firstyear, $firstmonth, $firstday, $director, $summary, $runtime, $airtime, $endtime, $writer, $category ) = @showdata; if ($opt_v) { print "(V1) Going to add '$showname - $title' to database!\n"; } my $chanid = 0; my $starttime = $firstyear . "-" . $firstmonth . "-" . $firstday . " " . $airtime; my $endtime = $firstyear . "-" . $firstmonth . "-" . $firstday . " " . $endtime; my $originalairdate = $firstyear . "-" . $firstmonth . "-" . $firstday; my $autoexpire = 0; my $programid; if (($season+0) >= 0) { if ($season < 10 ) { $season = "0" . $season; } if ($s_episode < 10 ) { $s_episode = "0" . $s_episode; } $programid = "S" . $season . "E" . $s_episode; $title = "s" . $season . "e" . $s_episode . " " . $title } else { $title = $title; $programid = $title; } if ($opt_t == 0) { print "Inserting \'$showname - $title\' into MythTV Database\n"; my $dbh = DBI->connect("dbi:mysql:database=$database:host=$dbhost","$user","$pass") or die "(EE) Cannot connect to database ($!)\n" . exit(-1); $dbh->{PrintError} = 0; #$dbh->{PrintWarn} = 1; #$dbh->{RaiseError} = 0; my $sth; my $i = "INSERT INTO recorded (chanid, starttime, endtime, title, subtitle, description, category, hostname, autoexpire, recgroup, playgroup, programid, originalairdate, basename, progstart, progend) VALUES ((?), (?), (?), (?), (?), (?), (?), (?), (?), (?), (?), (?), (?), (?), (?), (?))"; $sth = $dbh->prepare($i); my $rv = $sth->execute($chanid, $starttime, $endtime, $showname, $title, $plot, $category, $host, $autoexpire, $recgroup, $playgroup, $programid, $originalairdate, $inputfile, $starttime, $endtime); if ($rv == 0) { print "Found, Replacing instead\n"; my $i = "REPLACE recorded SET chanid = (?), starttime = (?), endtime = (?), title = (?), subtitle = (?), description = (?), category = (?), hostname = (?), autoexpire = (?), recgroup = (?), playgroup = (?), programid = (?), originalairdate = (?), basename = (?), progstart = (?), progend = (?)"; $sth = $dbh->prepare($i); $rv = $sth->execute($chanid, $starttime, $endtime, $showname, $title, $plot, $category, $host, $autoexpire, $recgroup, $playgroup, $programid, $originalairdate, $inputfile, $starttime, $endtime); } if ($rv == 0) { print "Could not execute ($i) ",$sth->errstr,"\n"; $dbh->disconnect; return 0; }; $dbh->disconnect; print "Database updated!\n"; return 1; } else { print("Test mode: insert would have been done\n"); print(" Query: '$i'\n"); print(" Query params: $plot, $inputfile\n"); return 1; } } # # # Generic Functions # # sub parseBetween { my ($data, $beg, $end)=@_; # grab parameters my $ldata = lc($data); my $start = index($ldata, lc($beg)) + length($beg); my $finish = index($ldata, lc($end), $start); if ($start != (length($beg) -1) && $finish != -1) { my $result = substr($data, $start, $finish - $start); # return w/ decoded numeric character references # (see http://www.w3.org/TR/html4/charset.html#h-5.3.1) decode_entities($result); return $result; } return ""; } sub trim { my $string = shift; for ($string) { s/^\s+//; s/\s+$//; } return $string; } sub stripParensFromName { my ($parensName) = @_; # grab parameters if ( rindex( $parensName, "(" ) != -1 ) { return trim( substr( $parensName, 0, rindex( $parensName, "(" ) ) ); } else { return trim( $parensName ); } }