bilibiliの動画をyt-dlpでダウンロードしていると、特定のCDNから配信される場合だけ、ダウンロード途中で速度が極端に低下したり、The read operation timed outが発生したりすることがあった。
Got error: Downloaded 87390979 bytes, expected 195457749 bytes. Retrying (1/10)...とか続いて、結局失敗する。
-vで詳細なログを出しながら何度か試しているうちに、少し不思議な挙動が見えてきた。
結論から言うと今回の現象は、bilibili側のCDNアクセス時に一部データの遅延によりエラー発生、という可能性が高そう。一度動画を最後まで取得すればその後のダウンロードは安定する。
ただしCDN内部のキャッシュ状態は外部から直接確認できないため、これはあくまで観測結果からの推測となります。
発生した現象:Got error. Retrying (1/10)…
yt-dlpで動画をダウンロードしていると、以下のようなログが出ることがあった。
Got error: Downloaded 87390979 bytes, expected 195457749 bytes. Retrying (1/10)...
つまり、約195MBのデータを受信するはずだったHTTPリクエストが、約87MB受信したところで途中終了している。
その後、何度かリトライされるも、結局完全なファイルとはならずに終了。
CDNによって挙動が違う
興味深かったのは、同じ動画でも配信されるCDNによって挙動が違ったこと。
今回問題が発生したのは、
upos-sz-mirrorcosov.bilivideo.com
一方、別のCDNである、
upos-hz-mirrorakam.akamaized.net
から配信された場合は、ダウンロード速度は遅いものの、途中で止まることなく最後まで完走した。
つまり、
bilivideo.com の場合、
速度は比較的速い
↓
途中で速度低下・タイムアウト
↓
リトライ
akamaized.net の場合、
速度は遅い
↓
しかし安定して完走
という違いがあった。
この時点で、動画ファイルそのものの破損や、yt-dlpの基本的なダウンロード処理が原因である可能性は低そうだと考えた。
socket-timeout を延長してみる
yt-dlpにはソケット通信のタイムアウトを指定する--socket-timeoutオプションがある。
そこで、タイムアウトをすこし長くして試してみた。
yt-dlp --socket-timeout 60 URL
すると、約27~30%付近で止まっていたダウンロードが、少し先まで進むようになった。
約27.9%
↓
タイムアウト
タイムアウト時間を延長(--socket-timeout設定)
↓
さらに先まで進む
やはり、動画ファイルのデータが特定の場所で壊れているわけではなさそう。
まぁこのような状況が発生するのは1つだけじゃないからね、それはそう。
http-chunk-size を小さくしてみる
次に、HTTPダウンロードのチャンクサイズを小さくして試した。
例えば、
yt-dlp --socket-timeout 60 --http-chunk-size 5M URL
のような設定。
すると、さらにダウンロードが先まで進むようになった。
最終的には、
27.9%付近で停止
↓
44.7%付近で停止
↓
86.4%付近で停止
↓
93%付近、速度低下しながらも・・・
↓
100% 完走
という状態になった。
最初は27~30%付近でおかしくなっていたものが、設定を変更すると44%、86%、93%と、徐々に先へ進んでいった。
つまり、その時々で通信が詰まったりしている?けれど一応最後までダウンロードはできる、ということ。
一度100%までダウンロードすると、次はスムーズに
そして今回の検証で最も重要だったのがここ。
まず、タイムアウト時間を延長し、HTTPチャンクサイズを小さくすることで、何度か詰まりながらも最終的に100%までダウンロードした。
そして、その直後に同じURLを、今度はオプションを付けずに再度ダウンロードしてみた。
結果は、
最初から最後までスムーズにダウンロード完了!
bilivideo.comだったのにも関わらず。
この結果から、
一度100%まで取得したことでCDN側のキャッシュ状態が変化し、2回目以降のダウンロードではデータが安定して配信されるようになった、のではないかと思う。
一定のサイズで通信に制限をかけているわけでもなかった。
初回アクセス
↓
bilivideo CDN
キャッシュに存在しないデータ
↓
originやバックエンドから取得
↓
一部のデータ範囲で取得に時間がかかる
↓
HTTPレスポンスが遅延
タイムアウトしながらもリトライ
↓
徐々にデータ取得
↓
100%まで完走
↓
(CDN側のキャッシュ状態が改善した可能性)
2回目のアクセス
↓
キャッシュから安定して配信
まとめ
ということで CDNのキャッシュが問題 なのだと思う。多分。
(所詮素人なので勝手な推測ですが)
そしてそれがわかったところでユーザー側にできることはあまりないかもしれない。
ただタイムアウトを長くしたり、チャンクサイズを小さくしたりすることで、途中で失敗せず最後まで取得できる可能性を高めることはできるかもしれない。
そんなことを思ったので簡単にまとめてみました。
重ねて言いますが、あくまでもこれは今回観測した環境での結果、推測であり、すべての状況に当てはまるとは限らないので悪しからず。
動画と音声を別々にDLして結合する
ここで終わってはそれこそ何の役にも立たない話で終わってしまうので、ffmpegを使って結合するやり方を書いておきます。
yt-dlpでも動画と音声をそれぞれダウンロードし、結合しています。
が、不完全なデータを無理やり結合しても不完全なファイルになるわけで、またダウンロードし直して・・・なんてやってたらいつまで経っても終わらない。
そういう時は-Fオプションでリストを表示、-fオプション(小文字)で指定してまずは個別にダウンロードします。
#一般的には「コマンド、オプション、URL」の順(URLの前)に書きますが、後ろでも問題はありません。その方が入力しやすいので
yt-dlp url -F
出力結果例:
30216 m4a audio only │ ≈ 16.56MiB 66k https │ audio only mp4a.40.2 66k 30232 m4a audio only │ ≈ 21.11MiB 84k https │ audio only mp4a.40.2 84k 30280 m4a audio only │ ≈ 21.11MiB 84k https │ audio only mp4a.40.2 84k 30011 mp4 640x360 30 │ ≈ 34.26MiB 136k https │ hvc1.1.6.L120 136k video only 100022 mp4 640x360 30 │ ≈ 30.42MiB 120k https │ av01.0.08M.08 120k video only 30016 mp4 640x360 30 │ ≈ 48.65MiB 193k https │ avc1.640033 193k video only 30033 mp4 852x480 30 │ ≈ 48.98MiB 194k https │ hvc1.1.6.L120 194k video only 100023 mp4 852x480 30 │ ≈ 43.01MiB 170k https │ av01.0.08M.08 170k video only 30032 mp4 852x480 30 │ ≈ 71.04MiB 281k https │ avc1.640033 281k video only 30066 mp4 1280x720 30 │ ≈ 82.85MiB 328k https │ hvc1.1.6.L120 328k video only 100024 mp4 1280x720 30 │ ≈ 70.91MiB 281k https │ av01.0.08M.08 281k video only 30064 mp4 1280x720 30 │ ≈132.65MiB 525k https │ avc1.640033 525k video only 30077 mp4 1920x1080 30 │ ≈168.07MiB 666k https │ hvc1.1.6.L150 666k video only 100026 mp4 1920x1080 30 │ ≈139.98MiB 554k https │ av01.0.08M.08 554k video only 30080 mp4 1920x1080 30 │ ≈285.27MiB 1130k https │ avc1.640033 1130k video only
そしたら左の数字(ID)を指定して個別にDL
#動画1080p av01コーデックを指定
yt-dlp url -f 100026
#音声を指定
yt-dlp url -f 30280
#ちなみに一緒にDLする時は
yt-dlp url -f 100026+30280
それぞれ、.mp4、.m4aファイルが出来上がります。
仮に test.mp4、test.m4a だとすると、
ffmpeg -i test.mp4 -i test.m4a -c copy sample.mp4
で劣化なく sample.mp4 を作ってくれます。
(-c copyはそのままコピーして出力ファイルに詰め替えるの意味で、再エンコードしません)
Mac使いの人はAutomatorで シェルスクリプトを実行 に組み込めば、作成された2つのファイルを選んで右クリックから簡単に結合することもできます。


