# This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . ################### ITV class ################# package Programme::itv; use Env qw[@PATH]; use Fcntl; use File::Copy; use File::Path; use File::stat; use HTML::Entities; use HTTP::Cookies; use HTTP::Headers; use IO::Seekable; use IO::Socket; use LWP::ConnCache; use LWP::UserAgent; use POSIX qw(mkfifo); use strict; use Time::Local; use URI; # Inherit from Programme class use base 'Programme'; # Class vars # Global options my $opt; # Constructor # Usage: $prog{$pid} = Programme->new( 'pid' => $pid, 'name' => $name, ); sub new { my $type = shift; my %params = @_; my $self = {}; for (keys %params) { $self->{$_} = $params{$_}; } # Ensure the subclass $opt var is pointing to the Superclass global optref $opt = $Programme::optref; bless $self, $type; } sub index_min { return 100001 } sub index_max { return 199999 } # Class cmdline Options sub opt_format { return { outputitv => [ 1, "outputitv=s", 'Output', '--outputitv ', "Output directory for itv recordings"], itvnothread => [ 1, "itvnothread", 'Recording', '--itvnothread', "Disable parallel threaded recording for itv"], }; } # Method to return optional list_entry format sub optional_list_entry_format { my $prog = shift; return ", $prog->{channel}, $prog->{categories}, $prog->{guidance}"; } # Returns the modes to try for this prog type sub modelist { return 'itvlow,itvnormal,itvhigh'; } # Cleans up a pid and removes url parts that might be specified sub clean_pid { my $prog = shift; # extract numerical format - remove surrounding url $prog->{pid} = $1 if $prog->{pid} =~ m{(\d{2,8})}; } # get full episode metadata given pid and ua. Uses two different urls to get data sub get_metadata { my $prog = shift; my $ua = shift; my $metadata; my $entry; my ($name, $episode, $duration, $available, $channel, $expiry, $longdesc, $versions, $guidance, $prog_type, $categories, $player, $thumbnail); main::logger "DEBUG: Getting Metadata for $prog->{pid}:\n" if $opt->{debug}; # ITV Player metadata my $prog_metadata_url_itv = 'http://www.itv.com/_app/Dynamic/CatchUpData.ashx?ViewType=5&Filter='; # + $entry = main::request_url_retry($ua, "${prog_metadata_url_itv}$prog->{pid}", 3, '', ''); decode_entities($entry); main::logger "DEBUG: ${prog_metadata_url_itv}$prog->{pid}:\n$entry\n\n" if $opt->{debug}; # Flatten $entry =~ s|[\r\n]||g; #div class="itvCatchUpPlayerPanel" xmlns:ms="urn:schemas-microsoft-com:xslt"> #
ITV Player is sponsored by Freeview
#

Doctor Zhivago

#

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

#

Mon 29 Dec 2008

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

#

3 Episodes Available #

# #
33105
#
17
#
http://www.itv.com//img/480x272/Doctor-Zhivago-c47828f8-a1af-4cd2-b5a2-40c18eb7e63c.jpg
# # #
#
ITV Player is sponsored by Freeview
#

Affinity

#

Victorian period drama with a murderous, pyschological twist.

#

Sun 28 Dec 2008

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

# #
#
ITV Video Guidance

This programme contains strong language and scenes of a sexual nature  

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

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

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

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

