From c2c732093deab0a10a5444dafff7859537441305 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=B4=80=E1=B4=8D=E1=B4=9B=E1=B4=8F=E1=B4=80=E1=B4=87?= =?UTF-8?q?=CA=80?= Date: Fri, 26 Dec 2025 14:24:52 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E6=9F=90=E4=BA=9B?= =?UTF-8?q?=E8=A7=86=E9=A2=91=E4=B8=8B=E8=BD=BD=E6=8F=90=E7=A4=BA=20404=20?= =?UTF-8?q?not=20found=20=E7=9A=84=E9=97=AE=E9=A2=98=20(#579)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/bili_sync/src/bilibili/analyzer.rs | 2 +- crates/bili_sync/src/downloader.rs | 56 ++++++++++++++++++++++- 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/crates/bili_sync/src/bilibili/analyzer.rs b/crates/bili_sync/src/bilibili/analyzer.rs index e2d7548..b6d7f2f 100644 --- a/crates/bili_sync/src/bilibili/analyzer.rs +++ b/crates/bili_sync/src/bilibili/analyzer.rs @@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize}; use crate::bilibili::error::BiliError; pub struct PageAnalyzer { - info: serde_json::Value, + pub(crate) info: serde_json::Value, } #[derive(Debug, strum::FromRepr, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Clone)] diff --git a/crates/bili_sync/src/downloader.rs b/crates/bili_sync/src/downloader.rs index 762277c..606c0c0 100644 --- a/crates/bili_sync/src/downloader.rs +++ b/crates/bili_sync/src/downloader.rs @@ -169,9 +169,11 @@ impl Downloader { concurrent_download: &ConcurrentDownloadLimit, ) -> Result<()> { let (concurrency, threshold) = (concurrent_download.concurrency, concurrent_download.threshold); + // 有些 B 站视频 url GET 有内容但 HEAD 会返回 404,此处使用 bytes=0-0 的 GET 代替 HEAD 以获取文件大小 let resp = self .client - .request(Method::HEAD, url, None) + .request(Method::GET, url, None) + .header(header::RANGE, "bytes=0-0") .send() .await? .error_for_status()?; @@ -247,3 +249,55 @@ impl ResponseExt for reqwest::Response { .and_then(|s| s.parse::().ok()) } } + +#[cfg(test)] +mod tests { + use std::path::Path; + + use anyhow::Result; + + use crate::bilibili::{BestStream, BiliClient, Video}; + use crate::config::VersionedConfig; + use crate::database::setup_database; + use crate::downloader::Downloader; + + #[ignore = "only for manual test"] + #[tokio::test] + async fn test_parse_and_download_video() -> Result<()> { + VersionedConfig::init_for_test(&setup_database(Path::new("./test.sqlite")).await?).await?; + let config = VersionedConfig::get().read(); + let client = BiliClient::new(); + let video = Video::new(&client, "BV14oCrBqEd2".to_owned(), &config.credential); + let pages = video.get_pages().await.expect("failed to get pages"); + let first_page = pages.into_iter().next().expect("no page found"); + let mut page_analyzer = video + .get_page_analyzer(&first_page) + .await + .expect("failed to get page analyzer"); + let json_info = serde_json::to_string_pretty(&page_analyzer.info)?; + tokio::fs::write("./debug_playurl.json", json_info).await?; + let best_stream = page_analyzer + .best_stream(&config.filter_option) + .expect("failed to get best stream"); + let BestStream::VideoAudio { + video, + audio: Some(audio), + } = best_stream + else { + panic!("best stream is not video & audio"); + }; + dbg!(&video); + dbg!(&audio); + let downloader = Downloader::new(client.client); + downloader + .multi_fetch_and_merge( + &video.urls(true), + &audio.urls(true), + Path::new("./output.mp4"), + &config.concurrent_limit.download, + ) + .await + .expect("failed to download video"); + Ok(()) + } +}