#!/usr/bin/perl # TODO: use ffmpeg =head1 NAME mythsshimport - transcode and install video files onto a MythTV box =head1 SYNOPSIS mythsshimport file1 [file2 ...] =head1 DESCRIPTION Transcodes video files (AVI, MPEG, MOV, WMV etc.) into MythTV-compatible and PVR-350-optimised MPEG-2 .nuv files, suitable for viewing on a 4/3 screen, then transfers them to the MythTV backend, inserts them into the "recorded programs" listings, and builds seek tables. All this happens on-the-fly, at faster-than-real-time rates; with a recent CPU in the transcoding box, and over an 802.11b wifi home network, you can start the process and start watching the video within 20 seconds, while it is transcoded and transferred in the background. SSH is used as the network transport. If you have the CPU power available on the MythTV backend itself, you can run this script there (as the mythtv user) and it will skip the SSH parts entirely. =head1 REQUIREMENTS - ssh password-less key access from transcode box into mythtv@mythbox (this could be localhost, if you're transcoding on the mythbox). Test using: "ssh mythtv@mythbox echo hi" If you run this script on the mythbox as the mythtv user, this is not required. - mencoder. Tested with 2:0.99+1.0pre7try2+cvs20060117-0ubuntu8 (I swear that's a version string and not just me rolling my head around the keyboard) - MythTV. Tested with MythTV 0.20. - The "contrib/myth.rebuilddatabase.pl" script from the MythTV source tarball, installed on the mythbox in $PATH: download from http://svn.mythtv.org/svn/tags/release-0-20/mythtv/contrib/myth.rebuilddatabase.pl - screen(1) installed on the transcoding box, used to keep the mencoder output readable =head1 INSTALLATION Edit the 'CHANGE THIS' section below for configuration. =head1 TODO - if an error occurs (e.g. read bad block from a DVD) during mencoder use, the mencoder screen will immediately disappear. this is suboptimal. =head1 VERSION Mar 31 2009 jm =cut # --------------------------------------------------------------------------- # CHANGE THIS: # username and hostname of MythTV box to SSH into, or "" for localhost. # Username should be the "mythtv" user on that box, since it's used to # update databases etc. my $MYTHBOX = 'mythtv@potato'; # path to MythTV recordings directory on your MythTV box (RecordFilePrefix # in the MythTV settings db) my $MYTHDIR = "/myth/tv"; # END OF CONFIGURATION STUFF # --------------------------------------------------------------------------- use POSIX qw(strftime); use warnings; use strict; (scalar @ARGV > 0) or die "usage: mythsshimport file"; # always run in 'screen' if (!$ENV{'STY'}) { print "not running in screen. starting now...\n"; exec "screen", $0, @ARGV; die "exec screen $0 @ARGV failed!"; } my $CHANNEL = 1001; # doesn't really have any effect! I don't think my $targetres = "720:576"; # my $targetres = "480:480"; # --------------------------------------------------------------------------- my %mplayer_info = (); my $forcetitle; my $next_is_title; my $test_titling; foreach my $inf (@ARGV) { if ($inf eq '--test-titling') { $test_titling = 1; next; } if ($inf eq '--title') { $next_is_title = 1; next; } if ($next_is_title) { $next_is_title = 0; $forcetitle = $inf; next; } # generate a filename in Myth format my $now = strftime("%Y%m%d%H%M%S", localtime); my $newf = strftime("${CHANNEL}_${now}.nuv", localtime); # convert the input filename into a readable title, removing noise like # filename extension, path, rip type, etc. my $title=$inf; if ($forcetitle) { $title = $forcetitle; } $title =~ s/^.*\///gs; $title =~ s/\.(?:mpeg|mpg|mpe|mp4|avi|asf|wmv|mov|vob)$//igs; $title =~ s/\b(?:ws|pdtv|dsrip|vhsrip|dvdrip|xvid|hdtv).*$//is; $title =~ s/[^-A-Za-z0-9\(\)\[\]]+/ /gs; $title =~ s/ +$//; $title =~ s/^ +//; my $subtitle = ''; # detect subtitles, inferring title/subtitle division using dash position # and "s2e02" series/episode strings # Battlestar Galactica - S01E10 - Hand Of God.avi if ($title =~ s/\s*-\s*(.*)$//) { $subtitle = $1; } # Battlestar Galactica s2 e01 Scattered.avi # Battlestar.Galactica.2x02 Valley of Darkness.avi elsif ($title =~ /^(.*?)\W([^\d\s]*\d+\s*[^\d\s]\d+.*)$/) { $title = $1; $subtitle = $2; } # eh, give up and use the full title else { $subtitle = $title; } $title =~ s/(^|\s)([a-z])/ $1.uc($2) /ge; $subtitle =~ s/(^|\s)([a-z])/ $1.uc($2) /ge; print "Programme: '$title' Subtitle: '$subtitle'\n"; my $subf = $inf; $subf =~ s/\.(?:mpeg|mpg|mpe|mp4|avi|asf|wmv|mov|vob)$/.srt/igs; if (!-f $subf) { $subf = undef; } else { print "With subtitles\n"; } if ($test_titling) { warn "test-title mode; exiting"; next; } my $hostname=`uname -n`; chop $hostname; my $desc = "Transcoded from $inf on $hostname"; # the "answers" for myth.rebuilddatabase.pl's questions; the title info my $answers = "/tmp/answers.in.$$"; open (ANSWERS, ">$answers") or die; print ANSWERS "y\n". "$CHANNEL\n". "$title\n". "$subtitle\n". "$desc\n"; close ANSWERS; # temp files my $tmp = "/tmp/fifo.$$"; my $mencpid; my $copypid; $SIG{INT} = $SIG{TERM} = sub { warn "interrupted!"; kill 15, $mencpid if (defined $mencpid); kill 15, $copypid if (defined $copypid); unlink $tmp; unlink $answers; die "exiting"; }; run("mkfifo $tmp"); my $start = time; my $infdir = ''; my $inffile = $inf; if ($inf =~ /^(.*\/)(.*?)$/) { $infdir = $1; $inffile = $2; } # protect against quotes etc in the filename my $qinf = quotemeta($infdir).quotemeta($inffile); my $subfdir = ''; my $subffile = $subf; my $qsubf; if ($subf) { if ($subf =~ /^(.*\/)(.*?)$/) { $subfdir = $1; $subffile = $2; } $qsubf = quotemeta($subfdir).quotemeta($subffile); } get_mplayer_info($tmp, $qinf); $mencpid = fork(); if ($mencpid == 0) { mencoder_screen_start_pvr350($tmp, $qinf, $qsubf); exit; } # give it time to get underway sleep 1; # start copying over the network $copypid = fork(); if ($copypid == 0) { if ($MYTHBOX) { run("ssh ${MYTHBOX} cat \\> '${MYTHDIR}/$newf' < $tmp"); } else { run("cat > '${MYTHDIR}/$newf' < $tmp"); } exit; } # give it MORE time to get established -- enough data for rebuilddb. # anything less than 20 seconds has caused trouble in the past; # symptom is that the file is not found when you try to # start playback. sleep 20; # insert the program in listings (so we can watch it as it uploads!) my $rebuild = "myth.rebuilddatabase.pl --norename --file $newf ". "--dbhost localhost < $answers"; if ($MYTHBOX) { run("ssh ${MYTHBOX} $rebuild > /dev/null"); } else { run("$rebuild > /dev/null"); } # don't need these anymore unlink $tmp; unlink $answers; print "waiting for transcode/ssh to complete...\n"; # now wait for the mencoder/ssh transfer to complete... waitpid $mencpid, 0; waitpid $copypid, 0; undef $mencpid; undef $copypid; # update seek table now that file is complete if ($MYTHBOX) { run("ssh ${MYTHBOX} mythtranscode --mpeg2 --buildindex --showprogress --infile '${MYTHDIR}/$newf'"); } else { run("mythtranscode --mpeg2 --buildindex --showprogress --infile '${MYTHDIR}/$newf'"); # old: run("mythcommflag --quiet --rebuild -f '${MYTHDIR}/$newf'"); } print "Completed transcode/ssh of $inf at ".scalar(localtime time)."\n". "(Started at ".scalar(localtime $start).")\n"; } exit; # --------------------------------------------------------------------------- sub run { my $cmd = shift; print "[$cmd]\n"; system $cmd; if ($? >> 8 != 0) { warn "command failed: $cmd"; kill 15, $$; # to kill helper procs } } # --------------------------------------------------------------------------- sub get_mplayer_info { my ($outf, $inf) = @_; # run mplayer -identify to grab useful metadata about the input file run("mplayer -vo null -ao null -frames 0 -identify $inf > $outf.id 2>&1"); open (IN, "<$outf.id") or die "mplayer -identify failed"; while () { if (/^(ID_.*?)=(.*)$/) { print; $mplayer_info{$1} = $2; } } close IN; } sub mencoder_screen_start_pvr350 { my ($outf, $inf, $subf) = @_; # The magic bit... # mencoder parameters, optimised for PVR-350 playback. # # MPEG-2 video and audio; 480x480 = NTSC resolution; 23.97 or 25 fps; 4/3 # aspect ratio. # # Bitrates: these seem to work well to avoid video stuttering; however, it # can be too low for other files. (Specifically: "This American Life", s1e1: # 750kbps was low enough to avoid stutter; but left very bad visual # artifacting in BSG, s3e12. 2500kbps with no max was too high for TAL, # though. Using 2500kbps with a strict max limit seems to work well as a # compromise for both. # # to shrink 16/9 video down to 4/3, we add 88 pixel black borders (to fit # widescreen on 4/3 screen; divisible by 8, good for interlaced input). # # -srate 48k to ensure valid MPEG-2 audio output even with odd non-MPEG # input. # # don't use: -noskip -mc 0 -- causes A/V desync! hqdn3d: unnoticeable on most # decent-quality rips despite # http://www.mplayerhq.hu/DOCS/HTML/en/menc-feat-dvd-mpeg4.html . probably # already applied. # # harddup -- I had a note that this causes problems, specifically audio # flutter, but for another file (This American Life, in HD) it was needed to # avoid an "ERROR: SCR:" message and audio desync. # # Run in a screen(1) so the output doesn't mingle with other output here # (mencoder is noisy, and that noise is actually quite useful to see # sometimes) # Figure out correct aspect ratio. # # non-16:9 MOV: # ID_VIDEO_WIDTH=352 # ID_VIDEO_HEIGHT=288 # ID_VIDEO_ASPECT=0.0000 # # 16:9 AVI: # ID_VIDEO_WIDTH=720 # ID_VIDEO_HEIGHT=404 # ID_VIDEO_ASPECT=1.2500 # # TODO: support non-4/3 output screens my %i = %mplayer_info; my $src_16_9 = 1; # default to 16:9 if ($i{ID_VIDEO_WIDTH} && $i{ID_VIDEO_HEIGHT}) { if ($i{ID_VIDEO_WIDTH} / $i{ID_VIDEO_HEIGHT} < 1.5) { $src_16_9 = 0; # aspect ratio is < 1.5, e.g. 4/3 == 1.3333, 16/9 == 1.7777 } } my $vfopts; if ($src_16_9) { $vfopts = "expand=:-88::::,dsize=$targetres,scale=$targetres,harddup"; } else { $vfopts = "scale=$targetres,harddup"; } # Figure out the FPS # # 23.977 FPS (NTSC): # ID_VIDEO_FPS=23.976 # 25 FPS (PAL): # ID_VIDEO_FPS=25 my $ofps = 25; if ($i{ID_VIDEO_FPS} && $i{ID_VIDEO_FPS} > 23.95 && $i{ID_VIDEO_FPS} < 24) { $ofps = '24000/1001'; } if ($subf) { $subf = "-subpos 90 -subwidth 90 -sub $subf"; } else { $subf = ""; } # Jan 13 2008 jm: change vrc_maxrate from 2500 to 4500 run("screen mencoder -of mpeg -oac lavc -ovc lavc ". "-lavcopts acodec=mp2:abitrate=224:vcodec=mpeg2video:vstrict=0:". "keyint=15:vbitrate=2500:vrc_maxrate=4500:vrc_buf_size=917 ". "$subf ". "-srate 48000 ". "-vf $vfopts ". "-mpegopts format=xsvcd:muxrate=15000:vaspect=4/3 ". "-ofps $ofps ". "-o $outf $inf"); # ffmpeg -y -target svcd -f mpeg2video -v -minrate 2500 -maxrate 15000 -acodec mp2 -ab 224k -ar 48000 -i $inf $outf } # --------------------------------------------------------------------------- __DATA__ cp ~/bin/mythsshimport ~/jmason.org/software/scripts/mythsshimport.txt