Skip to content

Instantly share code, notes, and snippets.

@unstabler
Last active August 29, 2015 14:02
Show Gist options
  • Save unstabler/e6e0df14aaad36d12cb1 to your computer and use it in GitHub Desktop.
Save unstabler/e6e0df14aaad36d12cb1 to your computer and use it in GitHub Desktop.
니코니코 동화 동영상 다운로드 스크립트
#!/usr/bin/env perl
# nicodownload.pl
# 니코니코 동화의 동영상을 다운받아 줍니다.
# 사용 전에 환경 변수 $NICOVIDEO_USERNAME에는 사용자 ID, $NICOVIDEO_PASSWORD에 비밀번호가 지정되어 있어야 합니다.
# 기본적으로는 이코노미(저화질) 모드에서 동영상은 다운로드 받지 않지만, --accept-economy 옵션을 넣으면 강제로 다운받도록 합니다.
# --force-economy 옵션으로 강제로 저화질로 다운로드받을 수 있습니다.
# --write-to-stdout로 표준 출력으로 동영상 파일을 쓰도록 할 수 있습니다. ㅜ
use 5.010;
use utf8;
use strict;
use warnings;
binmode "STDOUT", ":encoding(utf8)";
binmode "STDERR", ":encoding(utf8)";
local $| = 1;
unless (@ARGV) {
say
"$0 - Download videos from nicovideo.\n\n".
"usage: $0 [video_id] [--accept-economy] [--force-economy] [--write-to-stdout]\n\n".
'NOTE : You must set Environment Variable ($NICOVIDEO_USERNAME and $NICOVIDEO_PASSWORD) to login Nicovideo.';
exit;
}
# TODO: --write-to-stdout가 구현되지 않음.
# 여러 옵션을 지정할 수 있도록 $nico->download() 함수에 해시로 옵션을 집어넣도록 수정
my $video_id = shift @ARGV;
my $economy = 0;
my $option = {};
while ($ARGV[0]){
my $arg = shift @ARGV;
if ($arg eq "--force-economy") {
$economy = 2;
$option->{economy} = 2;
} elsif ($arg eq "--accept-economy") {
$economy = 1;
$option->{economy} = 1;
} elsif ($arg eq "--write-to-stdout") {
$option->{stdout} = 1;
}
}
my $nico = NicoVideo->new($ENV{NICOVIDEO_USERNAME}, $ENV{NICOVIDEO_PASSWORD});
my $video_info = $nico->download_video($video_id, $economy);
#my $video_info = $nico->download_video($video_id, $option);
package NicoVideo;
use 5.010;
use strict;
use warnings;
use utf8;
use Carp;
use LWP::UserAgent;
use URI::Escape;
use XML::Simple;
sub NICOVIDEO_DEFAULT() { "http://www.nicovideo.jp/" }
sub NICOVIDEO_SECURE() { "https://secure.nicovideo.jp" }
sub NICOVIDEO_LOGIN() { "https://secure.nicovideo.jp/secure/login?site=niconico" }
sub NICOVIDEO_VIDEOINFO() { "http://ext.nicovideo.jp/api/getthumbinfo/" }
sub NICOVIDEO_GETFLV() { "http://flapi.nicovideo.jp/api/getflv/" }
sub NICOVIDEO_MYLIST() { "http://www.nicovideo.jp/my/mylist" }
sub NICOVIDEO_WATCH() { "http://nicovideo.jp/watch/" }
sub DEFAULT_COOKIE() { $ENV{HOME}."/.nico_cookies.dat" }
sub new {
my $class = shift;
my ($username, $password) = @_;
my $body = {
ua => LWP::UserAgent->new (
agent => 'Mozilla/5.0'
),
username => $username,
password => $password,
};
my $self = bless $body, $class;
$self->ua->cookie_jar( {file => DEFAULT_COOKIE(), autosave => 1} );
# 쿠키를 체크하도록 합니다.
say "Checking login..";
if ($self->check_login) {
say "Already logged in :)";
return $self;
} else {
if ($username && $password) {
$self->login();
} else {
croak "Warning: to download videos, username and password are required.";
}
}
return $self;
}
sub parse_url {
my ($self, $url) = @_;
if ($url =~ m/^[sn]m\d+/) {
return $url;
} elsif ($url =~ m{(nicovideo\.jp|/watch|nico\.ms)/(?<video_id>[sn]m\d+)}) {
return $+{video_id};
}
}
sub login {
my $self = shift;
say "Performing login..";
my $res = $self->ua->post(NICOVIDEO_LOGIN,
{
mail_tel => $self->{username},
password => $self->{password},
}
);
if ($res->code == 302) {
unless ($res->header("Location") eq NICOVIDEO_DEFAULT) {
croak "Failed to login. (Wrong username or password?)";
}
say "Login Success.";
$self->ua->cookie_jar->save();
} else {
croak "Connection Error :(";
}
}
sub check_login {
my $self = shift;
# TODO: 로그인 체크가 dirty하게 이루어집니다.
# 이쁘게 다시 씁시다.
my $res = $self->ua->get(NICOVIDEO_MYLIST());
my $nicovideo_secure_url = NICOVIDEO_SECURE();
if (($res->header("Content-Location") || "") eq "login_form.php") {
return 0;
} else {
return 1;
}
}
sub get_video_info {
my $self = shift;
my $video_id = shift;
my $res = $self->ua->get(NICOVIDEO_VIDEOINFO . $video_id);
return unless $res->is_success;
my $video_info = XMLin($res->decoded_content);
return $video_info->{thumb};
}
sub access_watch_page {
my $self = shift;
my $video_id = shift;
my $economy = shift;
# Watch url로 접근하여 쿠키를 취득합니다.
my $video_url = NICOVIDEO_WATCH . $video_id;
$video_url .= "?eco=1" if $economy;
my $res = $self->ua->get($video_url);
return $res->is_success;
}
sub get_video_address {
my $self = shift;
my $video_id = shift;
my $economy = shift || 0;
my $url = NICOVIDEO_GETFLV . $video_id;
$url .= "?eco=1" if $economy;
my $res = $self->ua->get($url);
return $+{url} if (uri_unescape($res->decoded_content) =~ m{&url=(?<url>http://.+?)&});
}
sub download_video {
my $self = shift;
my $video_id = shift;
#my $options = shift;
my $economy = shift || 0; # 1: 이코노미 모드를 허용, 2: 강제 이코노미
my $video_info = $self->get_video_info($video_id);
croak "Failed to fetch video information!" unless ($video_info);
printf "--> Trying to download %s (%s)..\n", $video_info->{title}, $video_info->{video_id};
say "-> Fetching Video address..";
my $url = $self->get_video_address($video_id, $economy);
say "-> Accessing video page to get cookies..";
$self->access_watch_page($video_id, (($economy == 2)? 1 : 0) );
say "-> Downloading video..";
if ($self->_download($url, $video_info, $economy)) {
say "Download Success!";
} else {
say "Failed to Download!";
}
}
sub _download {
my $self = shift;
my $url = shift;
my $video_info = shift;
my $economy = shift;
my $res = $self->ua->head($url);
my $video_size = $res->header("Content-Length");
if ($video_size != $video_info->{size_high}) {
carp "Your requested video was in Economy mode (low-quality): " . $video_size ." != " . $video_info->{size_high};
unless ($economy) {
return;
} else {
carp "Continuing download in low-quality.";
$video_info->{movie_type} = "eco.".$video_info->{movie_type};
}
}
my $filename = sprintf("%s - %s.%s", $video_info->{video_id}, $video_info->{title}, $video_info->{movie_type});
open my $video_file, '>', $filename or croak "Cannot create file $filename";
binmode $video_file;
my $writed = 0;
$self->ua->get($url, ':content_cb' => sub {
my $data = shift;
$writed += length $data;
my $percent = ( $writed / $video_size ) * 100;
printf "Downloading .. %.2f%% (%d / %d) writed to disk (buffer size: %d bytes)\r", $percent, $writed, $video_size, length $data;
print $video_file $data;
});
say "";
close $video_file;
return 1;
}
sub ua () {
my $self = shift;
return $self->{ua};
}
1;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment