Skip to main content

bpi_rs/bangumi/
videostream_url.rs

1//! 视频流URL
2//!
3//! [查看 API 文档](https://github.com/Yuelioi/bilibili-API-collect/tree/cfc5fddcc8a94b74d91970bb5b4eaeb349addc47/docs/bangumi/videostream_url.md)
4use crate::models::{ Fnval, VideoQuality };
5use crate::{ BilibiliRequest, BpiClient, BpiError, BpiResponse };
6use serde::{ Deserialize, Serialize };
7
8/// 番剧视频流响应
9#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct BangumiVideoStreamData {
11    #[serde(flatten)]
12    pub base: crate::models::VideoStreamData,
13
14    /// 响应码
15    pub code: u32,
16    /// fnver参数
17    pub fnver: u32,
18    /// 是否为视频项目
19    pub video_project: bool,
20    /// 数据类型
21    pub r#type: String,
22    /// bp参数
23    pub bp: u32,
24    /// VIP类型
25    pub vip_type: Option<u32>,
26    /// VIP状态
27    pub vip_status: Option<u32>,
28    /// 是否为DRM
29    pub is_drm: bool,
30    /// 是否重编码
31    pub no_rexcode: u32,
32    /// 记录信息
33    pub record_info: Option<BangumiRecordInfo>,
34}
35
36#[derive(Debug, Clone, Serialize, Deserialize)]
37pub struct BangumiRecordInfo {
38    pub record_icon: String,
39    pub record: String,
40}
41
42impl BpiClient {
43    /// 获取番剧视频流 URL
44    ///
45    /// # 参数
46    /// | 名称    | 类型   | 说明                                |
47    /// | ------- | ------ | ----------------------------------- |
48    /// | `ep_id` | `Option<u64>`    | 稿件 epid                           |
49    /// | `cid`   | `Option<u64>` | 视频 cid(可选,与 ep_id 二选一) |
50    /// | `qn`    | u32    | 视频清晰度选择                       |
51    /// | `fnval` | u32    | 视频获取方式选择                     |
52    ///
53    /// # 文档
54    /// [获取番剧视频流URL](https://github.com/Yuelioi/bilibili-API-collect/tree/cfc5fddcc8a94b74d91970bb5b4eaeb349addc47/docs/bangumi/videostream_url.md#获取番剧视频流url)
55    pub async fn bangumi_video_stream(
56        &self,
57        ep_id: Option<u64>,
58        cid: Option<u64>,
59        qn: Option<VideoQuality>,
60        fnval: Option<Fnval>
61    ) -> Result<BpiResponse<BangumiVideoStreamData>, BpiError> {
62        // 验证参数
63        if ep_id.is_none() && cid.is_none() {
64            return Err(BpiError::InvalidParameter {
65                field: "ep_id/cid",
66                message: "ep_id和cid必须提供其中一个",
67            });
68        }
69
70        let mut params = vec![("fnver", "0".to_string())];
71
72        if fnval.is_some_and(|f| f.is_fourk()) {
73            params.push(("fourk", "1".to_string()));
74        }
75
76        if let Some(ep) = ep_id {
77            params.push(("ep_id", ep.to_string()));
78        }
79        if let Some(c) = cid {
80            params.push(("cid", c.to_string()));
81        }
82        if let Some(q) = qn {
83            params.push(("qn", q.as_u32().to_string()));
84        }
85        if let Some(fv) = fnval {
86            params.push(("fnval", fv.bits().to_string()));
87        }
88
89        self
90            .get("https://api.bilibili.com/pgc/player/web/playurl")
91            .with_bilibili_headers()
92            .query(&params)
93            .send_bpi("获取番剧视频流URL").await
94    }
95
96    /// 获取番剧视频流 URL
97    ///
98    /// # 参数
99    /// | 名称    | 类型   | 说明                                |
100    /// | ------- | ------ | ----------------------------------- |
101    /// | `ep_id` | u64    | 稿件 epid                           |
102    /// | `qn`    | u32    | 视频清晰度选择                       |
103    /// | `fnval` | u32    | 视频获取方式选择                     |
104    ///
105    /// # 文档
106    /// [获取番剧视频流URL](https://github.com/Yuelioi/bilibili-API-collect/tree/cfc5fddcc8a94b74d91970bb5b4eaeb349addc47/docs/bangumi/videostream_url.md#获取番剧视频流url)
107    pub async fn bangumi_video_stream_by_epid(
108        &self,
109        ep_id: u64,
110        qn: Option<VideoQuality>,
111        fnval: Option<Fnval>
112    ) -> Result<BpiResponse<BangumiVideoStreamData>, BpiError> {
113        self.bangumi_video_stream(Some(ep_id), None, qn, fnval).await
114    }
115
116    /// 获取番剧视频流 URL
117    ///
118    /// # 参数
119    /// | 名称    | 类型   | 说明                                |
120    /// | ------- | ------ | ----------------------------------- |
121    /// | `cid`   | u64 | 视频 cid(|
122    /// | `qn`    | u32    | 视频清晰度选择                       |
123    /// | `fnval` | u32    | 视频获取方式选择                     |
124    ///
125    /// # 文档
126    /// [获取番剧视频流URL](https://github.com/Yuelioi/bilibili-API-collect/tree/cfc5fddcc8a94b74d91970bb5b4eaeb349addc47/docs/bangumi/videostream_url.md#获取番剧视频流url)
127    pub async fn bangumi_video_stream_by_cid(
128        &self,
129        cid: u64,
130        qn: Option<VideoQuality>,
131        fnval: Option<Fnval>
132    ) -> Result<BpiResponse<BangumiVideoStreamData>, BpiError> {
133        self.bangumi_video_stream(None, Some(cid), qn, fnval).await
134    }
135}
136
137#[cfg(test)]
138mod tests {
139    use super::*;
140
141    const TEST_EP_ID: u64 = 10001; // epid
142    const TEST_CID: u64 = 772096113;
143
144    #[tokio::test]
145    async fn test_bangumi_video_stream_url_simple() -> Result<(), Box<BpiError>> {
146        let bpi = BpiClient::new();
147        let result = bpi.bangumi_video_stream_by_epid(
148            TEST_EP_ID,
149            Some(VideoQuality::P8K),
150            Some(
151                Fnval::DASH |
152                    Fnval::FOURK |
153                    Fnval::EIGHTK |
154                    Fnval::HDR |
155                    Fnval::DOLBY_AUDIO |
156                    Fnval::DOLBY_VISION |
157                    Fnval::AV1
158            )
159        ).await?;
160
161        let data = result.into_data()?;
162        tracing::info!("==========最佳格式==========\n{:#?}", data.base.best_format());
163        tracing::info!("==========最佳视频==========\n{:#?}", data.base.best_video());
164
165        assert!(data.base.timelength.unwrap() > 0);
166        assert!(!data.base.accept_format.is_empty());
167        assert!(!data.base.accept_quality.is_empty());
168
169        Ok(())
170    }
171
172    #[tokio::test]
173    async fn test_bangumi_video_stream_url_by_cid() -> Result<(), Box<BpiError>> {
174        let bpi = BpiClient::new();
175        let result = bpi.bangumi_video_stream_by_cid(
176            TEST_CID,
177            Some(VideoQuality::P480),
178            Some(Fnval::DASH)
179        ).await?;
180        let data = result.into_data()?;
181        tracing::info!("{:#?}", data);
182        Ok(())
183    }
184
185    #[tokio::test]
186    async fn test_bangumi_video_stream_url_no_params() {
187        let bpi = BpiClient::new();
188        let result = bpi.bangumi_video_stream(None, None, None, None).await;
189        assert!(result.is_err());
190        let error = result.unwrap_err();
191        match error {
192            BpiError::InvalidParameter { field, message } => {
193                assert_eq!(field, "ep_id/cid");
194                assert_eq!(message, "ep_id和cid必须提供其中一个");
195            }
196            _ => panic!("Expected InvalidParameter error"),
197        }
198    }
199}