00001 #!/usr/bin/perl
00002 #
00003 # MythWeb Streaming/Download module
00004 #
00005 # @url $URL: http://svn.mythtv.org/svn/branches/release-0-21-fixes/mythplugins/mythweb/modules/stream/handler.pl $
00006 # @date $Date: 2008-02-27 10:51:48 -0500 (Wed, 27 Feb 2008) $
00007 # @version $Revision: 16289 $
00008 # @author $Author: jarod $
00009 #
00010
00011 # Necessary constants for sysopen
00012 use Fcntl;
00013
00014 # Other includes
00015 use Sys::Hostname;
00016
00017 # Autoflush
00018 $|++;
00019
00020 our $ffmpeg_pid;
00021
00022 # Shutdown cleanup, of various types
00023 $SIG{'TERM'} = \&shutdown_handler;
00024 $SIG{'PIPE'} = \&shutdown_handler;
00025 END {
00026 shutdown_handler();
00027 }
00028 sub shutdown_handler {
00029 kill(1, $ffmpeg_pid) if ($ffmpeg_pid);
00030 }
00031
00032 # Which show are we streaming?
00033 our $chanid = url_param('chanid');
00034 our $starttime = url_param('starttime');
00035 if ($Path[1]) {
00036 $chanid = $Path[1];
00037 $starttime = $Path[2];
00038 $starttime =~ s/\.\w+$
00039 }
00040
00041 # Find ffmpeg
00042 $ffmpeg = '';
00043 foreach my $path (split(/:/, $ENV{'PATH'}.':/usr/local/bin:/usr/bin'), '.') {
00044 if (-e "$path/ffmpeg") {
00045 $ffmpeg = "$path/ffmpeg";
00046 last;
00047 }
00048 elsif ($^O eq 'darwin' && -e "$path/ffmpeg.app") {
00049 $ffmpeg = "$path/ffmpeg.app";
00050 last;
00051 }
00052 }
00053
00054 # Get the basename from the database
00055 my $sh = $dbh->prepare('SELECT basename, title, subtitle
00056 FROM recorded
00057 WHERE starttime=FROM_UNIXTIME(?)
00058 AND recorded.chanid = ?');
00059 $sh->execute($starttime, $chanid);
00060 my ($basename, $title, $subtitle) = $sh->fetchrow_array();
00061 $sh->finish;
00062
00063 # No match?
00064 unless ($basename =~ /\w/) {
00065 print header(),
00066 "Unknown recording requested.\n";
00067 exit;
00068 }
00069
00070 # Find the local file
00071 my $filename;
00072 $sh = $dbh->prepare('SELECT DISTINCT dirname
00073 FROM storagegroup');
00074 $sh->execute();
00075 while (my ($video_dir) = $sh->fetchrow_array()) {
00076 next unless (-e "$video_dir/$basename");
00077 $filename = "$video_dir/$basename";
00078 last;
00079 }
00080 $sh->finish;
00081
00082 unless ($filename) {
00083 print header(),
00084 "$basename does not exist in any recognized storage group directories for this host.";
00085 exit;
00086 }
00087
00088 # Load some conversion settings from the database
00089 $sh = $dbh->prepare('SELECT data FROM settings WHERE value=? AND hostname IS NULL');
00090 $sh->execute('WebFLV_w');
00091 my ($width) = $sh->fetchrow_array;
00092 $sh->execute('WebFLV_vb');
00093 my ($vbitrate) = $sh->fetchrow_array;
00094 $sh->execute('WebFLV_ab');
00095 my ($abitrate) = $sh->fetchrow_array;
00096
00097 $width = 320 unless ($width && $width > 1);
00098 $vbitrate = 256 unless ($vbitrate && $vbitrate > 1);
00099 $abitrate = 64 unless ($abitrate && $abitrate > 1);
00100
00101 # Someday, we can auto-detect height based on aspect ratio
00102 my $height = int($width * 3/4);
00103
00104 # ASX mode?
00105 if ($ENV{'REQUEST_URI'} =~ /\.asx$/i) {
00106 # URI back to this file? We just need to take the current URI and strip
00107 # off the .asx suffix.
00108 my $uri = ($ENV{'HTTPS'} || $ENV{'SERVER_PORT'} == 443)
00109 ? 'https'
00110 : 'http';
00111 my $serverAddr = $ENV{'HTTP_X_FORWARDED_HOST'} || $ENV{'SERVER_NAME'} || $ENV{'SERVER_ADDR'};
00112 $uri .= '://'.$serverAddr.':'.$ENV{'SERVER_PORT'}
00113 .$ENV{'REQUEST_URI'};
00114
00115 $uri =~ s/\.asx$
00116 # Build the ASX file so we can know how long it is
00117 my $file = <<EOF;
00118 <ASX version = "3.0">
00119 <TITLE>$title</TITLE>
00120 <ENTRY>
00121 <TITLE>$title - $subtitle</TITLE>
00122 <AUTHOR>MythTV - MythWeb</AUTHOR>
00123 <COPYRIGHT>GPL</COPYRIGHT>
00124 <REF HREF = "$uri" />
00125 </ENTRY>
00126 </ASX>
00127 EOF
00128 # Print out the HTML headers and the ASX file itself
00129 print header(-type => 'video/x-ms-asf',
00130 -Content_length => length($file),
00131 -Content_disposition => " attachment; filename=\"$title-$subtitle.asx\"",
00132 ),
00133 $file;
00134 exit;
00135 }
00136
00137 # Flash?
00138 elsif ($ENV{'REQUEST_URI'} =~ /\.flvp$/i) {
00139 # URI back to this file? We just need to take the current URI and strip
00140 # off the .flvp suffix.
00141 my $uri = ($ENV{'HTTPS'} || $ENV{'SERVER_PORT'} == 443)
00142 ? 'https'
00143 : 'http';
00144 $uri .= '://'.($ENV{'SERVER_NAME'} or $ENV{'SERVER_ADDR'}).':'.$ENV{'SERVER_PORT'}
00145 .$ENV{'REQUEST_URI'};
00146 $uri =~ s/\.flvp$/\.flv/i;
00147 # Print a page to hold the player
00148 print header();
00149 print <<EOF;
00150 <html>
00151 <head>
00152 </head>
00153 <body>
00154 <embed src="${web_root}tv/flvplayer.swf" width="320" height="260" bgcolor="#FFFFFF"
00155 type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer"
00156 flashvars="file=$uri&autoStart=true" />
00157 </body>
00158 EOF
00159 exit;
00160 }
00161 elsif ($ENV{'REQUEST_URI'} =~ /\.flv$/i) {
00162 # Print the movie
00163 $ffmpeg_pid = open(DATA,
00164 "$ffmpeg -y -i ".shell_escape($filename)
00165 .' -s '.shell_escape("${width}x$height")
00166 .' -r 24 -f flv -ac 2 -ar 11025'
00167 .' -ab '.shell_escape("${abitrate}k")
00168 .' -b '.shell_escape("${vbitrate}k")
00169 .' /dev/stdout 2>/dev/null |'
00170 );
00171 unless ($ffmpeg_pid) {
00172 print header(),
00173 "Can't do ffmpeg: $!";
00174 exit;
00175 }
00176 print header(-type => 'video/x-flv');
00177 my $buffer;
00178 while (read DATA, $buffer, 262144) {
00179 print $buffer;
00180 }
00181 close DATA;
00182 exit;
00183 }
00184
00185 # File size
00186 my $size = -s $filename;
00187
00188 # Zero bytes?
00189 if ($size < 1) {
00190 print header(),
00191 "$basename is an empty file.";
00192 exit;
00193 }
00194
00195 # File type
00196 my $type = 'text/html';
00197 my $suffix = '';
00198 if ($basename =~ /\.mpe?g$/) {
00199 $type = 'video/mpeg';
00200 $suffix = '.mpg';
00201 }
00202 elsif ($basename =~ /\.nuv$/) {
00203 $type = 'video/nuppelvideo';
00204 $suffix = '.nuv';
00205 }
00206 else {
00207 print header(),
00208 "Unknown video type requested: $basename\n";
00209 exit;
00210 }
00211
00212 # Download filename
00213 my $name = $basename;
00214 if ($name =~ /^\d+_\d+\.\w+$/) {
00215 $name = $title;
00216 if ($subtitle =~ /\w/) {
00217 $name .= " - $subtitle";
00218 }
00219 $name .= $suffix;
00220 }
00221
00222 # Open the file for reading
00223 unless (sysopen DATA, $filename, O_RDONLY) {
00224 print header(),
00225 "Can't read $basename: $!";
00226 exit;
00227 }
00228
00229 # Binmode, in case someone is running this from Windows.
00230 binmode DATA;
00231
00232 # Requested a range?
00233 my $start = 0;
00234 my $end = $size;
00235 my $total_size = $size;
00236 if ($ENV{'HTTP_RANGE'}) {
00237 # Figure out the size of the requested chunk
00238 ($start, $end) = $ENV{'HTTP_RANGE'} =~ /bytes\W+(\d*)-(\d*)\W*$/;
00239 $start ||= 0;
00240 if ($end < 1 || $end > $size) {
00241 $end = $size;
00242 }
00243 $size = $end - $start;
00244 }
00245
00246 # Print the header
00247 print header(-type => $type,
00248 -Content_length => $size,
00249 -Accept_Ranges => 'bytes',
00250 -Content_disposition => " attachment; filename=\"$name\"",
00251 -Content_Range => "bytes $start-$end/$total_size"
00252 );
00253
00254 # Seek to the requested position
00255 sysseek DATA, $start, 0;
00256
00257 # Print the content to the browser
00258 my $buffer;
00259 while (sysread DATA, $buffer, 262144) {
00260 print $buffer;
00261 }
00262 close DATA;
00263
00264 ###############################################################################
00265
00266 # Escape a parameter for safe use in a commandline call
00267 sub shell_escape {
00268 $str = shift;
00269 $str =~ s/'/'\\''/sg;
00270 return "'$str'";
00271 }
00272
00273 # Return true
00274 1;
00275