(.+?)

}; # Fill in from cache if not got from metadata $prog->{name} = $name || $prog->{name}; $prog->{episode} = $episode || $prog->{episode}; $prog->{type} = $prog_type || $prog->{type}; $prog->{duration} = $duration || $prog->{duration}; $prog->{channel} = $channel || $prog->{channel}; $prog->{available} = $available || $prog->{available}; $prog->{expiry} = $expiry || $prog->{expiry}; $prog->{versions} = $versions || $prog->{versions}; $prog->{guidance} = $guidance || $prog->{guidance}; $prog->{categories} = $categories || $prog->{categories}; $prog->{desc} = $longdesc || $prog->{desc}; $prog->{player} = $player; $prog->{thumbnail} = $thumbnail || $prog->{thumbnail}; # just use the default modelist $prog->{modes} = 'default: '.$prog->modelist(); return 0; } # Usage: Programme::itv->get_links( \%prog, 'itv' ); # Uses: \%prog sub get_links { shift; # ignore obj ref my $progref = shift; my $prog_type = shift; my $itv_catchup_page_prefix = 'http://www.itv.com/CatchUp/Video/default.html?ViewType=5&Filter='; # $pid my $xml; my $res; my %series_pid; my %episode_pid; main::logger "INFO: Getting itv Index Feeds\n"; # Setup User agent my $ua = main::create_ua(); # 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_feed_url = 'http://www.itv.com/_data/xml/CatchUpData/CatchUp360/CatchUpMenu.xml'; my $xmlindex = main::request_url_retry($ua, $itv_index_feed_url, 3, '.', "WARNING: Failed to get itv index from site\n"); $xmlindex =~ s/[\n\r]//g; # This gives a list of programme series (sometimes episodes) # # 50 # A CHRISTMAS CAROL # 615915 # # http://www.itv.com//img/150x113/A-Christmas-Carol-2f16d25a-de1d-4a3a-90cb-d47489eee98e.jpg # 2009-01-06T12:24:22.7419643+00:00 # # http://www.itv.com/CatchUp/Video/default.html?ViewType=5&Filter=32910 # 1 # 32910 # -1 # # # # for my $feedxml ( split //, $xmlindex ) { # Extract feed data my ($episodecount, $viewtype, $videoid, $url); my @entries; main::logger "\n\nDEBUG: XML: $feedxml\n" if $opt->{debug}; decode_entities($feedxml); # Remove non utf8 StringUtils::clean_utf8_and_whitespace($feedxml); # 1 $episodecount = $1 if $feedxml =~ m{\s*(\d+)\s*<\/EpisodeCount>}; # http://www.itv.com/CatchUp/Video/default.html?ViewType=5&Filter=32910 ($viewtype, $videoid) = ($1, $2) if $feedxml =~ m{\s*.+?ViewType=(\d+).+?Filter=(\d+)\s*<\/Url>}i; ## 32910 #$videoid = $1 if $feedxml =~ m{\s*(\d+)\s*<\/VideoID>}; # Skip if there is no feed data for channel next if ($viewtype =~ /^0*$/ || $videoid =~ /^0*$/ ); main::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 Player'; # Skip if this pid is a duplicate if ( defined $progref->{$pid} ) { main::logger "WARNING: '$pid, $progref->{$pid}->{name} - $progref->{$pid}->{episode}, $progref->{$pid}->{channel}' already exists (this channel = $channel)\n" if $opt->{verbose}; next; } $name = $1 if $feedxml =~ m{\s*(.+?)\s*<\/ProgrammeTitle>}; $guidance = $1 if $feedxml =~ m{\s*(.*?)\s*<\/DentonRating>}; $thumbnail = $1 if $feedxml =~ m{\s*(.+?)\s*<\/ProgrammeMediaUrl>}; $episode = $pid; # Strip end/extra whitespace StringUtils::clean_utf8_and_whitespace($guidance); # build data structure $progref->{$pid} = Programme::itv->new( 'pid' => $pid, 'name' => $name, 'versions' => 'default', 'episode' => $episode, 'guidance' => $guidance, 'desc' => $desc, 'available' => $available, 'duration' => $duration, 'thumbnail' => $thumbnail, 'channel' => $channel, 'categories' => 'TV', 'type' => 'itv', 'web' => ${itv_catchup_page_prefix}.${pid}, ); # Get next episode list and parse #
#
#

Emmerdale

#

Mon 05 Jan 2009

#

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

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

Emmerdale

#

Fri 02 Jan 2009

#

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

    #
  • # Duration: 30 min #
  • #
  • # Expires in # 26 # days #
  • #
#
#
# } elsif ( $viewtype == 1 ) { # Make sure we don't duplicate parsing a series next if $series_pid{$videoid}; $series_pid{$videoid} = 1; # Get metadata pages for each series main::logger "DEBUG: Getting series metadata $url\n" if $opt->{debug}; $xml = main::main::request_url_retry($ua, $url, 2, '.', "WARNING: Failed to get itv series data for ${videoid} from itv site\n") if $opt->{verbose}; $xml = main::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; StringUtils::clean_utf8_and_whitespace($xml); # Extract Filter (pids) from this list # e.g.

Emmerdale

my @videoids = (split /

{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 Player'; # Skip if this pid is a duplicate if ( defined $progref->{$pid} ) { main::logger "WARNING: '$pid, $progref->{$pid}->{name} - $progref->{$pid}->{episode}, $progref->{$pid}->{channel}' already exists (this channel = $channel)\n" if $opt->{verbose}; next; } $name = $1 if $feedxml =~ m{\s*(.+?)\s*<\/ProgrammeTitle>}; $available = $1 if $xml =~ m{(.+?)<\/p>}i; $episode = $available; $duration = $1 if $xml =~ m{
  • Duration:\s*(.+?)\s*<\/li>}i; $desc = $1 if $xml =~ m{(.+?)\s*<\/p>}; $guidance = $1 if $feedxml =~ m{\s*(.*?)\s*<\/DentonRating>}; $thumbnail = $1 if $feedxml =~ m{\s*(.+?)\s*<\/ProgrammeMediaUrl>}; $guidance =~ s/[\s\x00\xc2\xa0]+$//ig; main::logger "DEBUG: name='$name' episode='$episode' pid=$pid available='$available' \n" if $opt->{debug}; # build data structure $progref->{$pid} = Programme::itv->new( 'pid' => $pid, 'name' => $name, 'versions' => 'default', 'episode' => $episode, 'guidance' => $guidance, 'desc' => $desc, 'available' => $available, 'duration' => $duration, 'thumbnail' => $thumbnail, 'channel' => $channel, 'categories' => 'TV', 'type' => 'itv', 'web' => ${itv_catchup_page_prefix}.${pid}, ); } } } main::logger "\n"; return 0; } sub download { my ( $prog, $ua, $mode, $version, $version_pid ) = ( @_ ); my %streamdata; # if subsonly required then skip return 'skip' if $opt->{subsonly}; %streamdata = %{ $prog->get_stream_data( undef, 'itv') }; # Get extension from streamdata if defined and raw not specified $prog->{ext} = $prog->{streams}->{$version}->{$mode}->{ext}; $prog->{ext} = 'asf' if $opt->{raw}; my @url_list = split /\|/, $prog->{streams}->{$version}->{$mode}->{streamurl}; if ( $#url_list < 0 ) { main::logger "WARNING: No programme stream URLs were found, skipping\n"; return 'skip'; } # Determine the correct filenames for this recording return 'skip' if $prog->generate_filenames( $ua, " ", $#url_list ); # Skip from here if we are only testing recordings return 'skip' if $opt->{test}; # Instantiate new streamer from streamdata my $class = "Streamer::$prog->{streams}->{$version}->{$mode}->{streamer}"; my $stream = $class->new; # Do the recording return $stream->get( $ua, $prog->{streams}->{$version}->{$mode}->{streamurl}, $prog ); } # get ITV prog info sub get_url { my ( undef, $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 '(.+?) HTTP/1.1' main::logger "INFO: ITV Video Stage 1 URL: $url_1\n" if $opt->{verbose}; $response = main::request_url_retry($ua, $url_1, 2, '', ''); main::logger "DEBUG: Response data: $response\n" if $opt->{debug}; $url_2 = $1 if $response =~ m{(.+?) HTTP/1.1}; # replace '&' with '&' and append '%20HTTP/1.1' $url_2 =~ s/&/&/g; $url_2 .= '%20HTTP/1.1'; return '' if $url_2 !~ m{http\:\/\/}i; main::logger "INFO: ITV Video Stage 2 URL: $url_2\n" if $opt->{verbose}; $response = main::request_url_retry($ua, $url_2, 2, '', ''); main::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. Doctor Zhivago #$prog->{name} = $1 if $response =~ m{(.+?)<\/Title>}; for my $entry (split /<Entry><ref\s+href=/, $response) { main::logger "DEBUG: Entry data: $entry\n" if $opt->{debug}; $entry .= '<Entry><ref href='.$entry; ( $url_3, $part, $filename, $duration ) = ( $1, $2, $3, $4 ) if $entry =~ m{<Entry><ref\s+href="(.+?)"\s+\/><param\s+value="true"\s+name="Prebuffer"\s+\/>\s*<PARAM\s+NAME="PrgPartNumber"\s+VALUE="(.+?)"\s*\/><PARAM\s+NAME="FileName"\s+VALUE="(.+?)"\s*\/><PARAM\s+NAME="PrgLength"\s+VALUE="(.+?)"\s*\/>}; next if not $url_3; # Replace '&' with '&' in url $url_3 =~ s/&/&/g; main::logger "INFO: ITV Video Name: $part\n"; main::logger "INFO: ITV Video Stage 3 URL: $url_3\n" if $opt->{verbose}; $entry = main::request_url_retry($ua, $url_3, 2, '', ''); main::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]+}; main::logger "INFO: ITV Video URL: $url_4\n" if $opt->{verbose}; push @url_list, $url_4; } return @url_list; } # Generic # Gets media streams data for this version pid # $media = undef|itv sub get_stream_data { my ( $prog, $verpid, $media ) = @_; my %data; # Setup user agent with redirection enabled my $ua = main::create_ua(); $opt->{quiet} = 0 if $opt->{streaminfo}; # ITV streams my $prog_type = 'itv'; $data{$prog_type}{type} = 'ITV ASF Video stream'; # Use asf until we can create mp4 from it #$data{$prog_type}{ext} = 'mp4'; $data{$prog_type}{ext} = 'asf'; $data{$prog_type}{streamer} = 'mms'; $opt->{quiet} = 1 if $opt->{streaminfo}; $data{$prog_type}{streamurl} = join('|', $prog->get_url( $ua, $prog->{pid} ) ); $opt->{quiet} = 0 if $opt->{streaminfo}; # Return a hash with media => url if '' is specified - otherwise just the specified url if ( ! $media ) { 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}; } } 1;