PSP Encode
I have been having loads of fun with my PSP slim. I researched playing video on this device, turns out it supports both standard MPEG4 and thanks to Sony being pretty keen on AVCHD, it now supports AVC MP4 file formats, which gives superior quality playback and/or compression.
Since I live in Australia, I wanted to convert both my widescreen camcorder files to view on my PSP and DVDs as well. We use the PAL standard, at 25 frames per second these files will not play on my Japanese PSP, which can only support NTSC frame rates of 23.976 and 29.97 fps.
So, I have developed a script that uses mencoder and ffmpeg, mainly to convert DVDs with subtitles to the PSP.
Features:
- Supports creation of full 480×272 screen size AVC MP4 videos.
- Supports subtitle rendering.
- Easy to use, just run it a directory with free space, with or without a filename – no filename means access the dvd device.
- Auto frame rate conversion – will convert 25 fps input to 23.976 by remuxing slower audio (a bit slow, 2 conversions).
- Auto track selection – at present just converts the longest track only – for movies.
- Adds a little gamma to the output, PSPs need it for video I think.
Requirements:
- Linux, prefer Debian or Ubuntu I think, should work on others too.
- mplayer/mencoder
- ffmpeg
- sox
I am using PSP slim firmware 3.71 – if you want to try this script, you probably need something recent like this or newer.
Usage:
Just make a new directory on a location with plenty of space – if it needs to down convert from PAL, this is a slow and space consuming process. It currently takes 2 conversions. It uses threads to speed things up a little, could take a few hours to complete on a 1.8Ghz dual core. So have around 10Gb free when you start.
Change to this new directory.
Now Simply run it with a video file or it will default to using the device /dev/cdrom.
It should give you some feedback about the 2 stages or 1 stage it needs to do.
When its done, you should have a file called output.mp4 in that directory. Maybe a log file or 2. Just move that mp4 to your PSPs video folder, maybe change the filename too. You should be able to watch it now.
Its still very basic, its just designed to work without much brilliant feedback at this stage.
#!/usr/bin/perl # script to automate the encoding of PSP AVC videos with subtitles # I release this under GPLv2 you know what that means. #$tvaspect = "scale"; # Plays on PSP in 'full screen' mode and on a 16:9 TV $tvaspect = "expand"; # Plays on PSP in 'zoom' mode and on a regular 4:3 TV $profile = 1; # 2 different profiles for AVC MP4, 1=regular and 2=large. $debug = 0; # 1 for messages and a short test, 2 for no final run # 3 for test run with output #$noise = ",noise=5t"; #$noise = ",noise=3t"; #$noise = ""; $gamma = "eq=4:0"; $slang = "-slang en"; $alang = "-alang zh"; $bframe = "i_certify_that_my_video_stream_does_not_use_b_frames:"; #$bframe = ""; if ($profile == 1) { $tvaspect = "scale"; } $disaspect = 1.7647; # this is used for calculating the subtitles position. $embed = 1; # embedd fonts - only appropriate for encoding $subexp = ":"; $output = "output.mp4"; $ffmpeglog = "ffmpeg.log"; $poll = 2; $rfsw = 720; # rate frame size - the width of the first stage in 2 stage encode $rfsh = 544; # rate frame size - the height of the first stage in 2 stage encode sub execdebug { my $cmd = join (" ",@_); if ($debug > 0) { print "$cmd\n"; } # my $out = spacepad(132,$cmd); if ($debug < 2) { $error = `sh -c \"$cmd\" 2>&1`; } if ($debug > 2) { system $cmd; } return $error; } # first arg is the ref num, rest is command sub execmencode { my $cmd = join (" ",@_[2..$#_]); my $out = ""; if ($debug > 0) { print "$cmd\n"; $out = "| tee stage1.log "; } if ($debug == 2) { return; } open (FILE,"$cmd 2>&1$out |"); print "Stage ".@_[0].".\n"; if ($debug == 3) { print; } $skip = 100; $i = 0; while (<FILE>) { $i++; if (($i % $skip) != 1) { next; } if (/^Pos:/) { # print; @row = split(/\(|\)/,$_); if ($complete != @row[1]) { $complete = @row[1]; print "Stage ".@_[0].": $complete\n"; } } } close FILE; print "Stage ".@_[0]." complete.\n"; } sub execffmpeg { my $cmd = join (" ",@_[2..$#_]); unless ($pid = fork) { unless (fork) { `sh -c \"$cmd\" > $ffmpeglog 2>&1`; exit; } exit; } my $found = 0; for ($i=0;$i<16;$i++) { if (-s $ffmpeglog) { $found = 1; last; } sleep 1; } if ($found == 0) { print "Error, cannot find log to analyse.\n"; exit -1; } if ($debug > 0) { print "$cmd\n"; } if ($debug == 2) { return; } if ($debug == 3) { print; } print "Stage ".@_[0].".\n"; while (1) { $_ = `tail -n 1 $ffmpeglog`; if (/^video/) { last; } my @row = split (/frame=/); my @info = split (/fps=/,@row[$#row]); $complete = (int((@info[0]/@_[1])*100)); if ($complete != $prev) { $prev = $complete; print "Stage ".@_[0].": ".$complete."%\n"; } sleep $poll; } } #$hori = 1024; #$hori = 720; $hori = 480; $vert = int(($hori/$disaspect)+0.5); if ($profile == 1) { $bitrate = 592; # $bitrate = 296; $hori = 480; $vert = 272; } elsif ($profile == 2) { $bitrate = 1184; # $bitrate = 592; $hori = 720; $vert = 480; } %titles = (); if (@ARGV[0] eq "") { $dev = "/dev/cdrom"; } else { $dev = @ARGV[0]; } my $maxlen = 0; if (!($dev =~ /\.avi$|\.mpg$|\.mp4$|\.mov$/i)) { # now extract info about this dvd with a cropdetect call $cmd = "mplayer -identify -monitoraspect $disaspect -zoom -vf cropdetect -frames 1 -ss 0:07:00 -vo aa -ao null -dvd-device \"$dev\" dvd://1"; if ($debug > 0) { print "$cmd\n"; } $detect = `sh -c \"$cmd\" 2>&1`; my %titles = (); my $crop = ""; my $aspect_internal = 0; my $aspect_viewing = 0; my $fps = 0; @detectdata = split(/\n/,$detect); foreach (@detectdata) { if (/^ID_DVD_TITLE/) { my @row = split(/\=/,$_); if (/^ID_DVD_TITLES/) { $numtitles = @row[1]; } else { $titles{@row[0]} = @row[1]; } } } # extract title lengths my $longest = 1; for ($i=1;$i<=$numtitles;$i++) { my $len = $titles{"ID_DVD_TITLE_".$i."_LENGTH"}; if ($len > $maxlen) { $maxlen = $len; $longest = $i; } } $dev = "-dvd-device \"$dev\" dvd://$longest"; $cmd = "mplayer -identify -monitoraspect $disaspect -zoom -vf cropdetect -frames 40 -ss 0:05:00 -vo aa -ao null $dev"; } else { $fileflag = 1; my @row = split (/\./,$dev); if (@row[1] ne "mp4") { $output = "\"".@row[0].".mp4\""; } $dev = "\"$dev\""; if ($dev eq $output) { $output =~ s/\.mp4/_PSP\.mp4/; } $cmd = "mplayer -identify -monitoraspect $disaspect -zoom -vf cropdetect -frames 40 -ss 0:05:30 -vo aa -ao null $dev"; } if ($debug > 0) { print "$cmd\n"; } $detect = `sh -c \"$cmd\" 2>&1`; @detectdata = split(/\n/,$detect); foreach (@detectdata) { if (/CROP/) { my @rowa = split(/\-vf\ crop\=/); my @rowb = split(/\)/,@rowa[1]); $crop = @rowb[0]; } if (/^VO:/) { my @rowa = split(/\s+/,$_); my @rowb = split(/x/,@rowa[2]); if (@rowb[1] > 0) { $aspect_internal = @rowb[0]/@rowb[1]; } } if (/^ID_VIDEO_ASPECT/) { my @row = split(/\=/,$_); $aspect_viewing = @row[1]; } if (/^ID_VIDEO_FPS/) { my @row = split(/\=/,$_); $fps = @row[1]*1; } if (/^number\ of\ subtitles/) { my @row = split(/\s+/,$_); $subs = @row[$#row]; if (($subs > 0)&&($slang ne "")) { $subexp = "0:0"; } } } my $frames = int($maxlen*$fps); if ($aspect_internal == 0) { print "could not determine the aspects for this DVD,\n"; exit -1; } my @row = split (/\:/,$crop); my $aspect_crop = (@row[0]/@row[1]); my $aspect_original = ($aspect_crop*$aspect_viewing)/$aspect_internal; $subpos = int(105-230*((1.96-$disaspect)*(1.01-((1+((1.78-$disaspect)/1.6))*($disaspect/$aspect_original))))); # this is a magic equation I just made up to help position subtitles if ($debug > 0 ){ print "ao: $aspect_original\n"; } if ($subpos < 81) { $subpos = 81; } if ($subpos > 99) { $subpos = 99; } my $mencode = ""; if ($fps == 25) { $mencode = "mencoder -sws 9 -vf $gamma -noautoexpand -oac pcm -ovc lavc -lavcopts vcodec=mpeg4:vbitrate=7800:keyint=250:threads=2 $slang $alang -af volnorm=1:.19 -sub-bg-alpha 255 -spualign 2 -spuaa 4 -spugauss 1.0 -ffactor 10 -o test.avi"; } else { $mencode = "mencoder -sws 9 $slang $alang -af volnorm=2:.19,channels=2 -ofps 24000/1001 -vf $gamma$noise -oac faac -faacopts br=192:mpeg=4:object=2:raw -ovc x264 -x264encopts bitrate=$bitrate:global_header:partitions=all:trellis=1:vbv_maxrate=".int(($bitrate*1.2973)+.5).":vbv_bufsize=2000:level_idc=30:threads=auto -of lavf -lavfopts $bframe"."format=psp -sub-bg-alpha 255 -spualign 2 -spuaa 4 -spugauss 0.2 -ffactor 10 -o $output"; } $len = $maxlen; if ($debug > 0) { $len = 60; $mencodelen = " -ss 00:25:00 -endpos $len"; } else { $mencodelen = " -endpos $len"; } $frames = int($fps*$len); my $finlen = " -endpos $len"; if ($fps == 25) { $finlen = " -endpos ".((($len*25000)/23976)); } if ($fileflag > 0) { $finlen = ""; $frames = 1; } else { $mencode .= $mencodelen; } my $pull = ""; my $cropfilt = ""; if ($fps == 25) { if ($aspect_original > $disaspect) { $scalex = $rfsw; $scaley = int(($rfsw/$disaspect)+.5); $cropfilt = "-vf-add scale=$scalex:".(int((($scalex/$aspect_original)+.5)/2)*2).",expand=$scalex:$scaley:$subexp:$embed"; } else { $scalex = int(($rfsh/$disaspect)+.5); $scaley = $rfsh; $cropfilt = "-vf-add scale=".(int(($scaley*$aspect_original)+.5)).":$scaley,expand=$scalex:$scaley:$subexp:$embed"; } } else { if ($aspect_original > $disaspect) { # $cropfilt = "-vf-add scale=$hori:-2,expand=$hori"."::$subexp:$embed:$disaspect,expand=$hori:$vert"; $cropfilt = "-vf-add scale=$hori:-2,expand=$hori:$vert:$subexp:$embed"; } else { # $cropfilt = "-vf-add scale=-2:$vert,expand=:$vert:$subexp:$embed:$disaspect,expand=$hori:$vert"; $cropfilt = "-vf-add scale=-2:$vert,expand=$hori:$vert:$subexp:$embed"; } } if ($fps == 29.97) { $pull = "-vf-pre filmdint"; } #if ($aspect_original > $disaspect) { # $cropfilt = "-vf-add scale=$hori:-2,expand=$hori:$vert:$subexp:$embed"; #} $cmd = "$mencode -subpos $subpos $cropfilt -vf-pre crop=$crop -vf-pre harddup $pull $dev -msglevel all=9"; execdebug "rm -f test.avi"; execmencode (1,$frames,$cmd); if ($fps == 25) { execdebug "rm -f track.wav"; execdebug "mplayer -vc null -vo null -ao pcm:fast:file=track.wav test.avi"; execdebug "rm -f track.raw"; execdebug "sox track.wav -r 50050 track.raw resample -qs"; execdebug "rm -f track.wav"; execdebug "sox -r 48000 -s -w -c 2 track.raw track.wav"; execdebug "rm -f track.raw"; execdebug "rm -f $output"; execdebug "rm -f $ffmpeglog"; # system "ffmpeg -i test.avi -vcodec rawvideo -pix_fmt yuv420p -f rawvideo - | mencoder -sws 9 -af volnorm=2:.19 -vf harddup,$tvaspect=$scalex:$scaley -aspect $disaspect -oac faac -faacopts br=192:mpeg=4:object=2:raw -ovc x264 -x264encopts bitrate=$bitrate:global_header:partitions=all:trellis=1:vbv_maxrate=".int(($bitrate*1.2973)+.5).":vbv_bufsize=2000:level_idc=30:threads=auto -of lavf -lavfopts i_certify_that_my_video_stream_does_not_use_b_frames:format=psp $limit -o $output -audiofile track.wav -demuxer rawvideo -rawvideo fps=23.976:i420:w=$hori:h=$vert -"; sleep 1; my $out = " > /dev/null"; if ($debug > 0) { $out = " > stage2.log"; } execffmpeg (2,$frames,"ffmpeg -i test.avi -v 9 -vcodec rawvideo -pix_fmt yuv420p -f rawvideo - | mencoder -sws 9 -af volnorm=2:.19 -vf harddup$noise,$tvaspect=$hori:$vert -aspect $disaspect -oac faac -faacopts br=192:mpeg=4:object=2:raw -ovc x264 -x264encopts bitrate=$bitrate:global_header:partitions=all:trellis=1:vbv_maxrate=".int(($bitrate*1.2973)+.5).":vbv_bufsize=2000:level_idc=30:threads=auto -of lavf -lavfopts $bframe"."format=psp$finlen $limit -o $output -audiofile track.wav -demuxer rawvideo -rawvideo fps=23.976:i420:w=$scalex:h=$scaley -$out 2>&1"); if ($debug == 0) { execdebug "rm -f track.wav"; execdebug "rm -f test.avi"; } execdebug "rm -f $ffmpeglog"; } waitpid($pid,0); print "complete!\n"; |
Discussion Area - Leave a Comment
You must be logged in to post a comment.