ニコニコ動画ダウンローダ with Irvine

Irvine を使ってニコニコ動画の flv をダウンロードする Perl スクリプト。こんな感じに使う。

perl NicoVideoDownloader.pm -c cookies.txt <ニコニコ動画のURL>

CookieFirefox なんかの Mozilla 系の Cookie を持って来る(プロファイルフォルダ以下の cookies.tx を使う)。
ぶっちゃけ、これのパクリ + α です。
本当は Python でやろうと思ったんだけど、Cookie を使った認証でうまくいかなかったので、Perl でゴリゴリ。

package NicoVideoDownloader;

use base qw(Class::Accessor);
__PACKAGE__->mk_accessors(qw(ua irvine));

use strict;
use warnings;
use utf8;

use Encode;
use Getopt::Long;
use Win32::OLE;
use LWP::UserAgent;
use HTTP::Cookies::Mozilla;
use HTML::HeadParser;
use URI::Escape;

sub new {
    my $class = shift;
    my $irvine = Win32::OLE->GetActiveObject('Irvine.Api')
        || Win32::OLE->new('Irvine.Api');
    my $ua = LWP::UserAgent->new;

    my $self = {
        irvine => $irvine,
        cookie_file => 'cookies.txt',
        ua => $ua,
    };

    return bless $self, $class;
};

sub register_download {
    my ($self, $parent, $folder, $url) = @_;
    my $response = $self->ua->request(
        HTTP::Request->new('GET', $url));
    my $headers = $response->headers;
    my $content = Encode::decode('utf8', $response->content);
    my $parser = HTML::HeadParser->new($headers);
    $parser->parse($content);
    my $title = $headers->title;
    $title =~ s/[^\|]+ \| //;

    my $flv_id;
    if ($url =~ m{watch/(.*)$}) {
        $flv_id = $1;
    } else {
        die;
    }

    my $getflv_url = "http://www.nicovideo.jp/getflv?v=$flv_id";
    my $query_string = $self->ua->simple_request(
        HTTP::Request->new('GET', $getflv_url))->content;
    my %params = map {
        map { uri_unescape($_) } split /=/, $_, 2
    } split /\&/, $query_string;

    ### %params

    my $download_url = $params{url};
    my $filename = sprintf '%s_%s.flv', $title, $flv_id;
    my $item = join("\t", ($download_url, $folder, $filename));
    $self->irvine->AddQueueItem($parent, Encode::encode('cp932', $item));
}

sub main {
    my $cookie_file = 'cookies.txt';
    my $parent = '/Default';
    my $folder = '';
    my $result = GetOptions(
        'cookie=s' => \$cookie_file,
        'parent=s' => \$parent,
        'folder=s' => \$folder);

    if ($parent !~ m{\A / }x) {
        $parent = '/' . $parent;
    }

    my $cj = HTTP::Cookies::Mozilla->new;
    $cj->load($cookie_file) or die;

    my $downloader = NicoVideoDownloader->new;
    $downloader->ua->cookie_jar($cj);
    my @urls = @ARGV;
    for my $url (@urls) {
        $downloader->register_download($parent, $folder, $url);
    }
}

if ($0 eq __FILE__) {
    binmode STDOUT, ':encoding(cp932)';
    main;
}

1;

あと、wxPerl を使った適当なフロントエンドとか。URL をクリップボードから読み取り、ダイアログでフォルダ名を入力。

#!/usr/bin/env perl

use strict;
use warnings;

use Encode;
use Wx;
use LWP::UserAgent;
use HTTP::Cookies::Mozilla;
use Win32::Clipboard;
use Win32::OLE;
use NicoVideoDownloader;

binmode STDOUT, ':encoding(cp932)';

my $clip = Win32::Clipboard();

my $parent = '/Default';
my $cookie = 'cookies.txt';
my $cookie_jar = HTTP::Cookies::Mozilla->new;
$cookie_jar->load($cookie) or die;
my $folder = Wx::GetTextFromUser('folder name', 'folder name');
my $downloader = NicoVideoDownloader->new;
$downloader->ua->cookie_jar($cookie_jar);

my $text = $clip->Get();
my @urls = split /\r?\n/, $text;

for my $url (@urls) {
    return if $url =~ /\A \s* \Z/x;
    $downloader->register_download($parent, $folder, $url);
}