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