summaryrefslogtreecommitdiffstats
path: root/abs/extra/mythvodka
diff options
context:
space:
mode:
authorCecil Hugh Watson <knoppmyth@gmail.com>2009-09-26 01:57:08 (GMT)
committerCecil Hugh Watson <knoppmyth@gmail.com>2009-09-26 01:57:08 (GMT)
commit7b29169fff9e7c624890c5edffe85def8a293136 (patch)
tree47753889faa3a2063b66d1c7e7681e703eb1b39a /abs/extra/mythvodka
parentc491dea779dac29afff3578bf8245943817c2339 (diff)
downloadlinhes_pkgbuild-7b29169fff9e7c624890c5edffe85def8a293136.zip
linhes_pkgbuild-7b29169fff9e7c624890c5edffe85def8a293136.tar.gz
linhes_pkgbuild-7b29169fff9e7c624890c5edffe85def8a293136.tar.bz2
LinHES 6.01.00
Diffstat (limited to 'abs/extra/mythvodka')
-rw-r--r--abs/extra/mythvodka/PKGBUILD36
-rwxr-xr-xabs/extra/mythvodka/hulu_grabber.sh16
-rw-r--r--abs/extra/mythvodka/mythvodka.diff3933
-rw-r--r--abs/extra/mythvodka/mythvodka.install47
4 files changed, 4032 insertions, 0 deletions
diff --git a/abs/extra/mythvodka/PKGBUILD b/abs/extra/mythvodka/PKGBUILD
new file mode 100644
index 0000000..0832093
--- /dev/null
+++ b/abs/extra/mythvodka/PKGBUILD
@@ -0,0 +1,36 @@
+# $Id: PKGBUILD 5936 2008-07-21 20:24:16Z thomas $
+# Maintainer: Cecil Watson<knoppmyth@gmail.com>
+
+pkgname=mythvodka
+pkgver=0.7
+pkgrel=11
+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' 'perl-xml-dom' 'beautiful-soup')
+install=mythvodka.install
+source=('http://mythvodka.googlecode.com/files/mythvodka.07.tar.gz' 'mythvodka.diff' 'hulu_grabber.sh' 'http://ftp.knoppmyth/R6/sources/huludata.tar.bz2')
+
+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/local/bin
+ chmod a+x ../scripts/*
+ cp -p ../scripts/* $startdir/pkg/usr/local/bin
+ mkdir -p $startdir/pkg/etc/cron.daily/
+ chmod 755 ../../hulu_grabber.sh
+ cp ../../hulu_grabber.sh $startdir/pkg/etc/cron.daily/
+ mkdir -p $startdir/pkg/var/tmp
+ cp $startdir/src/huludata.xml $startdir/pkg/var/tmp
+}
diff --git a/abs/extra/mythvodka/hulu_grabber.sh b/abs/extra/mythvodka/hulu_grabber.sh
new file mode 100755
index 0000000..4840dc0
--- /dev/null
+++ b/abs/extra/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
+/usr/local/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/mythvodka/mythvodka.diff b/abs/extra/mythvodka/mythvodka.diff
new file mode 100644
index 0000000..5735a42
--- /dev/null
+++ b/abs/extra/mythvodka/mythvodka.diff
@@ -0,0 +1,3933 @@
+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-12 07:28:31.000000000 +0000
+@@ -646,7 +646,7 @@
+
+ MythProgressDialog *buffer_progress;
+ buffer_progress = new MythProgressDialog(
+- QObject::tr("If she kicks you in the balls, you have the ability and the right..."), bufferSize, true, this, SLOT(cancelPressed()));
++ QObject::tr("Buffering... Just a moment please."), bufferSize, true, this, SLOT(cancelPressed()));
+
+ QFile file(filename);
+
+@@ -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);
+
+@@ -822,7 +822,7 @@
+
+ MythProgressDialog *buffer_progress;
+ buffer_progress = new MythProgressDialog(
+- QObject::tr("RING RING... I FUCKED YOUR GRANDDAUGHTER"), bufferSizemov, true, this, SLOT(cancelPressed()));
++ QObject::tr("We seem to have hit some sort of problem..."), bufferSizemov, true, this, SLOT(cancelPressed()));
+
+ QFile filemov(filenamemov);
+
+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-12 07:27:44.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 [<search options>] [<regex|index|pid|pidurl> ...]
+ Download files: get_iplayer --get [<search options>] <regex|index|pid|pidurl> ...
+ get_iplayer --pid <pid|pidurl> [<options>]
+-Stream Downloads: get_iplayer --stdout [<options>] <regex|index|pid|pidurl> | mplayer -cache 2048 -
++Stream Downloads: get_iplayer --stdout [<options>] <regex|index|pid|pidurl> | mplayer -cache 3072 -
+ Update get_iplayer: get_iplayer --update
+
+ Search Options:
+@@ -83,9 +94,10 @@
+ --channel <regex> Narrow search to matched channel(s)
+ --category <regex> Narrow search to matched categories
+ --versions <regex> Narrow search to matched programme version(s)
++ --exclude <regex> Narrow search to exclude matched programme names
+ --exclude-channel <regex> Narrow search to exclude matched channel(s)
+ --exclude-category <regex> Narrow search to exclude matched catogories
+- --type <radio|tv|podcast|all> Only search in these types of programmes (tv is default)
++ --type <type> Only search in these types of programmes: radio, tv, podcast, all, itv (tv is default)
+ --since <hours> 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 <categories|channel> 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 <url> Web proxy URL spec
+ --partial-proxy Works around for some broken web proxies (try this extra option if your proxy fails)
+- --pid <pid|url> Download an arbitrary pid that does not appear in the index
++ --pid <pid|url> Download an arbitrary pid that does not appear in the index (itv:<pid> 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 <mode>,<mode>,... Audio Download mode(s): iphone,flashaudio,realaudio (default: iphone,flashaudio,realaudio)
++ --vmode <mode>,<mode>,... 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> Offset the subtitle timestamps by the specified number of milliseconds
+ --version-list <versions> 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 <search name> Add the current search terms to the named PVR search
+@@ -139,18 +150,18 @@
+ -f, --flush, --refresh Refresh cache
+ -e, --expiry <secs> Cache expiry in seconds (default 4hrs)
+ --symlink <file> Create symlink to <file> once we have the header of the download
+- --fxd <file> Create Freevo FXD XML in specified file
+- --mythtv <file> Create Mythtv streams XML in specified file
++ --fxd <file> Create Freevo FXD XML of matching programmes in specified file
++ --mythtv <file> 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 <file> Create basic HTML index of programmes in specified file
++ --html <file> Create basic HTML index of matching programmes in specified file
+ --mplayer <path> Location of mplayer binary
++ --ffmpeg <path> Location of ffmpeg binary
+ --lame <path> Location of lame binary
+ --id3v2 <path> Location of id3v2 binary
+ --rtmpdump <path> Location of rtmpdump binary
+- --vlc <path> Location of vlc binary
+- --streaminfo Returns all of the media stream urls of the programme(s)
++ --vlc <path> 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' => <programme thumbnail url>
+ # 'channel => <channel>
+ # 'categories' => <Comma separated list of categories>
+-# 'type' => <Type: tv, radio or podcast>
++# 'type' => <Type: tv, radio, itv or podcast>
+ # 'timeadded' => <timestamp when programme was added to cache>
+ # 'longname' => <Long name (only parsed in stage 1 download)>,
+ # 'version' => <selected version e.g default, signed, etc - only set before d/load>
+@@ -292,11 +307,35 @@
+ # 'fileprefix' => <Filename Prefix of saved file - set only while downloading>
+ # 'ext' => <Filename Extension of saved file - set only while downloading>
+ #};
++
++# 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:<pid>
++ $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 ) {
++ # <li class="first-child"><a href="http://www.itv.com/ClassicTVshows/comedy/ABitofaDo/default.html">A Bit of a Do</a><br></li>
++ # <li><a href="http://www.itv.com/ClassicTVshows/familydrama/achristmascarol/default.html">A Christmas Carol</a><br></li>
++ # Get page, search for relevent lines which contain series links and loop through each matching line
++ for my $s_line ( grep /(<li><a\s+href=".+?"><img\s+src=".+?"\s+alt=".+?"><\/a><h4>|<li.*?><a href=".+?">.+?<\/a><br><\/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{<li><a\s+href="\s*(.+?)\s*"><img\s+src=".+?"\s+alt="\s*(.+?)\s*"><\/a><h4>};
++ ($url, $name) = ($1, $2) if $s_line =~ m{<li.*?><a href="\s*(.+?)\s*">\s*(.+?)\s*<\/a><br><\/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. <li class="first-child"><a title="Play" href="?vodcrid=crid://itv.com/993&amp;DF=0">Episode one</a><br>The Sun in a Bottle</li>
++ # <li><a title="Play" href="?vodcrid=crid://itv.com/994&amp;DF=0">Episode two</a><br>Castle Saburac</li>
++ # <li class="first-child"><a class="nsat" title="This programme contains strong language and violence  " href="?vodcrid=crid://itv.com/588&amp;G=10&amp;DF=0">Episode one</a><br>The Dead of Jericho</li>
++ #
++ # e.g. <li><a class="playVideo" title="Play" href="?vodcrid=crid://itv.com/1232&amp;DF=0"><img src="img/60x45/Crossroads-Rosemary-shoots-David-efeef7cd-8d41-416c-9e30-26ce1b3d625c.jpg" alt="Crossroads: Rosemary shoots David"><span>Play</span></a><h4>
++ # vodcrid=crid://itv.com/971&amp;DF=0"><img src="img/60x45/9d20fd47-5d4b-44f5-9188-856505de0d0f.jpg" alt="Emmerdale 2002 Louise kills Ray"
++ #
++ # e.g. <a class="playVideo" title="Play" href="?vodcrid=crid://itv.com/1854&amp;DF=1"><img src="img/157x104/140c456c-d8bd-49d5-90f8-f7cc6d86f132.jpg" alt="Soldier Soldier "><span>Play</span></a><h2>Soldier Soldier</h2>
++ #
++ 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{<li.*?><a\s+(class="nsat"\s+)?title="\s*(.+?)\s*"\s+href="\?vodcrid=crid://itv.com/(\d+?)&.+?>\s*(.+?)\s*<};
++ ($pid, $thumbnail, $episode) = ($1, $2, $3) if $e_line =~ m{vodcrid=crid://itv.com/(\d+?)&.+?><img\s+src="(.+?)"\s+alt="\s*(.+?)\s*"};
++ next if ! ($pid && $episode);
++ # Remove 'Play'
++ $guidance =~ s/^Play$//ig;
++ # Strip non-printables
++ $guidance =~ s/[\s\x00\xc2\xa0]+$//ig;
++ #$guidance =~ s|[^\w\s\-\!"£\$\\/%\^&\*\(\)\+=,\.\?':;@~\[\]]+||gi;
++ #$guidance =~ s/(\s\s)+//g;
++ logger "DEBUG: PID: '$pid' Episode: '$episode' Guidance: '$guidance'\n" if $opt{debug};
++
++ # 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};
++ # Merge data (hack)
++ #$prog{$pid}{episode} .= ','.$episode;
++ my $oldname = $prog{$pid}{name};
++ $prog{$pid}{episode} = $episode if (! $prog{$pid}{episode}) || $prog{$pid}{episode} =~ /$oldname/i;
++ $prog{$pid}{thumbnail} = $thumbnail if ! $prog{$pid}{thumbnail};
++ $prog{$pid}{guidance} = $guidance if ! $prog{$pid}{guidance};
++ next;
++ }
++
++ # build data structure
++ $prog{$pid} = {
++ 'name' => $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)
++ # <ITVCatchUpProgramme>
++ # <ProgrammeId>50</ProgrammeId>
++ # <ProgrammeTitle>A CHRISTMAS CAROL</ProgrammeTitle>
++ # <ProgrammeMediaId>615915</ProgrammeMediaId>
++ # <ProgrammeMediaUrl>
++ # http://www.itv.com//img/150x113/A-Christmas-Carol-2f16d25a-de1d-4a3a-90cb-d47489eee98e.jpg</ProgrammeMediaUrl>
++ # <LastUpdated>2009-01-06T12:24:22.7419643+00:00</LastUpdated>
++ # <Url>
++ # http://www.itv.com/CatchUp/Video/default.html?ViewType=5&amp;Filter=32910</Url>
++ # <EpisodeCount>1</EpisodeCount>
++ # <VideoID>32910</VideoID>
++ # <DentonID>-1</DentonID>
++ # <DentonRating></DentonRating>
++ # <AdditionalContentUrl />
++ # <AdditionalContentUrlText />
++ # </ITVCatchUpProgramme>
++
++ for my $feedxml ( split /<ITVCatchUpProgramme>/, $xmlindex ) {
++ # Extract feed data
++ my ($episodecount, $viewtype, $videoid, $url);
++ my @entries;
++
++ logger "\n\nDEBUG: XML: $feedxml\n" if $opt{debug};
++
++ # <EpisodeCount>1</EpisodeCount>
++ $episodecount = $1 if $feedxml =~ m{<EpisodeCount>\s*(\d+)\s*<\/EpisodeCount>};
++
++ # <Url>http://www.itv.com/CatchUp/Video/default.html?ViewType=5&amp;Filter=32910</Url>
++ ($viewtype, $videoid) = ($1, $2) if $feedxml =~ m{<Url>\s*.+?ViewType=(\d+).+?Filter=(\d+)\s*<\/Url>}i;
++
++ ## <VideoID>32910</VideoID>
++ #$videoid = $1 if $feedxml =~ m{<VideoID>\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{<ProgrammeTitle>\s*(.+?)\s*<\/ProgrammeTitle>};
++ $guidance = $1 if $feedxml =~ m{<DentonRating>\s*(.+?)\s*<\/DentonRating>};
++ $thumbnail = $1 if $feedxml =~ m{<ProgrammeMediaUrl>\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
++ # <div class="listItem highlight contain">
++ # <div class="floatLeft"><a href="http://www.itv.com/CatchUp/Video/default.html?ViewType=5&amp;Filter=33383"><img src="http://www.itv.com//img/157x88/P7-67e0b86f-b335-4f6b-8db
++ # <div class="content">
++ # <h3><a href="http://www.itv.com/CatchUp/Video/default.html?ViewType=5&amp;Filter=33383">Emmerdale</a></h3>
++ # <p class="date">Mon 05 Jan 2009</p>
++ # <p class="progDesc">Donna is stunned to learn Marlon has pointed the finger at Ross. Aaron defaces Tom King's grave.</p>
++ # <ul class="progDetails">
++ # <li>
++ # Duration: 30 min
++ # </li>
++ # <li class="days">
++ # Expires in
++ # <strong>29</strong>
++ # days
++ # </li>
++ # </ul>
++ # </div>
++ # </div>
++ # <div class="listItem contain">
++ # <div class="floatLeft"><a href="http://www.itv.com/CatchUp/Video/default.html?ViewType=5&amp;Filter=33245"><img src="http://www.itv.com//img/157x88/Marlon-Dingle-742c50b3-3b
++ # <div class="content">
++ # <h3><a href="http://www.itv.com/CatchUp/Video/default.html?ViewType=5&amp;Filter=33245">Emmerdale</a></h3>
++ # <p class="date">Fri 02 Jan 2009</p>
++ # <p class="progDesc">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
++ # <ul class="progDetails">
++ # <li>
++ # Duration: 30 min
++ # </li>
++ # <li class="days">
++ # Expires in
++ # <strong>26</strong>
++ # days
++ # </li>
++ # </ul>
++ # </div>
++ # </div>
++ #
++ } 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. <h3><a href="http://www.itv.com/CatchUp/Video/default.html?ViewType=5&amp;Filter=32042">Emmerdale</a></h3>
++ my @videoids = (split /<h3><a href=.+?Filter=/, $xml);
++
++ # Get episode data for each videoid
++ $viewtype = 5;
++
++ my @episode_data = split/<h3><a href=.+?Filter=/, $xml;
++ # Ignore first entry
++ shift @episode_data;
++ logger "INFO: Got ".($#episode_data+1)." programmes\n" if $opt{verbose};
++
++ for my $xml (@episode_data) {
++ $videoid = $1 if $xml =~ m{^(\d+?)".+$}i;
++
++ # Make sure we don't duplicate parsing an episode
++ next if $episode_pid{$videoid};
++ $episode_pid{$videoid} = 1;
++
++ 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{<ProgrammeTitle>\s*(.+?)\s*<\/ProgrammeTitle>};
++ $available = $1 if $xml =~ m{<p\s+class="date">(.+?)<\/p>}i;
++ $episode = $available;
++ $duration = $1 if $xml =~ m{<li>Duration:\s*(.+?)\s*<\/li>}i;
++ $desc = $1 if $xml =~ m{<p\s+class="progDesc">(.+?)\s*<\/p>};
++ $guidance = $1 if $feedxml =~ m{<DentonRating>\s*(.+?)\s*<\/DentonRating>};
++ $thumbnail = $1 if $feedxml =~ m{<ProgrammeMediaUrl>\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( <radio|tv|podcast> )
++# get_links( <prog_type> )
+ 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]/, <CACHE>;
+ 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 (<pid>)
+ 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} || "<longname> - <episode> $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} || "<longname> - <episode> <pid>" );
+- 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} || "<name> <pid>" );
++ 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} || "<longname> - <episode> $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} || "<longname> - <episode> <pid>" );
++ 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} || "<longname> - <episode> <pid> <version>" );
++ 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} || "<longname> - <episode> <pid> <version>" );
++ 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 (<pid>)
++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} || "<longname> - <episode> <pid> <version>" );
++ $prog{$pid}{fileprefix} = generate_download_filename_prefix( $pid, $prog{$pid}{dir}, $opt{fileprefix} || "<longname> - <episode> <pid> <version>" );
+ 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
+ # <title>Amazon with Bruce Parry: Episode 1</title>
+- my ( $title, $type );
++ my ( $title, $prog_type );
+ $title = $1 if $xml =~ m{<title>\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 '&amp;' with '&' and append '%20HTTP/1.1'
++ $url_2 =~ s/&amp;/&/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</Title><EntryRef href="HTTP://SAM.ITV.COM/XTSERVER/ACC_RANDOM=1231194223/SITE=ITV/AREA=CATCHUP.VIDEO/SEG=CATCHUP.VIDEO HTTP/1.1/SOURCE=CATCH.UP/GENRE=DRAMA/PROGNAME=DOCTOR.ZHIVAGO/PROGID=33105/SERIES=DOCTOR.ZHIVAGO/EPNUM=/EPTITLE=/BREAKNUM=0/ADPOS=1/PAGEID=01231194223/DENTON=0/CUSTOMRATING=/TOTDUR=90/PREDUR=0/POSDUR=905/GENERIC=6e0536bf-7883-4aaa-9230-94ecc4aea403/AAMSZ=VIDEO" /><EntryRef href="HTTP://SAM.ITV.COM/XTSERVER/ACC_RANDOM=1231194223/SITE=ITV/AREA=CATCHUP.VIDEO/SEG=CATCHUP.VIDEOHTTP/1.1/SOURCE=CATCH.UP/GENRE=DRAMA/PROGNAME=DOCTOR.ZHIVAGO/PROGID=33105/SERIES=DOCTOR.ZHIVAGO/EPNUM=/EPTITLE=/BREAKNUM=0/ADPOS=LAST/PAGEID=01231194223/DENTON=0/CUSTOMRATING=/TOTDUR=90/PREDUR=0/POSDUR=905/GENERIC=6e0536bf-7883-4aaa-9230-94ecc4aea403/AAMSZ=VIDEO" />
++ $prog{$pid}{name} = $1 if $response =~ m{<Title>(.+?)<\/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 '&amp;' with '&' in url
++ $url_3 =~ s/&amp;/&/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</title>
+- # <subtitle>Sara Cox sits in for Edith with another Cryptic Randomizer.</subtitle>
+- # <updated>2008-09-29T10:59:45Z</updated>
+- # <id>tag:feeds.bbc.co.uk,2008:/iplayer/feed/episode/b00djtfh</id>
+- # <link rel="related" href="http://www.bbc.co.uk/iplayer" type="text/html" />
+- # <link rel="self" href="http://feeds.bbc.co.uk/iplayer/episode/b00djtfh" type="application/atom+xml" />
+- # <author>
+- # <name>BBC</name>
+- # <uri>http://www.bbc.co.uk</uri>
+- # </author>
+- # <entry>
+- # <title type="text">Edith Bowman: 22/09/2008</title>
+- # <id>tag:feeds.bbc.co.uk,2008:PIPS:b00djtfh</id>
+- # <updated>2008-09-15T01:28:36Z</updated>
+- # <summary>Sara Cox sits in for Edith with another Cryptic Randomizer.</summary>
+- # <content type="html">
+- # &lt;p&gt;
+- # &lt;a href=&quot;http://www.bbc.co.uk/iplayer/episode/b00djtfh?src=a_syn30&quot;&gt;
+- # &lt;img src=&quot;http://www.bbc.co.uk/iplayer/images/episode/b00djtfh_150_84.jpg&quot; alt=&quot;Edith Bowman: 22/09/2008&quot; /&gt;
+- # &lt;/a&gt;
+- # &lt;/p&gt;
+- # &lt;p&gt;
+- # Sara Cox sits in for Edith with movie reviews and great new music, plus another Cryptic Randomizer.
+- # &lt;/p&gt;
+- # </content>
+- # <link rel="alternate" href="http://www.bbc.co.uk/iplayer/episode/b00djtfh?src=a_syn31" type="text/html" title="Edith Bowman: 22/09/2008">
+- # <media:content medium="audio" duration="10800">
+- # <media:title>Edith Bowman: 22/09/2008</media:title>
+- # <media:description>Sara Cox sits in for Edith with movie reviews and great new music, plus another Cryptic Randomizer.</media:description>
+- # <media:player url="http://www.bbc.co.uk/iplayer/episode/b00djtfh?src=a_syn31" />
+- # <media:category scheme="urn:bbc:metadata:cs:iPlayerUXCategoriesCS" label="Entertainment">9100099</media:category>
+- # <media:category scheme="urn:bbc:metadata:cs:iPlayerUXCategoriesCS" label="Music">9100006</media:category>
+- # <media:category scheme="urn:bbc:metadata:cs:iPlayerUXCategoriesCS" label="Pop &amp; Chart">9200069</media:category>
+- # <media:credit role="Production Department" scheme="urn:ebu">BBC Radio 1</media:credit>
+- # <media:credit role="Publishing Company" scheme="urn:ebu">BBC Radio 1</media:credit>
+- # <media:thumbnail url="http://www.bbc.co.uk/iplayer/images/episode/b00djtfh_86_48.jpg" width="86" height="48" />
+- # <media:thumbnail url="http://www.bbc.co.uk/iplayer/images/episode/b00djtfh_150_84.jpg" width="150" height="84" />
+- # <media:thumbnail url="http://www.bbc.co.uk/iplayer/images/episode/b00djtfh_178_100.jpg" width="178" height="100" />
+- # <media:thumbnail url="http://www.bbc.co.uk/iplayer/images/episode/b00djtfh_512_288.jpg" width="512" height="288" />
+- # <media:thumbnail url="http://www.bbc.co.uk/iplayer/images/episode/b00djtfh_528_297.jpg" width="528" height="297" />
+- # <media:thumbnail url="http://www.bbc.co.uk/iplayer/images/episode/b00djtfh_640_360.jpg" width="640" height="360" />
+- # <dcterms:valid>
+- # start=2008-09-22T15:44:20Z;
+- # end=2008-09-29T15:02:00Z;
+- # scheme=W3C-DTF
+- # </dcterms:valid>
+- # </media:content>
+- # </link>
+- # <link rel="self" href="http://feeds.bbc.co.uk/iplayer/episode/b00djtfh?format=atom" type="application/atom+xml" title="22/09/2008" />
+- # <link rel="related" href="http://www.bbc.co.uk/programmes/b006wks4/microsite" type="text/html" title="Edith Bowman" />
+- # <link rel="parent" href="http://feeds.bbc.co.uk/iplayer/programme_set/b006wks4" type="application/atom+xml" title="Edith Bowman" />
+- # </entry>
+- #</feed>
+-
+- my ($duration, $available, $channel, $expiry, $longdesc, $versions, $guidance, $type, $categories, $player, $thumbnail);
+-
+- $expiry = $1 if $entry3 =~ m{<dcterms:valid>\s*start=.+?;\s*end=(.*?);};
+- $available = $1 if $entry3 =~ m{<dcterms:valid>\s*start=(.+?);\s*end=.*?;};
+- $duration = $1 if $entry3 =~ m{duration=\"(\d+?)\"};
+- $type = $1 if $entry3 =~ m{medium=\"(\w+?)\"};
+- $longdesc = $1 if $entry3 =~ m{<media:description>\s*(.*?)\s*<\/media:description>};
+- $guidance = $1 if $entry3 =~ m{<media:rating scheme="urn:simple">(.+?)<\/media:rating>};
+- $player = $1 if $entry3 =~ m{<media:player\s*url=\"(.*?)\"\s*\/>};
+- $thumbnail = $1 if $entry3 =~ m{<media:thumbnail url="([^"]+?)"\s+width="150"\s+height="84"\s*/>};
+-
+- my @cats;
+- for (split /<media:category scheme=\".+?\"/, $entry3) {
+- push @cats, $1 if m{\s*label="(.+?)">\d+<\/media:category>};
+- }
+- $categories = join ',', @cats;
++ # 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</title>
++ # <subtitle>Sara Cox sits in for Edith with another Cryptic Randomizer.</subtitle>
++ # <updated>2008-09-29T10:59:45Z</updated>
++ # <id>tag:feeds.bbc.co.uk,2008:/iplayer/feed/episode/b00djtfh</id>
++ # <link rel="related" href="http://www.bbc.co.uk/iplayer" type="text/html" />
++ # <link rel="self" href="http://feeds.bbc.co.uk/iplayer/episode/b00djtfh" type="application/atom+xml" />
++ # <author>
++ # <name>BBC</name>
++ # <uri>http://www.bbc.co.uk</uri>
++ # </author>
++ # <entry>
++ # <title type="text">Edith Bowman: 22/09/2008</title>
++ # <id>tag:feeds.bbc.co.uk,2008:PIPS:b00djtfh</id>
++ # <updated>2008-09-15T01:28:36Z</updated>
++ # <summary>Sara Cox sits in for Edith with another Cryptic Randomizer.</summary>
++ # <content type="html">
++ # &lt;p&gt;
++ # &lt;a href=&quot;http://www.bbc.co.uk/iplayer/episode/b00djtfh?src=a_syn30&quot;&gt;
++ # &lt;img src=&quot;http://www.bbc.co.uk/iplayer/images/episode/b00djtfh_150_84.jpg&quot; alt=&quot;Edith Bowman: 22/09/2008&quot; /&gt;
++ # &lt;/a&gt;
++ # &lt;/p&gt;
++ # &lt;p&gt;
++ # Sara Cox sits in for Edith with movie reviews and great new music, plus another Cryptic Randomizer.
++ # &lt;/p&gt;
++ # </content>
++ # <link rel="alternate" href="http://www.bbc.co.uk/iplayer/episode/b00djtfh?src=a_syn31" type="text/html" title="Edith Bowman: 22/09/2008">
++ # <media:content medium="audio" duration="10800">
++ # <media:title>Edith Bowman: 22/09/2008</media:title>
++ # <media:description>Sara Cox sits in for Edith with movie reviews and great new music, plus another Cryptic Randomizer.</media:description>
++ # <media:player url="http://www.bbc.co.uk/iplayer/episode/b00djtfh?src=a_syn31" />
++ # <media:category scheme="urn:bbc:metadata:cs:iPlayerUXCategoriesCS" label="Entertainment">9100099</media:category>
++ # <media:category scheme="urn:bbc:metadata:cs:iPlayerUXCategoriesCS" label="Music">9100006</media:category>
++ # <media:category scheme="urn:bbc:metadata:cs:iPlayerUXCategoriesCS" label="Pop &amp; Chart">9200069</media:category>
++ # <media:credit role="Production Department" scheme="urn:ebu">BBC Radio 1</media:credit>
++ # <media:credit role="Publishing Company" scheme="urn:ebu">BBC Radio 1</media:credit>
++ # <media:thumbnail url="http://www.bbc.co.uk/iplayer/images/episode/b00djtfh_86_48.jpg" width="86" height="48" />
++ # <media:thumbnail url="http://www.bbc.co.uk/iplayer/images/episode/b00djtfh_150_84.jpg" width="150" height="84" />
++ # <media:thumbnail url="http://www.bbc.co.uk/iplayer/images/episode/b00djtfh_178_100.jpg" width="178" height="100" />
++ # <media:thumbnail url="http://www.bbc.co.uk/iplayer/images/episode/b00djtfh_512_288.jpg" width="512" height="288" />
++ # <media:thumbnail url="http://www.bbc.co.uk/iplayer/images/episode/b00djtfh_528_297.jpg" width="528" height="297" />
++ # <media:thumbnail url="http://www.bbc.co.uk/iplayer/images/episode/b00djtfh_640_360.jpg" width="640" height="360" />
++ # <dcterms:valid>
++ # start=2008-09-22T15:44:20Z;
++ # end=2008-09-29T15:02:00Z;
++ # scheme=W3C-DTF
++ # </dcterms:valid>
++ # </media:content>
++ # </link>
++ # <link rel="self" href="http://feeds.bbc.co.uk/iplayer/episode/b00djtfh?format=atom" type="application/atom+xml" title="22/09/2008" />
++ # <link rel="related" href="http://www.bbc.co.uk/programmes/b006wks4/microsite" type="text/html" title="Edith Bowman" />
++ # <link rel="parent" href="http://feeds.bbc.co.uk/iplayer/programme_set/b006wks4" type="application/atom+xml" title="Edith Bowman" />
++ # </entry>
++ #</feed>
++
++ $expiry = $1 if $entry3 =~ m{<dcterms:valid>\s*start=.+?;\s*end=(.*?);};
++ $available = $1 if $entry3 =~ m{<dcterms:valid>\s*start=(.+?);\s*end=.*?;};
++ $duration = $1 if $entry3 =~ m{duration=\"(\d+?)\"};
++ $prog_type = $1 if $entry3 =~ m{medium=\"(\w+?)\"};
++ $longdesc = $1 if $entry3 =~ m{<media:description>\s*(.*?)\s*<\/media:description>};
++ $guidance = $1 if $entry3 =~ m{<media:rating scheme="urn:simple">(.+?)<\/media:rating>};
++ $player = $1 if $entry3 =~ m{<media:player\s*url=\"(.*?)\"\s*\/>};
++ $thumbnail = $1 if $entry3 =~ m{<media:thumbnail url="([^"]+?)"\s+width="150"\s+height="84"\s*/>};
++
++ my @cats;
++ for (split /<media:category scheme=\".+?\"/, $entry3) {
++ push @cats, $1 if m{\s*label="(.+?)">\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='; # +<pid>
++ $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">
++ # <div class="cu-sponsor"><a href="http://sam.itv.com/accipiter/adclick/CID=000040d70000000000000000/acc_random=1/SITE=CLICKTRACK/AREAITVCATCHUP.VIDEO=CLICKTRACK..FREEVIEW.SPONSORBUTTON.OCT08/AAMSZ=120X60/pageid=1" title="ITV Player in assocation with Freeview"><img src="/_app/img/catchup/catchup_video_freeview2.jpg" alt="ITV Player is sponsored by Freeview"></a></div>
++ # <h2>Doctor Zhivago</h2>
++ # <p>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 ...</p>
++ # <p class="timings"><span class="date">Mon 29 Dec 2008</span><br /><br /><span>
++ #
++ # Duration: 1hr 30 min |
++ # Expires in
++ # <strong>22</strong>
++ # days
++ # </span></p>
++ # <p><a href="http://www.itv.com/CatchUp/Programmes/default.html?ViewType=1&amp;Filter=2352">3 Episodes Available
++ # </a><br></br></p>
++ # <p class="channelLogo"><img src="/_app/img/logos/itv3-black.gif" alt="ITV 4"></p>
++ # <div id="cu-2-0-VideoID">33105</div>
++ # <div id="cu-2-0-DentonId">17</div>
++ # <div id="cu-2-0-ItemMediaUrl">http://www.itv.com//img/480x272/Doctor-Zhivago-c47828f8-a1af-4cd2-b5a2-40c18eb7e63c.jpg</div>
++ #</div><script language="javascript" type="text/javascript" xmlns:ms="urn:schemas-microsoft-com:xslt">
++ # SetCatchUpModuleID(0);
++ # </script>
++ #
+
+- # populate version pid metadata
+- get_version_pids($ua, $pid);
++ #<div class="itvCatchUpPlayerPanel" xmlns:ms="urn:schemas-microsoft-com:xslt">
++ # <div class="cu-sponsor"><a href="http://sam.itv.com/accipiter/adclick/CID=000040d70000000000000000/acc_random=1/SITE=CLICKTRACK/AREAITVCATCHUP.VIDEO=CLICKTRACK..FREEVIEW.SPONSORBUTTON.OCT08/AAMSZ=120X60/pageid=1" title="ITV Player in assocation with Freeview"><img src="/_app/img/catchup/catchup_video_freeview2.jpg" alt="ITV Player is sponsored by Freeview"></a></div>
++ # <h2>Affinity</h2>
++ # <p>Victorian period drama with a murderous, pyschological twist.</p>
++ # <p class="timings"><span class="date">Sun 28 Dec 2008</span><br /><br /><span>
++ #
++ # Duration: 2hr 00 min |
++ # Expires in
++ # <strong>21</strong>
++ # days
++ # </span></p>
++ # <p class="channelLogo"><img src="/_app/img/logos/itv1-black.gif" alt="ITV 2"></p>
++ # <div class="guidance">
++ # <div><strong>ITV Video Guidance</strong><p>This programme contains strong language and scenes of a sexual nature  </p>
++ # </div>
++ # </div>
++ # <div id="cu-2-0-VideoID">33076</div>
++ # <div id="cu-2-0-DentonId">11</div>
++ # <div id="cu-2-0-ItemMediaUrl">http://www.itv.com//img/480x272/Affinity-9624033b-6e05-4784-85f7-114be0559b24.jpg</div>
++ #</div><script language="javascript" type="text/javascript" xmlns:ms="urn:schemas-microsoft-com:xslt">
++ # SetCatchUpModuleID(0);
++ # </script>
++ #
++
++ #$expiry = $1 if $entry3 =~ m{<dcterms:valid>\s*start=.+?;\s*end=(.*?);};
++ $available = $1 if $entry3 =~ m{<p\s+class="timings">\s*<span\s+class="date">(.+?)<\/span>};
++ $duration = $1 if $entry3 =~ m{Duration:\s*(.+?)\s+\|};
++ #$prog_type = $1 if $entry3 =~ m{medium=\"(\w+?)\"};
++ $longdesc = $1 if $entry3 =~ m{<p>(.+?)<\/p>}i;
++ $guidance = $1 if $entry3 =~ m{ITV Video Guidance<\/strong><p>\s*(.+?)[\W\s]*<\/p>};
++ #$player = $1 if $entry3 =~ m{<media:player\s*url=\"(.*?)\"\s*\/>};
++ $thumbnail = $1 if $entry3 =~ m{<div id="cu-2-0-ItemMediaUrl">(.+?)</div>};
++ $name = $1 if $entry3 =~ m{<h2>(.+?)</h2>};
++ }
+
+ # 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-12 07:27:44.000000000 +0000
+@@ -123,7 +123,8 @@
+ if($ephtml =~ m/thumbnail_url: "(.+?)"/) { $epimg=$1 } ;
+
+ print MYTHMENU "<Stream>\n";
+- print MYTHMENU "<Name>$eptitle</Name>\n";
++ print MYTHMENU "<Name>$title-$eptitle</Name>\n";
++ #print MYTHMENU "<Name>$eptitle</Name>\n";
+ print MYTHMENU "<Url>http://www.hulu.com/watch/$epid</Url>\n";
+ print MYTHMENU "<Subtitle>$season - $epno</Subtitle>\n";
+ print MYTHMENU "<Synopsis>$epdate - $epdesc</Synopsis>\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-12 07:27:44.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-12 07:27:44.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
diff --git a/abs/extra/mythvodka/mythvodka.install b/abs/extra/mythvodka/mythvodka.install
new file mode 100644
index 0000000..4a9827e
--- /dev/null
+++ b/abs/extra/mythvodka/mythvodka.install
@@ -0,0 +1,47 @@
+# arg 1: the new package version
+post_install() {
+ mv /usr/share/mythtv/is.xml /tmp
+ grep -v -e /mythmenu /tmp/is.xml > /tmp/is.xml.tmp
+ echo "<!--#MythVodka-->" >> /tmp/is.xml.tmp
+ echo " <button>" >> /tmp/is.xml.tmp
+ echo " <type>STREAM</type>" >> /tmp/is.xml.tmp
+ echo " <text>Myth Vodka</text>" >> /tmp/is.xml.tmp
+ echo " <action>PLUGIN mythvodka</action>" >> /tmp/is.xml.tmp
+ echo " <depends>mythvodka</depends>" >> /tmp/is.xml.tmp
+ echo " </button>" >> /tmp/is.xml.tmp
+ echo "<!--#MythVodka-->" >> /tmp/is.xml.tmp
+ echo "" >> /tmp/is.xml.tmp
+ echo "</mythmenu>" >> /tmp/is.xml.tmp
+ mv /tmp/is.xml.tmp /usr/share/mythtv/is.xml
+
+ mv /usr/share/mythtv/media_settings.xml /tmp
+ grep -v -e /mythmenu /tmp/media_settings.xml > /tmp/ms.xml.tmp
+ echo "<!--#MythVodka-->" >> /tmp/ms.xml.tmp
+ echo " <button>" >> /tmp/ms.xml.tmp
+ echo " <type>SETTINGS MYTHVODKA</type>" >> /tmp/ms.xml.tmp
+ echo " <text>MythVodka Settings</text>" >> /tmp/ms.xml.tmp
+ echo " <action>CONFIGPLUGIN mythvodka</action>" >> /tmp/ms.xml.tmp
+ echo " <depends>mythvodka</depends>" >> /tmp/ms.xml.tmp
+ echo " </button>" >> /tmp/ms.xml.tmp
+ echo "<!--#MythVodka-->" >> /tmp/ms.xml.tmp
+ echo "" >> /tmp/ms.xml.tmp
+ echo "</mythmenu>" >> /tmp/ms.xml.tmp
+ mv /tmp/ms.xml.tmp /usr/share/mythtv/media_settings.xml
+}
+# arg 1: the new package version
+# arg 2: the old package version
+post_upgrade() {
+ /bin/true
+}
+
+# arg 1: the old package version
+post_remove() {
+ mv /usr/share/mythtv/is.xml /tmp/is.xml.tmp
+ sed -e '/\#MythVodka/,/\#MythVodka/d' < /tmp/is.xml.tmp > /usr/share/mythtv/is.xml
+ mv /usr/share/mythtv/media_settings.xml /tmp/ms.xml.tmp
+ sed -e '/\#MythVodka/,/\#MythVodka/d' < /tmp/ms.xml.tmp > /usr/share/mythtv/media_settings.xml
+}
+
+op=$1
+shift
+$op $*