コメント
初めまして。コメント失礼します。
yt-dlpでbilibli動画をmp3で保存したく利用を始めたのですが、ダウンロードされた音声データが途中で切れてしまいます。
また、bilibili.pyをダウンロードしたのですが、使い方がわからない状態です。
そこで以下2点についてご教授を頂きたいです。
・下記のコマンドで問題ないでしょうか。
yt-dlp -x –audio-format mp3 –socket-timeout 60 –http-chunk-size 5M “動画URL”
・bilibili.pyの使い方について(yt-dlpを保存しているフォルダの配下に保存?)
以上です。
初心者質問で大変恐縮ですが、よろしくお願いいたします。
socket-timeout や http-chunk-size はサイズの大きいvideoをDLしたい場合で、音声には必要ないと思います。
音声のみでいいなら音声だけDL(m4aやwebm)、
yt-dlp -f ba "動画URL"互換性などでm4a等をmp3にしたいなら、
yt-dlp -f ba -x --audio-format mp3 "動画URL"bilibili.pyは置き換える必要ありません。最新のyt-dlpにアップデートしてください。(ファイル自体はextractorフォルダの中です)
音声が途切れるのは途中で失敗しているからですか?元ファイルは途切れていませんか?
タイムアウトで途切れるのなら、繰り返しトライし続けるのが現状一番の対応策だと思います。あるいはDLBunnyなどのサイトを使うのも一つの手です。