Skip to content

Instantly share code, notes, and snippets.

@managementboy
Created October 11, 2011 14:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save managementboy/1278201 to your computer and use it in GitHub Desktop.
Save managementboy/1278201 to your computer and use it in GitHub Desktop.
pseudostreaming for mythweb with ffmpeg -ss instead of tail
#!/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