User:Mludowise

From MythTV Official Wiki
Jump to: navigation, search

Hulu Perl Script

This script downloads the specified RSS feeds and caches the metadata and URL into the videometadata table in mythtv. The idea is that you can run it once a night in a cron job and it will display all the information about each video will be stored in the videometadata table, including how many days until that video expires. Then the playcommand for each video opens your favorite browser to that video on Hulu.

This script worked beautifully for me until Hulu limited their RSS feeds to only 20 items. So if all you want is the 20 most recent items in your queue, then this script will work fine for you.

myth_hulu_grabber.pl

 #!/usr/local/bin/perl
 
 #  Copyright (C) 2008 Mel Ludowise <mludowise.code@gmail.com>
 # 
 #  Description: Hulu Video Grabber for mythtv
 #
 #  Grabs the video URLS from the Hulu RSS feeds and creates a dummy 0 length 
 #  file to force MythVideo to display the entry and then opens the URL in the
 #  browser.
 #
 #  Version 0.1.0
 #
 #  Hulu Video Grabber is free software: you can redistribute it and/or modify
 #  it under the terms of the GNU General Public License as published by
 #  the Free Software Foundation, either version 3 of the License, or
 #  (at your option) any later version.
 #
 #  Hulu Video Grabber is distributed in the hope that it will be useful,
 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 #  GNU General Public License for more details.
 #
 #  You should have received a copy of the GNU General Public License
 #  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 #
 #  Based off of Apple Trailer Grabber by Ben Leto <undertoe@chemlab.org>:
 #
 #  Apple Trailer Grabber is free software; you can redistribute it and/or modify
 #  it under the terms of the GNU General Public License as published by
 #  the Free Software Foundation; either version 2 of the License, or
 #  (at your option) any later version.
 #
 #  Apple Trailer Grabber is distributed in the hope that it will be useful,
 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 #  GNU General Public License for more details.
 #
 #  You should have received a copy of the GNU General Public License
 #  along with this program; if not, write to the Free Software
 #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 #
 #  Usage: read the INSTALL file
 #      Setup as a cron script to run as frequently as you want
 #
 
 ##########################################################################
 
 # CONFIGURATION
 
 # Any RSS feeds from Hulu. This includes your Queue, all episodes from a
 # specific show, popular episodes, search results, and any other RSS feed
 # that Hulu provides
 @RSS_URLS = (
 
 # Your Queue
 "http://www.hulu.com/feed/queue/1234567",
 
 # The RSS feeds for a specific show can be obtained from the RSS link on a 
 # show's page
 
 # It's Always Sunny In Philadelphia
 "http://www.hulu.com/feed/show/35/episodes",
 
 # Battlestar Galactica
 "http://www.hulu.com/feed/show/72/episodes",
 
 # Psych
 "http://www.hulu.com/feed/show/81/episodes",
  
 # Firefly
 "http://www.hulu.com/feed/show/827/episodes"
 );
 
 # MythTV Database Configuration
 # This is specific to your configuration of MythTV
 $DATABASE_USERNAME      = "root";
 $DATABASE_PASSWORD      = "password";
 $DATABASE_HOSTNAME      = localhost;
 $DATABASE_NAME          = "mythconverg";
 
 # Directory MythVideo downloads movie posters to
 $POSTER_FILES_DIR       = "/home/melanka/.mythtv/MythVideo";
 
 # Where to save the downloaded files if we are to download them
 $DOWNLOAD_FILES_DIR     = "/var/lib/mythtv/hulu";
 
 # Browser Command
 # You can use firefox or whatever flash enabled browser of our choice
 $BROWSER_EXEC_CMD       = "/usr/local/bin/firefox32";
 
 # Hulu Player
 #                       default - Use Hulu's default player
 #                       popout - Use Hulu's popout player
 $PLAYER                 = "popout";
 
 # Category used to label hulu clips and episodes in MythVideo
 # Leave this string empty for no category
 $VIDEO_CATEGORY         = "Hulu";
 
 # Display newly added videos seperately
 # A copy of recently added videos will be displayed in a seperate directory
 $SHOW_NEW               = true;
 
 # What directory should be created for newly added videos
 $NEW_VIDEOS_DIR         = "0 Recently Added";
 
 # How many days old is still considered new?
 $DAYS_FOR_NEW           = 14;
 
 # Display soon to expire videos seperately
 $SHOW_EXPIRING          = true;
 
 # What directory should be created for soon to expire videos
 $EXPIRE_VIDEOS_DIR      = "0 Soon to Expire";
 
 # How many days before expiration should a video be flagged?
 # Use v
 $DAYS_FOR_EXPIRE        = 7;
 
 # Print debugging output
 $DEBUG = true;
 
 
 $popoutURL = "http://www.hulu.com/stand_alone/";
 
 use DBI;
 use XML::XPath;
 use XML::XPath::XMLParser;
 use LWP::UserAgent;
 use HTTP::Request;
 use HTML::Entities;
 use File::Basename;
 
 sub mysql_getcategoryid {
     $category_name = $_[0];
     if($category_name eq  "") {
 	return 0;
     }
 
     $category = $mysql->quote($category_name);
     $query = "SELECT intid FROM videocategory WHERE category=$category";
     $result = $mysql->prepare($query);
     $result->execute();
     # category doesn't exist, add it
     if($result->rows == 0) {
 	$query = "INSERT INTO videocategory VALUES(NULL, $category)";
 
 	$mysql->do($query);
 
 	$query = "SELECT intid FROM videocategory WHERE category=$category";
 	$result = $mysql->prepare($query);
 	$result->execute();
     }
     $res = $result->fetchrow_hashref()->{'intid'};
     $result->finish;
 
     return $res;
 }
 
 sub trim {
     $result = $_[0];
     $result =~ s/^\s*(.*?)\s*$/\1/;
     return $result;
 }
 
 sub parse {
     $url = $_[0];
     $ua = LWP::UserAgent->new;
     my $req = HTTP::Request->new(GET => $url);
     my $response = $ua->request($req);
     my $content = $response->content();
 
     $xp = XML::XPath->new(ioref => $content);
     $items = $xp->find('//item');
     
     my @arr_rss;
 
     foreach $item ($items->get_nodelist) {
 	$pubDate = $xp->find("./pubDate", $item)->string_value();
 	
 	$str_title = decode_entities($xp->find("./title", $item)->string_value());
 	@arr_title = split(/:/, $str_title);
 	$showTitle = trim(shift @arr_title);
 	$" = ":";
 	$episodeTitle = trim("@arr_title");
 	
 	if($episodeTitle =~ /(.*?)\(s([0-9]*?)e([0-9]*?)\)/) {
 	    $episodeTitle = trim($1);
 
 	    $season = $2;
 
 	    $episode = $3;
 	    if($episode < 10) {
 		$episode = "0".$episode;
 	    }
 	    
 	    $episodeNumber = $season.x.$episode." ";
 	} else {
 	    $episodeNumber = "";
 	}
 
 	# parse link tag
 	$link = $xp->find("./link", $item)->string_value();
 	
 	# get the unique id of the show
 	if($PLAYER eq "popout") {
 	    my $ua = LWP::UserAgent->new;
 	    my $req = HTTP::Request->new(GET => $link);
 	    my $response = $ua->request($req);
 	    my $content = $response->content();
 	    if($content =~ /\("content_id", "([0-9]*?)"\)/) {
 		$contentId = $1;
 	    }
 	    
 	    $req HTTP::Request->new(GET => $popoutURL.$contentId);
 	    $response = $ua->request($req);
 	    $content = $response->content();
 	    if($content =~ /\("lcname", "(.*?)"\)/) {
 		$contentId = $contentId."?lcname=".$1;
 	    }
 	}
 	if($link =~ /\/watch\/([0-9]*?)\//) {
 	    $clipId = $1;
 	}
 
 	# parse thumbnail url
 	$thumbnailURL = XML::XPath::XMLParser::as_string($xp->find("./media:thumbnail", $item)->get_node(0));
 	if($thumbnailURL =~ /url=\"(.*?)"/) {
 	    $thumbnailURL = $1;
 	}
 
 	# parse description tag
 	$str_description = decode_entities(XML::XPath::XMLParser::as_string($xp->find("./description", $item)->get_node(0)));
 	$str_description =~ s/.*<![CDATA[(.*?)]]/\1/;
 	$xp2 = XML::XPath->new(ioref => $str_description);
 	$descriptionNode = $xp2->find("//p");
 
 	# get description test
 	$description = decode_entities($descriptionNode->get_node(1)->string_value());
 
 	# get length, user rating, air date, added, expiration
 	$metadata = XML::XPath::XMLParser::as_string($descriptionNode->get_node(0));
 
 	# get length
 	if($metadata =~ /Duration: (.*?)
2)  ? (60 * $arr_length[0]) + $arr_length[1] + ($arr_length[2] > 0 ? 1 : 0)  : $arr_length[0] + ($arr_length[1] > 0 ? 1 : 0); # get rating if($metadata =~ /Rating: (.*?)</) { @arr_rating = split(/\//, $1); } $rating = trim($arr_rating[0]) / trim($arr_rating[1]); # get air date my $airDate; if($metadata =~ /Air date: (.*?)</) { $airDate = $1; } # get date added my $added; if($metadata =~ /Added: (.*?)<br/) { $added = $1; } # get expiration my $expireDays; my $expireDate; if($metadata =~ /Expires in ([0-9]*?) day(s??) on (.*?)</) { $expireDays = $1; $expireDate = $3; } elsif($metadata =~ /Expires in about ([0-9]*?) month(s??) on (.*?)</) { $expireDays = 30 * $1; $expireDate = $3; } elsif($metadata =~ /Expires in about [0-9]* hours</) { $expireDays = 0; $expireDate = "Today"; } else { $expireDays = $DAYS_FOR_EXPIRE+1; } my %hash_temp; $hash_temp{'series'} = $showTitle; $hash_temp{'episode'} = $episodeTitle; $hash_temp{'episode number'} = $episodeNumber; $hash_temp{'link'} = $link; $hash_temp{'clip id'} = $clipId; $hash_temp{'description'} = $description; $hash_temp{'length'} = $length; $hash_temp{'rating'} = $rating; $hash_temp{'airdate'} = $airDate; $hash_temp{'expire days'} = $expireDays; $hash_temp{'expire date'} = $expireDate; $hash_temp{'pub date'} = $pubDate; $hash_temp{'added'} = $added; $hash_temp{'thumbnail'} = $thumbnailURL; $hash_temp{'content id'} = $contentId; push(@arr_rss, {%hash_temp}); } return @arr_rss; } sub main { # Connect to MythTV Database $dsn = "DBI:mysql:database=$DATABASE_NAME;host=$DATABASE_HOSTNAME"; $mysql = DBI->connect($dsn, $DATABASE_USERNAME, $DATABASE_PASSWORD); $drh = DBI->install_driver("mysql"); # first check if the directory we're writing to exists if(! -d $DOWNLOAD_FILES_DIR) { if(mkdir($DOWNLOAD_FILES_DIR)) { if($DEBUG) { print "The directory \"$DOWNLOAD_FILES_DIR\" didn't exist, but we created it for you...\n"; } } else { if($DEBUG) { print "ERROR: Could not create the directory \"$DOWNLOAD_FILES_DIR\"\n"; } exit(1); } } # check that expire directory exists if($SHOW_EXPIRING) { $expire_videos_abs_dir = "$DOWNLOAD_FILES_DIR/$EXPIRE_VIDEOS_DIR"; if( ! -d $expire_videos_abs_dir) { if(mkdir($expire_videos_abs_dir)) { if($DEBUG) { print "The directory \"$expire_videos_abs_dir\" didn't exist, but we created it for you...\n"; } } else { if($DEBUG) { print "ERROR: Could not create the directory \"$expire_videos_abs_dir\"\n"; } exit(1); } } } if($SHOW_NEW) { # check that new directory exists $new_videos_abs_dir = "$DOWNLOAD_FILES_DIR/$NEW_VIDEOS_DIR"; if(! -d $new_videos_abs_dir) { if(mkdir($new_videos_abs_dir)) { if($DEBUG) { print "The directory \"$new_videos_abs_dir\" didn't exist, but we created it for you...\n"; } } else { if($DEBUG) { print "ERROR: Could not create the directory \"$new_videos_abs_dir\"\n"; } exit(1); } } # remove new videos that aren't new anymore $query="SELECT * FROM videometadata WHERE filename LIKE ".$mysql->quote($new_videos_abs_dir.'%')." AND inetref <= ADDDATE(DATE(NOW()) , -$DAYS_FOR_NEW)"; $result=$mysql->prepare($query); $result->execute(); if($DEBUG) { print "Removing new videos that are no longer new...\n"; } while($row = $result->fetchrow_hashref) { $filename = $row->{'filename'}; system("rm \"$filename\""); if($DEBUG) { print "rm \"$filename\"\n"; } } # remove entries from database $query="DELETE FROM videometadata WHERE filename LIKE ".$mysql->quote($new_videos_abs_dir.'%')." AND inetref <= ADDDATE(DATE(NOW()) , -$DAYS_FOR_NEW)"; $mysql->do($query); } # find the category id in the MythTV database $category_id = mysql_getcategoryid($VIDEO_CATEGORY); # mark all hulu videos $query = "UPDATE videometadata SET plot='expired' WHERE filename REGEXP '.hulu\$'"; $mysql->do($query); # get videos from the RSS for $rss_url (@RSS_URLS) { # parse rss @arr_rss = parse($rss_url); for $values (@arr_rss) { $downloadDir = $DOWNLOAD_FILES_DIR."/".$values->{'series'}; $videoFilename = $downloadDir."/".$values->{'clip id'}.".hulu"; # compose the video's description $description = ""; if($values->{'expire date'}) { $description .= "Expires: ".$values->{'expire date'}. (($values->{'expire days'} > 0) ? " - ".$values->{'expire days'}." days\n" : "\n"); } if($values->{'airdate'}) { $description .= "Air date: ".$values->{'airdate'}."\n"; } $description .= "Added: ".$values->{'added'}."\n"; $description .= "\n".$values->{'description'}; if($SHOW_EXPIRING) { $expireVideoFilename = $expire_videos_abs_dir."/".$values->{'clip id'}.".hulu"; $query="SELECT * FROM videometadata WHERE filename=".$mysql->quote($expireVideoFilename); $result=$mysql->prepare($query); $result->execute(); # check if video is about to expire if($values->{'expire days'} <= $DAYS_FOR_EXPIRE && $result->rows < 1) { # create dummy file so MythVideo doesn't delete metadata when updating browse system("touch \"$expireVideoFilename\""); $query="INSERT INTO videometadata (title,plot,userrating,length,filename,coverfile,playcommand,category,showlevel) VALUES(" . $mysql->quote($values->{'series'}.": ".$values->{'episode number'}.$values->{'episode'})."," . $mysql->quote($description)."," . $values->{'rating'}."," . $values->{'length'}."," . $mysql->quote($expireVideoFilename)."," . $mysql->quote($POSTER_FILES_DIR."/".basename($values->{'thumbnail'}))."," . $mysql->quote($mythtvPlayerCmd)."," . "$category_id," . "1)"; $mysql->do($query); if($DEBUG) { print "About to expire video \"".$values->{'series'}.": ".$values->{'episode number'}.$values->{'episode'}."\"...\n"; } } # check if the video was about to expire but isn't anymore elsif($values->{'expire days'} > $DAYS_FOR_EXPIRE && $result->rows > 0) { system("rm \"$expireVideoFilename\""); $query="DELETE FROM videometadata WHERE filename=".$mysql->quote($expireVideoFilename); $mysql->do($query); if($DEBUG) { print "No longer expiring video \"".$values->{'series'}.": ".$values->{'episode number'}.$values->{'episode'}."\"...\n"; } } } # check if that the video is already in the database $query="SELECT * FROM videometadata WHERE filename=".$mysql->quote($videoFilename); $result=$mysql->prepare($query); $result->execute(); # check if that video already exists if($result->rows > 0) { # update the description in case this video is expiring $query = "UPDATE videometadata SET plot=".$mysql->quote($description)." WHERE filename REGEXP ".$mysql->quote("/".$values->{'clip id'}.".hulu\$"); $mysql->do($query); if($DEBUG) { print "Updating plot for video \"".$values->{'series'}.": ".$values->{'episode number'}.$values->{'episode'}."\"...\n"; } next; } # check if the directory we're writing to exists if(! -d ($downloadDir)) { if(mkdir($downloadDir)) { if($DEBUG) { print "The directory \"$downloadDir\" didn't exist, but we created it for you...\n"; } } else { if($DEBUG) { print "ERROR: Could not create the directory \"$downloadDir\"\n"; } exit(1); } } # create dummy file so MythVideo doesn't delete metadata when updating browse system("touch \"$videoFilename\""); chdir($POSTER_FILES_DIR); system("/usr/bin/wget --quiet ".$values->{'thumbnail'}); # command to use to play the local movie if($PLAYER eq "popout") { $mythtvPlayerCmd = "$BROWSER_EXEC_CMD http://www.hulu.com/stand_alone/".$values->{'content id'}; } else { $mythtvPlayerCmd = "$BROWSER_EXEC_CMD ".$values->{'link'}; } $query="INSERT INTO videometadata (title,plot,userrating,length,filename,coverfile,playcommand,category,showlevel) VALUES(" . $mysql->quote($values->{'episode number'}.$values->{'episode'})."," . $mysql->quote($description)."," . $values->{'rating'}."," . $values->{'length'}."," . $mysql->quote($videoFilename)."," . $mysql->quote($POSTER_FILES_DIR."/".basename($values->{'thumbnail'}))."," . $mysql->quote($mythtvPlayerCmd)."," . "$category_id," . "1)"; if($DEBUG) { print "Adding video \"".$values->{'series'}." ".$values->{'episode number'}.$values->{'episode'}."\" to database...\n"; } $mysql->do($query); # add a copy to recently added if($SHOW_NEW) { $newVideoFilename = $new_videos_abs_dir."/".$values->{'clip id'}.".hulu"; # create dummy file so MythVideo doesn't delete metadata when updating browse system("touch \"$newVideoFilename\""); $query="INSERT INTO videometadata (title,plot,userrating,length,filename,coverfile,playcommand,category,showlevel,inetref) VALUES(" . $mysql->quote($values->{'series'}.": ".$values->{'episode number'}.$values->{'episode'})."," . $mysql->quote($description)."," . $values->{'rating'}."," . $values->{'length'}."," . $mysql->quote($newVideoFilename)."," . $mysql->quote($POSTER_FILES_DIR."/".basename($values->{'thumbnail'}))."," . $mysql->quote($mythtvPlayerCmd)."," . "$category_id," . "1," . "STR_TO_DATE(".$mysql->quote($values->{'pub date'}).", '%a, %e %b %Y'))"; # "DATE(NOW()))"; $mysql->do($query); } } } # remove all expired or unwanted videos $query = "SELECT * FROM videometadata WHERE plot='expired' AND filename REGEXP '.hulu\$'"; $result=$mysql->prepare($query); $result->execute(); if($DEBUG) { print "Removing expired videos...\n"; } while($row = $result->fetchrow_hashref) { $filename = $row->{'filename'}; system("rm \"$filename\""); if($DEBUG) { print "rm \"$filename\"\n"; } $coverfile = $row->{'coverfile'}; system("rm \"$coverfile\""); if($DEBUG) { print "rm \"$coverfile\"\n"; } } $query = "DELETE FROM videometadata WHERE plot='expired' AND filename REGEXP '.hulu\$'"; $mysql->do($query); $result->finish; $mysql->disconnect(); } main();