Created
October 11, 2011 14:20
-
-
Save managementboy/1278201 to your computer and use it in GitHub Desktop.
pseudostreaming for mythweb with ffmpeg -ss instead of tail
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/perl | |
# | |
# MythWeb Streaming/Download module | |
# | |
# @url $URL$ | |
# @date $Date$ | |
# @version $Revision$ | |
# @author $Author$ | |
# | |
use POSIX qw(ceil floor); | |
# round to the nearest even integer | |
sub round_even { | |
my ($in) = @_; | |
my $n = floor($in); | |
return ($n % 2 == 0) ? $n : ceil($in); | |
} | |
our $ffmpeg_pid; | |
our $ffmpeg_pgid; | |
# Shutdown cleanup, of various types | |
$ffmpeg_pgid = setpgrp(0,0); | |
$SIG{'TERM'} = \&shutdown_handler; | |
$SIG{'PIPE'} = \&shutdown_handler; | |
END { | |
shutdown_handler(); | |
} | |
sub shutdown_handler { | |
kill(1, $ffmpeg_pid) if ($ffmpeg_pid); | |
kill(-1, $ffmpeg_pgid) if ($ffmpeg_pgid); | |
} | |
# Find ffmpeg | |
$ffmpeg = ''; | |
foreach my $path (split(/:/, $ENV{'PATH'}.':/usr/local/bin:/usr/bin'), '.') { | |
if (-e "$path/mythffmpeg") { | |
$ffmpeg = "$path/mythffmpeg"; | |
last; | |
} | |
if (-e "$path/ffmpeg") { | |
$ffmpeg = "$path/ffmpeg"; | |
last; | |
} | |
elsif ($^O eq 'darwin' && -e "$path/ffmpeg.app") { | |
$ffmpeg = "$path/ffmpeg.app"; | |
last; | |
} | |
} | |
# Load some conversion settings from the database | |
$sh = $dbh->prepare('SELECT data FROM settings WHERE value=? AND hostname IS NULL'); | |
$sh->execute('WebFLV_w'); | |
my ($width) = $sh->fetchrow_array; | |
$sh->execute('WebFLV_vb'); | |
my ($vbitrate) = $sh->fetchrow_array; | |
$sh->execute('WebFLV_ab'); | |
my ($abitrate) = $sh->fetchrow_array; | |
$sh->finish(); | |
# auto-detect height based on aspect ratio | |
$sh = $dbh->prepare('SELECT data FROM recordedmarkup WHERE chanid=? ' . | |
'AND starttime=FROM_UNIXTIME(?) AND type=30 ' . | |
'AND data IS NOT NULL ORDER BY mark LIMIT 1'); | |
$sh->execute($chanid,$starttime); | |
$x = $sh->fetchrow_array; # type = 30 | |
$sh->finish(); | |
$sh = $dbh->prepare('SELECT data FROM recordedmarkup WHERE chanid=? ' . | |
'AND starttime=FROM_UNIXTIME(?) AND type=31 ' . | |
'AND data IS NOT NULL ORDER BY mark LIMIT 1'); | |
$sh->execute($chanid,$starttime); | |
$y = $sh->fetchrow_array if ($x); # type = 31 | |
$sh->finish(); | |
if (!$x || !$y || $x <= 720) { # <=720 means SD | |
$sh = $dbh->prepare('SELECT recordedmarkup.type, ' . | |
'recordedmarkup.data '. | |
'FROM recordedmarkup ' . | |
'WHERE recordedmarkup.chanid = ? ' . | |
'AND recordedmarkup.starttime = FROM_UNIXTIME(?) ' . | |
'AND recordedmarkup.type IN (10, 11, 12, 13, 14) ' . | |
'GROUP BY recordedmarkup.type ' . | |
'ORDER BY SUM((SELECT IFNULL(rm.mark, recordedmarkup.mark) ' . | |
' FROM recordedmarkup AS rm ' . | |
' WHERE rm.chanid = recordedmarkup.chanid ' . | |
' AND rm.starttime = recordedmarkup.starttime ' . | |
' AND rm.type IN (10, 11, 12, 13, 14) ' . | |
' AND rm.mark > recordedmarkup.mark ' . | |
' ORDER BY rm.mark ASC LIMIT 1)- recordedmarkup.mark) DESC ' . | |
'LIMIT 1'); | |
$sh->execute($chanid,$starttime); | |
$aspect = $sh->fetchrow_hashref; | |
$sh->finish(); | |
if( $aspect->{'type'} == 10 ) { | |
$x = $y = 1; | |
} elsif( $aspect->{'type'}== 11 ) { | |
$x = 4; $y = 3; | |
} elsif( $aspect->{'type'}== 12 ) { | |
$x = 16; $y = 9; | |
} elsif( $aspect->{'type'}== 13 ) { | |
$x = 2.21; $y = 1; | |
} elsif( $aspect->{'type'}== 14 ) { | |
$x = $aspect->{'data'}; $y = 10000; | |
} else { | |
$x = 4; $y = 3; | |
} | |
} | |
$height = round_even($width * ($y/$x)); | |
$width = 320 unless ($width && $width > 1); | |
$height = 240 unless ($height && $height > 1); | |
$vbitrate = 256 unless ($vbitrate && $vbitrate > 1); | |
$abitrate = 64 unless ($abitrate && $abitrate > 1); | |
if ("$ENV{'REQUEST_URI'}" =~ /start\=(\d+)$/i) { | |
$start = $1; } | |
else { | |
$start = 0; } | |
warn("Specified start time: ".$start); | |
# Are we still recording, and how long should the program be? | |
$sh = $dbh->prepare('SELECT UNIX_TIMESTAMP(endtime) - UNIX_TIMESTAMP(starttime), endtime-now() | |
FROM recorded | |
WHERE starttime=FROM_UNIXTIME(?) | |
AND recorded.chanid = ?'); | |
$sh->execute($starttime, $chanid); | |
our ($lengthSec, $inprogress) = $sh->fetchrow_array(); | |
$sh->finish; | |
# Calculate the offset given the start time | |
$frameNum = $start * 24; | |
warn("Looking for frame number: ".$frameNum); | |
$sh = $dbh->prepare('SELECT count(*), offset | |
FROM recordedseek | |
WHERE chanid=? | |
AND starttime=FROM_UNIXTIME(?) | |
AND mark >= ? | |
AND mark < ?'); | |
$sh->execute($chanid,$starttime, $frameNum, $frameNum + 12); | |
our($numRows, $byteOffset) = $sh->fetchrow_array(); | |
$sh->finish(); | |
if ($numRows < 1) { | |
warn("Got no rows, need to wait for recording to catchup to offset, sleeping"); | |
# print header(), "Seeked beyond the file"; | |
# exit; | |
} | |
warn("Looked up a byte offset of: ".$byteOffset); | |
my $ffmpeg_command = $ffmpeg | |
.' -ss ' | |
.$start | |
.' -y' | |
.' -i '.shell_escape($filename) | |
.' -s '.shell_escape("${width}x${height}") | |
.' -g 30' | |
.' -r 15' | |
.' -qmin 8' | |
.' -qmax 25' | |
.' -f flv' | |
.' -deinterlace' | |
.' -async 2' | |
.' -ac 2' | |
.' -ar 22050' | |
.' -ab '.shell_escape("${abitrate}k") | |
.' -b '.shell_escape("${vbitrate}k") | |
.' /dev/stdout 2>/dev/null |'; | |
warn("Command to be used is: ".$ffmpeg_command); | |
# Print the movie | |
$ffmpeg_pid = open(DATA, $ffmpeg_command); | |
warn("PID is: ".$ffmpeg_pid); | |
unless ($ffmpeg_pid) { | |
print header(), | |
"Can't do ffmpeg: $!\n${ffmpeg_command}"; | |
exit; | |
} | |
# Guess the filesize based on duration and bitrate. This allows for progressive download behavior | |
warn("Length in seconds from DB is: ".$lengthSec); | |
if ($inprogress > 0) { | |
warn("Recording is in progress"); | |
} else { | |
warn("Recording is finished"); | |
$dur = `ffmpeg -i $filename 2>&1 | grep "Duration" | cut -d ' ' -f 4 | sed s/,//`; | |
if ($dur && $dur =~ /\d*:\d*:.*/) { | |
warn("Accurate duration from ffmpeg is: ".$dur); | |
@times = split(':',$dur); | |
$lengthSec = $times[0]*3600+$times[1]*60+$times[2] - $start; | |
} | |
} | |
warn("Using a duration of: ".$lengthSec." seconds"); | |
if ($lengthSec > 0) { #This should always be true since we use the DB runtime. | |
#Get the content size as a whole interger. | |
$size = int(1.05*$lengthSec*($vbitrate*1024+$abitrate*1024)/8); | |
warn("Determined content size is: ".$size); | |
print header(-type => 'video/x-flv','Content-Length' => $size); | |
} else { | |
print header(-type => 'video/x-flv'); | |
} | |
my $buffer; | |
if (read DATA, $buffer, 53) { | |
print $buffer; | |
read DATA, $buffer, 8; | |
$durPrint = reverse pack("d",$lengthSec); | |
print $durPrint; | |
while (read DATA, $buffer, 262144) { | |
print $buffer; | |
} | |
} | |
close DATA; | |
1; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment