aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJames Bottomley <JBottomley@Parallels.com>2013-07-03 16:41:07 -0700
committerJames Bottomley <JBottomley@Parallels.com>2013-07-03 17:32:40 -0700
commitb1cd1f266417ec374f7d73d59a2501f789986048 (patch)
tree062cac5546042d678b6366d8388cba4520504adf
parent30658442936f4bee79ad1926fc0adf3611e3cd41 (diff)
downloadget-flash-videos-b1cd1f266417ec374f7d73d59a2501f789986048.tar.gz
Channel4: Update for new F4V download method
Channel4 recently changed to F4V streaming. If F4V streaming is used, the token decode is different and the downloader must be the F4V one. Signed-off-by: James Bottomley <JBottomley@Parallels.com>
-rw-r--r--lib/FlashVideo/Site/Channel4.pm205
1 files changed, 121 insertions, 84 deletions
diff --git a/lib/FlashVideo/Site/Channel4.pm b/lib/FlashVideo/Site/Channel4.pm
index 9222b0e..00d8b31 100644
--- a/lib/FlashVideo/Site/Channel4.pm
+++ b/lib/FlashVideo/Site/Channel4.pm
@@ -6,12 +6,14 @@ package FlashVideo::Site::Channel4;
use strict;
use Crypt::Blowfish_PP;
+use Crypt::Rijndael;
use FlashVideo::Utils;
use FlashVideo::JSON;
use MIME::Base64;
use Time::HiRes qw(time);
use constant TOKEN_DECRYPT_KEY => 'STINGMIMI';
+use constant AES_TOKEN_DECRYPT_KEY => 'n9cLieYkqwzNCqvi';
our $VERSION = '0.04';
sub Version() { $VERSION;}
@@ -62,7 +64,7 @@ sub find_video {
}
my $xml_ref = from_xml($raw_xml);
- my $xml;
+ my $xml = $xml_ref;
# Determine series and episode. Channel 4 sometimes have these backwards,
@@ -70,11 +72,89 @@ sub find_video {
# resuming previous episodes from the same series. Currently take this from
# dc:relation.SeriesNumber and sd:relation.EpisodeNumber below
my $series_and_episode;
+ my $stream_url = $xml->{assetInfo}->{uriData}->{streamUri};
+ my $token = $xml->{assetInfo}->{uriData}->{token};
+ my $cdn = $xml->{assetInfo}->{uriData}->{cdn};
+
+ my $decoded_token = decode_4od_token($token, $xml_ref->{assetInfo}->{uriData}->{streamUri} =~ /f4m$/);
+
+ info "CDN=$cdn, token=$token, stream_url=$stream_url";
+
+ # RTMP authentication - varies depending on which CDN is in use.
+ my $auth;
+
+ # Different CDNs require different handling.
+ if ($cdn eq 'll') {
+ # Limelight
+ my $ip = $xml->{assetInfo}->{uriData}->{ip};
+ my $e = $xml->{assetInfo}->{uriData}->{e};
+
+ if (defined $ip) {
+ $auth = sprintf "e=%s&ip=%s&h=%s", $e, $ip, $decoded_token;
+ }
+ else {
+ $auth = sprintf "e=%s&h=%s", $e, $decoded_token;
+ }
+ }
+ else {
+ # Akamai presumably
+ my $fingerprint = $xml->{assetInfo}->{uriData}->{fingerprint};
+ my $slist = $xml->{assetInfo}->{uriData}->{slist};
+
+ $auth = sprintf "auth=%s&aifp=%s&slist=%s",
+ $decoded_token, $fingerprint, $slist;
+ }
+
+ # Get filename to use.
+ my $title;
+ my @title_components = grep defined,
+ map { $xml->{assetInfo}->{$_} }
+ qw(brandTitle episodeTitle);
+
+ if ($series_and_episode) {
+ push @title_components, $series_and_episode;
+ }
+
+ if (@title_components) {
+ $title = join " - ", @title_components;
+ }
+ debug("title_to_filename on $title");
+ my $filename = title_to_filename($title, "flv");
+
+ # Get subtitles if necessary.
+ if ($prefs->subtitles) {
+ if (my $subtitles_url = $xml->{assetInfo}->{subtitlesFileUri}) {
+ $subtitles_url = "http://ais.channel4.com$subtitles_url";
+
+ $browser->get($subtitles_url);
+
+ if (!$browser->success) {
+ info "Couldn't download 4od subtitles: " . $browser->response->status_line;
+ }
+
+ my $subtitles_file = title_to_filename($title, "srt");
+ convert_sami_subtitles_to_srt($browser->content, $subtitles_file);
+
+ info "Saved subtitles to $subtitles_file";
+ }
+ else {
+ debug("Subtitles requested for '$title' but none available.");
+ }
+ }
+
# Check for mp4 if not then try different assetId
my $lower_id = 0;
my $upper_id = 9999999;
- if ($xml_ref->{assetInfo}->{uriData}->{streamUri} !~ /mp4$/ ) {
+ if ($xml_ref->{assetInfo}->{uriData}->{streamUri} =~ /f4m$/) {
+ info "Found F4M manifest";
+ return {
+ downloader => 'f4m',
+ manifest => $xml_ref->{assetInfo}->{uriData}->{streamUri},
+ flv => $filename,
+ auth => '?'.$auth,
+ };
+ } elsif ($xml_ref->{assetInfo}->{uriData}->{streamUri} !~ /mp4$/ ) {
my $find_url = "http://ps3.channel4.com/pmlsd/" .
$xml_ref->{assetInfo}->{webSafeBrandTitle} .
@@ -158,74 +238,6 @@ sub find_video {
die "Unable to find suitable stream - may not be available yet\n" unless defined $xml;
- my $stream_url = $xml->{assetInfo}->{uriData}->{streamUri};
- my $token = $xml->{assetInfo}->{uriData}->{token};
- my $cdn = $xml->{assetInfo}->{uriData}->{cdn};
-
- my $decoded_token = decode_4od_token($token);
-
- # RTMP authentication - varies depending on which CDN is in use.
- my $auth;
-
- # Different CDNs require different handling.
- if ($cdn eq 'll') {
- # Limelight
- my $ip = $xml->{assetInfo}->{uriData}->{ip};
- my $e = $xml->{assetInfo}->{uriData}->{e};
-
- if (defined $ip) {
- $auth = sprintf "e=%s&ip=%s&h=%s", $e, $ip, $decoded_token;
- }
- else {
- $auth = sprintf "e=%s&h=%s", $e, $decoded_token;
- }
- }
- else {
- # Akamai presumably
- my $fingerprint = $xml->{assetInfo}->{uriData}->{fingerprint};
- my $slist = $xml->{assetInfo}->{uriData}->{slist};
-
- $auth = sprintf "auth=%s&aifp=%s&slist=%s",
- $decoded_token, $fingerprint, $slist;
- }
-
- # Get filename to use.
- my $title;
- my @title_components = grep defined,
- map { $xml->{assetInfo}->{$_} }
- qw(brandTitle episodeTitle);
-
- if ($series_and_episode) {
- push @title_components, $series_and_episode;
- }
-
- if (@title_components) {
- $title = join " - ", @title_components;
- }
- debug("title_to_filename on $title");
- my $filename = title_to_filename($title, "flv");
-
- # Get subtitles if necessary.
- if ($prefs->subtitles) {
- if (my $subtitles_url = $xml->{assetInfo}->{subtitlesFileUri}) {
- $subtitles_url = "http://ais.channel4.com$subtitles_url";
-
- $browser->get($subtitles_url);
-
- if (!$browser->success) {
- info "Couldn't download 4od subtitles: " . $browser->response->status_line;
- }
-
- my $subtitles_file = title_to_filename($title, "srt");
- convert_sami_subtitles_to_srt($browser->content, $subtitles_file);
-
- info "Saved subtitles to $subtitles_file";
- }
- else {
- debug("Subtitles requested for '$title' but none available.");
- }
- }
-
# Create the various options for rtmpdump.
my $rtmp_url;
@@ -267,27 +279,52 @@ sub find_video {
};
}
+sub aes_unpad {
+ my ($t) = @_;
+ my $c = substr($t, length($t)-1, 1);
+ my $pl = unpack('C', $c);
+ #info 'Pad length '.$pl;
+ return undef if ($pl == 0 || $pl > 16);
+ # verify
+ my $p = substr($t, length($t)-$pl, $pl);
+ return undef if ($p ne $c x $pl);
+ return substr($t, 0, length($t) - $pl);
+}
+
sub decode_4od_token {
- my $encrypted_token = shift;
+ my ($encrypted_token, $aes_decode) = @_;
$encrypted_token = decode_base64($encrypted_token);
- my $blowfish = Crypt::Blowfish_PP->new(TOKEN_DECRYPT_KEY);
-
my $decrypted_token = '';
- # Crypt::Blowfish_PP only decrypts 8 bytes at a time.
- my $position = 0;
-
- while ( $position < length $encrypted_token) {
- $decrypted_token .= $blowfish->decrypt(substr $encrypted_token, $position, 8);
- $position += 8;
- }
+ if ($aes_decode) {
+ # new 4od tokens are aes-128-cbc encrypted with the first
+ # 16 bytes of the token being the IV
+ my $aes = Crypt::Rijndael->new(AES_TOKEN_DECRYPT_KEY, Crypt::Rijndael::MODE_CBC());
+ my $iv = substr($encrypted_token, 0, 16);
+ $encrypted_token = substr($encrypted_token, 16);
+ $aes->set_iv($iv);
+ $decrypted_token = $aes->decrypt($encrypted_token);
+ $decrypted_token = aes_unpad($decrypted_token);
+ die 'Failed to decrypt Channel 4 Token' if (!defined($decrypted_token));
+ $decrypted_token = (split /\?token=/,$decrypted_token)[1];
+ } else {
+ my $blowfish = Crypt::Blowfish_PP->new(TOKEN_DECRYPT_KEY);
+
+ # Crypt::Blowfish_PP only decrypts 8 bytes at a time.
+ my $position = 0;
+
+ while ( $position < length $encrypted_token) {
+ $decrypted_token .= $blowfish->decrypt(substr $encrypted_token, $position, 8);
+ $position += 8;
+ }
- # remove padding.. PKCS7/RFC5652..
- my $npad = unpack("c", substr($decrypted_token, -1));
- if ($npad > 0 && $npad < 9) {
- $decrypted_token = substr($decrypted_token, 0, length($decrypted_token)-$npad);
+ # remove padding.. PKCS7/RFC5652..
+ my $npad = unpack("c", substr($decrypted_token, -1));
+ if ($npad > 0 && $npad < 9) {
+ $decrypted_token = substr($decrypted_token, 0, length($decrypted_token)-$npad);
+ }
}
return $decrypted_token;
}