Hello all,
Here is a modified version of myth2ipod I wrote to rip DVDs to H.264/AAC videos and create an RSS feed for iTunes. I use my KnoppMyth box as a video jukebox rather than a PVR, and I have a dedicated 500GB drive with ripped DVD ISOs on it. This script takes an ISO, mounts it, and rips the video to H.264/AAC. I also put in options to transcode other videos (.avi, etc.) but I haven't tried that yet.
You could easily modify it to directly rip DVDs, but they'd have to be unencrypted, so it's better to just rip to ISO first. The ISO must be indexed by Video Manager to grab the title, plot synopsis, etc. for the RSS feed, and the script is interactive so it must be run from a terminal.
It uses your choice of either ffmpeg or HandBrake (not included in KM by default, but easily added, just go to handbrake.m0k.org and download the Linux command line binary). I added a line to the XML for cover art, do "ln -s /myth/video/.covers /myth/ipodfeed/covers" for it to work, but it doesn't seem to show up properly in iTunes anyway.
Here is the code, comments, suggestions, improvements, etc are welcome:
Code:
#!/usr/bin/perl
use DBI;
my $handbrake_options_ipod_low = "-e x264b30 -b 700 -B 160 -R 48 -E faac -f mp4 -w 320 -x keyint=300:keyint-min=30:bframes=0:cabac=0:ref=1:vbv-maxrate=768:vbv-bufsize=2000:analyse=all:me=umh:subme=6:no-fast-pskip=1 -m";
my $handbrake_options_ipod_hi = "-e x264b30 -b 1500 -B 160 -R 48 -E faac -f mp4 -w 640 -x keyint=300:keyint-min=30:bframes=0:cabac=0:ref=1:vbv-maxrate=1500:vbv-bufsize=2000:analyse=all:me=umh:subme=6:no-fast-pskip=1 -m";
my $handbrake_options_standard = "-e x264 -b 1500 -B 160 -R 48 -E faac -f mp4 -x ref=2:bframes=2:subme=5:me=umh -m -p -2 -T -w 320";
my $handbrake_options_classic = "-b 1000 -B 160 -R 48 -E faac -f mp4 -w 320";
my $ffmpeg_options = "-f mp4 -r 29.97 -vcodec mpeg4 -maxrate 768 -b 700 -qmin 3 -qmax 5 -bufsize 4096 -g 250 -acodec aac -ar 44100 -ab 160 -async 1 -s 320x240 -aspect 4:3";
my $ffmpeg_options_pal = "-f mp4 -r 25 -vcodec mpeg4 -maxrate 768 -b 700 -qmin 3 -qmax 5 -bufsize 4096 -g 250 -acodec aac -ar 44100 -ab 160 -async 1 -s 320x240 -aspect 4:3";
$handbrake = $handbrake_options_ipod_low;
my $portable = "ipod";
# paths for RSS feed, leave these unless you've modified your KM install
my $feedfile = "/myth/ipodfeed/feed.php";
my $feedpath = "/myth/ipodfeed/";
# default path where ISO/video is, you can also specify on the cmd line
my $dvdisopath = "/myth/video/dvd/";
# path where ISO will be mounted
my $mountpath = "/mnt/loop";
# path to www files, leave this unless you've modified your KM install
my $wwwloc = "/var/www/";
my $host = "PUT YOUR HOSTNAME HERE";
my $feedurl = "http://$host/ipodfeed/";
sub DisplayUsage {
print "usage: dvd2ipod <iso file> <encoder> <directory>\n\nDirectory is optional, if not specified dvd2ipod will look for the file in $dvdisopath\n\nEncoder can be one of:\n\thandbrake: Use HandBrake to convert to H.264/AAC\n\tffmpeg: Use ffmpeg to convert to H.264/AAC\n\tffmpegpal: Use ffmpeg for PAL source\n\n";
exit;
}
if($ARGV[1] ne "handbrake" && $ARGV[1] ne "ffmpeg" && $ARGV[1] ne "ffmpegpal") {
print "Invalid encoder\n\n";
DisplayUsage();
exit;
} else {
if($ARGV[1] eq "ffmpegpal") {
$ffmpeg_options = $ffmpeg_options_pal;
our $encoder = "ffmpeg";
} else {
our $encoder = $ARGV[1];
}
}
if($ARGV[2]) {
$dvdisopath = $ARGV[2];
}
my $debug = 1;
if(!($ARGV[0]) || !($ARGV[1])) {!
DisplayUsage();
}
our $file = $ARGV[0];
our ($basefile, $fileext) = split(/\./,$file);
if($fileext == "iso") {
$is_iso = 1;
}
our $mountfile = $dvdisopath.$file;
if (! -e $mountfile) {
print "$file does not exist in $dvdisopath\n";
exit;
}
print "Encoding file...\n";
Encode4Portable();
if(! -e $feedfile) {
print "(Re)generating RSS feed...\n";
GenerateRSSFeed();
}
sub Encode4Portable {
if($is_iso == 1) {
system("mount -o loop $mountfile $mountpath");
}
PrepSQLRead();
$db_handle = DBI->connect("dbi:mysql:database=$db_name:host=$db_host", $db_user, $db_pass) or die "Cannot connect to database\n";
$sql = "SELECT title,plot,category,coverfile,rating,year,length FROM videometadata WHERE filename='$mountfile'";
if($debug == 1) { print "SQL: $sql\n"; }
$statement = $db_handle->prepare($sql) or die "Couldn't prepare query $sql: $DBI::errstr\n";
$statement->execute() or die "Couldn't execute query $sql: $DBI::errstr\n";
$row_ref = $statement->fetchrow_hashref();
$title = $row_ref->{title};
$description = $row_ref->{plot};
$category = $row_ref->{category};
$coverfile = $row_ref->{coverfile};
$rating = $row_ref->{rating};
$year = $row_ref->{year};
$length = $row_ref->{length};
$filename = $mountfile;
printf("Starting encoder...\n");
EncodeIt();
printf("Encoding complete. Generating RSS...\n");
CreateItemXML();
printf("XML created for $filename\n");
printf("Cleaning up...\n");
if($is_iso == 1) {
system("umount $mountpath");
}
system("rm -f $feedpath$basefile.temp.mp4");
}
sub EncodeIt {
if($is_iso == 1) {
if($encoder eq "handbrake") {
system("/usr/local/bin/HandBrakeCLI -i $mountpath/VIDEO_TS -t 0");
$titletorip = &promptUser("\nSelect a title to rip: ","1");
$outfile = $basefile."_title$titletorip".".temp.mp4";
$outpath = $feedpath.$outfile;
$finaloutpath = $feedpath.$basefile."_title$titletorip".".".$portable.".mp4";
$cmd = "/usr/local/bin/HandBrakeCLI -i $mountpath/VIDEO_TS -o $outpath -t $titletorip $handbrake";
}
if($encoder eq "ffmpeg") {
$outfile = $basefile.".temp.mp4";
$outpath = $feedpath.$outfile;
$finaloutpath = $feedpath.$basefile.".".$portable.".mp4";
$cmd = "cat $mountpath/VIDEO_TS/*.VOB | ffmpeg -i - $ffmpeg_options $outpath";
}
} else {
$outfile = $basefile.".temp.mp4";
$outpath = $feedpath.$outfile;
$finaloutpath = $feedpath.$basefile.".".$portable.".mp4";
if($encoder eq "handbrake") {
$cmd = "/usr/local/bin/HandBrakeCLI -i $dvdisopath/$file -o $outpath $handbrake";
}
if($encoder eq "ffmpeg") {
$cmd = "ffmpeg -i $dvdisopath/$file $ffmpeg_options $outpath";
}
}
if($debug == 1) { print "Encoder: $cmd\n"; }
if(system($cmd)) { print "Encoder failed.\n"; }
$cmd = "/usr/local/bin/MP4Box -add $outpath $finaloutpath";
if($debug == 1) { print "MP4Box: $cmd\n"; }
if(system($cmd)) { print "MP4Box cleanup failed.\n"; }
}
sub CreateItemXML {
$usetitle = &promptUser("\nEnter a title (hit return for \"$title\"): ",$title);
$usedescription = &promptUser("\nEnter a description (hit return for \"$description\"): ",$description);
$finalouturl = $feedurl.$basefile.".".$portable.".mp4";
my ($a, $b, $c, $d, $cover) = split("/", $coverfile);
my $coverurl = $feedurl."covers/".$cover;
open(ITEM, ">$feedpath$basefile.$portable.xml");
print ITEM "<item>\n";
print ITEM "<title>".&encodeForXML($usetitle)."</title>\n";
print ITEM "<itunes:author>MythTV</itunes:author>\n";
print ITEM "<author>MythTV</author>\n";
print ITEM "<itunes:category text=\"Movies\"></itunes:category>\n";
print ITEM "<comments>".&encodeForXML($file)."</comments>\n";
print ITEM "<description>".&encodeForXML($usedescription)."</description>\n";
print ITEM "<pubDate>".&encodeForXML(`date`)."</pubDate>\n";
print ITEM "<enclosure url=\"".&encodeForXML($finalouturl)."\" type=\"video/quicktime\" />\n";
print ITEM "<itunes:duration></itunes:duration>\n";
print ITEM "<itunes:keywords>".&encodeForXML($title)."</itunes:keywords>\n";
print ITEM "<itunes:image href=\"".&encodeForXML($coverurl)."\" />\n";
print ITEM "</item>\n";
print "$filename added to feed.\n";
close(ITEM);
return 0;
}
sub GenerateRSSFeed {
open(RSS, ">$feedfile");
print RSS "<?php\n";
print RSS "header(\"Content-Type: text/xml\");\n";
print RSS "echo \"<?xml version=\\\"1.0\\\" encoding=\\\"UTF-8\\\"?>\"; ?>\n";
print RSS "<rss xmlns:itunes=\"http://www.itunes.com/DTDs/Podcast-1.0.dtd\" version=\"2.0\">\n";
print RSS "<channel>\n";
print RSS "<title>MythTV - <? if (\$_GET['title'] == \"\") { \$title = \"*\"; echo \"Recorded Programs\"; }\n";
print RSS "else { \$title = \$_GET['title']; echo str_replace(\"_\",\" \",\$_GET['title']); } ?> </title>\n";
print RSS "<itunes:author>MythTV - myth2ipod</itunes:author>\n";
print RSS "<link>".&encodeForXML($feedurl)."</link>\n";
print RSS "<itunes:subtitle>Transcoded recording for your iPod Video.</itunes:subtitle>\n";
print RSS "<itunes:summary>Myth TV Recorded Programs for the iPod v.1</itunes:summary>\n";
print RSS "<description>Myth TV Recorded Programs for the iPod v.1</description>\n";
print RSS "<itunes:owner>\n";
print RSS "<itunes:name>MythTV</itunes:name>\n";
print RSS "<itunes:email>mythtv\@localhost</itunes:email>\n";
print RSS "</itunes:owner>\n";
print RSS "<itunes:explicit>No</itunes:explicit>\n";
print RSS "<language>en-us</language>\n";
print RSS "<copyright>Copyright 2005.</copyright>\n";
print RSS "<webMaster>mythtv\@localhost</webMaster>\n";
print RSS "<itunes:image href=\"http://myth2ipod.com/mythipod_200.jpg\" />\n";
print RSS "<itunes:category text=\"TV Shows\"></itunes:category>\n";
print RSS "<category>TV Shows</category>\n";
print RSS "<itunes:image href=\"http://myth2ipod.com/mythipod_200.jpg\"/>";
print RSS "<image>";
print RSS "<url>http://myth2ipod.com/mythipod_200.jpg</url>\n";
print RSS "<title>MythTV 2 iPod</title>\n";
print RSS "<link>".&encodeForXML($feedurl)."</link>\n";
print RSS "<width>200</width>\n";
print RSS "<height>200</height>\n";
print RSS "</image>\n";
print RSS "<? foreach (glob(\$title\.\"*\.$portable\.xml\") as \$file) {include \$file;} ?>\n";
print RSS "</channel>\n";
print RSS "</rss>\n";
close(RSS);
if($debug == 1){ print "I created a feed file, was I supposed to?\n"};
return 0;
}
#
# This code taken from one of the mythlink.sh scripts to get MySQL information
#
sub PrepSQLRead{
# Get the hostname of this machine
$hostname = `hostname`;
chomp($hostname);
# Read the mysql.txt file in use by MythTV.
# could be in a couple places, so try the usual suspects
my $found = 0;
my @mysql = ('/usr/local/share/mythtv/mysql.txt',
'/usr/share/mythtv/mysql.txt',
'/etc/mythtv/mysql.txt',
'/usr/local/etc/mythtv/mysql.txt',
"$ENV{HOME}/.mythtv/mysql.txt",
'mysql.txt'
);
foreach my $file (@mysql) {
next unless (-e $file);
$found = 1;
open(CONF, $file) or die "Unable to open $file: $!\n\n";
while (my $line = <CONF>) {
# Cleanup
next if ($line =~ /^\s*#/);
$line =~ s/^str //;
chomp($line);
# Split off the var=val pairs
my ($var, $val) = split(/\=/, $line, 2);
next unless ($var && $var =~ /\w/);
if ($var eq 'DBHostName') {
$db_host = $val;
}
elsif ($var eq 'DBUserName') {
$db_user = $val;
}
elsif ($var eq 'DBName') {
$db_name = $val;
}
elsif ($var eq 'DBPassword') {
$db_pass = $val;
}
# Hostname override
elsif ($var eq 'LocalHostName') {
$hostname = $val;
}
}
close CONF;
}
die "Unable to locate mysql.txt: $!\n\n" unless ($found && $db_host);
return 0;
}
sub promptUser {
local($promptString,$defaultValue) = @_;
if ($defaultValue) {
print $promptString, "[", $defaultValue, "]: ";
} else {
print $promptString, ": ";
}
$| = 1; # force a flush after our print
$_ = <STDIN>; # get the input from STDIN (presumably the keyboard)
chomp;
if ("$defaultValue") {
return $_ ? $_ : $defaultValue; # return $_ if it has a value
} else {
return $_;
}
}
# substitute for XML entities
sub encodeForXML {
local $result;
$result = $_[0];
$result =~ s/&/&/g;
$result =~ s/</</g;
$result =~ s/>/>/g;
$result;
}