YouTube仕様変更に対応したダウンローダースクリプト
YouTubeダウンローダの改修の記事で書いていますが、YouTubeの仕様が一斉に変更されています。提供しているダウンローダスクリプトでもこれに対応しています。仕様変更内容はこちらのページをご参照下さい。ここでは、中身について簡単に解説いたします。
YouTubeのページを読む
まずはおなじみ、YouTubeのリンクに使われるページを読みます。
$ytpid = $ARGV[0];
if ($ytpid !~ /http:\/\//) {
if ($ytpid =~ /\?v=/) {
$ytpid = "http://jp.youtube.com/watch?" . $ytpid;
} else {
$ytpid = "http://jp.youtube.com/watch?v=" . $ytpid;
}
}
#--- read the page to find the URL to download the stream directory.
@wgetrsp = `wget -q -O - $ytpid`;
中身を解析します。
最初の"fullscreenUrl"が旧来の方法です。2番目のifの中にある"var swfArgs"はFlashPlayerに渡す引数で、この中の"fmt_url_map"を捜します。ここには、解像度パラメタとそのファイル取得URLがペアになっているので、ハッシュに保存します。(Dprintはデバッグ文です)。タイトルは取れれば取っておきます。
foreach (@wgetrsp) {
if ($_ =~ /fullscreenUrl/) {
$raw = $_;
$raw =~ s/[\t\n]//g;
} elsif ($_ =~ m|var swfArgs =|) {
@args = split(/,/);
foreach $arg (@args) {
if ($arg =~ m|"fmt_url_map":|) {
$ar = urldecode($arg);
Dprint "ARG1: $ar";
$ar =~ m|"fmt_url_map":[\s]*"([^"]+)"|;
$a = $1;
Dprint "ARG2: $a";
@vpbacks = split(/,/, $a);
Dprint "ARG3: $vpbacks[0]";
$vpbacks[0] =~ m/^([0-9]*)|(.*)$/;
Dprint "ARG4: ($vpfmt) $vpurl";
%vphash = ();
foreach $vpback (@vpbacks) {
Dprint "ARG3: $vpback";
$vpback =~ m/^[\s]*([0-9]*)|(.*)[\s]*$/;
$vphash{$1} = $2;
Dprint "ARG5: vphash{$1} = $2";
}
}
}
} elsif ($_ =~ m|<meta name="title" content="([^"]*)">|) {
$title = "$1";
}
}
ストリームのURLを取得します。
最初のifは旧来のもの。昔の書式に合っていたら、"get_video"のメソッドで取ることにします。(現在、この書式のものはないと思われます)
if ($raw =~ m|(video_id=.*)&title=|) {
#--- Success to get ID
$id = $1;
Dprint "ID = $id\n";
$BaseURL = "http://youtube.com/get_video?";
$FileTail = ".flv";
$url = "http://youtube.com/get_video?" . $id;
m|title=([^\'\; ]*)[\'\; ]|;
$title = $1;
Dprint "TITLE = $title\n";
$filename = $title . $FileTail;
$filename =~ s|/|_|g;
次のifが新しい仕様に合わせたものです。"fmt_url_map"の中身が取得できた場合に、このメソッドで取得することにしていますが、厳密かどうかは分りません。タイトルがあれば、なるべくタイトルを生かしたファイル名にします。(pathに使えない文字を'_'にしてあります。"TypeSelect"では、なるべく高画質なビデオファイルを選択しています。
} elsif ($#vphashk >= 0) {
$vpfmt = TypeSelect();
if ($vpfmt < 0) {
Dprint "Invalid fmt type: $vpfmt\n";
exit(1);
}
$cntnr = ContainerType($vpfmt);
$vpurl = $vphash{$vpfmt};
Dprint "ARG: ($vpfmt) $vpurl";
$url = $vpurl;
if (defined($title)) {
$filename = "$title.${cntnr}";
$filename =~ s|[/\$|\?'" \t\s]|_|g;
if (-f $filename) {
$filename =~ s|\.([^.]+)$|_0.$1|;
$n = 0;
while (-f $filename) {
$filename =~ s|\_[0-9]+\.([^.]*)$|_${n}.$1|;
Dprint "FileName: $filename";
$n += 1;
}
}
} else {
srand();
$rndn = int(rand(0xFFFF));
$rndbase = "YT" . time() . "-" . $rndn;
$filename = "$rndbase.${cntnr}";
}
最後のifは使っていませんが、コードとしては残してあります。昨年後半に行われた仕様変更により、一部のビデオが対応したと言われるメソッドです。"t"パラメータ("&t=")であるトークンを取得する方法は"get_video_info"によるものと、"api2_rest"によるものの2種類あり、片方をコメントアウトしてあります。
} elsif (0) {
if ($raw =~ m|\'/watch_popup\?v=(.*)\'\;$|) {
$id = $1;
} else {
$ytpid =~ m|http://[^/\.]youtube.com/watch?v=(.*)$"|;
$id = $1;
}
Dprint "ID: $id";
#http://www.youtube.com/get_video_info?&video_id=8-_9n5DtKOc
#$tvarurl = "http://www.youtube.com/get_video_info?&video_id=${id}";
#http://www.youtube.com/api2_rest?method=youtube.videos.get_video_token&video_id=8-_9n5DtKOc
$tvarurl = "http://www.youtube.com/api2_rest?method=youtube.videos.get_video_token&video_id=${id}";
Dprint "wget -q -O - \'$tvarurl\'";
open(IN, "wget -q -O - \'$tvarurl\' |") or die("wget error\n");
while (<IN>) {
#if ($_ =~ m|&token=([^&]*)|) {
if ($_ =~ m|<t>([^<>]*)</t>|) {
$tvar = $1;
last;
}
}
close(IN);
if (!defined($tvar)) {
Dprint "Can't get t variable.";
exit(1);
}
$VFormat = 5; # 5 18 22 35
Dprint "t variable = $tvar";
$url = "http://www.youtube.com/get_video?video_id=${id}&t=${tvar}&el=detailpage&ps=&fmt=$VFormat&noflv=1";
srand();
$rndn = int(rand(0xFFFF));
$rndbase = "YT" . time() . "-" . $rndn;
$filename = $rndbase . ".flv";
}
最後に実際のストリームを取得します。
$GetCmd = "wget $FlagQuiet -O \"$filename\" \'$url\'";
Dprint "$GetCmd\n";
`$GetCmd`;
$ret = ($?>>8);
if ($ret != 0) {
print STDERR "wget returns error for $filename $url\n";
unlink($filename);
}
exit $ret;
なお、ファイルの選択ですが、基本的に下記"Spriority"の順番に高画質なのですが、22は大き過ぎてダウンロードに時間が掛かる上に再生も重いので、特に希望がなければ取らないようにしています。
#--- Select the suitable type.
sub TypeSelect {
my $t, @Spriority = (22, 35, 18, 6, 34, 5);
foreach $t (@Spriority) {
# Dprint "TypeSelect: ($t) exists($vphash{$t})";
if (exists($vphash{$t})) {
if (!($t == 22 && !$SupportHD)) {
return $t;
}
}
}
return -1;
}
帯域の制御
実際にダウンロードしてみると、ダウンロード開始時に500kbpsくらいのスピードで始まるのですが、30秒くらいで徐々に遅くなり始め、100kbps〜50kbpsくらいまで落ちて行きます。以前は無制御(一定のスピード)でダウンロードできていたので、帯域制御をサーバ側でするようになったと思われます。
というのも、これが問題になるのはダウンロードのときだけで、ストリーミングの場合は支障ありません。ストリーミング視聴であれば、最初にバッファをある程度一杯にしないとならないので高速で送り、バッファが満たされれば、後はビデオストリームのビットレート程度のスピードで送れば充分ということになるからです。この場合、クライアント側のブラウザは実時間(10分のビデオなら10分間)サーバに接続状態になってしまうのはサーバ負荷ともなってしまうとも考えられます。しかし、実質的にブラウザ側でバッファ充足度による伝送速度のフィードバック制御を行っていますので、殆どのクライアントは最後まで速く送ろうとしても受け取れません。このため、今回のサーバ側の帯域制御はこれで仕方ないことになります。