Group
Extension

App-get_flash_videos/lib/FlashVideo/Site/Brightcove.pm

# Part of get-flash-videos. See get_flash_videos for copyright.
package FlashVideo::Site::Brightcove;

use strict;
use FlashVideo::Utils;
use MIME::Base64;

sub find_video {
  my ($self, $browser, $embed_url) = @_;

  my $metadata = { };
  my ($video_id, $player_id);

  # URL params, JSON, etc..
  $video_id  = ($browser->content =~ /(?:clip|video)Id["'\] ]*[:=]["' ]*(\d+)/i)[0];
  $player_id = ($browser->content =~ /playerId["'\] ]*[:=]["' ]*(\d+)/i)[0];

  # <object> params
  $player_id ||= ($browser->content =~ /<param name=["']?playerID["']? value=["'](\d+) ?["']/i)[0];
  $video_id ||= ($browser->content =~ /<param name=["']?\@?video(?:Player|id)["']? value=["'](\d+)["']/i)[0];

  # flashVar params (e.g. <embed>)
  $player_id ||= ($browser->content =~ /flashVars.*playerID=(\d+)/i)[0];
  $video_id ||= ($browser->content =~ /flashVars.*video(?:Player|ID)=(\d+)/i)[0];

  # Brightcove JavaScript API
  if(!$player_id && $browser->content =~ /brightcove.player.create\(['"]?(\d+)['"]?,\s*['"]?(\d+)/) {
    $video_id = $1;
    $player_id = $2;
  }

  # Support direct links to videos
  for my $url($browser->uri->as_string, $embed_url) {
    if($url =~ /(?:videoID|bctid)=?(\d+)/i) {
      $video_id ||= $1;
    }

    if($url =~ /(?:playerID|bcpid)=?(\d+)/i) {
      $player_id ||= $1;
    }

    if($url =~ /(?:lineupID|bclid)=?(\d+)/i) {
      $metadata->{lineupId} ||= $1;
    }
  }

  debug "Extracted playerId: $player_id, videoId: $video_id, lineupID: $metadata->{lineupId}"
    if $player_id or $video_id;

  die "Unable to extract Brightcove IDs from page" unless $player_id;

  $metadata->{videoId} = $video_id;
  return $self->amfgateway($browser, $player_id, $metadata);
}

sub amfgateway {
  my($self, $browser, $player_id, $metadata) = @_;

  my $has_amf_packet = eval { require Data::AMF::Packet };
  if (!$has_amf_packet) {
    die "Must have Data::AMF::Packet installed to download Brightcove videos";
  }

  my $page_url = $browser->uri;

  my $packet = Data::AMF::Packet->deserialize(decode_base64(<<EOF));
AAAAAAABAEhjb20uYnJpZ2h0Y292ZS50ZW1wbGF0aW5nLlRlbXBsYXRpbmdGYWNhZGUuZ2V0Q29u
dGVudEZvclRlbXBsYXRlSW5zdGFuY2UAAi8yAAACNQoAAAACAEH4tP+1EAAAEAA1Y29tLmJyaWdo
dGNvdmUudGVtcGxhdGluZy5Db250ZW50UmVxdWVzdENvbmZpZ3VyYXRpb24ACnZpZGVvUmVmSWQG
AAd2aWRlb0lkBgAIbGluZXVwSWQGAAtsaW5ldXBSZWZJZAYAF29wdGltaXplRmVhdHVyZWRDb250
ZW50AQEAF2ZlYXR1cmVkTGluZXVwRmV0Y2hJbmZvEAAkY29tLmJyaWdodGNvdmUucGVyc2lzdGVu
Y2UuRmV0Y2hJbmZvAApjaGlsZExpbWl0AEBZAAAAAAAAAA5mZXRjaExldmVsRW51bQBAEAAAAAAA
AAALY29udGVudFR5cGUCAAtWaWRlb0xpbmV1cAAACQAKZmV0Y2hJbmZvcwoAAAACEAAkY29tLmJy
aWdodGNvdmUucGVyc2lzdGVuY2UuRmV0Y2hJbmZvAApjaGlsZExpbWl0AEBZAAAAAAAAAA5mZXRj
aExldmVsRW51bQA/8AAAAAAAAAALY29udGVudFR5cGUCAAtWaWRlb0xpbmV1cAAACRAAJGNvbS5i
cmlnaHRjb3ZlLnBlcnNpc3RlbmNlLkZldGNoSW5mbwAKY2hpbGRMaW1pdABAWQAAAAAAAAAPZ3Jh
bmRjaGlsZExpbWl0AEBZAAAAAAAAAA5mZXRjaExldmVsRW51bQBACAAAAAAAAAALY29udGVudFR5
cGUCAA9WaWRlb0xpbmV1cExpc3QAAAkAAAk=
EOF

  if (defined $player_id) {
    $packet->messages->[0]->{value}->[0] = "$player_id";
  }

  if (ref $metadata) {
    for(keys %$metadata) {
      $packet->messages->[0]->{value}->[1]->{$_} = "$metadata->{$_}";
    }
  }

  my $data = $packet->serialize;

  $browser->post(
    "http://c.brightcove.com/services/amfgateway",
    Content_Type => "application/x-amf",
    Content => $data
  );

  die "Failed to post to Brightcove AMF gateway"
    unless $browser->response->is_success;

  $packet = Data::AMF::Packet->deserialize($browser->content);

  if($self->debug) {
    require Data::Dumper;
    debug Data::Dumper::Dumper($packet);
  }

  if(ref $packet->messages->[0]->{value} ne 'ARRAY') {
    die "Unexpected data from AMF gateway";
  }

  my @found;
  for (@{$packet->messages->[0]->{value}}) {
    if ($_->{data}->{videoDTO}) {
      push @found, $_->{data}->{videoDTO};
    }
    if ($_->{data}->{videoDTOs}) {
      push @found, @{$_->{data}->{videoDTOs}};
    }
  }

  my @rtmpdump_commands;

  for my $d (@found) {
    next if $metadata->{videoId} && $d->{id} != $metadata->{videoId};

    my $host = ($d->{FLVFullLengthURL} =~ m!rtmp://(.*?)/!)[0];
    my $file = ($d->{FLVFullLengthURL} =~ m!&([a-z0-9:]+/.*?)(?:&|$)!)[0];
    my $app = ($d->{FLVFullLengthURL} =~ m!//.*?/(.*?)/&!)[0];
    my $filename = ($d->{FLVFullLengthURL} =~ m!&.*?/([^/&]+)(?:&|$)!)[0];

    $app .= "?videoId=$d->{id}&lineUpId=$d->{lineupId}&pubId=$d->{publisherId}&playerId=$player_id&playerTag=&affiliateId=";

    my $args = {
      app => $app,
      pageUrl => $page_url,
      swfUrl => "http://admin.brightcove.com/viewer/federated/f_012.swf?bn=590&pubId=$d->{publisherId}",
      tcUrl => "rtmp://$host:1935/$app",
      auth => ($d->{FLVFullLengthURL} =~ /^[^&]+&(.*)$/)[0],
      rtmp => "rtmp://$host/$app",
      playpath => $file,
      flv => "$filename.flv",
    };

    # Use sane filename
    if ($d->{publisherName} and $d->{displayName}) {
      $args->{flv} = title_to_filename("$d->{publisherName} - $d->{displayName}");
    }

    # In some cases, Brightcove doesn't use RTMP streaming - the file is
    # downloaded via HTTP.
    if (!$d->{FLVFullLengthStreamed}) {
      info "Brightcove HTTP download detected";
      return ($d->{FLVFullLengthURL}, $args->{flv});
    }

    push @rtmpdump_commands, $args;
  }

  if (@rtmpdump_commands > 1) {
    return \@rtmpdump_commands;
  }
  else {
    return $rtmpdump_commands[-1];
  }
}

sub can_handle {
  my($self, $browser, $url) = @_;

  return 1 if $url && URI->new($url)->host =~ /\.brightcove\.com$/;

  return $browser->content =~ /(playerI[dD]|brightcove.player.create)/
    && $browser->content =~ /brightcove/i;
}

1;


Powered by Groonga
Maintained by Kenichi Ishigaki <ishigaki@cpan.org>. If you find anything, submit it on GitHub.