Last active
August 29, 2015 14:02
-
-
Save unstabler/e6e0df14aaad36d12cb1 to your computer and use it in GitHub Desktop.
니코니코 동화 동영상 다운로드 스크립트
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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