bpi_rs/bangumi/
videostream_url.rs

1//! 视频流URL
2//!
3//! https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/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/SocialSisterYi/bilibili-API-collect/blob/master/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.get("https://api.bilibili.com/pgc/player/web/playurl")
90            .with_bilibili_headers()
91            .query(&params)
92            .send_bpi("获取番剧视频流URL")
93            .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/SocialSisterYi/bilibili-API-collect/blob/master/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)
114            .await
115    }
116
117    /// 获取番剧视频流 URL
118    ///
119    /// # 参数
120    /// | 名称    | 类型   | 说明                                |
121    /// | ------- | ------ | ----------------------------------- |
122    /// | `cid`   | u64 | 视频 cid(|
123    /// | `qn`    | u32    | 视频清晰度选择                       |
124    /// | `fnval` | u32    | 视频获取方式选择                     |
125    ///
126    /// # 文档
127    /// [获取番剧视频流URL](https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/bangumi/videostream_url.md#获取番剧视频流url)
128    pub async fn bangumi_video_stream_by_cid(
129        &self,
130        cid: u64,
131        qn: Option<VideoQuality>,
132        fnval: Option<Fnval>,
133    ) -> Result<BpiResponse<BangumiVideoStreamData>, BpiError> {
134        self.bangumi_video_stream(None, Some(cid), qn, fnval).await
135    }
136}
137
138#[cfg(test)]
139mod tests {
140    use super::*;
141
142    const TEST_EP_ID: u64 = 10001; // epid
143    const TEST_CID: u64 = 772096113;
144
145    #[tokio::test]
146    async fn test_bangumi_video_stream_url_simple() -> Result<(), Box<BpiError>> {
147        let bpi = BpiClient::new();
148        let result = bpi
149            .bangumi_video_stream_by_epid(
150                TEST_EP_ID,
151                Some(VideoQuality::P8K),
152                Some(
153                    Fnval::DASH
154                        | Fnval::FOURK
155                        | Fnval::EIGHTK
156                        | Fnval::HDR
157                        | Fnval::DOLBY_AUDIO
158                        | Fnval::DOLBY_VISION
159                        | Fnval::AV1,
160                ),
161            )
162            .await?;
163
164        let data = result.into_data()?;
165        tracing::info!(
166            "==========最佳格式==========\n{:#?}",
167            data.base.best_format()
168        );
169        tracing::info!(
170            "==========最佳视频==========\n{:#?}",
171            data.base.best_video()
172        );
173
174        assert!(data.base.timelength.unwrap() > 0);
175        assert!(!data.base.accept_format.is_empty());
176        assert!(!data.base.accept_quality.is_empty());
177
178        Ok(())
179    }
180
181    #[tokio::test]
182    async fn test_bangumi_video_stream_url_by_cid() -> Result<(), Box<BpiError>> {
183        let bpi = BpiClient::new();
184        let result = bpi
185            .bangumi_video_stream_by_cid(TEST_CID, Some(VideoQuality::P480), Some(Fnval::DASH))
186            .await?;
187        let data = result.into_data()?;
188        tracing::info!("{:#?}", data);
189        Ok(())
190    }
191
192    #[tokio::test]
193    async fn test_bangumi_video_stream_url_no_params() {
194        let bpi = BpiClient::new();
195        let result = bpi.bangumi_video_stream(None, None, None, None).await;
196        assert!(result.is_err());
197        let error = result.unwrap_err();
198        match error {
199            BpiError::InvalidParameter { field, message } => {
200                assert_eq!(field, "ep_id/cid");
201                assert_eq!(message, "ep_id和cid必须提供其中一个");
202            }
203            _ => panic!("Expected InvalidParameter error"),
204        }
205    }
206}