From 1f780b78f0c1f12457a28e7f166c7a9875af33ad Mon Sep 17 00:00:00 2001 From: Cecil Hugh Watson Date: Wed, 4 Feb 2009 22:45:34 -0800 Subject: Adds MythVodka to LinHES. --- abs/extra-testing/mythvodka/PKGBUILD | 33 + abs/extra-testing/mythvodka/hulu_grabber.sh | 16 + abs/extra-testing/mythvodka/mythvodka.diff | 3915 +++++++++++++++++++++++++++ 3 files changed, 3964 insertions(+) create mode 100644 abs/extra-testing/mythvodka/PKGBUILD create mode 100755 abs/extra-testing/mythvodka/hulu_grabber.sh create mode 100644 abs/extra-testing/mythvodka/mythvodka.diff diff --git a/abs/extra-testing/mythvodka/PKGBUILD b/abs/extra-testing/mythvodka/PKGBUILD new file mode 100644 index 0000000..2dbb9ec --- /dev/null +++ b/abs/extra-testing/mythvodka/PKGBUILD @@ -0,0 +1,33 @@ +# $Id: PKGBUILD 5936 2008-07-21 20:24:16Z thomas $ +# Maintainer: Cecil Watson + +pkgname=mythvodka +pkgver=0.7 +pkgrel=4 +pkgdesc="MythVodka (Video On Demand Killer App) is a plugin for MythTV allowing streaming of BBC iPlayer, Hulu, HTTP and NZB content." +arch=('i686' 'x86_64') +license=('GPL2') +url="http://code.google.com/p/mythvodka/" +depends=('rtmpdump') +source=('http://mythvodka.googlecode.com/files/mythvodka.07.tar.gz' 'mythvodka.diff' 'hulu_grabber.sh') + +build() { + patch -p0 < mythvodka.diff + cd $startdir/src/mythvodka/mythvodka + rm -fr Makefile + qmake mythvodka.pro + make + mkdir -p $startdir/pkg/usr/lib/mythtv/plugins/ + cp libmythvodka.so $startdir/pkg/usr/lib/mythtv/plugins/ + strip --strip-unneeded $startdir/pkg/usr/lib/mythtv/plugins/libmythvodka.so + mkdir -p $startdir/pkg/usr/share/mythtv/themes/default/ + cp streams-ui.xml $startdir/pkg/usr/share/mythtv/themes/default/ + mkdir -p $startdir/pkg/usr/share/mythtv/themes/default-wide/ + cp theme-wide/streams-ui.xml $startdir/pkg/usr/share/mythtv/themes/default-wide/ + mkdir -p $startdir/pkg/usr/bin + chmod a+x ../scripts/* + cp -p ../scripts/* $startdir/pkg/usr/bin + mkdir -p $startdir/pkg/etc/cron.daily/ + chmod 755 ../../hulu_grabber.sh + cp ../../hulu_grabber.sh $startdir/pkg/etc/cron.daily/ +} diff --git a/abs/extra-testing/mythvodka/hulu_grabber.sh b/abs/extra-testing/mythvodka/hulu_grabber.sh new file mode 100755 index 0000000..d285379 --- /dev/null +++ b/abs/extra-testing/mythvodka/hulu_grabber.sh @@ -0,0 +1,16 @@ +#! /bin/bash +log=/var/log/mythtv/hulu_grabber.log +out=/var/tmp/huludata.xml +rm -f $log +echo "Start on `date`" >>$log 2>&1 +time /usr/bin/gethulu.pl $out.new >>$log 2>&1 +stat=$? +if [ $stat -ne 0 ]; then + echo "Bad status $stat from gethulu.pl" >>$log 2>&1 + exit 1 +fi +rm -f $out.old +mv $out $out.old +mv $out.new $out +echo "Done on `date`" >>$log 2>&1 +ls -lh $out >>$log 2>&1 diff --git a/abs/extra-testing/mythvodka/mythvodka.diff b/abs/extra-testing/mythvodka/mythvodka.diff new file mode 100644 index 0000000..7b6ebb1 --- /dev/null +++ b/abs/extra-testing/mythvodka/mythvodka.diff @@ -0,0 +1,3915 @@ +diff -ruaN mythvodka.orig/mythvodka/streamsui.cpp mythvodka/mythvodka/streamsui.cpp +--- mythvodka.orig/mythvodka/streamsui.cpp 2009-01-06 00:18:35.000000000 +0000 ++++ mythvodka/mythvodka/streamsui.cpp 2009-02-05 06:23:10.000000000 +0000 +@@ -729,7 +729,7 @@ + + MythProgressDialog *buffer_progress; + buffer_progress = new MythProgressDialog( +- QObject::tr("Nascar Sucks / Hillary For President / Man Love Rules Ok"), bufferSize, true, this, SLOT(cancelPressed())); ++ QObject::tr("Your video is being loaded..."), bufferSize, true, this, SLOT(cancelPressed())); + + QFile file(filename); + +diff -ruaN mythvodka.orig/scripts/get_iplayer mythvodka/scripts/get_iplayer +--- mythvodka.orig/scripts/get_iplayer 2009-01-06 19:11:24.000000000 +0000 ++++ mythvodka/scripts/get_iplayer 2009-02-05 06:28:36.000000000 +0000 +@@ -3,16 +3,17 @@ + # get_iplayer + # + # Lists and downloads BBC iPlayer audio and video streams +-# ++# + Downloads ITVplayer Catch-Up video streams ++# + # Author: Phil Lewis + # Email: iplayer (at sign) linuxcentre.net + # Web: http://linuxcentre.net/iplayer + # License: GPLv3 (see LICENSE.txt) + # + # Other credits: +-# RTMP additions: Andrej Stepanchuk ++# RTMP additions: Andrej Stepanchuk + # +-my $version = 1.04; ++my $version = 1.17; + # + # Help: + # ./get_iplayer --help +@@ -29,13 +30,16 @@ + # * Index/Download live radio streams w/schedule feeds to assist timing + # * Podcasts for 'local' stations are missing (only a handful). They use a number of different station ids which will involve reading html to determine rss feed. + # * Remove all rtsp/mplayer/lame/tee dross when realaudio streams become obselete (not quite yet) +-# * Cope with radio via rtmp + # * Stdout mode with rtmp +-# ++# * Do subtitle downloading after programme download so that rtmp auth doesn't timeout ++ + # Known Issues: + # * In ActivePerl/windows downloaded iPhone video files do not get renamed (remain with .partial.mov) + # * vlc does not quit after downloading an rtsp N95 video stream (ctrl-c is required) - need a --play-and-quit option if such a thing exists +-# * flv conversions from rtmp downloads aren't quite right yet. A/V sync issues? ++# * rtmpdump (v1.2) of flashaudio fails at end of stream => non-zero exit code ++# * if ffmpeg trys to convert flv to mp3 it succeeds but => non-zero exit code ++# * Some rtmpdump downloads always give a non-zero exit code regardless of success (using a min-filesize workaround for now) ++# * resuming a flashaudio download fails + + use Env qw[@PATH]; + use Fcntl; +@@ -62,10 +66,17 @@ + my %opt_cmdline = (); # a hash of which options came from the cmdline rather than the options files + my %opt_file = (); # a hash of which options came from the options files rather than the cmdline + +-# Print to STDERR if not quiet unless verbose or debug ++# Print to STDERR/STDOUT if not quiet unless verbose or debug + sub logger(@) { + # Make sure quiet can be overridden by verbose and debug options +- print STDERR $_[0] if (! $opt{quiet}) || $opt{verbose} || $opt{debug}; ++ if ( $opt{verbose} || $opt{debug} || ! $opt{quiet} ) { ++ # Only send messages to STDERR if pvr or stdout options are being used. ++ if ( $opt{stdout} || $opt{pvr} || $opt{stderr} ) { ++ print STDERR $_[0]; ++ } else { ++ print STDOUT $_[0]; ++ } ++ } + } + + sub usage { +@@ -74,7 +85,7 @@ + Search Programmes: get_iplayer [] [ ...] + Download files: get_iplayer --get [] ... + get_iplayer --pid [] +-Stream Downloads: get_iplayer --stdout [] | mplayer -cache 2048 - ++Stream Downloads: get_iplayer --stdout [] | mplayer -cache 3072 - + Update get_iplayer: get_iplayer --update + + Search Options: +@@ -83,9 +94,10 @@ + --channel Narrow search to matched channel(s) + --category Narrow search to matched categories + --versions Narrow search to matched programme version(s) ++ --exclude Narrow search to exclude matched programme names + --exclude-channel Narrow search to exclude matched channel(s) + --exclude-category Narrow search to exclude matched catogories +- --type Only search in these types of programmes (tv is default) ++ --type Only search in these types of programmes: radio, tv, podcast, all, itv (tv is default) + --since Limit search to programmes added to the cache in the last N hours + + Display Options: +@@ -95,26 +107,25 @@ + -i, --info Show full programme metadata (only if number of matches < 50) + --list Show a list of available categories/channels for the selected type and exit + --hide Hide previously downloaded programmes ++ --streaminfo Returns all of the media stream urls of the programme(s) + + Download Options: + -g, --get Download matching programmes + -x, --stdout Additionally stream to STDOUT (so you can pipe output to a player) + -p, --proxy Web proxy URL spec + --partial-proxy Works around for some broken web proxies (try this extra option if your proxy fails) +- --pid Download an arbitrary pid that does not appear in the index ++ --pid Download an arbitrary pid that does not appear in the index (itv: for itv programmes) + --force-download Ignore download history (unsets --hide option also) +- --realaudio Use the RealAudio radio stream and not the MP3 stream +- --mp3audio Use the MP3 radio stream for radio and dont fallback to the RealAudio stream ++ --amode ,,... Audio Download mode(s): iphone,flashaudio,realaudio (default: iphone,flashaudio,realaudio) ++ --vmode ,,... Video Download mode(s): iphone,rtmp,flashhigh,flashnormal,flashwii,n95_wifi (default: iphone,flashhigh,flashnormal) + --wav In radio realaudio mode output as wav and don't transcode to mp3 +- --raw In radio/realaudio or iPhone/video mode don't transcode or change the downloaded stream in any way +- --n95 In TV mode download/stream low quality Nokia N95 H.264 stream (alpha) +- --rtmp In TV mode download/stream high quality flash stream (alpha) ++ --raw Don't transcode or change the downloaded stream in any way (i.e. radio/realaudio, rtmp/flv, iphone/mov) + --bandwidth In radio realaudio mode specify the link bandwidth in bps for rtsp streaming (default 512000) + --subtitles In TV mode, download subtitles into srt/SubRip format if available + --suboffset Offset the subtitle timestamps by the specified number of milliseconds + --version-list Override the version of programme to download (e.g. '--version-list signed,default') + -t, --test Test only - no download (will show programme type) +- ++ + PVR Options: + --pvr Runs the PVR download using all saved PVR searches (intended to be run every hour from cron etc) + --pvradd Add the current search terms to the named PVR search +@@ -139,18 +150,18 @@ + -f, --flush, --refresh Refresh cache + -e, --expiry Cache expiry in seconds (default 4hrs) + --symlink Create symlink to once we have the header of the download +- --fxd Create Freevo FXD XML in specified file +- --mythtv Create Mythtv streams XML in specified file ++ --fxd Create Freevo FXD XML of matching programmes in specified file ++ --mythtv Create Mythtv streams XML of matching programmes in specified file + --xml-channels Create freevo/Mythtv menu of channels -> programme names -> episodes + --xml-names Create freevo/Mythtv menu of programme names -> episodes + --xml-alpha Create freevo/Mythtv menu sorted alphabetically by programme name +- --html Create basic HTML index of programmes in specified file ++ --html Create basic HTML index of matching programmes in specified file + --mplayer Location of mplayer binary ++ --ffmpeg Location of ffmpeg binary + --lame Location of lame binary + --id3v2 Location of id3v2 binary + --rtmpdump Location of rtmpdump binary +- --vlc Location of vlc binary +- --streaminfo Returns all of the media stream urls of the programme(s) ++ --vlc Location of vlc or cvlc binary + -v, --verbose Verbose + -u, --update Update get_iplayer if a newer one exists + -h, --help Help +@@ -192,14 +203,17 @@ + Getopt::Long::Configure ("bundling"); + # cmdline opts take precedence + GetOptions( ++ "amode=s" => \$opt_cmdline{amode}, + "bandwidth=n" => \$opt_cmdline{bandwidth}, + "category=s" => \$opt_cmdline{category}, + "channel=s" => \$opt_cmdline{channel}, + "c|command=s" => \$opt_cmdline{command}, + "debug" => \$opt_cmdline{debug}, ++ "exclude=s" => \$opt_cmdline{exclude}, + "exclude-category=s" => \$opt_cmdline{excludecategory}, + "exclude-channel=s" => \$opt_cmdline{excludechannel}, + "expiry|e=n" => \$opt_cmdline{expiry}, ++ "ffmpeg=s" => \$opt_cmdline{ffmpeg}, + "file-prefix|fileprefix=s" => \$opt_cmdline{fileprefix}, + "flush|refresh|f" => \$opt_cmdline{flush}, + "force-download" => \$opt_cmdline{forcedownload}, +@@ -238,7 +252,7 @@ + "rtmpdump=s" => \$opt_cmdline{rtmpdump}, + "save" => \$save, + "since=n" => \$opt_cmdline{since}, +- "stdout|stream|x" => \$opt_cmdline{stdout}, ++ "stdout|x" => \$opt_cmdline{stdout}, + "streaminfo" => \$opt_cmdline{streaminfo}, + "subdirs|subdir|s" => \$opt_cmdline{subdir}, + "suboffset=n" => \$opt_cmdline{suboffset}, +@@ -253,6 +267,7 @@ + "versions=s" => \$opt_cmdline{versions}, + "verbose|v" => \$opt_cmdline{verbose}, + "vlc=s" => \$opt_cmdline{vlc}, ++ "vmode=s" => \$opt_cmdline{vmode}, + "wav" => \$opt_cmdline{wav}, + "whitespace|ws|w" => \$opt_cmdline{whitespace}, + "xml-channels|fxd-channels" => \$opt_cmdline{xmlchannels}, +@@ -269,7 +284,7 @@ + save_options_file( $optfile ) if $save; + + +-# Global vars ++### Global vars ### + + # Programme data structure + # $prog{$pid} = { +@@ -283,7 +298,7 @@ + # 'thumbnail' => + # 'channel => + # 'categories' => +-# 'type' => ++# 'type' => + # 'timeadded' => + # 'longname' => , + # 'version' => +@@ -292,11 +307,35 @@ + # 'fileprefix' => + # 'ext' => + #}; ++ ++# Define cache file format ++my @cache_format = qw/index type name pid available episode versions duration desc channel categories thumbnail timeadded guidance/; ++ ++# List of all types ++my @all_prog_types = qw/ tv radio podcast itv /; ++ ++# Ranges of numbers used in the indicies for each programme type ++my %index_range; ++$index_range{tv}{min} = 1; ++$index_range{tv}{max} = 9999; ++$index_range{radio}{min} = 10001; ++$index_range{radio}{max} = 19999; ++$index_range{podcast}{min} = 20001; ++$index_range{podcast}{max} = 29999; ++$index_range{itv}{min} = 100001; ++$index_range{itv}{max} = 199999; ++# Set maximun index number ++my $max_index; ++for (@all_prog_types) { ++ $max_index = $index_range{$_}{max} if $index_range{$_}{max} > $max_index; ++} + my %prog; ++my %type; + my %pids_history; + my %index_pid; # Hash to obtain pid given an index + my $now; + my $childpid; ++my $min_download_size = 1000000; + + # Static URLs + my $channel_feed_url = 'http://feeds.bbc.co.uk/iplayer'; # /$channel/list/limit/400 +@@ -396,6 +435,18 @@ + 'bbc_radio_jersey' => 'radio|BBC Jersey', + }; + ++$channels{itv} = { ++ 'crime' => 'itv|TV Classics Crime Drama', ++ 'perioddrama' => 'itv|TV Classics Period Drama', ++ 'familydrama' => 'itv|TV Classics Family Drama', ++ 'documentary' => 'itv|TV Classics Documentaries', ++ 'comedy' => 'itv|TV Classics Comedy', ++ 'kids' => 'itv|TV Classics Children\'s TV', ++ 'soaps' => 'itv|TV Classics Soaps', ++ '/' => 'itv|TV Classics', ++}; ++ ++ + # User Agents + my %user_agent = ( + coremedia => 'Apple iPhone v1.1.1 CoreMedia v1.0.0.3A110a', +@@ -410,6 +461,7 @@ + + # Other Non-option dependant vars + my %cachefile = ( ++ 'itv' => "${profile_dir}/itv.cache", + 'tv' => "${profile_dir}/tv.cache", + 'radio' => "${profile_dir}/radio.cache", + 'podcast' => "${profile_dir}/podcast.cache", +@@ -430,6 +482,7 @@ + my $mplayer; + #my $mencoder; + my $ffmpeg; ++my $ffmpeg_opts; + my $rtmpdump; + my $mplayer_opts; + my $lame; +@@ -480,7 +533,7 @@ + # Display default options + display_default_options(); + # For each PVR search +- for my $name ( sort {$a <=> $b} keys %pvrsearches ) { ++ for my $name ( sort {lc $a cmp lc $b} keys %pvrsearches ) { + # Ignore if this search is disabled + if ( $pvrsearches{$name}{disable} ) { + logger "\nSkipping disabled PVR Search '$name'\n" if $opt{verbose}; +@@ -519,27 +572,29 @@ + # Option dependant vars + %download_dir = ( + 'tv' => $opt{outputtv} || $opt{output} || $ENV{IPLAYER_OUTDIR} || '.', ++ 'itv' => $opt{outputtv} || $opt{output} || $ENV{IPLAYER_OUTDIR} || '.', + 'radio' => $opt{outputradio} || $opt{output} || $ENV{IPLAYER_OUTDIR} || '.', + 'podcast' => $opt{outputpodcast} || $opt{output} || $ENV{IPLAYER_OUTDIR} || '.', + ); +- # Default to type=tv +- $opt{type} = 'tv' if ! $opt{type}; +- # Expand 'all' to various prog types +- $opt{type} = 'tv,radio,podcast' if $opt{type} =~ /(all|any)/i; ++ + # Ensure lowercase +- $opt{type} = lc( $opt{type} ); ++ $opt{type} = lc( $opt{type} ); ++ # Expand 'all' to comma separated list all prog types ++ $opt{type} = join(',', @all_prog_types) if $opt{type} =~ /(all|any)/i; ++ # Hash to store specified prog types ++ %type = (); ++ $type{$_} = 1 for split /,/, $opt{type}; ++ # Default to type=tv if no type option is set ++ $type{tv} = 1 if keys %type == 0; + $cache_secs = $opt{expiry} || 14400; + $mplayer = $opt{mplayer} || 'mplayer'; + $mplayer_opts = '-nolirc'; + $mplayer_opts .= ' -really-quiet' if $opt{quiet}; +- # Assume mencoder/ffmpeg is in the same path as mplayer +-# $mencoder = $mplayer; +-# $mencoder =~ s|^(.*?)mplayer|$1mencoder|g; +- $ffmpeg = $mplayer; +- $ffmpeg =~ s|^(.*?)mplayer|$1ffmpeg|g; ++ $ffmpeg = $opt{ffmpeg} || 'ffmpeg'; ++ $ffmpeg_opts = ''; + $lame = $opt{lame} || 'lame'; +- $lame_opts = '-f '; +- $lame_opts .= '--quiet ' if $opt{quiet}; ++ $lame_opts = '-f'; ++ $lame_opts .= ' --quiet ' if $opt{quiet}; + $vlc = $opt{vlc} || 'cvlc'; + $vlc_opts = '-vv'; + $id3v2 = $opt{id3v2} || 'id3v2'; +@@ -562,11 +617,11 @@ + exit 1; + } + +- # Disable rtmp mode if rtmpdump does not exist +- if ( $opt{rtmp} && ! exists_in_path($rtmpdump)) { +- logger "\nERROR: Required program $rtmpdump does not exist (see http://linuxcentre.net/getiplayer/installation and http://linuxcentre.net/getiplayer/download), falling back to iphone mode\n"; +- delete $opt{rtmp}; +- } ++ # Backward compatability options - to be removed eventually ++ $opt{vmode} = 'rtmp' if $opt{rtmp}; ++ $opt{vmode} = 'n95_wifi' if $opt{n95}; ++ $opt{amode} = 'realaudio' if $opt{realaudio}; ++ $opt{amode} = 'iphone' if $opt{mp3audio}; + + # Web proxy + $proxy_url = $opt{proxy} || $ENV{HTTP_PROXY} || $ENV{http_proxy} || ''; +@@ -585,8 +640,21 @@ + } + } + +- # Get arbitrary pid ++ # Get prog by arbitrary pid (then exit) + if ( $opt{pid} ) { ++ ++ # Temporary hack to get 'ITV Catch-up' downloads specified as --pid itv: ++ $type{itv} = 1 if $opt{pid} =~ m{^itv:(.+?)$}; ++ if ( $type{itv} ) { ++ exit 1 if ( ! $opt{streaminfo} ) && check_download_history( $opt{pid} ); ++ # Remove leading itv: tag (backwards compat) ++ $opt{pid} =~ s/^itv:(.+?)$/$1/ig; ++ # Force prog type to itv ++ $prog{$opt{pid}}{type} = 'itv'; ++ download_programme( $opt{pid} ); ++ exit 0; ++ } ++ + # Remove any url parts from the pid + $opt{pid} =~ s/^.*(b0[a-z,0-9]{6}).*$/$1/g; + # Retry loop +@@ -594,21 +662,29 @@ + my $retries = 3; + my $retcode; + exit 1 if ( ! $opt{streaminfo} ) && check_download_history( $opt{pid} ); +- while ( $count < $retries && ($retcode = download_programme( $opt{pid} )) eq 'retry' ) { +- logger "WARNING: Retrying download for PID $opt{pid}\n"; +- $count++; ++ for ($count = 1; $count <= $retries; $count++) { ++ $retcode = download_programme( $opt{pid} ); ++ return 0 if $retcode eq 'skip'; ++ if ( $retcode eq 'retry' && $count < $retries ) { ++ logger "WARNING: Retrying download for PID $opt{pid}\n"; ++ } else { ++ $retcode = 1 if $retcode eq 'retry'; ++ last; ++ } + } + # Add to history, tag and Run post download command if download was successful + if ($retcode == 0) { + add_to_download_history( $opt{pid} ); + tag_file( $opt{pid} ); + run_user_command( $opt{pid}, $opt{command} ) if $opt{command}; +- } ++ } elsif (! $opt{test}) { ++ logger "ERROR: Failed to download PID $opt{pid}\n"; ++ } + exit 0; + } + + # Get stream links from BBC iplayer site or from cache (also populates all hashes) specified in --type option +- get_links( $_ ) for split /,/, $opt{type}; ++ get_links( $_ ) for keys %type; + + # List elements (i.e. 'channel' 'categories') if required and exit + if ( $opt{list} ) { +@@ -616,18 +692,13 @@ + exit 0; + } + +- # Write HTML and XML files if required +- create_html( sort {$a <=> $b} keys %index_pid ) if $opt{html}; +- create_xml( $opt{fxd}, sort {$a <=> $b} keys %index_pid ) if $opt{fxd}; +- create_xml( $opt{mythtv}, sort {$a <=> $b} keys %index_pid ) if $opt{mythtv}; +- + # Parse remaining args + my @match_list; + for ( @search_args ) { + chomp(); +- +- # If Numerical value < 30000 +- if ( /^[\d]+$/ && $_ < 30000) { ++ ++ # If Numerical value < $max_index ++ if ( /^[\d]+$/ && $_ <= $max_index) { + push @match_list, $_; + + # If PID then find matching programmes with this PID +@@ -649,24 +720,29 @@ + # Go get the cached data for other programme types if the index numbers require it + my %require; + for ( @match_list ) { +- $require{tv} = 1 if $_ >= 1 && $_ < 10000 && ( ! $require{tv} ) && $opt{type} !~ /tv/; +- $require{radio} = 1 if $_ >= 10000 && $_ < 20000 && ( ! $require{radio} ) && $opt{type} !~ /radio/; +- $require{podcast} = 1 if $_ >= 20000 && $_ < 30000 && ( ! $require{podcast} ) && $opt{type} !~ /podcast/; ++ for my $types ( @all_prog_types ) { ++ $require{$types} = 1 if $_ >= $index_range{$types}{min} && $_ <= $index_range{$types}{max} && ( ! $require{$types} ) && ( ! $type{$types} ); ++ } + } ++ + # Get extra required programme caches + logger "INFO: Additionally getting cached programme data for ".(join ', ', keys %require)."\n" if %require > 0; + # Get stream links from BBC iplayer site or from cache (also populates all hashes) + for (keys %require) { + # Get $_ stream links + get_links( $_ ); +- # Add new prog types to the type option +- $opt{type} .= ",$_"; ++ # Add new prog types to the type list ++ $type{$_} = 1; + } +- + # Display list for download + logger "Matches:\n" if @match_list; + @match_list = list_progs( @match_list ); + ++ # Write HTML and XML files if required (with search options applied) ++ create_html( @match_list ) if $opt{html}; ++ create_xml( $opt{fxd}, @match_list ) if $opt{fxd}; ++ create_xml( $opt{mythtv}, @match_list ) if $opt{mythtv}; ++ + # Do the downloads based on list of index numbers if required + if ( $opt{get} || $opt{stdout} ) { + for (@match_list) { +@@ -681,16 +757,27 @@ + logger "ERROR: No PID for index $_ (try using --type option ?)\n"; + next; + } +- while ( $count < $retries && $pid && ($retcode = download_programme( $pid )) eq 'retry' ) { +- logger "WARNING: Retrying download for '$prog{$pid}{name} - $prog{$pid}{episode}'\n"; +- $count++; ++ for ($count = 1; $count <= $retries; $count++) { ++ $retcode = download_programme( $pid ); ++ last if $retcode eq 'skip'; ++ if ( $retcode eq 'retry' && $count < $retries ) { ++ logger "WARNING: Retrying download for '$prog{$pid}{name} - $prog{$pid}{episode}'\n"; ++ } else { ++ $retcode = 1 if $retcode eq 'retry'; ++ last; ++ } + } + # Add to history, tag file, and run post download command if download was successful +- if ($retcode eq '0') { ++ if ($retcode == 0) { + add_to_download_history( $pid ); + tag_file( $pid ); + run_user_command( $pid, $opt{command} ) if $opt{command}; + pvr_report( $pid ) if $opt{pvr}; ++ # Next match if 'skip' was returned ++ } elsif ( $retcode eq 'skip' ) { ++ last; ++ } elsif (! $opt{test}) { ++ logger "ERROR: Failed to download '$prog{$pid}{name} - $prog{$pid}{episode}'\n"; + } + } + } +@@ -702,16 +789,11 @@ + + # Lists progs given an array of index numbers, also returns an array with non-existent entries removed + sub list_progs { +- my $ua; ++ my $ua = create_ua('desktop'); + my @checked; + my %names; + # Setup user agent for a persistent connection to get programme metadata + if ( $opt{info} ) { +- $ua = LWP::UserAgent->new; +- $ua->timeout([$lwp_request_timeout]); +- $ua->proxy( ['http'] => $proxy_url ); +- $ua->agent( $user_agent{desktop} ); +- $ua->conn_cache(LWP::ConnCache->new()); + # Truncate array if were lisiting info and > $info_limit entries are requested - be nice to the beeb! + if ( $#_ >= $info_limit ) { + $#_ = $info_limit - 1; +@@ -733,18 +815,7 @@ + push @checked, $_; + if ( $opt{info} ) { + my %metadata = get_pid_metadata( $ua, $pid ); +- logger "\nPid:\t\t$metadata{pid}\n"; +- logger "Index:\t\t$metadata{index}\n"; +- logger "Type:\t\t$metadata{type}\n"; +- logger "Duration:\t$metadata{duration}\n"; +- logger "Channel:\t$metadata{channel}\n"; +- logger "Available:\t$metadata{available}\n"; +- logger "Expires:\t$metadata{expiry}\n"; +- logger "Versions:\t$metadata{versions}\n"; +- logger "Guidance:\t$metadata{guidance}\n"; +- logger "Categories:\t$metadata{categories}\n"; +- logger "Description:\t$metadata{desc}\n"; +- logger "Player:\t\t$metadata{player}\n"; ++ display_metadata( \%metadata, qw/ pid index type duration channel available expiry versions guidance categories desc player / ); + } + } + logger "\n"; +@@ -758,8 +829,9 @@ + # Display a line containing programme info (using long, terse, and type options) + sub list_prog_entry { + my ( $pid, $prefix, $tree ) = ( @_ ); +- my $type = ''; +- $type = "$prog{$pid}{type}, " if $opt{type} !~ /^(tv|radio|podcast)$/i; ++ my $prog_type = ''; ++ # Show the type field if >1 type has been specified ++ $prog_type = "$prog{$pid}{type}, " if keys %type > 1; + my $name; + # If tree view + if ( $opt{tree} ) { +@@ -768,20 +840,21 @@ + } else { + $name = "$prog{$pid}{name} - "; + } +- # Remove some info depending on type ++ # Remove some info depending on prog_type + my $optional; + $optional = ", '$prog{$pid}{channel}', $prog{$pid}{categories}, $prog{$pid}{versions}" if $prog{$pid}{type} eq 'tv'; ++ $optional = ", '$prog{$pid}{channel}'" if $prog{$pid}{type} eq 'itv'; + $optional = ", '$prog{$pid}{channel}', $prog{$pid}{categories}" if $prog{$pid}{type} eq 'radio'; + $optional = ", '$prog{$pid}{available}', '$prog{$pid}{channel}', $prog{$pid}{categories}" if $prog{$pid}{type} eq 'podcast'; +- logger "\n${type}$prog{$pid}{name}\n" if $opt{tree} && ! $tree; ++ logger "\n${prog_type}$prog{$pid}{name}\n" if $opt{tree} && ! $tree; + # Display based on output options + if ( $opt{long} ) { + my @time = gmtime( time() - $prog{$pid}{timeadded} ); +- logger "${prefix}$prog{$pid}{index}:\t${type}${name}$prog{$pid}{episode}${optional}, $time[7] days $time[2] hours ago - $prog{$pid}{desc}\n"; ++ logger "${prefix}$prog{$pid}{index}:\t${prog_type}${name}$prog{$pid}{episode}${optional}, $time[7] days $time[2] hours ago - $prog{$pid}{desc}\n"; + } elsif ( $opt{terse} ) { +- logger "${prefix}$prog{$pid}{index}:\t${type}${name}$prog{$pid}{episode}\n"; ++ logger "${prefix}$prog{$pid}{index}:\t${prog_type}${name}$prog{$pid}{episode}\n"; + } else { +- logger "${prefix}$prog{$pid}{index}:\t${type}${name}$prog{$pid}{episode}${optional}\n"; ++ logger "${prefix}$prog{$pid}{index}:\t${prog_type}${name}$prog{$pid}{episode}${optional}\n"; + } + return 0; + } +@@ -795,6 +868,7 @@ + my $channel_regex = $opt{channel} || '.*'; + my $category_regex = $opt{category} || '.*'; + my $versions_regex = $opt{versions} || '.*'; ++ my $exclude_regex = $opt{exclude} || '^ROGUE$'; + my $channel_exclude_regex = $opt{excludechannel} || '^ROGUE$'; + my $category_exclude_regex = $opt{excludecategory} || '^ROGUE$'; + my $since = $opt{since} || 99999; +@@ -808,6 +882,7 @@ + && $prog{$pid}{categories} =~ /$category_regex/i + && $prog{$pid}{versions} =~ /$versions_regex/i + && $prog{$pid}{channel} !~ /$channel_exclude_regex/i ++ && $prog{$pid}{name} !~ /$exclude_regex/i + && $prog{$pid}{categories} !~ /$category_exclude_regex/i + && $prog{$pid}{timeadded} >= $now - ($since * 3600) + ) { +@@ -828,25 +903,22 @@ + ); + } + } ++ + return sort {$a <=> $b} keys %download_hash; + } + + +-# get_links_atom (%channels) +-sub get_links_atom { +- my $type = shift; ++# get_links_bbciplayer (%channels) ++sub get_links_bbciplayer { ++ my $prog_type = shift; + my %channels = %{$_[0]}; + + my $xml; + my $feed_data; + my $res; +- logger "INFO: Getting $type Index Feeds\n"; ++ logger "INFO: Getting $prog_type Index Feeds\n"; + # Setup User agent +- my $ua = LWP::UserAgent->new; +- $ua->timeout([$lwp_request_timeout]); +- $ua->proxy( ['http'] => $proxy_url ); +- $ua->agent( $user_agent{desktop} ); +- $ua->conn_cache(LWP::ConnCache->new()); ++ my $ua = create_ua('desktop'); + + # Download index feed + # Sort feeds so that category based feeds are done last - this makes sure that the channels get defined correctly if there are dups +@@ -909,7 +981,7 @@ + # Discard first element == header + shift @entries; + +- my ( $name, $episode, $desc, $pid, $available, $channel, $duration, $thumbnail, $type, $versions ); ++ my ( $name, $episode, $desc, $pid, $available, $channel, $duration, $thumbnail, $prog_type, $versions ); + foreach my $entry (@entries) { + + my $entry_flat = $entry; +@@ -939,7 +1011,7 @@ + } + + # Extract channel and type +- ($type, $channel) = (split /\|/, $channels{$_})[0,1]; ++ ($prog_type, $channel) = (split /\|/, $channels{$_})[0,1]; + + logger "DEBUG: '$pid, $name - $episode, $channel'\n" if $opt{debug}; + +@@ -980,7 +1052,7 @@ + 'thumbnail' => "${thumbnail_prefix}/${pid}_150_84.jpg", + 'channel' => $channel, + 'categories' => join(',', @category), +- 'type' => $type, ++ 'type' => $prog_type, + }; + } + } +@@ -996,13 +1068,8 @@ + + # Add index field based on alphabetical sorting by prog name + my %index; +- $index{tv} = 1; +- +- # Start index counter at 10001 for radio progs +- $index{radio} = 10001; +- +- # Start index counter at 20001 for podcast progs +- $index{podcast} = 20001; ++ # Start index counter at 'min' for each prog type ++ $index{$_} = $index_range{$_}{min} for @all_prog_types; + + my @prog_pid; + +@@ -1013,10 +1080,10 @@ + for (sort @prog_pid) { + # Extract pid + my $pid = (split /\|/)[1]; +- my $type = $prog{$pid}{type}; +- $index_pid{ $index{$type} } = $pid; +- $prog{$pid}{index} = $index{$type}; +- $index{$type}++; ++ my $prog_type = $prog{$pid}{type}; ++ $index_pid{ $index{$prog_type} } = $pid; ++ $prog{$pid}{index} = $index{$prog_type}; ++ $index{$prog_type}++; + } + return 0; + } +@@ -1024,18 +1091,14 @@ + + + # Uses: $podcast_index_feed_url +-# get_podcast_links () +-sub get_podcast_links { ++# get_links_bbcpodcast () ++sub get_links_bbcpodcast { + + my $xml; + my $res; + logger "INFO: Getting podcast Index Feeds\n"; + # Setup User agent +- my $ua = LWP::UserAgent->new; +- $ua->timeout([$lwp_request_timeout]); +- $ua->proxy( ['http'] => $proxy_url ); +- $ua->agent( $user_agent{get_iplayer} ); +- $ua->conn_cache(LWP::ConnCache->new()); ++ my $ua = create_ua('get_iplayer'); + + # Method + # $podcast_index_feed_url (gets list of rss feeds for each podcast prog) => +@@ -1224,6 +1287,302 @@ + + + ++# Uses: ++# get_links_itv () ++sub get_links_itv { ++ my %channels = %{$_[0]}; ++ my $xml; ++ my $res; ++ my %series_pid; ++ my %episode_pid; ++ logger "INFO: Getting itv Index Feeds\n"; ++ # Setup User agent ++ my $ua = create_ua('desktop'); ++ ++ # Method ++ # http://www.itv.com/_data/xml/CatchUpData/CatchUp360/CatchUpMenu.xml (gets list of urls for each prog series) => ++ # => ++ ++ # Download index feed ++ my $itv_index_shows_url = 'http://www.itv.com/ClassicTVshows/'; # $channel/default.html ++ my $itv_index_feed_url = 'http://www.itv.com/_data/xml/CatchUpData/CatchUp360/CatchUpMenu.xml'; ++ ++ # Sort feeds so that pages are done last - this makes sure that the channels get defined correctly if there are dups ++ my @channel_list; ++ push @channel_list, grep !/\//, keys %channels; ++ push @channel_list, grep /\//, keys %channels; ++ # ITV ClassicShows parsing ++ for my $channel ( @channel_list ) { ++ #
  • A Bit of a Do
  • ++ #
  • A Christmas Carol
  • ++ # Get page, search for relevent lines which contain series links and loop through each matching line ++ for my $s_line ( grep /(
  • <\/a>

    |.+?<\/a>
    <\/li>)/, ( split /\n/, request_url_retry($ua, $itv_index_shows_url.${channel}.'/default.html', 3, '.', "WARNING: Failed to get itv ${channel} index from site\n") ) ) { ++ my ($url, $name); ++ # Extract series url + series description ++ ($url, $name) = ($1, $2) if $s_line =~ m{
  • <\/a>

    }; ++ ($url, $name) = ($1, $2) if $s_line =~ m{\s*(.+?)\s*<\/a>
    <\/li>}; ++ chomp($url); ++ chomp($name); ++ next if ! ($url && $name); ++ logger "DEBUG: Channel: '$channel' Series: '$name' URL: '$url'\n" if $opt{verbose}; ++ ++ # Get list of episodes for this series ++ # e.g.
  • Episode one
    The Sun in a Bottle
  • ++ #
  • Episode two
    Castle Saburac
  • ++ #
  • Episode one
    The Dead of Jericho
  • ++ # ++ # e.g.
  • Crossroads: Rosemary shoots DavidPlay

    ++ # vodcrid=crid://itv.com/971&DF=0">Emmerdale  2002 Louise kills RaySoldier Soldier Play

    Soldier Soldier

    ++ # ++ for my $e_line ( grep /vodcrid=crid/, ( split /\n/, request_url_retry($ua, $url, 3, '.', "WARNING: Failed to get ${name} index from site\n") ) ) { ++ my ($guidance, $pid, $episode, $thumbnail); ++ logger "DEBUG: Match Line: $e_line\n" if $opt{debug}; ++ # Extract episode data ++ ($guidance, $pid, $episode) = ($2, $3, $4) if $e_line =~ m{\s*(.+?)\s*<}; ++ ($pid, $thumbnail, $episode) = ($1, $2, $3) if $e_line =~ m{vodcrid=crid://itv.com/(\d+?)&.+?> $name, ++ 'versions' => 'default', ++ 'episode' => $episode, ++ 'channel' => (split /\|/, $channels{$channel})[1], ++ 'guidance' => $guidance, ++ 'categories' => (split /\|/, $channels{$channel})[1], ++ 'type' => 'itv', ++ }; ++ } ++ } ++ } ++ ++ my $xmlindex = request_url_retry($ua, $itv_index_feed_url, 3, '.', "WARNING: Failed to get itv index from site\n"); ++ $xmlindex =~ s/[\n\r]//g; ++ ++ # This gives a list of programme series (sometimes episodes) ++ # ++ # 50 ++ # A CHRISTMAS CAROL ++ # 615915 ++ # ++ # http://www.itv.com//img/150x113/A-Christmas-Carol-2f16d25a-de1d-4a3a-90cb-d47489eee98e.jpg ++ # 2009-01-06T12:24:22.7419643+00:00 ++ # ++ # http://www.itv.com/CatchUp/Video/default.html?ViewType=5&Filter=32910 ++ # 1 ++ # 32910 ++ # -1 ++ # ++ # ++ # ++ # ++ ++ for my $feedxml ( split //, $xmlindex ) { ++ # Extract feed data ++ my ($episodecount, $viewtype, $videoid, $url); ++ my @entries; ++ ++ logger "\n\nDEBUG: XML: $feedxml\n" if $opt{debug}; ++ ++ # 1 ++ $episodecount = $1 if $feedxml =~ m{\s*(\d+)\s*<\/EpisodeCount>}; ++ ++ # http://www.itv.com/CatchUp/Video/default.html?ViewType=5&Filter=32910 ++ ($viewtype, $videoid) = ($1, $2) if $feedxml =~ m{\s*.+?ViewType=(\d+).+?Filter=(\d+)\s*<\/Url>}i; ++ ++ ## 32910 ++ #$videoid = $1 if $feedxml =~ m{\s*(\d+)\s*<\/VideoID>}; ++ ++ # Skip if there is no feed data for channel ++ next if ($viewtype =~ /^0*$/ || $videoid =~ /^0*$/ ); ++ ++ logger "DEBUG: Got ViewType=$viewtype VideoId=$videoid EpisodeCount=$episodecount\n" if $opt{debug}; ++ ++ my $url = "http://www.itv.com/_app/Dynamic/CatchUpData.ashx?ViewType=${viewtype}&Filter=${videoid}"; ++ ++ # Add response from episode metadata url to list to be parsed if this is an episode link ++ if ( $viewtype == 5 ) { ++ next if $episode_pid{$videoid}; ++ $episode_pid{$videoid} = 1; ++ # Get metadata pages for episode ++ ++ my ( $name, $guidance, $channel, $episode, $desc, $pid, $available, $duration, $thumbnail ); ++ ++ $pid = $videoid; ++ $channel = 'ITV Catch-up'; ++ ++ # Skip if this pid is a duplicate ++ if ( defined $prog{$pid} ) { ++ logger "WARNING: '$pid, $prog{$pid}{name} - $prog{$pid}{episode}, $prog{$pid}{channel}' already exists (this channel = $channel)\n" if $opt{verbose}; ++ next; ++ } ++ ++ $name = $1 if $feedxml =~ m{\s*(.+?)\s*<\/ProgrammeTitle>}; ++ $guidance = $1 if $feedxml =~ m{\s*(.+?)\s*<\/DentonRating>}; ++ $thumbnail = $1 if $feedxml =~ m{\s*(.+?)\s*<\/ProgrammeMediaUrl>}; ++ $episode = $pid; ++ # Strip non-printable chars ++ $guidance =~ s/[\s\x00\xc2\xa0]+$//ig; ++ ++ # build data structure ++ $prog{$pid} = { ++ 'name' => $name, ++ 'versions' => 'default', ++ 'episode' => $episode, ++ 'guidance' => $guidance, ++ 'desc' => $desc, ++ 'available' => $available, ++ 'duration' => $duration, ++ 'thumbnail' => $thumbnail, ++ 'channel' => $channel, ++ 'categories' => 'TV', ++ 'type' => 'itv', ++ }; ++ ++ ++ ++ ++ ++ # Get next episode list and parse ++ #
    ++ #
    ++ #

    Emmerdale

    ++ #

    Mon 05 Jan 2009

    ++ #

    Donna is stunned to learn Marlon has pointed the finger at Ross. Aaron defaces Tom King's grave.

    ++ #
      ++ #
    • ++ # Duration: 30 min ++ #
    • ++ #
    • ++ # Expires in ++ # 29 ++ # days ++ #
    • ++ #
    ++ #
    ++ #
    ++ #
    ++ #
    ++ #

    Emmerdale

    ++ #

    Fri 02 Jan 2009

    ++ #

    Marlon gets his revenge on Ross. The King brothers struggle to restart their business without Matthew. Scarlett is fed up with Victoria getting all Daz ++ #

      ++ #
    • ++ # Duration: 30 min ++ #
    • ++ #
    • ++ # Expires in ++ # 26 ++ # days ++ #
    • ++ #
    ++ #
    ++ #
    ++ # ++ } elsif ( $viewtype == 1 ) { ++ # Make sure we don't duplicate parsing a series ++ next if $series_pid{$videoid}; ++ $series_pid{$videoid} = 1; ++ ++ # Get metadata pages for each series ++ logger "DEBUG: Getting series metadata $url\n" if $opt{debug}; ++ $xml = request_url_retry($ua, $url, 2, '.', "WARNING: Failed to get itv series data for ${videoid} from itv site\n") if $opt{verbose}; ++ $xml = request_url_retry($ua, $url, 2, '.', '') if ! $opt{verbose}; ++ ++ # skip if no data ++ next if ! $xml; ++ ++ decode_entities($xml); ++ # Flatten entry ++ $xml =~ s/[\n\r]//g; ++ ++ # Extract Filter (pids) from this list ++ # e.g.

    Emmerdale

    ++ my @videoids = (split /

    \s*(.+?)\s*<\/ProgrammeTitle>}; ++ $available = $1 if $xml =~ m{(.+?)<\/p>}i; ++ $episode = $available; ++ $duration = $1 if $xml =~ m{
  • Duration:\s*(.+?)\s*<\/li>}i; ++ $desc = $1 if $xml =~ m{(.+?)\s*<\/p>}; ++ $guidance = $1 if $feedxml =~ m{\s*(.+?)\s*<\/DentonRating>}; ++ $thumbnail = $1 if $feedxml =~ m{\s*(.+?)\s*<\/ProgrammeMediaUrl>}; ++ $guidance =~ s/[\s\x00\xc2\xa0]+$//ig; ++ ++ logger "DEBUG: name='$name' episode='$episode' pid=$pid available='$available' \n" if $opt{debug}; ++ ++ # build data structure ++ $prog{$pid} = { ++ 'name' => $name, ++ 'versions' => 'default', ++ 'episode' => $episode, ++ 'guidance' => $guidance, ++ 'desc' => $desc, ++ 'available' => $available, ++ 'duration' => $duration, ++ 'thumbnail' => $thumbnail, ++ 'channel' => $channel, ++ 'categories' => 'TV', ++ 'type' => 'itv', ++ }; ++ } ++ } ++ ++ } ++ logger "\n"; ++ return 0; ++} ++ ++ ++ + # Feed info: + # # Also see http://derivadow.com/2008/07/18/interesting-bbc-data-to-hack-with/ + # # All podcasts menu (iphone) +@@ -1261,14 +1620,14 @@ + # http://www.bbc.co.uk/cbbc/programmes/genres/childrens/player + # http://www.bbc.co.uk/programmes/genres/childrens/schedules/upcoming.ics + # +-# get_links( ) ++# get_links( ) + sub get_links { + my @cache; + my $now = time(); +- my $type = shift; ++ my $prog_type = shift; + + # Open cache file (need to verify we can even read this) +- if ( open(CACHE, "< $cachefile{$type}") ) { ++ if ( open(CACHE, "< $cachefile{$prog_type}") ) { + # Get file contents less any comments + @cache = grep !/^[\#\s]/, ; + close (CACHE); +@@ -1281,47 +1640,40 @@ + for (@cache) { + # Populate %prog from cache + chomp(); +- my ($index, $type, $name, $pid, $available, $episode, $versions, $duration, $desc, $channel, $categories, $thumbnail, $timeadded) = split /\|/; +- # Create data structure with prog data +- $prog_old{$pid} = { +- 'index' => $index, +- 'name' => $name, +- 'episode' => $episode, +- 'desc' => $desc, +- 'available' => $available, +- 'duration' => $duration, +- 'versions' => $versions, +- 'channel' => $channel, +- 'categories' => $categories, +- 'thumbnail' => $thumbnail, +- 'type' => $type, +- 'timeadded' => $timeadded, +- }; +- $index_pid_old{$index} = $pid; ++ # Get cache line ++ my @record = split /\|/; ++ my %record_entries; ++ # Update fields in %prog hash for $pid ++ $record_entries{$_} = shift @record for @cache_format; ++ $prog_old{ $record_entries{pid} } = \%record_entries; ++ $index_pid_old{ $record_entries{index} } = $record_entries{pid}; + } + } + + # if a cache file doesn't exist/corrupted, flush option is specified or original file is older than $cache_sec then download new data +- if ( (! @cache) || (! -f $cachefile{$type}) || $opt{flush} || ($now >= ( stat($cachefile{$type})->mtime + $cache_secs )) ) { ++ if ( (! @cache) || (! -f $cachefile{$prog_type}) || $opt{flush} || ($now >= ( stat($cachefile{$prog_type})->mtime + $cache_secs )) ) { + +- # Podcast only +- get_podcast_links() if $type eq 'podcast'; ++ # BBC Podcast only ++ get_links_bbcpodcast() if $prog_type eq 'podcast'; + +- # Radio and TV +- get_links_atom( $type, \%{$channels{$type}} ) if $type =~ /(tv|radio)/; ++ # ITV only ++ get_links_itv( \%{$channels{$prog_type}} ) if $prog_type eq 'itv'; ++ ++ # BBC Radio and TV ++ get_links_bbciplayer( $prog_type, \%{$channels{$prog_type}} ) if $prog_type =~ /^(tv|radio)$/; + + # Sort indexes + sort_indexes(); + + # Open cache file for writing +- unlink $cachefile{$type}; ++ unlink $cachefile{$prog_type}; + my $now = time(); +- if ( open(CACHE, "> $cachefile{$type}") ) { +- print CACHE "#Index|Type|Name|Pid|Available|Episode|Versions|Duration|Desc|Channel|Categories|Thumbnail|TimeAdded\n"; ++ if ( open(CACHE, "> $cachefile{$prog_type}") ) { ++ print CACHE "#".(join '|', @cache_format)."\n"; + for (sort {$a <=> $b} keys %index_pid) { + my $pid = $index_pid{$_}; + # Only write entries for correct prog type +- if ($prog{$pid}{type} eq $type) { ++ if ($prog{$pid}{type} eq $prog_type) { + # Merge old and new data to retain timestamps + # if the entry was in old cache then retain timestamp from old entry + if ( $prog_old{$pid}{timeadded} ) { +@@ -1332,12 +1684,17 @@ + list_prog_entry( $pid, 'Added: ' ); + } + # write to cache file +- print CACHE "$_|$prog{$pid}{type}|$prog{$pid}{name}|$pid|$prog{$pid}{available}|$prog{$pid}{episode}|$prog{$pid}{versions}|$prog{$pid}{duration}|$prog{$pid}{desc}|$prog{$pid}{channel}|$prog{$pid}{categories}|$prog{$pid}{thumbnail}|$prog{$pid}{timeadded}\n"; ++ $prog{$pid}{pid} = $pid; ++ # Write each field into cache line ++ for my $field (@cache_format) { ++ print CACHE $prog{$pid}{$field}.'|'; ++ } ++ print CACHE "\n"; + } + } + close (CACHE); + } else { +- logger "WARNING: Couldn't open cache file '$cachefile{$type}' for writing\n"; ++ logger "WARNING: Couldn't open cache file '$cachefile{$prog_type}' for writing\n"; + } + + +@@ -1354,172 +1711,434 @@ + # Usage: download_programme () + sub download_programme { + my $pid = shift; ++ my %streamdata; ++ my %version_pids; ++ my $return; + + # Setup user-agent +- # Switch off automatic redirects +- my $ua = LWP::UserAgent->new( requests_redirectable => [] ); +- # Setup user agent +- $ua->timeout([$lwp_request_timeout]); +- $ua->proxy( ['http'] => $proxy_url ); +- $ua->cookie_jar( HTTP::Cookies->new( file => $cookiejar, autosave => 1, ignore_discard => 1 ) ); ++ my $ua = create_ua('desktop'); + +- my $dir = $download_dir{ $prog{$pid}{type} }; +- $prog{$pid}{ext} = 'mov'; ++ # download depending on the prog type ++ logger "INFO: Attempting to Download $prog{$pid}{type}: $prog{$pid}{name} - $prog{$pid}{episode}\n"; + +- # If were a podcast... ++ # ITV TV ++ if ( $prog{$pid}{type} eq 'itv' ) { ++ # stream data ++ # Display media stream data if required ++ if ( $opt{streaminfo} ) { ++ display_stream_info( $pid, undef, 'all' ); ++ $opt{quiet} = 1; ++ return 'skip'; ++ } ++ return download_programme_itv( $ua, $pid ); ++ } ++ ++ # BBC Podcasts + if ( $prog{$pid}{type} eq 'podcast' ) { +- # Determine the correct filename and extension for this download +- my $filename_orig = $pid; +- $prog{$pid}{ext} = $pid; +- $filename_orig =~ s|^.+/(.+?)\.\w+$|$1|g; +- $prog{$pid}{ext} =~ s|^.*\.(\w+)$|$1|g; +- $prog{$pid}{fileprefix} = generate_download_filename_prefix($pid, $dir, $opt{fileprefix} || " - $filename_orig"); +- $prog{$pid}{dir} = $dir; +- logger "\rINFO: File name prefix = $prog{$pid}{fileprefix} \n"; +- my $file_done = "${dir}/$prog{$pid}{fileprefix}.$prog{$pid}{ext}"; +- my $file = "${dir}/$prog{$pid}{fileprefix}.partial.$prog{$pid}{ext}"; +- $prog{$pid}{filename} = $file_done; +- if ( -f $file_done ) { +- logger "WARNING: File $file_done already exists\n\n"; +- return 1; ++ # stream data not available ++ return 'skip' if $opt{streaminfo}; ++ return download_programme_podcast( $ua, $pid ); ++ } ++ ++ # For BBC Radio/TV we might have a pid with no prog_type - determine here first ++ ( $prog{$pid}{type}, $prog{$pid}{longname}, %version_pids ) = get_version_pids( $ua, $pid ); ++ ++ # BBC TV ++ if ( $prog{$pid}{type} eq 'tv' ) { ++ ++ # Deal with BBC TV fallback modes ++ # Valid modes are iphone,rtmp,flashhigh,flashnormal,flashwii,n95_wifi ++ $opt{vmode} = 'flashhigh,flashnormal' if $opt{vmode} eq 'rtmp' || $opt{vmode} eq 'flash'; ++ # Defaults ++ if ( $opt{vmode} eq 'auto' || ! $opt{vmode} ) { ++ if ( ! exists_in_path($rtmpdump) ) { ++ $opt{vmode} = 'iphone'; ++ } else { ++ $opt{vmode} = 'iphone,flashhigh,flashnormal'; ++ } + } ++ # Expand the modes into a loop ++ logger "INFO: $opt{vmode} modes will be tried\n"; ++ for my $mode ( split /,/, $opt{vmode} ) { ++ chomp( $mode ); ++ logger "INFO: Attempting to download using $mode mode\n"; ++ $return = download_programme_tv( $ua, $pid, $mode, \%version_pids ); ++ logger "DEBUG: Download using $mode mode return code: '$return'\n" if $opt{debug}; + +- # Skip from here if we are only testing downloads +- return 1 if $opt{test}; ++ # Give up trying alternative download methods ++ return 2 if $return eq 'abort'; + +- # Create symlink filename if required +- my $file_symlink; +- if ( $opt{symlink} ) { +- # Substitute the fields for the pid +- $file_symlink = substitute_fields( $pid, $opt{symlink} ); ++ # Return to retry loop if successful or retry requested ++ return 'retry' if $return eq 'retry'; ++ ++ # Return to retry loop and do nothing ++ return 'skip' if $return eq 'skip'; ++ ++ # Return 0 if successful ++ return 0 if ! $return; ++ ++ # Return failed if there is no 'next' ++ return 1 if $return ne 'next'; + } +- +- return download_podcast_stream( $ua, $pid, $file, $file_done, $file_symlink ); + } + +- logger "INFO: Attempting to Download: $prog{$pid}{name} - $prog{$pid}{episode}\n"; ++ # BBC Radio ++ if ( $prog{$pid}{type} eq 'radio' ) { ++ # This will always be the pid version for radio ++ $prog{$pid}{version} = 'default'; ++ # Display media stream data if required ++ if ( $opt{streaminfo} ) { ++ display_stream_info( $pid, $version_pids{default}, 'all' ); ++ $opt{quiet} = 1; ++ return 'skip'; ++ } ++ ++ # Deal with radio fallback modes ++ # Valid modes are mp3|iphone,flash|rtmp,real|ra ++ # Defaults ++ $opt{amode} = 'iphone,flash,real' if $opt{amode} eq 'auto' || ! $opt{amode}; ++ ++ # Expand the modes into a loop ++ logger "INFO: $opt{amode} modes will be tried\n"; ++ for my $mode ( split /,/, $opt{amode} ) { ++ chomp( $mode ); ++ logger "INFO: Attempting to download using $mode mode\n"; ++ # RealAudio ++ if ( $mode =~ /^(real|ra)/ ) { ++ $return = download_programme_radio_realaudio( $ua, $pid, \%version_pids ); ++ ++ # FlashAudio ++ } elsif ( $mode =~ /^(flash|rtmp)/ ) { ++ $return = download_programme_radio_flashaudio( $ua, $pid, $mode, \%version_pids ); ++ ++ # iPhone ++ } elsif ( $mode =~ /^(iphone|mp3)/ ) { ++ $return = download_programme_radio_iphone( $ua, $pid, \%version_pids ); ++ } ++ logger "DEBUG: Download using $mode mode return code: '$return'\n" if $opt{debug}; + +- # Get version => pid hash +- my ( $type, $title, %version_pids ) = get_version_pids( $ua, $pid ); ++ # Give up trying alternative download methods ++ return 2 if $return eq 'abort'; + +- # Extract Long Name, e.g.: iplayer.episode.setTitle("DIY SOS: Series 16: Swansea"); +- $prog{$pid}{longname} = $title; ++ # Not going to allow retries here until rtmpdump/flashaudio exits correctly - so just just skip to next mode for now ++ $return = 'next' if $return eq 'retry'; ++ ## Return to retry loop if successful or retry requested ++ #return 'retry' if $return eq 'retry'; + +- # Strip off the episode name +- $prog{$pid}{longname} =~ s/^(.+):.*?$/$1/g; ++ # Return to retry loop and do nothing ++ return 'skip' if $return eq 'skip'; + +- # Detect if this content is for radio +- my $usemp3 = 0; +- if ( $type eq 'radio' ) { ++ # Return 0 if successful ++ return 0 if ! $return; + +- # Display media stream data if required +- if ( $opt{streaminfo} ) { +- get_media_stream_data( $pid, $version_pids{'default'}, 'all' ); +- return 1; ++ # Return failed if there is no 'next' ++ return 1 if $return ne 'next'; + } ++ } + +- # Type is definitely radio +- $prog{$pid}{type} = 'radio'; +- $dir = $download_dir{ $prog{$pid}{type} }; +- +- # Check for mp3 stream - unless realaudio option is specified +- if ( ! $opt{realaudio} ) { +- # Check for iphone mp3 radio stream +- if ( get_media_stream_data( $pid, $version_pids{default}, 'iphone' ) ) { +- $usemp3 = 1; +- $prog{$pid}{ext} = 'mp3'; +- logger "INFO: MP3 stream media is available\n" if $opt{verbose}; +- +- # if mp3audio option is specified do not fallback to realaudio +- } elsif ( $opt{mp3audio} ) { +- logger "ERROR: No MP3 stream media is available - not falling back to RealAudio\n"; +- return 1; ++ # If we get here then we have failed ++ return 1; ++} + +- # if not then force realaudio option as fallback +- } else { +- $opt{realaudio} = 1; +- logger "INFO: No MP3 stream media is available - falling back to RealAudio\n" if $opt{verbose}; +- } +- } + +- # Use realplayer stream +- if ( $opt{realaudio} ) { + +- # Check dependancies for radio programme transcoding / streaming +- # Check if we need 'tee' +- if ( (! exists_in_path($tee)) && $opt{stdout} && (! $opt{nowrite}) ) { +- logger "\nERROR: $tee does not exist in path, skipping\n"; +- return 20; +- } +- # Check if we have mplayer and lame +- if ( (! $opt{wav}) && (! $opt{raw}) && (! exists_in_path($lame)) ) { +- logger "\nWARNING: Required $lame does not exist, falling back to wav mode\n"; +- $opt{wav} = 1; +- } +- if (! exists_in_path($mplayer)) { +- logger "\nERROR: Required $mplayer does not exist, skipping\n"; +- return 20; +- } ++sub download_programme_itv { ++ my ( $ua, $pid ) = ( @_ ); ++ my %streamdata; ++ ++ # Check for mplayer (required) ++ if (! exists_in_path($mplayer)) { ++ logger "\nERROR: Required $mplayer does not exist, skipping\n"; ++ return 21; ++ } + +- my $url_2 = get_media_stream_data( $pid, $version_pids{default}, 'realaudio' ); ++ $prog{$pid}{dir} = $download_dir{ $prog{$pid}{type} }; ++ $prog{$pid}{pid} = $pid; ++ $prog{$pid}{ext} = 'mp4'; ++ $prog{$pid}{ext} = 'asf' if $opt{raw}; + +- logger "INFO: Version = $prog{$pid}{version}\n" if $opt{verbose}; +- logger "INFO: Stage 2 URL = $url_2\n" if $opt{verbose}; ++ my @url_list = %{get_media_stream_data( $pid, undef, 'itv')}->{streamurl}; + +- # Report error if no versions are available +- if ( ! $url_2 ) { +- logger "ERROR: No Stage 2 URL\n" if $opt{verbose}; +- return 15; +- } ++ # Get and set more meta data - Set the %prog values from metadata if they aren't already set ++ my %metadata = get_pid_metadata($ua, $pid); ++ for ( qw/ name episode available duration thumbnail desc guidance / ) { ++ $prog{$pid}{$_} = $metadata{$_} if ! $prog{$pid}{$_}; ++ } + +- # Determine the correct filenames for this download +- $prog{$pid}{ext} = 'mp3'; +- $prog{$pid}{ext} = 'ra' if $opt{raw}; +- $prog{$pid}{ext} = 'wav' if $opt{wav}; +- $prog{$pid}{fileprefix} = generate_download_filename_prefix( $pid, ${dir}, $opt{fileprefix} || " - " ); +- logger "\rINFO: File name prefix = $prog{$pid}{fileprefix} \n"; +- $prog{$pid}{dir} = $dir; +- my $file_done = "${dir}/$prog{$pid}{fileprefix}.$prog{$pid}{ext}"; +- my $file = "${dir}/$prog{$pid}{fileprefix}.partial.$prog{$pid}{ext}"; +- $prog{$pid}{filename} = $file_done; +- if ( -f $file_done ) { +- logger "WARNING: File $file_done already exists\n\n"; +- return 1; +- } ++ $prog{$pid}{fileprefix} = generate_download_filename_prefix( $pid, $prog{$pid}{dir}, $opt{fileprefix} || " " ); ++ logger "\rINFO: File name prefix = $prog{$pid}{fileprefix} \n"; ++ # Create a subdir if there are multiple parts ++ if ($#url_list > 0) { ++ $prog{$pid}{dir} .= "/$prog{$pid}{fileprefix}"; ++ logger "INFO: Creating subdirectory $prog{$pid}{dir} for programme\n" if $opt{verbose}; ++ mkpath $prog{$pid}{dir} if ! -d $prog{$pid}{dir}; ++ } ++ my $file_done = "$prog{$pid}{dir}/$prog{$pid}{fileprefix}.$prog{$pid}{ext}"; ++ my $file = "$prog{$pid}{dir}/$prog{$pid}{fileprefix}.partial.$prog{$pid}{ext}"; ++ $prog{$pid}{filename} = $file_done; + +- # Skip from here if we are only testing downloads +- return 1 if $opt{test}; ++ # Display metadata ++ display_metadata( $prog{$pid}, qw/ pid index name duration available expiry desc / ); + +- # Create symlink filename if required +- my $file_symlink; +- if ( $opt{symlink} ) { +- # Substitute the fields for the pid +- $file_symlink = substitute_fields( $pid, $opt{symlink} ); +- } ++ # Skip from here if we are only testing downloads ++ return 1 if $opt{test}; + +- # Do the audio download +- return download_rtsp_stream( $ua, $url_2, $file, $file_done, $file_symlink, $pid ); +- } ++ return download_stream_mms_video( $ua, (join '|', @url_list), $file, $file_done, $pid ); ++} ++ ++ ++ ++sub download_programme_podcast { ++ my ( $ua, $pid ) = ( @_ ); ++ my %streamdata; ++ ++ $prog{$pid}{dir} = $download_dir{ $prog{$pid}{type} }; ++ ++ # Determine the correct filename and extension for this download ++ my $filename_orig = $pid; ++ $prog{$pid}{ext} = $pid; ++ $filename_orig =~ s|^.+/(.+?)\.\w+$|$1|g; ++ $prog{$pid}{ext} =~ s|^.*\.(\w+)$|$1|g; ++ $prog{$pid}{fileprefix} = generate_download_filename_prefix($pid, $prog{$pid}{dir}, $opt{fileprefix} || " - $filename_orig"); ++ logger "\rINFO: File name prefix = $prog{$pid}{fileprefix} \n"; ++ my $file_done = "$prog{$pid}{dir}/$prog{$pid}{fileprefix}.$prog{$pid}{ext}"; ++ my $file = "$prog{$pid}{dir}/$prog{$pid}{fileprefix}.partial.$prog{$pid}{ext}"; ++ $prog{$pid}{filename} = $file_done; ++ if ( -f $file_done && stat($file_done)->size > $min_download_size ) { ++ logger "WARNING: File $file_done already exists\n\n"; ++ return 1; ++ } ++ ++ # Skip from here if we are only testing downloads ++ return 1 if $opt{test}; ++ ++ # Create symlink filename if required ++ my $file_symlink; ++ if ( $opt{symlink} ) { ++ # Substitute the fields for the pid ++ $file_symlink = substitute_fields( $pid, $opt{symlink} ); ++ } ++ ++ return download_stream_podcast( $ua, $pid, $file, $file_done, $file_symlink ); ++} ++ ++ ++ ++sub download_programme_radio_realaudio { ++ my $ua = shift; ++ my $pid = shift; ++ my %version_pids = %{@_[0]}; ++ my %streamdata; ++ ++ # Check dependancies for radio programme transcoding / streaming ++ # Check if we need 'tee' ++ if ( (! exists_in_path($tee)) && $opt{stdout} && (! $opt{nowrite}) ) { ++ logger "\nERROR: $tee does not exist in path, skipping\n"; ++ return 'abort'; ++ } ++ if (! exists_in_path($mplayer)) { ++ logger "\nWARNING: Required $mplayer does not exist\n"; ++ return 'next'; ++ } ++ # Check if we have mplayer and lame ++ if ( (! $opt{wav}) && (! $opt{raw}) && (! exists_in_path($lame)) ) { ++ logger "\nWARNING: Required $lame does not exist, will save file in wav format\n"; ++ $opt{wav} = 1; ++ } ++ ++ $prog{$pid}{dir} = $download_dir{ $prog{$pid}{type} }; ++ $prog{$pid}{ext} = 'mp3'; ++ $prog{$pid}{ext} = 'ra' if $opt{raw}; ++ $prog{$pid}{ext} = 'wav' if $opt{wav}; + ++ my $url_2 = %{get_media_stream_data( $pid, $version_pids{default}, 'realaudio')}->{streamurl}; ++ ++ # Report error if no versions are available ++ if ( ! $url_2 ) { ++ logger "WARNING: RealAudio version not available\n"; ++ return 'next'; + } else { +- # Type is definitely tv +- $prog{$pid}{type} = 'tv'; +- $dir = $download_dir{ $prog{$pid}{type} }; ++ logger "INFO: Stage 2 URL = $url_2\n" if $opt{verbose}; ++ } ++ ++ # Determine the correct filenames for this download ++ $prog{$pid}{fileprefix} = generate_download_filename_prefix( $pid, $prog{$pid}{dir}, $opt{fileprefix} || " - " ); ++ logger "\rINFO: File name prefix = $prog{$pid}{fileprefix} \n"; ++ my $file_done = "$prog{$pid}{dir}/$prog{$pid}{fileprefix}.$prog{$pid}{ext}"; ++ my $file = "$prog{$pid}{dir}/$prog{$pid}{fileprefix}.partial.$prog{$pid}{ext}"; ++ $prog{$pid}{filename} = $file_done; ++ if ( -f $file_done ) { ++ logger "WARNING: File $file_done already exists\n\n"; ++ return 'abort'; + } + ++ # Skip from here if we are only testing downloads ++ return 'abort' if $opt{test}; + +- # iPhone mp3/h.264 stream downloading... ++ # Create symlink filename if required ++ my $file_symlink; ++ if ( $opt{symlink} ) { ++ # Substitute the fields for the pid ++ $file_symlink = substitute_fields( $pid, $opt{symlink} ); ++ } + +- # Check if we have vlc - if not use iPhone mode +- if ( $opt{n95} && (! exists_in_path($vlc)) ) { +- logger "\nWARNING: Required $vlc does not exist, falling back to iPhone mode\n"; +- $opt{n95} = 0; +- } ++ # Do the audio download ++ return download_stream_rtsp( $ua, $url_2, $file, $file_done, $file_symlink, $pid ); ++} + + ++ ++sub download_programme_radio_iphone { ++ my $ua = shift; ++ my $pid = shift; ++ my %version_pids = %{@_[0]}; ++ my %streamdata; ++ my $url_2; ++ ++ $prog{$pid}{dir} = $download_dir{ $prog{$pid}{type} }; ++ $prog{$pid}{ext} = 'mp3'; ++ ++ my $url_2 = %{get_media_stream_data( $pid, $version_pids{ $prog{$pid}{version} }, 'iphone')}->{streamurl}; ++ ++ # Report error if no versions are available ++ if ( ! $url_2 ) { ++ logger "WARNING: iPhone stream media not available\n"; ++ return 'next'; ++ } else { ++ logger "INFO: Stage 2 URL = $url_2\n" if $opt{verbose}; ++ } ++ ++ # Determine the correct filenames for this download ++ $prog{$pid}{fileprefix} = generate_download_filename_prefix( $pid, $prog{$pid}{dir}, $opt{fileprefix} || " - " ); ++ logger "\rINFO: File name prefix = $prog{$pid}{fileprefix} \n"; ++ my $file_done = "$prog{$pid}{dir}/$prog{$pid}{fileprefix}.$prog{$pid}{ext}"; ++ my $file = "$prog{$pid}{dir}/$prog{$pid}{fileprefix}.partial.$prog{$pid}{ext}"; ++ $prog{$pid}{filename} = $file_done; ++ if ( -f $file_done ) { ++ logger "WARNING: File $file_done already exists\n\n"; ++ return 'abort'; ++ } ++ ++ # Skip from here if we are only testing downloads ++ return 'abort' if $opt{test}; ++ ++ # Create symlink filename if required ++ my $file_symlink; ++ if ( $opt{symlink} ) { ++ # Substitute the fields for the pid ++ $file_symlink = substitute_fields( $pid, $opt{symlink} ); ++ } ++ ++ my $return; ++ # Disable proxy here if required ++ $ua->proxy( ['http'] => undef ) if $opt{partialproxy}; ++ $return = download_stream_iphone( $ua, $url_2, $pid, $file, $file_done, $file_symlink, 0 ); ++ # Re-enable proxy here if required ++ $ua->proxy( ['http'] => $proxy_url ) if $opt{partialproxy}; ++ ++ return $return; ++} ++ ++ ++ ++sub download_programme_radio_flashaudio { ++ my $ua = shift; ++ my $pid = shift; ++ my $mode = shift; ++ my %version_pids = %{@_[0]}; ++ my %streamdata; ++ my $url_2; ++ ++ # Force raw mode if ffmpeg is not installed ++ if ( ! exists_in_path($ffmpeg) ) { ++ logger "\nWARNING: $ffmpeg does not exist - not converting flv file\n"; ++ $opt{raw} = 1; ++ } ++ # Disable rtmp modes if rtmpdump does not exist ++ if ( ! exists_in_path($rtmpdump) ) { ++ logger "\nERROR: Required program $rtmpdump does not exist (see http://linuxcentre.net/getiplayer/installation and http://linuxcentre.net/getiplayer/download)\n"; ++ return 'next'; ++ } ++ ++ $prog{$pid}{dir} = $download_dir{ $prog{$pid}{type} }; ++ $prog{$pid}{ext} = 'mp3'; ++ $prog{$pid}{ext} = 'flv' if $opt{raw}; ++ ++ logger "INFO: Trying to get media stream metadata for flashaudio RTMP mode\n" if $opt{verbose}; ++ %streamdata = %{ get_media_stream_data( $pid, $version_pids{ $prog{$pid}{version} }, 'flashaudio') }; ++ $url_2 = $streamdata{streamurl}; ++ if ( ! $url_2 ) { ++ logger "WARNING: No flashaudio version available\n"; ++ return 'next'; ++ } ++ ++ # Determine the correct filenames for this download ++ $prog{$pid}{fileprefix} = generate_download_filename_prefix( $pid, $prog{$pid}{dir}, $opt{fileprefix} || " - " ); ++ logger "\rINFO: File name prefix = $prog{$pid}{fileprefix} \n"; ++ my $file_done = "$prog{$pid}{dir}/$prog{$pid}{fileprefix}.$prog{$pid}{ext}"; ++ my $file = "$prog{$pid}{dir}/$prog{$pid}{fileprefix}.partial.$prog{$pid}{ext}"; ++ $prog{$pid}{filename} = $file_done; ++ if ( -f $file_done ) { ++ logger "WARNING: File $file_done already exists\n\n"; ++ return 'abort'; ++ } ++ ++ # Skip from here if we are only testing downloads ++ return 'abort' if $opt{test}; ++ ++ # Create symlink filename if required ++ my $file_symlink; ++ if ( $opt{symlink} ) { ++ # Substitute the fields for the pid ++ $file_symlink = substitute_fields( $pid, $opt{symlink} ); ++ } ++ ++ # Do the RTMP flashaudio download ++ return download_stream_rtmp( $ua, $streamdata{streamurl}, $pid, $mode, $streamdata{application}, $streamdata{tcurl}, $streamdata{authstring}, $streamdata{swfurl}, $file, $file_done, $file_symlink ); ++} ++ ++ ++ ++# Usage: download_programme_tv () ++sub download_programme_tv { ++ my $ua = shift; ++ my $pid = shift; ++ my $mode = shift; ++ my %version_pids = %{@_[0]}; ++ my %streamdata; + my $url_2; + my $got_url; + ++ # Check if we have vlc - if not use iPhone mode ++ if ( $opt{vmode} eq 'n95' && (! exists_in_path($vlc)) ) { ++ logger "\nWARNING: Required $vlc does not exist\n"; ++ return 'next'; ++ } ++ # if rtmpdump does not exist ++ if ( $mode =~ /^(rtmp|flash)/ && ! exists_in_path($rtmpdump)) { ++ logger "WARNING: Required program $rtmpdump does not exist (see http://linuxcentre.net/getiplayer/installation and http://linuxcentre.net/getiplayer/download)\n"; ++ return 'next'; ++ } ++ # Force raw mode if ffmpeg is not installed ++ if ( $mode =~ /^(flash|rtmp)/ && ! exists_in_path($ffmpeg)) { ++ logger "\nWARNING: $ffmpeg does not exist - not converting flv file\n"; ++ $opt{raw} = 1; ++ } ++ ++ $prog{$pid}{dir} = $download_dir{ $prog{$pid}{type} }; ++ $prog{$pid}{ext} = 'mov'; ++ # Lookup table to determine which ext to use for different download methods ++ my %stream_ext = ( ++ iphone => 'mov', ++ flashhigh => 'mp4', ++ flashnormal => 'avi', ++ flashwii => 'avi', ++ n95_wifi => '3gp', ++ n95_3g => '3gp', ++ ); ++ $prog{$pid}{ext} = $stream_ext{$mode} if not $opt{raw}; ++ $prog{$pid}{ext} = 'flv' if $mode =~ /^(flash|rtmp)/ && $opt{raw}; ++ + # Do this for each version tried in this order (if they appeared in the content) + for my $version ( @version_search_list ) { + +@@ -1528,52 +2147,41 @@ + logger "INFO: Checking existence of $version version\n"; + $prog{$pid}{version} = $version; + logger "INFO: Version = $prog{$pid}{version}\n" if $opt{verbose}; +- if( ! $opt{rtmp} ) { +- $url_2 = get_iphone_stream_download_url( $ua, $version_pids{$version} ); +- } else { +- $url_2 = get_media_stream_data( $pid, $version_pids{ $prog{$pid}{version} }, 'flashhigh' ); +- } +- $got_url = 1; ++ # Try to get stream data ++ %streamdata = %{ get_media_stream_data( $pid, $version_pids{ $prog{$pid}{version} }, $mode) }; ++ $url_2 = $streamdata{streamurl}; + } + # Break out of loop if we have an actual URL +- last if $got_url && $url_2; +- } +- +- # Report error if no versions are available +- if ( ! $got_url ) { +- logger "ERROR: No versions exist for download\n"; +- return 14; ++ last if $url_2; + } + + # Display media stream data if required + if ( $opt{streaminfo} ) { +- get_media_stream_data( $pid, $version_pids{'default'}, 'all' ); +- return 1; ++ display_stream_info( $pid, $version_pids{ $prog{$pid}{version} }, 'all' ); ++ $opt{quiet} = 1; ++ return 'skip'; + } + +- # Report error if failed to get URL for version +- if ( $got_url && ! $url_2 ) { +- logger "ERROR: No Stage 2 URL\n" if $opt{verbose}; +- # If mp3 audio stream does not exist force realaudio mode and retry +- if ( $usemp3 && ! $opt{mp3audio}) { +- $opt{realaudio} = 1; +- return 'retry'; +- } +- return 15; ++ # Report error if no versions are available ++ if ( ! $url_2 ) { ++ logger "WARNING: No $mode versions available\n"; ++ return 'next'; + } +- ++ + # Determine the correct filenames for this download +- $prog{$pid}{fileprefix} = generate_download_filename_prefix( $pid, $dir, $opt{fileprefix} || " - " ); ++ $prog{$pid}{fileprefix} = generate_download_filename_prefix( $pid, $prog{$pid}{dir}, $opt{fileprefix} || " - " ); + logger "\rINFO: File name prefix = $prog{$pid}{fileprefix} \n"; +- $prog{$pid}{dir} = $dir; +- my $file_done = "${dir}/$prog{$pid}{fileprefix}.$prog{$pid}{ext}"; +- my $file = "${dir}/$prog{$pid}{fileprefix}.partial.$prog{$pid}{ext}"; ++ my $file_done = "$prog{$pid}{dir}/$prog{$pid}{fileprefix}.$prog{$pid}{ext}"; ++ my $file = "$prog{$pid}{dir}/$prog{$pid}{fileprefix}.partial.$prog{$pid}{ext}"; + $prog{$pid}{filename} = $file_done; + if ( -f $file_done ) { +- logger "WARNING: File $file_done already exists\n\n"; +- return 1; ++ logger "ERROR: File $file_done already exists\n\n"; ++ return 'abort'; + } + ++ # Skip from here if we are only testing downloads ++ return 'abort' if $opt{test}; ++ + # Create symlink filename if required + my $file_symlink; + if ( $opt{symlink} ) { +@@ -1581,53 +2189,34 @@ + $file_symlink = substitute_fields( $pid, $opt{symlink} ); + } + +- # Skip from here if we are only testing downloads +- return 1 if $opt{test}; +- +- # Get subtitles if they exist and are required ++ # Get subtitles if they exist and are required ++ # best to do this before d/l of file so that the subtitles can be enjoyed while download progresses + my $subfile_done; + my $subfile; + if ( $opt{subtitles} ) { +- $subfile_done = "${dir}/$prog{$pid}{fileprefix}.srt"; +- $subfile = "${dir}/$prog{$pid}{fileprefix}.partial.srt"; +- download_subtitles( $ua, $subfile, $version_pids{ $prog{$pid}{version} } ); ++ $subfile_done = "$prog{$pid}{dir}/$prog{$pid}{fileprefix}.srt"; ++ $subfile = "$prog{$pid}{dir}/$prog{$pid}{fileprefix}.partial.srt"; ++ $ua->proxy( ['http'] => undef ) if $opt{partialproxy}; ++ download_stream_subtitles( $ua, $subfile, $version_pids{ $prog{$pid}{version} } ); ++ $ua->proxy( ['http'] => $proxy_url ) if $opt{partialproxy}; + } + + my $return; + # Do rtmp download +- if ( $opt{rtmp} ) { +- # Get player url +- my %metadata = get_pid_metadata( $ua, $pid ); +- # get this redirected page and find out where it is redirected to +- my ($ub, $request, $response, $prog_url); +- $ub = new LWP::UserAgent; +- $request = new HTTP::Request HEAD => $metadata{player}; +- $response = $ub->request($request); +- $prog_url = $response->request->url; +- $return = download_h264_rtmp_stream( $ua, $url_2, $prog_url, $file, $file_done, $file_symlink ); ++ if ( $mode =~ /^(rtmp|flash)/ ) { ++ $return = download_stream_rtmp( $ua, $streamdata{streamurl}, $pid, $mode, $streamdata{application}, $streamdata{tcurl}, $streamdata{authstring}, $streamdata{swfurl}, $file, $file_done, $file_symlink ); + + # Do the N95 h.264 download +- } elsif ( $opt{n95} ) { +- my $url = get_media_stream_data( $pid, $version_pids{ $prog{$pid}{version} }, 'n95_wifi' ); +- $return = download_h264_low_stream( $ua, $url, $file, $file_done ); ++ } elsif ( $mode =~ /^n95/ ) { ++ $return = download_stream_h264_low( $ua, $url_2, $file, $file_done, $pid, $mode ); + + # Do the iPhone h.264 download +- } elsif ( $prog{$pid}{type} eq 'tv' ) { +- # Disable proxy here if required +- $ua->proxy( ['http'] => undef ) if $opt{partialproxy}; +- $return = download_iphone_stream( $ua, $url_2, $file, $file_done, $file_symlink, 1 ); +- # Re-enable proxy here if required +- $ua->proxy( ['http'] => $proxy_url ) if $opt{partialproxy}; +- +- # Do the iPhone mp3 download +- } elsif ( $prog{$pid}{type} eq 'radio' ) { ++ } else { + # Disable proxy here if required + $ua->proxy( ['http'] => undef ) if $opt{partialproxy}; +- $return = download_iphone_stream( $ua, $url_2, $file, $file_done, $file_symlink, 0 ); ++ $return = download_stream_iphone( $ua, $url_2, $pid, $file, $file_done, $file_symlink, 1 ); + # Re-enable proxy here if required + $ua->proxy( ['http'] => $proxy_url ) if $opt{partialproxy}; +- # If the iphone mp3 download fails then it's probably not ready yet so retry using realaudio +- $opt{realaudio} = 1 if $return eq 'retry'; + } + + # Rename the subtitle file accordingly +@@ -1647,13 +2236,12 @@ + + + # Download Subtitles, convert to srt(SubRip) format and apply time offset +-sub download_subtitles { ++sub download_stream_subtitles { + my ( $ua, $file, $verpid ) = @_; + my $suburl; + my $subs; + logger "INFO: Getting Subtitle metadata for $verpid\n" if $opt{verbose}; +- $suburl = get_media_stream_data( undef, $verpid, 'subtitles' ); +- ++ $suburl = %{get_media_stream_data( undef, $verpid, 'subtitles')}->{streamurl}; + # Return if we have no url + if (! $suburl) { + logger "INFO: Subtitles not available\n"; +@@ -1793,12 +2381,12 @@ + + # Get title + # Amazon with Bruce Parry: Episode 1 +- my ( $title, $type ); ++ my ( $title, $prog_type ); + $title = $1 if $xml =~ m{\s*(.+?)\s*<\/title>}; + + # Get type +- $type = 'tv' if grep /kind="programme"/, $xml; +- $type = 'radio' if grep /kind="radioProgramme"/, $xml; ++ $prog_type = 'tv' if grep /kind="programme"/, $xml; ++ $prog_type = 'radio' if grep /kind="radioProgramme"/, $xml; + + # Split into <item kind="programme"> sections + for ( split /<item\s+kind="(radioProgramme|programme)"/, $xml ) { +@@ -1812,310 +2400,389 @@ + $version_pids{$version} = $verpid; + logger "INFO: Version: $version, VersionPid: $verpid\n" if $opt{verbose}; + } ++ ++ # Extract Long Name, e.g.: iplayer.episode.setTitle("DIY SOS: Series 16: Swansea"), Strip off the episode name ++ $title =~ s/^(.+):.*?$/$1/g; ++ + # Add to prog hash + $prog{$pid}{versions} = join ',', keys %version_pids; +- return ( $type, $title, %version_pids ); ++ return ( $prog_type, $title, %version_pids ); + } + + + + # Gets media streams data for this version pid +-# $media = all|flashhigh|flashnormal|iphone|flashwii|n95_wifi|n95_3g|mobile|flashaudio|realaudio|wma|subtitles ++# $media = all|itv|flashhigh|flashnormal|iphone|flashwii|n95_wifi|n95_3g|mobile|flashaudio|realaudio|wma|subtitles + sub get_media_stream_data { + my ( $pid, $verpid, $media ) = @_; +- my %streams; +- my $ua = LWP::UserAgent->new(); ++ my %data; ++ + # Setup user agent with redirection enabled +- $ua->timeout([$lwp_request_timeout]); +- $ua->proxy( ['http'] => $proxy_url ); +- $ua->cookie_jar( HTTP::Cookies->new( file => $cookiejar, autosave => 1, ignore_discard => 1 ) ); ++ my $ua = create_ua('desktop'); + $opt{quiet} = 0 if $opt{streaminfo}; +- logger "INFO: Getting media stream metadata for $prog{$pid}{name} - $prog{$pid}{episode}, $verpid\n" if $pid; +- my $xml1 = request_url_retry($ua, $media_stream_data_prefix.$verpid, 3, '', ''); +- logger "\n$xml1\n" if $opt{debug}; +- # flatten +- $xml1 =~ s/\n/ /g; + +- for my $xml ( split /<media/, $xml1 ) { +- $xml = "<media".$xml; ++ # ITV streams ++ if ( $prog{$pid}{type} eq 'itv' ) { ++ my $prog_type = 'itv'; ++ $data{$prog_type}{type} = 'ITV ASF Video stream'; ++ $opt{quiet} = 1 if $opt{streaminfo}; ++ $data{$prog_type}{streamurl} = join('|', get_stream_url_itv($ua, $pid) ); ++ $opt{quiet} = 0 if $opt{streaminfo}; ++ ++ # BBC streams ++ } else { ++ my $xml1 = request_url_retry($ua, $media_stream_data_prefix.$verpid, 3, '', ''); ++ logger "\n$xml1\n" if $opt{debug}; ++ # flatten ++ $xml1 =~ s/\n/ /g; ++ ++ for my $xml ( split /<media/, $xml1 ) { ++ $xml = "<media".$xml; ++ my $prog_type; ++ ++ # h.264 high quality stream ++ # <media kind="video" ++ # width="640" ++ # height="360" ++ # type="video/mp4" ++ # encoding="h264" > ++ # <connection ++ # priority="10" ++ # application="bbciplayertok" ++ # kind="level3" ++ # server="bbciplayertokfs.fplive.net" ++ # identifier="mp4:b000zxf4-H26490898078" ++ # authString="d52f77fede048f1ffd6587fd47446dee" ++ # /> ++ # application: bbciplayertok ++ # tcURL: rtmp://bbciplayertokfs.fplive.net:80/bbciplayertok ++ if ( $media =~ /^(flashhigh|all)$/ && $xml =~ m{<media\s+kind="video".+?type="video/mp4".+?encoding="h264".+?application="(.+?)".+?kind="level3"\s+server="(.+?)"\s+?identifier="(.+?)"\s+?authString="(.+?)"} ) { ++ $prog_type = 'flashhigh'; ++ logger "DEBUG: Processing $prog_type stream\n" if $opt{verbose}; ++ ( $data{$prog_type}{application}, $data{$prog_type}{server}, $data{$prog_type}{identifier}, $data{$prog_type}{authstring} ) = ( $1, $2, $3, $4 ); ++ $data{$prog_type}{type} = 'Flash RTMP H.264 high quality stream'; ++ $data{$prog_type}{tcurl} = "rtmp://$data{$prog_type}{server}:80/$data{$prog_type}{application}"; ++ $data{$prog_type}{swfurl} = "http://www.bbc.co.uk/emp/9player.swf?revision=7276"; ++ $data{$prog_type}{streamurl} = "rtmp://$data{$prog_type}{server}:1935/ondemand?_fcs_vhost=$data{$prog_type}{server}&auth=$data{$prog_type}{authstring}&aifp=v001&slist=$data{$prog_type}{identifier}"; ++ } ++ ++ # h.264 normal quality stream ++ # <media kind="video" ++ # width="640" ++ # height="360" ++ # type="video/x-flv" ++ # encoding="vp6" > ++ # <connection ++ # priority="10" ++ # kind="akamai" ++ # server="cp41752.edgefcs.net" ++ # identifier="secure/b000zxf4-streaming90898078" ++ # authString="daEdSdgbcaibFa7biaobCaYdadyaTamazbq-biXsum-cCp-FqrECnEoGBwFvwG" ++ # /> ++ # </media> ++ # ++ # application (e.g.): ondemand?_fcs_vhost=cp41752.edgefcs.net&auth=daEcia8aQaRardxdwb_dCbvc0cPbLavc2cL-bjw5rj-cCp-JnlDCnzn.MEqHpxF&aifp=v001&slist=secure/b000gy717streaming103693754 ++ # tcURL: rtmp://88.221.26.165:80/ondemand?_fcs_vhost=cp41752.edgefcs.net&auth=daEcia8aQaRardxdwb_dCbvc0cPbLavc.2cL-bjw5rj-cCp-JnlDCnznMEqHpxF&aifp=v001&slist=secure/b000gy717streaming103693754 ++ if ( $media =~ /^(flashnormal|all)$/ && $xml =~ m{<media\s+kind="video".+?type="video/x-flv".+?encoding="vp6".+?kind="akamai"\s+server="(.+?)"\s+?identifier="(.+?)"\s+?authString="(.+?)"} ) { ++ $prog_type = 'flashnormal'; ++ logger "DEBUG: Processing $prog_type stream\n" if $opt{verbose}; ++ ( $data{$prog_type}{server}, $data{$prog_type}{identifier}, $data{$prog_type}{authstring} ) = ( $1, $2, $3 ); ++ $data{$prog_type}{application} = "ondemand?_fcs_vhost=$data{$prog_type}{server}&auth=$data{$prog_type}{authstring}&aifp=v001&slist=$data{$prog_type}{identifier}"; ++ $data{$prog_type}{type} = 'Flash RTMP H.264 normal quality stream'; ++ $data{$prog_type}{tcurl} = "rtmp://$data{$prog_type}{server}:80/$data{$prog_type}{application}"; ++ $data{$prog_type}{swfurl} = "http://www.bbc.co.uk/emp/9player.swf?revision=7276"; ++ $data{$prog_type}{streamurl} = "rtmp://$data{$prog_type}{server}:1935/ondemand?_fcs_vhost=$data{$prog_type}{server}&auth=$data{$prog_type}{authstring}&aifp=v001&slist=$data{$prog_type}{identifier}"; ++ } ++ ++ # Wii h.264 standard quality stream ++ #<media kind="video" ++ # width="512" ++ # height="288" ++ # type="video/x-flv" ++ # encoding="spark" > ++ # <connection ++ # priority="10" ++ # kind="akamai" ++ # server="cp41752.edgefcs.net" ++ # identifier="secure/5242138581547639062" ++ # authString="daEd8dLbGaPaZdzdNcwd.auaydJcxcHandp-biX5YL-cCp-BqsECnxnGEsHwyE" ++ # /> ++ #</media> ++ # application (e.g.): ondemand?_fcs_vhost=cp41752.edgefcs.net&auth=daEcpc6cYbhdIakdWduc6bJdPbydbazdmdp-bjxPBF-cCp-GptFAoDqJBnHvzC&aifp=v001&slist=secure/b000g884xstreaming101052333 ++ # tcURL: rtmp: //88.221.26.173:1935/ondemand?_fcs_vhost=cp41752.edgefcs.net&auth=daEcpc6cYbhdIakdWduc6bJdPbydbazdmdp-bjxPBF-cCp-GptFAoDqJBnHvzC&aifp=v001&slist=secure/b000g884xstreaming101052333 ++ # swfUrl: http://www.bbc.co.uk/emp/iplayer/7player.swf?revision=3897 ++ if ( $media =~ /^(flashwii|all)$/ && $xml =~ m{<media\s+kind="video".+?type="video/x-flv".+?encoding="spark".+?kind="akamai"\s+server="(.+?)"\s+?identifier="(.+?)"\s+?authString="(.+?)"} ) { ++ $prog_type = 'flashwii'; ++ logger "DEBUG: Processing $prog_type stream\n" if $opt{verbose}; ++ ( $data{$prog_type}{server}, $data{$prog_type}{identifier}, $data{$prog_type}{authstring} ) = ( $1, $2, $3 ); ++ $data{$prog_type}{application} = "ondemand?_fcs_vhost=$data{$prog_type}{server}&auth=$data{$prog_type}{authstring}&aifp=v001&slist=$data{$prog_type}{identifier}"; ++ $data{$prog_type}{type} = 'Flash RTMP H.264 Wii stream'; ++ $data{$prog_type}{tcurl} = "rtmp://$data{$prog_type}{server}:1935/$data{$prog_type}{application}"; ++ $data{$prog_type}{swfurl} = "http://www.bbc.co.uk/emp/iplayer/7player.swf?revision=3897"; ++ $data{$prog_type}{streamurl} = "rtmp://$data{$prog_type}{server}:1935/ondemand?_fcs_vhost=$data{$prog_type}{server}&auth=$data{$prog_type}{authstring}&aifp=v001&slist=$data{$prog_type}{identifier}"; ++ } ++ ++ # iPhone h.264/mp3 stream ++ #<media kind="video" ++ # width="480" ++ # height="272" ++ # type="video/mp4" ++ # encoding="h264" > ++ # <connection ++ # priority="10" ++ # kind="sis" ++ # server="http://www.bbc.co.uk/mediaselector/3/auth/stream/" ++ # identifier="5242138581547639062" ++ # href="http://www.bbc.co.uk/mediaselector/3/auth/stream/5242138581547639062.mp4" ++ # /> ++ #</media> ++ if ( $media =~ /^(iphone|all)$/ && $xml =~ m{<media\s+kind="video".+?type="video/mp4".+?encoding="h264".+?kind="sis"\s+server="(.+?)"\s+?identifier="(.+?)"\s+?href="(.+?)"} ) { ++ $prog_type = 'iphone'; ++ logger "DEBUG: Processing $prog_type stream\n" if $opt{verbose}; ++ ( $data{$prog_type}{server}, $data{$prog_type}{identifier}, $data{$prog_type}{streamurl} ) = ( $1, $2, $3 ); ++ $data{$prog_type}{type} = 'iPhone stream'; ++ } ++ ++ # Nokia N95 h.264 low quality stream (WiFi) ++ #<media kind="video" ++ # type="video/mpeg" ++ # encoding="h264" > ++ # <connection ++ # priority="10" ++ # kind="sis" ++ # server="http://www.bbc.co.uk/mediaselector/4/sdp/" ++ # identifier="b00108ld/iplayer_streaming_n95_wifi" ++ # href="http://www.bbc.co.uk/mediaselector/4/sdp/b00108ld/iplayer_streaming_n95_wifi" ++ # /> ++ #</media> ++ if ( $media =~ /^(n95_wifi|all)$/ && $xml =~ m{<media\s+kind="video".+?type="video/mpeg".+?encoding="h264".+?kind="sis"\s+server="(.+?)"\s+?identifier="(.+?)"\s+?href="(.+?)"} ) { ++ $prog_type = 'n95_wifi'; ++ logger "DEBUG: Processing $prog_type stream\n" if $opt{verbose}; ++ ( $data{$prog_type}{server}, $data{$prog_type}{identifier}, $data{$prog_type}{href} ) = ( $1, $2, $3 ); ++ $data{$prog_type}{type} = 'Nokia N95 h.264 low quality WiFi stream'; ++ $opt{quiet} = 1 if $opt{streaminfo}; ++ chomp( $data{$prog_type}{streamurl} = request_url_retry($ua, $data{$prog_type}{href}, 2, '', '') ); ++ $opt{quiet} = 0 if $opt{streaminfo}; ++ } ++ ++ # Nokia N95 h.264 low quality stream (3G) ++ #<media kind="" ++ # expires="2008-10-30T12:29:00+00:00" ++ # type="video/mpeg" ++ # encoding="h264" > ++ # <connection ++ # priority="10" ++ # kind="sis" ++ # server="http://www.bbc.co.uk/mediaselector/4/sdp/" ++ # identifier="b009tzxx/iplayer_streaming_n95_3g" ++ # href="http://www.bbc.co.uk/mediaselector/4/sdp/b009tzxx/iplayer_streaming_n95_3g" ++ # /> ++ #</media> ++ if ( $media =~ /^(n95_3g|all)$/ && $xml =~ m{<media\s+kind="".+?type="video/mpeg".+?encoding="h264".+?kind="sis"\s+server="(.+?)"\s+?identifier="(.+?)"\s+?href="(.+?)"} ) { ++ $prog_type = 'n95_3g'; ++ logger "DEBUG: Processing $prog_type stream\n" if $opt{verbose}; ++ ( $data{$prog_type}{server}, $data{$prog_type}{identifier}, $data{$prog_type}{href} ) = ( $1, $2, $3 ); ++ $data{$prog_type}{type} = 'Nokia N95 h.264 low quality 3G stream'; ++ $opt{quiet} = 1 if $opt{streaminfo}; ++ chomp( $data{$prog_type}{streamurl} = request_url_retry($ua, $data{$prog_type}{href}, 2, '', '') ); ++ $opt{quiet} = 0 if $opt{streaminfo}; ++ } ++ ++ # Mobile WMV DRM ++ #<media kind="video" ++ # expires="2008-10-20T21:59:00+01:00" ++ # type="video/wmv" > ++ # <connection ++ # priority="10" ++ # kind="licence" ++ # server="http://iplayldsvip.iplayer.bbc.co.uk/WMLicenceIssuer/LicenceDelivery.asmx" ++ # identifier="0A1CA43B-98A8-43EA-B684-DA06672C0575" ++ # href="http://iplayldsvip.iplayer.bbc.co.uk/WMLicenceIssuer/LicenceDelivery.asmx/0A1CA43B-98A8-43EA-B684-DA06672C0575" ++ # /> ++ #<connection ++ # priority="10" ++ # kind="sis" ++ # server="http://directdl.iplayer.bbc.co.uk/windowsmedia/" ++ # identifier="AmazonwithBruceParry_Episode5_200810132100_mobile" ++ # href="http://directdl.iplayer.bbc.co.uk/windowsmedia/AmazonwithBruceParry_Episode5_200810132100_mobile.wmv" ++ # /> ++ #</media> ++ if ( $media =~ /^(mobile|all)$/ && $xml =~ m{<media\s+kind="video".+?type="video/wmv".+?kind="sis"\s+server="(.+?)"\s+?identifier="(.+?)"\s+?href="(.+?)"} ) { ++ $prog_type = 'mobile'; ++ logger "DEBUG: Processing $prog_type stream\n" if $opt{verbose}; ++ ( $data{$prog_type}{server}, $data{$prog_type}{identifier}, $data{$prog_type}{streamurl} ) = ( $1, $2, $3 ); ++ $data{$prog_type}{type} = 'Mobile WMV DRM stream'; ++ } ++ ++ # Audio rtmp mp3 ++ #<media kind="audio" ++ # type="audio/mpeg" ++ # encoding="mp3" > ++ # <connection ++ # priority="10" ++ # kind="akamai" ++ # server="cp48181.edgefcs.net" ++ # identifier="mp3:secure/radio1/RBN2_mashup_b00d67h9_2008_09_05_22_14_25" ++ # authString="daEbQa1c6cda6aHdudxagcCcUcVbvbncmdK-biXtzq-cCp-DnoFIpznNBqHnzF" ++ # /> ++ #</media> ++ #app: ondemand?_fcs_vhost=cp48181.edgefcs.net&auth=daEasducLbidOancObacmc0amd6d7ana8c6-bjx.9v-cCp-JqlFHoEq.FBqGnxC&aifp=v001&slist=secure/radio1/RBN2_radio_1_-_wednesday_1000_b00g3xcj_2008_12_31_13_21_49 ++ #swfUrl: http://www.bbc.co.uk/emp/9player.swf?revision=7276 ++ #tcUrl: rtmp://92.122.210.173:1935/ondemand?_fcs_vhost=cp48181.edgefcs.net&auth=daEasducLbidOancObacmc0amd6d7ana8c6-bjx.9v-cCp-JqlFHoEqFBqGnxC&aifp=v001&slist=secure/radio1/RBN2_radio_1_-_wednesday_1.000_b00g3xcj_2008_12_31_13_21_49 ++ #pageUrl: http://www.bbc.co.uk/iplayer/episode/b00g3xp7/Annie_Mac_31_12_2008/ ++ if ( $media =~ /^(flashaudio|all)$/ && $xml =~ m{<media\s+kind="audio".+?type="audio/mpeg".+?encoding="mp3".+?kind="akamai"\s+server="(.+?)"\s+?identifier="(.+?)"\s+?authString="(.+?)"} ) { ++ $prog_type = 'flashaudio'; ++ logger "DEBUG: Processing $prog_type stream\n" if $opt{verbose}; ++ ( $data{$prog_type}{server}, $data{$prog_type}{identifier}, $data{$prog_type}{authstring} ) = ( $1, $2, $3 ); ++ $data{$prog_type}{streamurl} = "rtmp://$data{$prog_type}{server}:1935/ondemand?_fcs_vhost=$data{$prog_type}{server}&auth=$data{$prog_type}{authstring}&aifp=v001&slist=$data{$prog_type}{identifier}"; ++ # Remove offending mp3: at the start of the identifier (don't remove in stream url) ++ $data{$prog_type}{identifier} =~ s/^mp3://; ++ $data{$prog_type}{application} = "ondemand?_fcs_vhost=$data{$prog_type}{server}&auth=$data{$prog_type}{authstring}&aifp=v001&slist=$data{$prog_type}{identifier}"; ++ $data{$prog_type}{type} = 'RTMP MP3 stream'; ++ $data{$prog_type}{tcurl} = "rtmp://$data{$prog_type}{server}:1935/$data{$prog_type}{application}"; ++ $data{$prog_type}{swfurl} = "http://www.bbc.co.uk/emp/9player.swf?revision=7276"; ++ } ++ ++ # RealAudio stream ++ #<media kind="audio" ++ # type="audio/real" ++ # encoding="real" > ++ # <connection ++ # priority="10" ++ # kind="sis" ++ # server="http://www.bbc.co.uk" ++ # identifier="/radio/aod/playlists/9h/76/d0/0b/2000_bbc_radio_one" ++ # href="http://www.bbc.co.uk/radio/aod/playlists/9h/76/d0/0b/2000_bbc_radio_one.ram" ++ # /> ++ #</media> ++ # Realaudio for worldservice ++ #<media kind="" ++ #type="audio/real" ++ #encoding="real" > ++ #<connection ++ # priority="10" ++ # kind="edgesuite" ++ # server="http://http-ws.bbc.co.uk.edgesuite.net" ++ # identifier="/generatecssram.esi?file=/worldservice/css/nb/410060838.ra" ++ # href="http://http-ws.bbc.co.uk.edgesuite.net/generatecssram.esi?file=/worldservice/css/nb/410060838.ra" ++ #/> ++ #</media> ++ #</mediaSelection> ++ if ( $media =~ /^(realaudio|all)$/ && $xml =~ m{<media\s+kind="(audio|)".+?type="audio/real".+?encoding="real".+?kind="(sis|edgesuite)"\s+server="(.+?)"\s+?identifier="(.+?)"\s+?href="(.+?)"} ) { ++ $prog_type = 'realaudio'; ++ logger "DEBUG: Processing $prog_type stream\n" if $opt{verbose}; ++ ( $data{$prog_type}{server}, $data{$prog_type}{identifier}, $data{$prog_type}{href} ) = ( $3, $4, $5 ); ++ $data{$prog_type}{type} = 'RealAudio RTSP stream'; ++ $opt{quiet} = 1 if $opt{streaminfo}; ++ chomp( $data{$prog_type}{streamurl} = request_url_retry($ua, $data{$prog_type}{href}, 2, '', '') ); ++ $data{$prog_type}{streamurl} =~ s/[\s\n]//g; ++ $opt{quiet} = 0 if $opt{streaminfo}; ++ } ++ ++ # Radio WMA (low quality) ++ #<mediaSelection xmlns="http://bbc.co.uk/2008/mp/mediaselection"> ++ #<media kind="" ++ # type="audio/wma" ++ # encoding="wma" > ++ # <connection ++ # priority="10" ++ # kind="edgesuite" ++ # server="http://http-ws.bbc.co.uk.edgesuite.net" ++ # identifier="/generatecssasx.esi?file=/worldservice/css/nb/410060838" ++ # href="http://http-ws.bbc.co.uk.edgesuite.net/generatecssasx.esi?file=/worldservice/css/nb/410060838.wma" ++ # /> ++ #</media> ++ if ( $media =~ /^(wma|all)$/ && $xml =~ m{<media\s+kind="(audio|)".+?type="audio/wma".+?encoding="wma".+?kind="(sis|edgesuite)"\s+server="(.+?)"\s+?identifier="(.+?)"\s+?href="(.+?)"} ) { ++ $prog_type = 'wma'; ++ logger "DEBUG: Processing $prog_type stream\n" if $opt{verbose}; ++ ( $data{$prog_type}{server}, $data{$prog_type}{identifier}, $data{$prog_type}{href} ) = ( $3, $4, $5 ); ++ $data{$prog_type}{type} = 'WMA MMS stream'; ++ $opt{quiet} = 1 if $opt{streaminfo}; ++ chomp( $data{$prog_type}{streamurl} = request_url_retry($ua, $data{$prog_type}{href}, 2, '', '') ); ++ $data{$prog_type}{streamurl} =~ s/[\s\n]//g; ++ # HREF="mms://a1899.v394403.c39440.g.vm.akamaistream.net/7/1899/39440/1/bbcworldservice.download.akamai.com/39440//worldservice/css/nb/410060838.wma" ++ $data{$prog_type}{streamurl} =~ s/^.*href=\"(.+?)\".*$/$1/gi; ++ $opt{quiet} = 0 if $opt{streaminfo}; ++ } ++ ++ # Subtitles stream ++ #<media kind="captions" ++ # type="application/ttaf+xml" > ++ # <connection ++ # priority="10" ++ # kind="http" ++ # server="http://www.bbc.co.uk/iplayer/subtitles/" ++ # identifier="b0008dc8rstreaming89808204.xml" ++ # href="http://www.bbc.co.uk/iplayer/subtitles/b0008dc8rstreaming89808204.xml" ++ # /> ++ #</media> ++ if ( $media =~ /^(subtitles|all)$/ && $xml =~ m{<media\s+kind="captions".+?type="application/ttaf\+xml".+?kind="http"\s+server="(.+?)"\s+?identifier="(.+?)"\s+?href="(.+?)"} ) { ++ $prog_type = 'subtitles'; ++ logger "DEBUG: Processing $prog_type stream\n" if $opt{verbose}; ++ ( $data{$prog_type}{server}, $data{$prog_type}{identifier}, $data{$prog_type}{streamurl} ) = ( $1, $2, $3 ); ++ $data{$prog_type}{type} = 'Subtitles stream'; ++ } ++ } ++ # Do iphone redirect check regardless of an xml entry for iphone - sometimes the iphone streams exist regardless ++ if ( my $streamurl = get_stream_url_iphone($ua, $verpid) ) { ++ my $prog_type = 'iphone'; ++ $data{$prog_type}{type} = 'iPhone stream'; ++ # Get iphone redirect ++ $data{$prog_type}{streamurl} = $streamurl; ++ } else { ++ logger "DEBUG: No iphone redirect stream\n" if $opt{verbose}; ++ } ++ ++ } ++ # Return a hash with media => url if 'all' is specified - otherwise just the specified url ++ if ( $media eq 'all' ) { ++ return %data; ++ } else { ++ # Make sure this hash exists before we pass it back... ++ $data{$media}{exists} = 0 if not defined $data{$media}; ++ return $data{$media}; ++ } ++} ++ + +- my ($server, $authstring, $identifier, $href); + +- # h.264 high quality stream +- # <media kind="video" +- # width="640" +- # height="360" +- # type="video/mp4" +- # encoding="h264" > +- # <connection +- # priority="10" +- # application="bbciplayertok" +- # kind="level3" +- # server="bbciplayertokfs.fplive.net" +- # identifier="mp4:b000zxf4-H26490898078" +- # authString="d52f77fede048f1ffd6587fd47446dee" +- # /> +- if ( $media =~ /^(flashhigh|all)$/ && $xml =~ m{<media\s+kind="video".+?type="video/mp4".+?encoding="h264".+?kind="level3"\s+server="(.+?)"\s+?identifier="(.+?)"\s+?authString="(.+?)"} ) { +- ( $server, $identifier, $authstring ) = ( $1, $2, $3 ); +- logger "INFO: RTMP h.264 high quality stream:\nINFO: server=$server\nINFO: identifier=$identifier\nINFO: authstring=$authstring\n" if $opt{verbose}; +- $streams{'flashhigh'} = "rtmp://${server}:1935/ondemand?_fcs_vhost=${server}&auth=${authstring}&aifp=v001&slist=${identifier}"; +- logger "INFO: RTMP high quality stream URL: $streams{'flashhigh'}\n"; +- } +- +- # h.264 normal quality stream +- # <media kind="video" +- # width="512" +- # height="288" +- # type="video/x-flv" +- # encoding="vp6" > +- # <connection +- # priority="10" +- # kind="akamai" +- # server="cp41752.edgefcs.net" +- # identifier="secure/b000zxf4-streaming90898078" +- # authString="daEdSdgbcaibFa7biaobCaYdadyaTamazbq-biXsum-cCp-FqrECnEoGBwFvwG" +- # /> +- # </media> +- # +- if ( $media =~ /^(flashnormal|all)$/ && $xml =~ m{<media\s+kind="video".+?type="video/x-flv".+?encoding="vp6".+?kind="akamai"\s+server="(.+?)"\s+?identifier="(.+?)"\s+?authString="(.+?)"} ) { +- ( $server, $identifier, $authstring ) = ( $1, $2, $3 ); +- logger "INFO: RTMP h.264 normal quality stream:\nINFO: server=$server\nINFO: identifier=$identifier\nINFO: authstring=$authstring\n" if $opt{verbose}; +- $streams{'flashnormal'} = "rtmp://${server}:1935/ondemand?_fcs_vhost=${server}&auth=${authstring}&aifp=v001&slist=${identifier}"; +- logger "INFO: RTMP normal quality stream URL: $streams{'flashnormal'}\n"; +- } +- +- # Wii h.264 standard quality stream +- #<media kind="video" +- # width="512" +- # height="288" +- # type="video/x-flv" +- # encoding="spark" > +- # <connection +- # priority="10" +- # kind="akamai" +- # server="cp41752.edgefcs.net" +- # identifier="secure/5242138581547639062" +- # authString="daEd8dLbGaPaZdzdNcwd.auaydJcxcHandp-biX5YL-cCp-BqsECnxnGEsHwyE" +- # /> +- #</media> +- if ( $media =~ /^(flashwii|all)$/ && $xml =~ m{<media\s+kind="video".+?type="video/x-flv".+?encoding="spark".+?kind="akamai"\s+server="(.+?)"\s+?identifier="(.+?)"\s+?authString="(.+?)"} ) { +- ( $server, $identifier, $authstring ) = ( $1, $2, $3 ); +- logger "INFO: RTMP Wii normal quality stream:\nINFO: server=$server\nINFO: identifier=$identifier\nINFO: authstring=$authstring\n" if $opt{verbose}; +- $streams{'flashwii'} = "rtmp://${server}:1935/ondemand?_fcs_vhost=${server}&auth=${authstring}&aifp=v001&slist=${identifier}"; +- logger "INFO: RTMP Wii normal quality stream URL: $streams{'flashwii'}\n"; +- } +- +- # iPhone h.264/mp3 stream +- #<media kind="video" +- # width="480" +- # height="272" +- # type="video/mp4" +- # encoding="h264" > +- # <connection +- # priority="10" +- # kind="sis" +- # server="http://www.bbc.co.uk/mediaselector/3/auth/stream/" +- # identifier="5242138581547639062" +- # href="http://www.bbc.co.uk/mediaselector/3/auth/stream/5242138581547639062.mp4" +- # /> +- #</media> +- if ( $media =~ /^(iphone|all)$/ && $xml =~ m{<media\s+kind="video".+?type="video/mp4".+?encoding="h264".+?kind="sis"\s+server="(.+?)"\s+?identifier="(.+?)"\s+?href="(.+?)"} ) { +- ( $server, $identifier, $href ) = ( $1, $2, $3 ); +- logger "INFO: iPhone stream:\nINFO: server=$server\nINFO: identifier=$identifier\nINFO: href=$href\n" if $opt{verbose}; +- $streams{'iphone'} = "$href"; +- logger "INFO: iPhone stream URL: $streams{'iphone'}\n"; +- } +- +- # Nokia N95 h.264 low quality stream (WiFi) +- #<media kind="video" +- # type="video/mpeg" +- # encoding="h264" > +- # <connection +- # priority="10" +- # kind="sis" +- # server="http://www.bbc.co.uk/mediaselector/4/sdp/" +- # identifier="b00108ld/iplayer_streaming_n95_wifi" +- # href="http://www.bbc.co.uk/mediaselector/4/sdp/b00108ld/iplayer_streaming_n95_wifi" +- # /> +- #</media> +- if ( $media =~ /^(n95_wifi|all)$/ && $xml =~ m{<media\s+kind="video".+?type="video/mpeg".+?encoding="h264".+?kind="sis"\s+server="(.+?)"\s+?identifier="(.+?)"\s+?href="(.+?)"} ) { +- ( $server, $identifier, $href ) = ( $1, $2, $3 ); +- $opt{quiet} = 1 if $opt{streaminfo}; +- chomp( my $rtsp = request_url_retry($ua, $href, 2, '', '') ); +- $opt{quiet} = 0 if $opt{streaminfo}; +- logger "INFO: Nokia N95 h.264 low quality WiFi stream:\nINFO: server=$server\nINFO: identifier=$identifier\nINFO: href=$href\n" if $opt{verbose}; +- $streams{'n95_wifi'} = "$rtsp"; +- logger "INFO: Nokia N95 h.264 low quality WiFi stream URL: $streams{'n95_wifi'}\n"; +- } +- +- # Nokia N95 h.264 low quality stream (3G) +- #<media kind="" +- # expires="2008-10-30T12:29:00+00:00" +- # type="video/mpeg" +- # encoding="h264" > +- # <connection +- # priority="10" +- # kind="sis" +- # server="http://www.bbc.co.uk/mediaselector/4/sdp/" +- # identifier="b009tzxx/iplayer_streaming_n95_3g" +- # href="http://www.bbc.co.uk/mediaselector/4/sdp/b009tzxx/iplayer_streaming_n95_3g" +- # /> +- #</media> +- if ( $media =~ /^(n95_3g|all)$/ && $xml =~ m{<media\s+kind="".+?type="video/mpeg".+?encoding="h264".+?kind="sis"\s+server="(.+?)"\s+?identifier="(.+?)"\s+?href="(.+?)"} ) { +- ( $server, $identifier, $href ) = ( $1, $2, $3 ); +- $opt{quiet} = 1 if $opt{streaminfo}; +- chomp( my $rtsp = request_url_retry($ua, $href, 2, '', '') ); +- $opt{quiet} = 0 if $opt{streaminfo}; +- logger "INFO: Nokia N95 h.264 low quality 3G stream:\nINFO: server=$server\nINFO: identifier=$identifier\nINFO: href=$href\n" if $opt{verbose}; +- $streams{'n95_3g'} = "$rtsp"; +- logger "INFO: Nokia N95 h.264 low quality 3G stream URL: $streams{'n95_3g'}\n"; +- } +- +- +- +- # Mobile WMV DRM +- #<media kind="video" +- # expires="2008-10-20T21:59:00+01:00" +- # type="video/wmv" > +- # <connection +- # priority="10" +- # kind="licence" +- # server="http://iplayldsvip.iplayer.bbc.co.uk/WMLicenceIssuer/LicenceDelivery.asmx" +- # identifier="0A1CA43B-98A8-43EA-B684-DA06672C0575" +- # href="http://iplayldsvip.iplayer.bbc.co.uk/WMLicenceIssuer/LicenceDelivery.asmx/0A1CA43B-98A8-43EA-B684-DA06672C0575" +- # /> +- #<connection +- # priority="10" +- # kind="sis" +- # server="http://directdl.iplayer.bbc.co.uk/windowsmedia/" +- # identifier="AmazonwithBruceParry_Episode5_200810132100_mobile" +- # href="http://directdl.iplayer.bbc.co.uk/windowsmedia/AmazonwithBruceParry_Episode5_200810132100_mobile.wmv" +- # /> +- #</media> +- if ( $media =~ /^(mobile|all)$/ && $xml =~ m{<media\s+kind="video".+?type="video/wmv".+?kind="sis"\s+server="(.+?)"\s+?identifier="(.+?)"\s+?href="(.+?)"} ) { +- ( $server, $identifier, $href ) = ( $1, $2, $3 ); +- logger "INFO: Mobile WMV DRM stream:\nINFO: server=$server\nINFO: identifier=$identifier\nINFO: href=$href\n" if $opt{verbose}; +- $streams{'mobile'} = "$href"; +- logger "INFO: Mobile WMV DRM stream URL: $streams{'mobile'}\n"; +- } +- +- # Audio rtmp mp3 +- #<media kind="audio" +- # type="audio/mpeg" +- # encoding="mp3" > +- # <connection +- # priority="10" +- # kind="akamai" +- # server="cp48181.edgefcs.net" +- # identifier="mp3:secure/radio1/RBN2_mashup_b00d67h9_2008_09_05_22_14_25" +- # authString="daEbQa1c6cda6aHdudxagcCcUcVbvbncmdK-biXtzq-cCp-DnoFIpznNBqHnzF" +- # /> +- #</media> +- if ( $media =~ /^(flashaudio|all)$/ && $xml =~ m{<media\s+kind="audio".+?type="audio/mpeg".+?encoding="mp3".+?kind="akamai"\s+server="(.+?)"\s+?identifier="(.+?)"\s+?authString="(.+?)"} ) { +- ( $server, $identifier, $authstring ) = ( $1, $2, $3 ); +- # Remove offending mp3: at the start of the identifier +- $identifier =~ s/^mp3://; +- logger "INFO: RTMP MP3 stream:\nINFO: server=$server\nINFO: identifier=$identifier\nINFO: authstring=$authstring\n" if $opt{verbose}; +- $streams{'flashaudio'} = "rtmp://${server}:1935/ondemand?_fcs_vhost=${server}&auth=${authstring}&aifp=v001&slist=${identifier}"; +- logger "INFO: RTMP stream URL: $streams{'flashaudio'}\n"; +- } +- +- # RealAudio stream +- #<media kind="audio" +- # type="audio/real" +- # encoding="real" > +- # <connection +- # priority="10" +- # kind="sis" +- # server="http://www.bbc.co.uk" +- # identifier="/radio/aod/playlists/9h/76/d0/0b/2000_bbc_radio_one" +- # href="http://www.bbc.co.uk/radio/aod/playlists/9h/76/d0/0b/2000_bbc_radio_one.ram" +- # /> +- #</media> +- # Realaudio for worldservice +- #<media kind="" +- #type="audio/real" +- #encoding="real" > +- #<connection +- # priority="10" +- # kind="edgesuite" +- # server="http://http-ws.bbc.co.uk.edgesuite.net" +- # identifier="/generatecssram.esi?file=/worldservice/css/nb/410060838.ra" +- # href="http://http-ws.bbc.co.uk.edgesuite.net/generatecssram.esi?file=/worldservice/css/nb/410060838.ra" +- #/> +- #</media> +- #</mediaSelection> +- if ( $media =~ /^(realaudio|all)$/ && $xml =~ m{<media\s+kind="(audio|)".+?type="audio/real".+?encoding="real".+?kind="(sis|edgesuite)"\s+server="(.+?)"\s+?identifier="(.+?)"\s+?href="(.+?)"} ) { +- ( $server, $identifier, $href ) = ( $3, $4, $5 ); +- $opt{quiet} = 1 if $opt{streaminfo}; +- chomp( my $rtsp = request_url_retry($ua, $href, 2, '', '') ); +- $rtsp =~ s/[\s\n]//g; +- $opt{quiet} = 0 if $opt{streaminfo}; +- logger "INFO: RealAudio RTSP stream:\nINFO: server=$server\nINFO: identifier=$identifier\nINFO: href=$href\n" if $opt{verbose}; +- $streams{'realaudio'} = "$rtsp"; +- logger "INFO: RealAudio RTSP stream URL: $streams{'realaudio'}\n"; +- } +- +- +- # Radio WMA (low quality) +- #<mediaSelection xmlns="http://bbc.co.uk/2008/mp/mediaselection"> +- #<media kind="" +- # type="audio/wma" +- # encoding="wma" > +- # <connection +- # priority="10" +- # kind="edgesuite" +- # server="http://http-ws.bbc.co.uk.edgesuite.net" +- # identifier="/generatecssasx.esi?file=/worldservice/css/nb/410060838" +- # href="http://http-ws.bbc.co.uk.edgesuite.net/generatecssasx.esi?file=/worldservice/css/nb/410060838.wma" +- # /> +- #</media> +- if ( $media =~ /^(wma|all)$/ && $xml =~ m{<media\s+kind="(audio|)".+?type="audio/wma".+?encoding="wma".+?kind="(sis|edgesuite)"\s+server="(.+?)"\s+?identifier="(.+?)"\s+?href="(.+?)"} ) { +- ( $server, $identifier, $href ) = ( $3, $4, $5 ); +- $opt{quiet} = 1 if $opt{streaminfo}; +- chomp( my $mms = request_url_retry($ua, $href, 2, '', '') ); +- $mms =~ s/[\n]//g; +- # HREF="mms://a1899.v394403.c39440.g.vm.akamaistream.net/7/1899/39440/1/bbcworldservice.download.akamai.com/39440//worldservice/css/nb/410060838.wma" +- $mms =~ s/^.*href=\"(.+?)\".*$/$1/gi; +- $opt{quiet} = 0 if $opt{streaminfo}; +- logger "INFO: WMA MMS stream:\nINFO: server=$server\nINFO: identifier=$identifier\nINFO: href=$href\n" if $opt{verbose}; +- $streams{'wma'} = "$mms"; +- logger "INFO: WMA MMS stream URL: $streams{'wma'}\n"; +- } +- +- +- # Subtitles stream +- #<media kind="captions" +- # type="application/ttaf+xml" > +- # <connection +- # priority="10" +- # kind="http" +- # server="http://www.bbc.co.uk/iplayer/subtitles/" +- # identifier="b0008dc8rstreaming89808204.xml" +- # href="http://www.bbc.co.uk/iplayer/subtitles/b0008dc8rstreaming89808204.xml" +- # /> +- #</media> +- if ( $media =~ /^(subtitles|all)$/ && $xml =~ m{<media\s+kind="captions".+?type="application/ttaf\+xml".+?kind="http"\s+server="(.+?)"\s+?identifier="(.+?)"\s+?href="(.+?)"} ) { +- ( $server, $identifier, $href ) = ( $1, $2, $3 ); +- logger "INFO: Subtitles stream:\nINFO: server=$server\nINFO: identifier=$identifier\nINFO: href=$href\n" if $opt{verbose}; +- $streams{'subtitles'} = "$href"; +- logger "INFO: Subtitles stream URL: $streams{'subtitles'}\n"; ++sub display_stream_info { ++ my ($pid, $verpid, $media) = (@_); ++ logger "INFO: Getting media stream metadata for $prog{$pid}{name} - $prog{$pid}{episode}, $verpid\n" if $pid; ++ my %data = get_media_stream_data( $pid, $verpid, $media); ++ # Print out stream data ++ for my $prog_type (sort keys %data) { ++ logger "stream: $prog_type\n"; ++ for my $entry ( sort keys %{ $data{$prog_type} } ) { ++ logger sprintf("%-11s %s\n", $entry.':', $data{$prog_type}{$entry} ); + } ++ logger "\n"; + } +- logger "\n" if $opt{streaminfo}; +- $opt{quiet} = 1 if $opt{streaminfo}; ++ return 0; ++} + +- # Return a hash with media => url if 'all' is specified - otherwise just the specified url +- return %streams if $media eq 'all'; +- return $streams{$media}; ++ ++ ++# Displays specified metadata from supplied hash ++# Usage: display_metadata( <hashref>, <array of elements to display> ) ++sub display_metadata { ++ my %data = %{$_[0]}; ++ shift; ++ my @keys = @_; ++ @keys = keys %data if $#_ < 0; ++ logger "\n"; ++ for (@keys) { ++ logger sprintf "%-15s %s\n", ucfirst($_).':', $data{$_} if $data{$_}; ++ } ++ return 0; + } + + + + # Actually do the h.264/mp3 downloading + # ( $ua, $pid, $url_2, $file, $file_done, '0|1 == rearrange moov' ) +-sub download_iphone_stream { +- my ( $ua, $url_2, $file, $file_done, $file_symlink, $rearrange ) = @_; ++sub download_stream_iphone { ++ my ( $ua, $url_2, $pid, $file, $file_done, $file_symlink, $rearrange ) = @_; + + # Stage 3a: Download 1st byte to get exact file length + logger "INFO: Stage 3 URL = $url_2\n" if $opt{verbose}; +@@ -2132,53 +2799,44 @@ + my $req = HTTP::Request->new ('GET', $url_2, $h); + my $res = $ua->request($req); + # e.g. Content-Range: bytes 0-1/181338136 (return if no content length returned) +- my $file_len = $res->header("Content-Range"); +- if ( ! $file_len ) { +- logger "ERROR: No Content-Range was obtained\n" if $opt{verbose}; ++ my $download_len = $res->header("Content-Range"); ++ if ( ! $download_len ) { ++ #logger "ERROR: No Content-Range was obtained\n" if $opt{verbose}; ++ logger "WARNING: iphone version not available\n"; + return 'retry' + } +- $file_len =~ s|^bytes 0-1/(\d+).*$|$1|; +- logger "INFO: Download File Length $file_len\n" if $opt{verbose}; ++ $download_len =~ s|^bytes 0-1/(\d+).*$|$1|; ++ logger "INFO: Download File Length $download_len\n" if $opt{verbose}; + + # Only do this if we're rearranging QT streams + my $mdat_start = 0; +- my $moov_start = $file_len + 1; ++ # default to this if we are not rearranging (tells the download chunk loop where to stop - i.e. EOF instead of end of mdat atom) ++ my $moov_start = $download_len + 1; + my $header; + if ($rearrange) { + # Get ftyp+wide header etc + $mdat_start = 0x1c; + my $buffer = download_block(undef, $url_2, $ua, 0, $mdat_start + 4); + # Get bytes upto (but not including) mdat atom start -> $header +- $header = download_block(undef, $url_2, $ua, 0, $mdat_start - 1, $file_len); +- ++ $header = substr($buffer, 0, $mdat_start); ++ + # Detemine moov start +- # Get mdat_end_offset_chars from downloaded block +- my $mdat_end_offset_chars = substr($buffer, $mdat_start, 4); +- my $mdat_end_offset = bytestring_to_int($mdat_end_offset_chars); +- logger "DEBUG: mdat_end_offset = ".get_hex($mdat_end_offset_chars)." = $mdat_end_offset\n" if $opt{debug}; +- logger "DEBUG: mdat_end_offset (decimal) = $mdat_end_offset\n" if $opt{debug}; ++ # Get mdat_length_chars from downloaded block ++ my $mdat_length_chars = substr($buffer, $mdat_start, 4); ++ my $mdat_length = bytestring_to_int($mdat_length_chars); ++ logger "DEBUG: mdat_length = ".get_hex($mdat_length_chars)." = $mdat_length\n" if $opt{debug}; ++ logger "DEBUG: mdat_length (decimal) = $mdat_length\n" if $opt{debug}; + # The MOOV box starts one byte after MDAT box ends +- $moov_start = $mdat_start + $mdat_end_offset; +- +- +- ## scan 2nd level atoms in moov atom until we get stco atom(s) +- # We can skip first 8 bytes (moov atom header) +- #my $i = 8; +- #while( $i < $moov_length - 4 ) { +- # my $atom_len = bytestring_to_int( substr($moovdata, $i, 4) ); +- # my $atom_name = substr($moovdata, $i+4, 4); +- # logger "Parsing atom: $atom_name, length: $atom_len\n"; +- # # Increment $i by atom_len to get next atom +- # $i += $atom_len; +- #} ++ $moov_start = $mdat_start + $mdat_length; + } + + # If we have partial content and wish to stream, resume the download & spawn off STDOUT from existing file start + # Sanity check - we cannot support downloading of partial content if we're streaming also. + if ( $opt{stdout} && (! $opt{nowrite}) && -f $file ) { + logger "WARNING: Partially downloaded file exists, streaming will start from the beginning of the programme\n"; +- # Don't do usual streaming code +- $opt{stdout} = 0; ++ # Don't do usual streaming code - also force all messages to go to stderr ++ delete $opt{stdout}; ++ $opt{stderr} = 1; + $childpid = fork(); + if (! $childpid) { + # Child starts here +@@ -2205,15 +2863,15 @@ + my $fh = open_file_append($file); + + # If the partial file already exists, then resume from the correct mdat/download offset +- my $restart_offset = $mdat_start; ++ my $restart_offset = 0; + my $moovdata; + my $moov_length = 0; + + if ($rearrange) { + # if cookie fails then trigger a retry after deleting cookiejar +- # Determine moov atom length so we can work out if the partially downloaded file has the moov atom in it already ++ # Determine orginal moov atom length so we can work out if the partially downloaded file has the moov atom in it already + $moov_length = bytestring_to_int( download_block( undef, $url_2, $ua, $moov_start, $moov_start+3 ) ); +- logger "INFO: moov atom length = $moov_length \n" if $opt{verbose}; ++ logger "INFO: original moov atom length = $moov_length \n" if $opt{verbose}; + # Sanity check this moov length - chances are that were being served up a duff file if this is > 10% of the file size or < 64k + if ( $moov_length > (${moov_start}/9.0) || $moov_length < 65536 ) { + logger "WARNING: Bad file download, deleting cookie \n"; +@@ -2222,33 +2880,73 @@ + unlink $file; + return 'retry'; + } +- } +- +- # If we have a too-small-sized file and not stdout and not no-write then this is a partial download +- if (-f $file && (! $opt{stdout}) && (! $opt{nowrite}) && stat($file)->size > ($moov_length+$mdat_start) ) { +- # Calculate new start offset (considering that we've put moov first in file) +- $restart_offset = stat($file)->size - $moov_length; +- logger "INFO: Resuming download from $restart_offset \n"; +- } + +- if ($rearrange) { ++ # we still need an accurate moovlength for the already downloaded moov atom for resume restart_offset..... + # If we have no existing file, a file which doesn't yet even have the moov atom, or using stdout (or no-write option) +- if ( $opt{stdout} || $opt{nowrite} || stat($file)->size < ($moov_length+$mdat_start) ) { ++ # (allow extra 1k on moov_length for metadata when testing) ++ if ( $opt{stdout} || $opt{nowrite} || stat($file)->size < ($moov_length+$mdat_start+1024) ) { + # get moov chunk into memory +- $moovdata = download_block( undef, $url_2, $ua, $moov_start, (${file_len}-1) ); ++ $moovdata = download_block( undef, $url_2, $ua, $moov_start, (${download_len}-1) ); ++ ++ # Create new udta atom with child atoms for metadata ++ my $udta_new = create_qt_atom('udta', ++ create_qt_atom( chr(0xa9).'nam', $prog{$pid}{name}.' - '.$prog{$pid}{episode}, 'string' ). ++ create_qt_atom( chr(0xa9).'alb', $prog{$pid}{name}, 'string' ). ++ create_qt_atom( chr(0xa9).'trk', $prog{$pid}{episode}, 'string' ). ++ create_qt_atom( chr(0xa9).'aut', $prog{$pid}{channel}, 'string' ). ++ create_qt_atom( chr(0xa9).'ART', $prog{$pid}{channel}, 'string' ). ++ create_qt_atom( chr(0xa9).'des', $prog{$pid}{desc}, 'string' ). ++ create_qt_atom( chr(0xa9).'cmt', 'Downloaded with get_iplayer', 'string' ). ++ create_qt_atom( chr(0xa9).'req', 'QuickTime 6.0 or greater', 'string' ). ++ create_qt_atom( chr(0xa9).'day', (localtime())[5] + 1900, 'string' ) ++ ); ++ # Insert new udta atom over the old one and get the new $moov_length (and update moov atom size field) ++ replace_moov_udta_atom ( $udta_new, $moovdata ); ++ + # Process the moov data so that we can relocate it (change the chunk offsets that are absolute) ++ # Also update moov+_length to be accurate after metadata is added etc + $moov_length = relocate_moov_chunk_offsets( $moovdata ); +- # write moov atom to file next (yes - were rearranging the file - moov+header+mdat - not header+mdat+moov) +- logger "INFO: Appending moov+ftype+wide atoms to $file\n" if $opt{verbose}; +- # Write moov atom +- print $fh $moovdata if ! $opt{nowrite}; +- print STDOUT $moovdata if $opt{stdout}; ++ logger "INFO: New moov atom length = $moov_length \n" if $opt{verbose}; ++ # write moov atom to file next (yes - were rearranging the file - header+moov+mdat - not header+mdat+moov) ++ logger "INFO: Appending ftype+wide+moov atoms to $file\n" if $opt{verbose}; + # Write header atoms (ftyp, wide) + print $fh $header if ! $opt{nowrite}; + print STDOUT $header if $opt{stdout}; ++ # Write moov atom ++ print $fh $moovdata if ! $opt{nowrite}; ++ print STDOUT $moovdata if $opt{stdout}; ++ # If were not resuming we want to only start the download chunk loop from mdat_start ++ $restart_offset = $mdat_start; ++ } ++ ++ # Get accurate moov_length from file (unless stdout or nowrite options are specified) ++ # Assume header+moov+mdat atom layout ++ if ( (! $opt{stdout}) && (! $opt{nowrite}) && stat($file)->size > ($moov_length+$mdat_start) ) { ++ logger "INFO: Getting moov atom length from partially downloaded file $file\n" if $opt{verbose}; ++ if ( ! open( MOOVDATA, "< $file" ) ) { ++ logger "ERROR: Cannot Read partially downloaded file\n"; ++ return 4; ++ } ++ my $data; ++ seek(MOOVDATA, $mdat_start, 0); ++ if ( read(MOOVDATA, $data, 4, 0) != 4 ) { ++ logger "ERROR: Cannot Read moov atom length from partially downloaded file\n"; ++ return 4; ++ } ++ close MOOVDATA; ++ # Get moov atom size from file ++ $moov_length = bytestring_to_int( substr($data, 0, 4) ); ++ logger "INFO: moov atom length (from partially downloaded file) = $moov_length \n" if $opt{verbose}; + } + } + ++ # If we have a too-small-sized file (greater than moov_length+mdat_start) and not stdout and not no-write then this is a partial download ++ if (-f $file && (! $opt{stdout}) && (! $opt{nowrite}) && stat($file)->size > ($moov_length+$mdat_start) ) { ++ # Calculate new start offset (considering that we've put moov first in file) ++ $restart_offset = stat($file)->size - $moov_length; ++ logger "INFO: Resuming download from $restart_offset \n"; ++ } ++ + # Create symlink if required + if ( $opt{symlink} ) { + # remove old symlink +@@ -2273,16 +2971,15 @@ + $e = $s + $chunk_size - 1; + } + # Get block from URL and append to $file +- if ( download_block($file, $url_2, $ua, $s, $e, $file_len, $fh ) ) { ++ if ( download_block($file, $url_2, $ua, $s, $e, $download_len, $fh ) ) { + logger "ERROR: Could not download block $s - $e from $file\n\n"; +- return 9; ++ return 'retry'; + } + } + + # end marker + my $end_time = time(); + +- # Should now be able to concatenate header.block + mdat.block + moov.block to get movie! + # Calculate average speed, duration and total bytes downloaded + logger sprintf("INFO: Downloaded %.2fMB in %s at %5.0fkbps to %s\n", + ($moov_start - 1 - $restart_offset) / (1024.0 * 1024.0), +@@ -2292,57 +2989,217 @@ + + # Moving file into place as complete (if not stdout) + move($file, $file_done) if ! $opt{stdout}; ++ ++ # Re-symlink file ++ if ( $opt{symlink} ) { ++ # remove old symlink ++ unlink $file_symlink if -l $file_symlink; ++ symlink $file_done, $file_symlink; ++ logger "INFO: Created symlink from '$file_symlink' -> '$file_done'\n" if $opt{verbose}; ++ } ++ $prog{$pid}{mode} = 'iphone'; + return 0; + } + + + +-sub download_h264_rtmp_stream { +- my ( $ua, $url_2, $prog_url, $file, $file_done, $file_symlink ) = @_; +- my $file_flv = $file; # .'.flv'; ++# Actually do the RTMP stream downloading ++sub download_stream_rtmp { ++ my ( $ua, $url_2, $pid, $mode, $application, $tcurl, $authstring, $swfurl, $file, $file_done, $file_symlink ) = @_; ++ my $file_tmp; ++ my $cmd; ++ ++ if ( $opt{raw} ) { ++ $file_tmp = $file; ++ } else { ++ $file_tmp = $file.'.flv' ++ } + +- logger "INFO: url: $url_2, prog_url: $prog_url, file: $file, file_done: $file_done\n" if $opt{verbose}; ++ # Remove failed file download (below a certain size) - hack to get around rtmpdump not returning correct exit code ++ if ( -f $file_tmp && stat($file_tmp)->size < $min_download_size ) { ++ unlink( $file_tmp ); ++ } ++ ++ logger "INFO: RTMP_URL: $url_2, tcUrl: $tcurl, application: $application, authString: $authstring, swfUrl: $swfurl, file: $file, file_done: $file_done\n" if $opt{verbose}; + + # Create symlink if required + if ( $opt{symlink} ) { + # remove old symlink + unlink $file_symlink if -l $file_symlink; +- symlink $file_flv, $file_symlink; +- logger "INFO: Created symlink from '$file_symlink' -> '$file_flv'\n" if $opt{verbose}; ++ symlink $file_tmp, $file_symlink; ++ logger "INFO: Created symlink from '$file_symlink' -> '$file_tmp'\n" if $opt{verbose}; + } ++ $cmd = "$rtmpdump --resume --rtmp \"$url_2\" --auth \"$authstring\" --swfUrl \"$swfurl\" --tcUrl \"$tcurl\" --app \"$application\" -o \"$file_tmp\" >&2"; ++ logger "\n\nINFO: Command: $cmd\n" if $opt{verbose}; ++ my $return = system($cmd); ++ # Hack to get around rtmpdump prentending to fail on successful flash downloads ++ if ( (! -f $file_tmp) || ($return && -f $file_tmp && stat($file_tmp)->size < $min_download_size) ) { ++ logger "\n\nINFO: Command: $cmd\n" if $opt{verbose}; ++ logger "\nWARNING: Failed to download file $file_tmp via RTMP\n"; ++ unlink $file_tmp; ++ return 'next'; ++ } ++ ++ # Retain raw flv format if required ++ if ( $opt{raw} ) { ++ move($file_tmp, $file_done) if ! $opt{stdout}; ++ return 0; + +- my $cmd = "$rtmpdump --rtmp \"$url_2\" --pageUrl \"$prog_url\" --swfUrl \"http://www.bbc.co.uk/emp/9player.swf?revision=6928_7030\" --tcUrl \"rtmp://bbciplayertokfs.fplive.net:80/bbciplayertok\" --app \"bbciplayertok\" -o \"$file_flv\" >&2"; +- #my $cmd2 = "$ffmpeg -i \"$file_flv\" -vcodec copy -acodec copy -f mp4 -y \"$file\" >&2"; +- #my $cmd2 = "$mencoder -oac copy -ovc copy -o \"$file\" \"$file_flv\" >&2"; ++ # Convert flv to mp3 for flash audio ++ } elsif ( $mode eq 'flashaudio' ) { ++ # We could do id3 tagging here but id3v2 does this later anyway ++ $cmd = "$ffmpeg -i \"$file_tmp\" -vn -acodec copy -y \"$file\" >&2"; + +- # logger "\n\nINFO: Command1: $cmd\nINFO: Command2: $cmd2\n\n" if $opt{verbose}; +- if ( system($cmd) ) { +- logger "\nWARNING: Failed to download file $file via RTMP\n"; +- return 1; ++ # Convert video flv to mp4/avi if required ++ } else { ++ $cmd = "$ffmpeg $ffmpeg_opts -i \"$file_tmp\" -vcodec copy -acodec copy -f $prog{$pid}{ext} -y \"$file\" >&2"; ++ } ++ ++ logger "\n\nINFO: Command: $cmd\n\n" if $opt{verbose}; ++ # Run flv conversion and delete source file on success ++ if ( (! system($cmd)) && -f $file && stat($file)->size > $min_download_size ) { ++ unlink( $file_tmp ); ++ ++ # If the ffmpeg conversion failed, remove the failed-converted file attempt - move the file as done anyway ++ } else { ++ logger "WARNING: flv conversion failed - retaining flv file\n"; ++ unlink $file; ++ $file = $file_tmp; ++ $file_done = $file_tmp; ++ } ++ # Moving file into place as complete (if not stdout) ++ move($file, $file_done) if ! $opt{stdout}; ++ ++ # Re-symlink file ++ if ( $opt{symlink} ) { ++ # remove old symlink ++ unlink $file_symlink if -l $file_symlink; ++ symlink $file_done, $file_symlink; ++ logger "INFO: Created symlink from '$file_symlink' -> '$file_done'\n" if $opt{verbose}; ++ } ++ ++ logger "INFO: Downloaded $file_done\n"; ++ $prog{$pid}{mode} = $mode; ++ return 0; ++} ++ ++ ++ ++# Actually do the MMS video stream downloading ++sub download_stream_mms_video { ++ my ( $ua, $urls, $file, $file_done, $pid ) = @_; ++ my $file_tmp; ++ my $cmd; ++ my $null; ++ my @url_list = split /\|/, $urls; ++ my @file_tmp_list; ++ my %threadpid; ++ ++ logger "INFO: MMS_URLs: ".(join ', ', @file_tmp_list).", file: $file, file_done: $file_done\n" if $opt{verbose}; ++ ++ # Start marker ++ my $start_time = time(); ++ # Download each mms url (multi-threaded to download in parallel) ++ my $file_part_prefix = "$prog{$pid}{dir}/$prog{$pid}{fileprefix}_part"; ++ for ( my $count = 0; $count <= $#url_list; $count++ ) { ++ ++ # Create temp download filename ++ $file_tmp = $file_part_prefix.($count+1).".asf"; ++ $file_tmp_list[$count] = $file_tmp; ++ $null = " 2>/dev/null " if (! $opt{verbose}) && (! $opt{debug}); ++ $cmd = "$mplayer -dumpstream \"$url_list[$count]\" -dumpfile \"$file_tmp\" $null >&2 </dev/null"; ++ logger "\n\nINFO: Command: $cmd\n" if $opt{verbose}; ++ ++ my $childpid = fork(); ++ if (! $childpid) { ++ # Child starts here ++ logger "INFO: Downloading file $file_tmp\n"; ++ if ( system($cmd) ) { ++ logger "\nWARNING: Failed to download file $file_tmp via MMS\n"; ++ exit 1; ++ } ++ logger "INFO: Download thread has completed for file $file_tmp\n"; ++ exit 0; ++ } ++ # Create a hash of process_id => 'count' ++ $threadpid{$childpid} = $count; ++ } ++ # Wait for all threads to complete ++ $| = 1; ++ # Autoreap zombies ++ $SIG{CHLD}='IGNORE'; ++ my $done = 0; ++ while (keys %threadpid) { ++ my @sizes; ++ my $total_size = 0; ++ my $total_size_new = 0; ++ my $format = "Threads: "; ++ sleep 1; ++ #logger "DEBUG: ProcessIDs: ".(join ',', keys %threadpid)."\n"; ++ for my $procid (sort keys %threadpid) { ++ my $size = 0; ++ # Is this child still alive? ++ if ( kill 0 => $procid ) { ++ logger "DEBUG Thread $threadpid{$procid} still alive ($file_tmp_list[$threadpid{$procid}])\n" if $opt{debug}; ++ # Build the status string ++ $format .= "%d) %.3fMB "; ++ $size = stat($file_tmp_list[$threadpid{$procid}])->size if -f $file_tmp_list[$threadpid{$procid}]; ++ push @sizes, $threadpid{$procid}+1, $size/(1024.0*1024.0); ++ $total_size_new += $size; ++ } else { ++ $size = stat($file_tmp_list[$threadpid{$procid}])->size if -f $file_tmp_list[$threadpid{$procid}]; ++ # end marker ++ my $end_time = time(); ++ # Calculate average speed, duration and total bytes downloaded ++ logger sprintf("INFO: Thread #%d Downloaded %.2fMB in %s at %5.0fkbps to %s\n", ++ ($threadpid{$procid}+1), ++ $size / (1024.0 * 1024.0), ++ sprintf("%02d:%02d:%02d", ( gmtime($end_time - $start_time))[2,1,0] ), ++ $size / ($end_time - $start_time) / 1024.0 * 8.0, ++ $file_tmp_list[$threadpid{$procid}] ); ++ # Remove from thread test list ++ delete $threadpid{$procid}; ++ } ++ } ++ $format .= " downloaded (%.0fkbps) \r"; ++ logger sprintf $format, @sizes, ($total_size_new - $total_size) / (time() - $start_time) / 1024.0 * 8.0; + } +- # Only convert to mp4 if we have mencoder in path +- # Disable rtmp mode if rtmpdump does not exist +- if ( ! exists_in_path($ffmpeg)) { +- logger "\nWARNING: $ffmpeg does not exist - not converting flv file to mp4\n"; +- #} else { +- # Run flv conversion and delete source file on success +- #if ( ! system($cmd2) ) { +- # unlink( $file_flv ); +- #} else { +- # logger "ERROR: flv to mp4 conversion failed\n"; +- # return 2; +- #} +- # Moving file into place as complete (if not stdout) +- #move($file, $file_done) if ! $opt{stdout}; ++ logger "INFO: All download threads completed\n"; ++ # Unset autoreap ++ delete $SIG{CHLD}; ++ # Retain raw format if required ++ if ( $opt{raw} ) { ++ return 0; + } ++ ++# # Convert video asf to mp4 if required - need to find a suitable converter... ++# } else { ++# # Create part of cmd that specifies each partial file ++# my $filestring; ++# $filestring .= " -i \"$_\" " for (@file_tmp_list); ++# $cmd = "$ffmpeg $ffmpeg_opts $filestring -vcodec copy -acodec copy -f $prog{$pid}{ext} -y \"$file\" >&2"; ++# } ++# ++# logger "\n\nINFO: Command: $cmd\n\n" if $opt{verbose}; ++# # Run asf conversion and delete source file on success ++# if ( ! system($cmd) ) { ++# unlink( @file_tmp_list ); ++# } else { ++# logger "ERROR: asf conversion failed - retaining files ".(join ', ', @file_tmp_list)."\n"; ++# return 2; ++# } ++# # Moving file into place as complete (if not stdout) ++# move($file, $file_done) if ! $opt{stdout}; ++ ++ $prog{$pid}{mode} = 'itv'; + return 0; + } + + + + # Actually do the N95 h.264 downloading +-sub download_h264_low_stream { +- my ( $ua, $url_2, $file, $file_done ) = @_; ++sub download_stream_h264_low { ++ my ( $ua, $url_2, $file, $file_done, $pid, $mode ) = @_; + + # Change filename extension + $file =~ s/mov$/mpg/gi; +@@ -2353,7 +3210,7 @@ + logger "INFO: Downloading Low Quality H.264 stream\n"; + my $cmd = "$vlc $vlc_opts --sout file/ts:${file} $url_2 1>&2"; + if ( system($cmd) ) { +- return 2; ++ return 'next'; + } + + # to STDOUT +@@ -2361,19 +3218,21 @@ + logger "INFO: Streaming Low Quality H.264 stream to stdout\n"; + my $cmd = "$vlc $vlc_opts --sout file/ts:- $url_2 1>&2"; + if ( system($cmd) ) { +- return 2; ++ return 'next'; + } + } + logger "INFO: Downloaded $file_done\n"; + # Moving file into place as complete (if not stdout) + move($file, $file_done) if ! $opt{stdout}; ++ ++ $prog{$pid}{mode} = $mode; + return 0; + } + + + + # Actually do the rtsp downloading +-sub download_rtsp_stream { ++sub download_stream_rtsp { + my ( $ua, $url, $file, $file_done, $file_symlink, $pid ) = @_; + my $childpid; + +@@ -2389,7 +3248,7 @@ + # Create ID3 tagging options for lame (escape " for shell) + my ( $id3_name, $id3_episode, $id3_desc, $id3_channel ) = ( $prog{$pid}{name}, $prog{$pid}{episode}, $prog{$pid}{desc}, $prog{$pid}{channel} ); + $id3_name =~ s|"|\"|g for ($id3_name, $id3_episode, $id3_desc, $id3_channel); +- $lame_opts .= "--ignore-tag-errors --ty ".( (localtime())[5] + 1900 )." --tl \"$id3_name\" --tt \"$id3_episode\" --ta \"$id3_channel\" --tc \"$id3_desc\" "; ++ $lame_opts .= " --ignore-tag-errors --ty ".( (localtime())[5] + 1900 )." --tl \"$id3_name\" --tt \"$id3_episode\" --ta \"$id3_channel\" --tc \"$id3_desc\" "; + + # Use post-download transcoding using lame if namedpipes are not supported (i.e. ActivePerl/Windows) + # (Fallback if no namedpipe support and raw/wav not specified) +@@ -2402,14 +3261,14 @@ + logger "INFO: Downloading wav format (followed by transcoding)\n"; + $cmd = "$mplayer $mplayer_opts -cache 128 -bandwidth $bandwidth -vc null -vo null -ao pcm:waveheader:fast:file=\"${file}.wav\" \"$url\" 1>&2"; + if ( system($cmd) ) { +- return 2; ++ return 'next'; + } + # Transcode + logger "INFO: Transcoding ${file}.wav\n"; + $cmd = "$lame $lame_opts \"${file}.wav\" \"${file}.mp3\" 1>&2"; + logger "DEGUG: Running $cmd\n" if $opt{debug}; +- if ( system($cmd) ) { +- return 2; ++ if ( system($cmd) || (-f "${file}.wav" && stat("${file}.wav")->size < $min_download_size) ) { ++ return 'next'; + } + unlink "${file}.wav"; + move "${file}.mp3", $file_done; +@@ -2425,7 +3284,7 @@ + my $cmd = "$mplayer $mplayer_opts -cache 128 -bandwidth $bandwidth -vc null -vo null -ao pcm:waveheader:fast:file=\"$file\" \"$url\" 1>&2"; + logger "DEGUG: Running $cmd\n" if $opt{debug}; + if ( system($cmd) ) { +- return 2; ++ return 'next'; + } + # Move file to done state + move $file, $file_done if ! $opt{nowrite}; +@@ -2438,7 +3297,7 @@ + my $cmd = "$mplayer $mplayer_opts -cache 128 -bandwidth $bandwidth -dumpstream -dumpfile \"$file\" \"$url\" 1>&2"; + logger "DEGUG: Running $cmd\n" if $opt{debug}; + if ( system($cmd) ) { +- return 2; ++ return 'next'; + } + # Move file to done state + move $file, $file_done if ! $opt{nowrite}; +@@ -2498,7 +3357,7 @@ + if ( system($cmd) ) { + # If we fail then kill off child processes + kill 9, $childpid; +- return 2; ++ return 'next'; + } + # WAV / mp3 mode + } else { +@@ -2506,7 +3365,7 @@ + if ( system($cmd) ) { + # If we fail then kill off child processes + kill 9, $childpid; +- return 2; ++ return 'next'; + } + } + # Wait for child processes to prevent zombies +@@ -2522,13 +3381,14 @@ + logger "INFO: Created symlink from '$file_symlink' -> '$file_done'\n" if $opt{verbose}; + } + ++ $prog{$pid}{mode} = 'realaudio'; + return 0; + } + + + + # Actually do the podcast downloading +-sub download_podcast_stream { ++sub download_stream_podcast { + my ( $ua, $url_2, $file, $file_done, $file_symlink ) = @_; + my $start_time = time(); + +@@ -2548,7 +3408,7 @@ + + if ( download_block($file, $url_2, $ua, $start, undef, undef, $fh) != 0 ) { + logger "ERROR: Download failed\n"; +- return 22; ++ return 'next'; + } else { + # end marker + my $end_time = time(); +@@ -2569,111 +3429,98 @@ + logger "INFO: Created symlink from '$file_symlink' -> '$file_done'\n" if $opt{verbose}; + } + } ++ ++ $prog{$url_2}{mode} = 'podcast'; + return 0; + } + + + + # Get streaming iphone URL +-sub get_iphone_stream_download_url { +- my $ua = shift; +- my $pid = shift; +- +- # Create url with appended 6 digit random number +- my $url_1 = ${iphone_download_prefix}.'/'.${pid}.'?'.(sprintf "%06.0f", 1000000*rand(0)).'%20'; +- logger "INFO: media stream download URL = $url_1\n" if $opt{verbose}; +- +- # Stage 2: e.g. "Location: http://download.iplayer.bbc.co.uk/iplayer_streaming_http_mp4/121285241910131406.mp4?token=iVXexp1yQt4jalB2Hkl%2BMqI25nz2WKiSsqD7LzRmowrwXGe%2Bq94k8KPsm7pI8kDkLslodvHySUyU%0ApM76%2BxEGtoQTF20ZdFjuqo1%2B3b7Qmb2StOGniozptrHEVQl%2FYebFKVNINg%3D%3D%0A" +- logger "\rGetting iplayer download URL " if ! $opt{verbose}; +- my $h = new HTTP::Headers( +- 'User-Agent' => $user_agent{coremedia}, +- 'Accept' => '*/*', +- 'Range' => 'bytes=0-1', +- ); +- my $req = HTTP::Request->new ('GET', $url_1, $h); +- # send request +- my $res = $ua->request($req); +- # Get resulting Location header (i.e. redirect URL) +- my $url_2 = $res->header("location"); +- if ( ! $res->is_redirect ) { +- logger "ERROR: Failed to get redirect from iplayer site\n\n"; +- return ''; +- } +- # Extract redirection Location URL +- $url_2 =~ s/^Location: (.*)$/$1/g; +- # If we get a Redirection containing statuscode=404 then this prog is not yet ready +- if ( $url_2 =~ /statuscode=404/ ) { +- logger "\rERROR: Programme is not yet ready for download\n"; +- return ''; +- } +- +- return $url_2; +-} +- +- ++sub get_stream_url_iphone { ++ my $ua = shift; ++ my $pid = shift; + +-# Get streaming audio URL (Real => rtsp) +-#<media kind="audio" +-# type="audio/real" +-# encoding="real" > +-# <connection +-# priority="10" +-# kind="sis" +-# server="http://www.bbc.co.uk" +-# identifier="/radio/aod/playlists/gs/5d/c0/0b/0900_bbc_radio_two" +-# href="http://www.bbc.co.uk/radio/aod/playlists/gs/5d/c0/0b/0900_bbc_radio_two.ram" +-# /> +-#</media> +-# OR +-#<media kind="" +-# type="audio/real" +-# encoding="real" > +-# <connection +-# priority="10" +-# kind="edgesuite" +-# server="http://http-ws.bbc.co.uk.edgesuite.net" +-# identifier="/generatecssram.esi?file=/worldservice/css/nb/410591221152760.ra" +-# href="http://http-ws.bbc.co.uk.edgesuite.net/generatecssram.esi?file=/worldservice/css/nb/410591221152760.ra" +-# /> +-#</media> +-# +-sub get_audio_stream_download_url { +- my $ua = shift; +- my $url_1 = shift; +- my $url_2; ++ # Create url with appended 6 digit random number ++ my $url_1 = ${iphone_download_prefix}.'/'.${pid}.'?'.(sprintf "%06.0f", 1000000*rand(0)).'%20'; ++ logger "INFO: media stream download URL = $url_1\n" if $opt{verbose}; + +- logger "\rGetting iplayer download URL " if ! $opt{verbose}; +- my $h = new HTTP::Headers( +- 'User-Agent' => $user_agent{coremedia}, +- 'Accept' => '*/*', +- 'Range' => 'bytes=0-', +- ); +- my $req = HTTP::Request->new ('GET', $url_1, $h); +- # send request +- my $res = $ua->request($req); +- # Get resulting content +- my $content = $res->content; +- # Flatten +- $content =~ s/\n/ /g; +- if ( ! $res->is_success ) { +- logger "ERROR: Failed to get audio url from iplayer site\n\n"; +- return ''; +- } +- # If we get a Redirection containing statuscode=404 then this prog is not yet ready +- if ( $content =~ /statuscode=404/ ) { +- logger "\rERROR: Programme is not yet ready for download\n"; +- return ''; +- } +- # extract ram URL +- $url_2 = $2 if $content =~ m{<media kind="(|audio)"\s*type="audio/real".*href="(.+?)"\s*}; +- +- # If we cannot see 'encoding="real"...' then we don't have real audio transcoded format then skip +- if ( ! $url_2 ) { +- logger "\rERROR: Programme is not yet ready for download in RealAudio format\n"; +- return ''; +- } +- +- return $url_2; ++ # Stage 2: e.g. "Location: http://download.iplayer.bbc.co.uk/iplayer_streaming_http_mp4/121285241910131406.mp4?token=iVXexp1yQt4jalB2Hkl%2BMqI25nz2WKiSsqD7LzRmowrwXGe%2Bq94k8KPsm7pI8kDkLslodvHySUyU%0ApM76%2BxEGtoQTF20ZdFjuqo1%2B3b7Qmb2StOGniozptrHEVQl%2FYebFKVNINg%3D%3D%0A" ++ logger "\rGetting iplayer download URL " if (! $opt{verbose}) && ! $opt{streaminfo}; ++ my $h = new HTTP::Headers( ++ 'User-Agent' => $user_agent{coremedia}, ++ 'Accept' => '*/*', ++ 'Range' => 'bytes=0-1', ++ ); ++ my $req = HTTP::Request->new ('GET', $url_1, $h); ++ # send request (use simple_request here because that will not allow redirects) ++ my $res = $ua->simple_request($req); ++ # Get resulting Location header (i.e. redirect URL) ++ my $url_2 = $res->header("location"); ++ if ( ! $res->is_redirect ) { ++ logger "ERROR: Failed to get redirect from iplayer site\n\n"; ++ return ''; ++ } ++ # Extract redirection Location URL ++ $url_2 =~ s/^Location: (.*)$/$1/g; ++ # If we get a Redirection containing statuscode=404 then this prog is not yet ready ++ if ( $url_2 =~ /statuscode=404/ ) { ++ logger "\rERROR: Programme is not yet ready for download\n" if $opt{verbose}; ++ return ''; ++ } ++ ++ return $url_2; ++} ++ ++ ++ ++sub get_stream_url_itv { ++ my ( $ua, $pid ) = ( @_ ); ++ ++ my ( $response, $url_1, $url_2, $url_3, $url_4 ); ++ my $part; ++ my $duration; ++ my $filename; ++ my @url_list; ++ ++ # construct stage 1 request url ++ $url_1 = 'http://www.itv.com/_app/video/GetMediaItem.ashx?vodcrid=crid://itv.com/'.$pid.'&bitrate=384&adparams=SITE=ITV/AREA=CATCHUP.VIDEO/SEG=CATCHUP.VIDEO%20HTTP/1.1'; ++ ++ # Extract '<LicencePlaylist>(.+?) HTTP/1.1</LicencePlaylist>' ++ logger "INFO: ITV Video Stage 1 URL: $url_1\n" if $opt{verbose}; ++ $response = request_url_retry($ua, $url_1, 2, '', ''); ++ logger "DEBUG: Response data: $response\n" if $opt{debug}; ++ $url_2 = $1 if $response =~ m{<LicencePlaylist>(.+?) HTTP/1.1</LicencePlaylist>}; ++ # replace '&' with '&' and append '%20HTTP/1.1' ++ $url_2 =~ s/&/&/g; ++ $url_2 .= '%20HTTP/1.1'; ++ logger "INFO: ITV Video Stage 2 URL: $url_2\n" if $opt{verbose}; ++ $response = request_url_retry($ua, $url_2, 2, '', ''); ++ logger "DEBUG: Response data: $response\n" if $opt{debug}; ++ ++ # Extract hrefs and names. There are multiple entries for parts of prog (due to ads): ++ # e.g. <asx><Title>Doctor Zhivago ++ $prog{$pid}{name} = $1 if $response =~ m{(.+?)<\/Title>}; ++ for my $entry (split /<Entry><ref\s+href=/, $response) { ++ logger "DEBUG: Entry data: $entry\n" if $opt{debug}; ++ $entry .= '<Entry><ref href='.$entry; ++ ++ ( $url_3, $part, $filename, $duration ) = ( $1, $2, $3, $4 ) if $entry =~ m{<Entry><ref\s+href="(.+?)"\s+\/><param\s+value="true"\s+name="Prebuffer"\s+\/>\s*<PARAM\s+NAME="PrgPartNumber"\s+VALUE="(.+?)"\s*\/><PARAM\s+NAME="FileName"\s+VALUE="(.+?)"\s*\/><PARAM\s+NAME="PrgLength"\s+VALUE="(.+?)"\s*\/>}; ++ next if not $url_3; ++ # Replace '&' with '&' in url ++ $url_3 =~ s/&/&/g; ++ logger "INFO: ITV Video Name: $part\n"; ++ ++ logger "INFO: ITV Video Stage 3 URL: $url_3\n" if $opt{verbose}; ++ $entry = request_url_retry($ua, $url_3, 2, '', ''); ++ logger "DEBUG: Response data: $entry\n" if $opt{debug}; ++ ++ # Extract mms (replace 'http' with 'mms') url: e.g.: Ref1=http://itvbrdbnd.wmod.llnwd.net/a1379/o21/ucontent/2007/6/22/1549_384_1_2.wmv?MSWMExt=.asf ++ chomp( $url_4 = 'mms'.$1 ) if $entry =~ m{Ref1=http(.+?)[\r\n]+}; ++ logger "INFO: ITV Video URL: $url_4\n" if $opt{verbose}; ++ push @url_list, $url_4; ++ } ++ return @url_list; + } + + +@@ -2711,7 +3558,7 @@ + # Change all the chunk offsets in moov->stco atoms and add moov_length to them all + # get moov atom length + my $moov_length = bytestring_to_int( substr($moovdata, 0, 4) ); +- # Use index() to seatch for a string within a string ++ # Use index() to search for a string within a string + my $i = -1; + while (($i = index($moovdata, 'stco', $i)) > -1) { + +@@ -2725,10 +3572,11 @@ + #logger "chunk_offset @ $i, $j = '".get_hex( substr($moovdata, $j, 4) )."', $chunk_offset + $moov_length = "; + $chunk_offset += $moov_length; + # write back bytes into $moovdata +- substr($moovdata, $j+0, 1) = chr( ($chunk_offset >> 24) & 0xFF ); +- substr($moovdata, $j+1, 1) = chr( ($chunk_offset >> 16) & 0xFF ); +- substr($moovdata, $j+2, 1) = chr( ($chunk_offset >> 8) & 0xFF ); +- substr($moovdata, $j+3, 1) = chr( ($chunk_offset >> 0) & 0xFF ); ++ #substr($moovdata, $j+0, 1) = chr( ($chunk_offset >> 24) & 0xFF ); ++ #substr($moovdata, $j+1, 1) = chr( ($chunk_offset >> 16) & 0xFF ); ++ #substr($moovdata, $j+2, 1) = chr( ($chunk_offset >> 8) & 0xFF ); ++ #substr($moovdata, $j+3, 1) = chr( ($chunk_offset >> 0) & 0xFF ); ++ write_msb_value_at_offset( $moovdata, $j, $chunk_offset ); + #$chunk_offset = bytestring_to_int( substr($moovdata, $j, 4) ); + #logger "$chunk_offset\n"; + } +@@ -2743,6 +3591,86 @@ + + + ++# Replace the moov->udta atom with a new user-supplied one and update the moov atom size ++# Usage: replace_moov_udta_atom ( $udta_new, $moovdata ) ++sub replace_moov_udta_atom { ++ my $udta_new = $_[0]; ++ my $moovdata = $_[1]; ++ ++ # get moov atom length ++ my $moov_length = bytestring_to_int( substr($moovdata, 0, 4) ); ++ ++ # Find the original udta atom start ++ # Use index() to search for a string within a string ($i will point at the beginning of the atom) ++ my $i = index($moovdata, 'udta', -1) - 4; ++ ++ # determine length of atom (4 bytes preceding the name) ++ my $udta_len = bytestring_to_int( substr($moovdata, $i, 4) ); ++ logger "INFO: Found udta atom at moov atom offset: $i length $udta_len\n" if $opt{verbose}; ++ ++ # Save the data before the udta atom ++ my $moovdata_before_udta = substr($moovdata, 0, $i); ++ ++ # Save the remainder portion of data after the udta atom for later ++ my $moovdata_after_udta = substr($moovdata, $i, $moovdata - $i + $udta_len); ++ ++ # Old udta atom should we need it ++ ### my $udta_old = substr($moovdata, $i, $udta_len); ++ ++ # Create new moov atom ++ $moovdata = $moovdata_before_udta.$udta_new.$moovdata_after_udta; ++ ++ # Recalculate the moov size and insert into moovdata ++ write_msb_value_at_offset( $moovdata, 0, length($moovdata) ); ++ ++ # Write $moovdata back to calling string ++ $_[1] = $moovdata; ++ ++ return 0; ++} ++ ++ ++ ++# Write the msb 4 byte $value starting at $offset into the passed string ++# Usage: write_msb_value($string, $offset, $value) ++sub write_msb_value_at_offset { ++ my $offset = $_[1]; ++ my $value = $_[2]; ++ substr($_[0], $offset+0, 1) = chr( ($value >> 24) & 0xFF ); ++ substr($_[0], $offset+1, 1) = chr( ($value >> 16) & 0xFF ); ++ substr($_[0], $offset+2, 1) = chr( ($value >> 8) & 0xFF ); ++ substr($_[0], $offset+3, 1) = chr( ($value >> 0) & 0xFF ); ++ return 0; ++} ++ ++ ++ ++# Returns a string containing an QT atom ++# Usage: create_qt_atom(<atome name>, <atom data>, ['string']) ++sub create_qt_atom { ++ my ($name, $data, $prog_type) = (@_); ++ if (length($name) != 4) { ++ logger "ERROR: Inavlid QT atom name length '$name'\n"; ++ exit 1; ++ } ++ # prepend string length if this is a string type ++ if ( $prog_type eq 'string' ) { ++ my $value = length($data); ++ $data = '1111'.$data; ++ # overwrite '1111' with total atom length in 2-byte MSB + 0x0 0x0 ++ substr($data, 0, 1) = chr( ($value >> 8) & 0xFF ); ++ substr($data, 1, 1) = chr( ($value >> 0) & 0xFF ); ++ substr($data, 2, 1) = chr(0); ++ substr($data, 3, 1) = chr(0); ++ } ++ my $atom = '0000'.$name.$data; ++ # overwrite '0000' with total atom length in MSB ++ write_msb_value_at_offset( $atom, 0, length($name.$data) + 4 ); ++ return $atom; ++} ++ ++ ++ + # Usage download_block($file, $url_2, $ua, $start, $end, $file_len, $fh); + # ensure filehandle $fh is open in append mode + # or, $content = download_block(undef, $url_2, $ua, $start, $end, $file_len); +@@ -2811,7 +3739,7 @@ + $rate = sprintf("%5.0fkbps", (8.0 / 1024.0) * $rate_bps); + $time = sprintf("%02d:%02d:%02d", ( gmtime( ($file_len - $size) / $rate_bps ) )[2,1,0] ); + } +- printf STDERR "%8.2fMB / %.2fMB %s %5.1f%%, %s remaining \r", ++ logger sprintf "%8.2fMB / %.2fMB %s %5.1f%%, %s remaining \r", + $size / 1024.0 / 1024.0, + $file_len / 1024.0 / 1024.0, + $rate, +@@ -2851,7 +3779,7 @@ + $time = sprintf("%02d:%02d:%02d", ( gmtime( ($file_len - $size) / $rate_bps ) )[2,1,0] ); + } + # time remaining +- printf STDERR "%8.2fMB / %.2fMB %s %5.1f%%, %s remaining \r", ++ logger sprintf "%8.2fMB / %.2fMB %s %5.1f%%, %s remaining \r", + $size / 1024.0 / 1024.0, + $file_len / 1024.0 / 1024.0, + $rate, +@@ -2865,7 +3793,7 @@ + } else { + $rate = sprintf("%5.0fkbps", (8.0 / 1024.0) * $size / ($timecalled - $now) ); + } +- printf STDERR "%8.2fMB %s \r", $size / 1024.0 / 1024.0, $rate; ++ logger sprintf "%8.2fMB %s \r", $size / 1024.0 / 1024.0, $rate; + } + }; + +@@ -2901,6 +3829,19 @@ + + + ++sub create_ua { ++ my $agent = shift; ++ my $ua = LWP::UserAgent->new; ++ $ua->timeout([$lwp_request_timeout]); ++ $ua->proxy( ['http'] => $proxy_url ); ++ $ua->agent( $user_agent{$agent} ); ++ $ua->conn_cache(LWP::ConnCache->new()); ++ $ua->cookie_jar( HTTP::Cookies->new( file => $cookiejar, autosave => 1, ignore_discard => 1 ) ); ++ return $ua; ++}; ++ ++ ++ + # Converts a string of chars to it's HEX representation + sub get_hex { + my $buf = shift || ''; +@@ -2985,11 +3926,7 @@ + sub update_script { + # Get version URL + my $script_file = $0; +- my $ua = LWP::UserAgent->new; +- $ua->timeout([$lwp_request_timeout]); +- $ua->proxy( ['http'] => $proxy_url ); +- $ua->agent( $user_agent{update} ); +- $ua->conn_cache(LWP::ConnCache->new()); ++ my $ua = create_ua('update'); + logger "INFO: Current version is $version\n"; + logger "INFO: Checking for latest version from linuxcentre.net\n"; + my $res = $ua->request( HTTP::Request->new( GET => $version_url ) ); +@@ -3070,10 +4007,8 @@ + <video><url id=\"p1\">${pid}.mov<playlist/></url></video> + <info><description>${desc}</description></info> + </movie>\n" if $opt{fxd}; +- my $newtitle = ${title} ; +- $newtitle =~ s/\&//g ; + print XML "<Stream> +- <Name>\"$newtitle\"</Name> ++ <Name>\"${title}\"</Name> + <url>${pid}.mov</url> + <Subtitle></Subtitle> + <Synopsis>${desc}</Synopsis> +@@ -3294,6 +4229,7 @@ + my $pid = shift; + my $metadata; + my $entry3; ++ my ($name, $episode, $duration, $available, $channel, $expiry, $longdesc, $versions, $guidance, $prog_type, $categories, $player, $thumbnail); + + # This URL works for all prog types: + # http://www.bbc.co.uk/iplayer/playlist/${pid} +@@ -3307,97 +4243,162 @@ + # This URL works for tv/radio prog types: + # $prog_feed_url = http://feeds.bbc.co.uk/iplayer/episode/$pid + +- if ( $prog{$pid}{type} =~ /(tv|radio)/i ) { ++ if ( $prog{$pid}{type} =~ /^(tv|radio)$/i ) { + $entry3 = request_url_retry($ua, $prog_feed_url.$pid, 3, '', ''); + decode_entities($entry3); + logger "DEBUG: $prog_feed_url.$pid:\n$entry3\n\n" if $opt{debug}; + # Flatten + $entry3 =~ s|\n| |g; +- } + +- # Entry3 format +- #<?xml version="1.0" encoding="utf-8"?> +- #<?xml-stylesheet href="http://www.bbc.co.uk/iplayer/style/rss.css" type="text/css"?> +- #<feed xmlns="http://www.w3.org/2005/Atom" xmlns:dcterms="http://purl.org/dc/terms/" xmlns:media="http://search.yahoo.com/mrss/" xml:lang="en-GB"> +- # <title>BBC iPlayer - Episode Detail: Edith Bowman: 22/09/2008 +- # Sara Cox sits in for Edith with another Cryptic Randomizer. +- # 2008-09-29T10:59:45Z +- # tag:feeds.bbc.co.uk,2008:/iplayer/feed/episode/b00djtfh +- # +- # +- # +- # BBC +- # http://www.bbc.co.uk +- # +- # +- # Edith Bowman: 22/09/2008 +- # tag:feeds.bbc.co.uk,2008:PIPS:b00djtfh +- # 2008-09-15T01:28:36Z +- # Sara Cox sits in for Edith with another Cryptic Randomizer. +- # +- # <p> +- # <a href="http://www.bbc.co.uk/iplayer/episode/b00djtfh?src=a_syn30"> +- # <img src="http://www.bbc.co.uk/iplayer/images/episode/b00djtfh_150_84.jpg" alt="Edith Bowman: 22/09/2008" /> +- # </a> +- # </p> +- # <p> +- # Sara Cox sits in for Edith with movie reviews and great new music, plus another Cryptic Randomizer. +- # </p> +- # +- # +- # +- # Edith Bowman: 22/09/2008 +- # Sara Cox sits in for Edith with movie reviews and great new music, plus another Cryptic Randomizer. +- # +- # 9100099 +- # 9100006 +- # 9200069 +- # BBC Radio 1 +- # BBC Radio 1 +- # +- # +- # +- # +- # +- # +- # +- # start=2008-09-22T15:44:20Z; +- # end=2008-09-29T15:02:00Z; +- # scheme=W3C-DTF +- # +- # +- # +- # +- # +- # +- # +- # +- +- my ($duration, $available, $channel, $expiry, $longdesc, $versions, $guidance, $type, $categories, $player, $thumbnail); +- +- $expiry = $1 if $entry3 =~ m{\s*start=.+?;\s*end=(.*?);}; +- $available = $1 if $entry3 =~ m{\s*start=(.+?);\s*end=.*?;}; +- $duration = $1 if $entry3 =~ m{duration=\"(\d+?)\"}; +- $type = $1 if $entry3 =~ m{medium=\"(\w+?)\"}; +- $longdesc = $1 if $entry3 =~ m{\s*(.*?)\s*<\/media:description>}; +- $guidance = $1 if $entry3 =~ m{(.+?)<\/media:rating>}; +- $player = $1 if $entry3 =~ m{}; +- $thumbnail = $1 if $entry3 =~ m{}; +- +- my @cats; +- for (split /\d+<\/media:category>}; +- } +- $categories = join ',', @cats; ++ # Entry3 format ++ # ++ # ++ # ++ # BBC iPlayer - Episode Detail: Edith Bowman: 22/09/2008 ++ # Sara Cox sits in for Edith with another Cryptic Randomizer. ++ # 2008-09-29T10:59:45Z ++ # tag:feeds.bbc.co.uk,2008:/iplayer/feed/episode/b00djtfh ++ # ++ # ++ # ++ # BBC ++ # http://www.bbc.co.uk ++ # ++ # ++ # Edith Bowman: 22/09/2008 ++ # tag:feeds.bbc.co.uk,2008:PIPS:b00djtfh ++ # 2008-09-15T01:28:36Z ++ # Sara Cox sits in for Edith with another Cryptic Randomizer. ++ # ++ # <p> ++ # <a href="http://www.bbc.co.uk/iplayer/episode/b00djtfh?src=a_syn30"> ++ # <img src="http://www.bbc.co.uk/iplayer/images/episode/b00djtfh_150_84.jpg" alt="Edith Bowman: 22/09/2008" /> ++ # </a> ++ # </p> ++ # <p> ++ # Sara Cox sits in for Edith with movie reviews and great new music, plus another Cryptic Randomizer. ++ # </p> ++ # ++ # ++ # ++ # Edith Bowman: 22/09/2008 ++ # Sara Cox sits in for Edith with movie reviews and great new music, plus another Cryptic Randomizer. ++ # ++ # 9100099 ++ # 9100006 ++ # 9200069 ++ # BBC Radio 1 ++ # BBC Radio 1 ++ # ++ # ++ # ++ # ++ # ++ # ++ # ++ # start=2008-09-22T15:44:20Z; ++ # end=2008-09-29T15:02:00Z; ++ # scheme=W3C-DTF ++ # ++ # ++ # ++ # ++ # ++ # ++ # ++ # ++ ++ $expiry = $1 if $entry3 =~ m{\s*start=.+?;\s*end=(.*?);}; ++ $available = $1 if $entry3 =~ m{\s*start=(.+?);\s*end=.*?;}; ++ $duration = $1 if $entry3 =~ m{duration=\"(\d+?)\"}; ++ $prog_type = $1 if $entry3 =~ m{medium=\"(\w+?)\"}; ++ $longdesc = $1 if $entry3 =~ m{\s*(.*?)\s*<\/media:description>}; ++ $guidance = $1 if $entry3 =~ m{(.+?)<\/media:rating>}; ++ $player = $1 if $entry3 =~ m{}; ++ $thumbnail = $1 if $entry3 =~ m{}; ++ ++ my @cats; ++ for (split /\d+<\/media:category>}; ++ } ++ $categories = join ',', @cats; ++ ++ # populate version pid metadata ++ get_version_pids($ua, $pid); ++ ++ # ITV Catch-Up metadata ++ } elsif ( $prog{$pid}{type} eq 'itv' ) { ++ my $prog_metadata_url_itv = 'http://www.itv.com/_app/Dynamic/CatchUpData.ashx?ViewType=5&Filter='; # + ++ $entry3 = request_url_retry($ua, "${prog_metadata_url_itv}${pid}", 3, '', ''); ++ decode_entities($entry3); ++ logger "DEBUG: ${prog_metadata_url_itv}${pid}:\n$entry3\n\n" if $opt{debug}; ++ # Flatten ++ $entry3 =~ s|[\r\n]||g; ++ ++ #div class="itvCatchUpPlayerPanel" xmlns:ms="urn:schemas-microsoft-com:xslt"> ++ #
    ITV Player is sponsored by Freeview
    ++ #

    Doctor Zhivago

    ++ #

    Part 1 of 3. Dramatisation of the epic novel by Boris Pasternak. Growing up in Moscow with his uncle, aunt and cousin Tonya, Yury is captivated by a stunning young girl called ...

    ++ #

    Mon 29 Dec 2008

    ++ # ++ # Duration: 1hr 30 min | ++ # Expires in ++ # 22 ++ # days ++ #

    ++ #

    3 Episodes Available ++ #

    ++ # ++ #
    33105
    ++ #
    17
    ++ #
    http://www.itv.com//img/480x272/Doctor-Zhivago-c47828f8-a1af-4cd2-b5a2-40c18eb7e63c.jpg
    ++ # ++ # + +- # populate version pid metadata +- get_version_pids($ua, $pid); ++ #
    ++ #
    ITV Player is sponsored by Freeview
    ++ #

    Affinity

    ++ #

    Victorian period drama with a murderous, pyschological twist.

    ++ #

    Sun 28 Dec 2008

    ++ # ++ # Duration: 2hr 00 min | ++ # Expires in ++ # 21 ++ # days ++ #

    ++ # ++ #
    ++ #
    ITV Video Guidance

    This programme contains strong language and scenes of a sexual nature  

    ++ #
    ++ #
    ++ #
    33076
    ++ #
    11
    ++ #
    http://www.itv.com//img/480x272/Affinity-9624033b-6e05-4784-85f7-114be0559b24.jpg
    ++ #
    ++ # ++ ++ #$expiry = $1 if $entry3 =~ m{\s*start=.+?;\s*end=(.*?);}; ++ $available = $1 if $entry3 =~ m{\s*(.+?)<\/span>}; ++ $duration = $1 if $entry3 =~ m{Duration:\s*(.+?)\s+\|}; ++ #$prog_type = $1 if $entry3 =~ m{medium=\"(\w+?)\"}; ++ $longdesc = $1 if $entry3 =~ m{

    (.+?)<\/p>}i; ++ $guidance = $1 if $entry3 =~ m{ITV Video Guidance<\/strong>

    \s*(.+?)[\W\s]*<\/p>}; ++ #$player = $1 if $entry3 =~ m{}; ++ $thumbnail = $1 if $entry3 =~ m{

    (.+?)
    }; ++ $name = $1 if $entry3 =~ m{

    (.+?)

    }; ++ } + + # Fill in from cache if not got from metadata + my %metadata; + $metadata{pid} = $pid; + $metadata{index} = $prog{$pid}{index}; +- $metadata{type} = $type || $prog{$pid}{type}; ++ $metadata{name} = $name || $prog{$pid}{name}; ++ $metadata{episode} = $episode || $prog{$pid}{episode}; ++ $metadata{type} = $prog_type || $prog{$pid}{type}; + $metadata{duration} = $duration || $prog{$pid}{duration}; + $metadata{channel} = $channel || $prog{$pid}{channel}; + $metadata{available} = $available || $prog{$pid}{available}; +@@ -3492,7 +4493,7 @@ + logger "WARNING: Cannot write or append to $historyfile\n\n"; + return 1; + } +- print HIST "$pid|$prog{$pid}{name}|$prog{$pid}{episode}|$prog{$pid}{type}|".time()."\n"; ++ print HIST "$pid|$prog{$pid}{name}|$prog{$pid}{episode}|$prog{$pid}{type}|".time()."|$prog{$pid}{mode}\n"; + close HIST; + return 0; + } +@@ -3555,7 +4556,10 @@ + # Add id3 tag to MP3 files if required + sub tag_file { + my $pid = shift; ++ + if ( $prog{$pid}{ext} eq 'mp3' ) { ++ # Return if file does not exist ++ return if ! -f $prog{$pid}{filename}; + # Create ID3 tagging options for external tagger program (escape " for shell) + my ( $id3_name, $id3_episode, $id3_desc, $id3_channel ) = ( $prog{$pid}{name}, $prog{$pid}{episode}, $prog{$pid}{desc}, $prog{$pid}{channel} ); + $id3_name =~ s|"|\"|g for ($id3_name, $id3_episode, $id3_desc, $id3_channel); +@@ -3580,9 +4584,16 @@ + sub list_unique_element_counts { + my $element_name = shift; + my %elements; +- logger "INFO: $opt{type} $element_name List:\n" if $opt{verbose}; ++ logger "INFO: ".(join ',', keys %type)." $element_name List:\n" if $opt{verbose}; + for my $pid (keys %prog) { +- for my $element ( split /,/, $prog{$pid}{$element_name} ) { ++ my @element; ++ # Need to separate the categories ++ if ($element_name eq 'categories') { ++ @element = split /,/, $prog{$pid}{$element_name}; ++ } else { ++ @element[0] = $prog{$pid}{$element_name}; ++ } ++ for my $element (@element) { + $elements{ $element }++; + } + } +@@ -3659,6 +4670,7 @@ + + + ++ + # Save the options on the cmdline as a PVR search with the specified name + sub pvr_add { + my $name = shift; +@@ -3670,7 +4682,7 @@ + return 1; + } + # Parse valid options and create array (ignore options from the options files that have not been overriden on the cmdline) +- for (grep /^(long|output.*|proxy|subdir|whitespace|versions|type|(exclude)?category|(exclude)?channel|command|realaudio|mp3audio|wav|raw|bandwidth|subtitles|suboffset|since|versionlist|verbose)$/, sort {$a <=> $b} keys %opt_cmdline) { ++ for (grep /^(amode|vmode|long|output.*|proxy|subdir|whitespace|versions|type|(exclude)?category|(exclude)?channel|command|realaudio|mp3audio|wav|raw|bandwidth|subtitles|suboffset|since|versionlist|verbose)$/, sort {lc $a cmp lc $b} keys %opt_cmdline) { + if ( defined $opt_cmdline{$_} ) { + push @options, "$_ $opt_cmdline{$_}"; + logger "DEBUG: Adding option $_ = $opt_cmdline{$_}\n" if $opt{debug}; +@@ -3715,7 +4727,7 @@ + pvr_load_list(); + # Print out list + logger "All PVR Searches:\n\n"; +- for my $name ( sort {$a <=> $b} keys %pvrsearches ) { ++ for my $name ( sort {lc $a cmp lc $b} keys %pvrsearches ) { + # Report whether disabled + if ( $pvrsearches{$name}{disable} ) { + logger "(Disabled) PVR Search '$name':\n"; +diff -ruaN mythvodka.orig/scripts/gethulu.pl mythvodka/scripts/gethulu.pl +--- mythvodka.orig/scripts/gethulu.pl 2009-01-06 19:26:30.000000000 +0000 ++++ mythvodka/scripts/gethulu.pl 2009-02-05 06:28:36.000000000 +0000 +@@ -123,7 +123,8 @@ + if($ephtml =~ m/thumbnail_url: "(.+?)"/) { $epimg=$1 } ; + + print MYTHMENU "\n"; +- print MYTHMENU "$eptitle\n"; ++ print MYTHMENU "$title-$eptitle\n"; ++ #print MYTHMENU "$eptitle\n"; + print MYTHMENU "http://www.hulu.com/watch/$epid\n"; + print MYTHMENU "$season - $epno\n"; + print MYTHMENU "$epdate - $epdesc\n"; +diff -ruaN mythvodka.orig/scripts/hulu mythvodka/scripts/hulu +--- mythvodka.orig/scripts/hulu 2009-01-04 15:25:24.000000000 +0000 ++++ mythvodka/scripts/hulu 2009-02-05 06:28:36.000000000 +0000 +@@ -22,6 +22,7 @@ + html=get_HTML(cid) + cidSoup=BeautifulStoneSoup(html) + pid=cidSoup.findAll('pid')[0].contents[0] ++logfile="/var/log/mythtv/hulu_quality.log" + + smilURL = "http://releasegeo.hulu.com/content.select?pid=" + pid + "&mbr=true&format=smil" + print smilURL +@@ -34,18 +35,58 @@ + #label streams + i=0 + quality=0 ++qual_medium=-1; qual_high=-1; qual_h264=-1; command2="echo hulu done." ++os.system("rm -f "+logfile+".0") ++os.system("mv -f "+logfile+" "+logfile+".0") ++f=open(logfile,'w') ++os.system("chmod a+rw "+logfile) ++print >>f, "Debug debug" ++print >>f, "hulu ",url, " ", fileout ++# ++# Find the various quality choices that are available ++# + for stream in video: +- if "480K" in stream['src'] or "480k" in stream['src']: ++ print >>f, stream ++ if "H264" in stream['src'] or "h264" in stream['src'] or "h264" in stream['profile'] or "H264" in stream['profile']: ++ streams.append(['H264',stream['src']]) ++ qual_h264=i ++ print >>f, "DebugQual h264", i ++ print >>f, "" ++ elif "480K" in stream['src'] or "480k" in stream['src']: + streams.append(['Flash (480k)',stream['src']]) ++ qual_medium=i ++ print >>f, "DebugQual medium", i ++ print >>f, "" + elif "700K" in stream['src'] or "700k" in stream['src']: + streams.append(['Flash (700k)',stream['src']]) + quality=i +- elif "H264" in stream['src'] or "h264" in stream['src']: +- streams.append(['H264',stream['src']]) ++ qual_high=i ++ print >>f, "DebugQual high", i ++ print >>f, "" ++ elif "medium" in stream['profile'] or "Medium" in stream['profile']: ++ streams.append(['Flash (Medium)',stream['src']]) ++ if qual_medium==-1: qual_medium=i ++ print >>f, "DebugQual Medium", i, qual_medium ++ print >>f, "" ++ elif "high" in stream['profile'] or "High" in stream['profile']: ++ streams.append(['Flash (High)',stream['src']]) ++ if qual_high==-1: qual_high=i ++ print >>f, "DebugQual High", i, qual_high ++ print >>f, "" + else: + streams.append(['unkown quality: '+stream['src'].split('/')[-1],stream['src']]) ++ print >>f, "DebugQual Unknown", i ++ print >>f, "" + i=i+1 + ++if qual_high>-1: ++ quality=qual_high ++elif qual_medium>-1: ++ quality=qual_medium ++ ++print >>f, "DebugQualVars: h264, high, medium, selected=",qual_h264,qual_high,qual_medium,quality ++ ++ + if quality!=-1: + print "stream url" + #generate random code +@@ -104,4 +145,9 @@ + command=command.replace(';','\\;') + + print command +- os.system(command) ++ print >>f,"Command is ",command ++ print >>f,"command2 is ",command2 ++ f.close() ++ os.system(command + "; " + command2) ++else: ++ f.close() +diff -ruaN mythvodka.orig/scripts/mythvodka_player.sh mythvodka/scripts/mythvodka_player.sh +--- mythvodka.orig/scripts/mythvodka_player.sh 1970-01-01 00:00:00.000000000 +0000 ++++ mythvodka/scripts/mythvodka_player.sh 2009-02-05 06:28:36.000000000 +0000 +@@ -0,0 +1,38 @@ ++#! /bin/bash ++# ++log=/var/log/mythtv/mythvodka_player.log ++player_list="/usr/local/bin/mplayer_h264 /usr/local/bin/mplayer \ ++ /usr/bin/mplayer /bin/mplayer" ++# ++player_list="/usr/local/bin/mplayer /usr/bin/mplayer /bin/mplayer" ++f="$1" ++shift ++rm -f $log ++echo "Request to play $f on `date`" >> $log ++player="" ++for player in $player_list; do ++ if [ -x "$player" ]; then ++ echo "Found player $player" >> $log ++ break ++ fi ++done ++if [ ! -x $player ]; then ++ echo "ERROR -- not able to find mplayer on your system. " >> $log ++ echo "I searched the following list" >> $log ++ echo " $player_list" >> $log ++ exit 1 ++fi ++for pass in 1 2 4 8 10; do ++ size="0" ++ if [ -e $f ]; then ++ size=$( du --apparent-size -sD "$f" | awk '{ print $1 }' ) ++ if [ $size -gt 1500 ]; then ++ break ++ fi ++ fi ++ echo "Pass $pass, filesize is $size kbytes, sleep $pass seconds" >> $log ++ sleep $pass ++done ++size=$( du --apparent-size -sD "$f" | awk '{ print $1 }' ) ++echo "Reached $size kb on pass $pass `date`" >> $log ++$player -fs -vo xv $f -- cgit v0.12