1use serde::Deserialize;
4use serde::Serialize;
5use serde_json::Value;
6
7use crate::client::BiliClient;
8use crate::error::Error;
9use crate::error::Result;
10use crate::model::param::VideoRequestParamBuilder;
11use crate::model::quality::AudioQuality;
12use crate::model::quality::VideoEncode;
13use crate::model::quality::VideoQuality;
14use crate::url::VIDEO_DOWNLOAD_URL;
15
16#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
17#[serde(rename_all = "camelCase")]
18pub struct PlayUrlResponse {
19 pub code: i64,
20 data: VideoData,
21 pub message: String,
22 pub ttl: i64,
23}
24
25impl PlayUrlResponse {
26 pub async fn new(
27 bili_client: &BiliClient,
28 bvid: &str,
29 ) -> Result<Self> {
30 let vrp = VideoRequestParamBuilder::new(bvid, bili_client)
32 .await?
33 .build(bili_client)
34 .await?;
35 let url = format!("{}?{}", VIDEO_DOWNLOAD_URL, vrp.to_query_string());
36 Ok(bili_client.get(&url).send().await?.json().await?)
38 }
39
40 pub fn get_data(&self) -> Result<&VideoData> {
41 self.valid()?;
42 Ok(&self.data)
43 }
44
45 fn valid(&self) -> Result<()> {
46 match self.code {
47 0 => Ok(()),
48 -101 => Err(Error::Normal("账号未登录".into())),
49 -403 => Err(Error::Normal("权限不足,可能需要大会员".into())),
50 -404 => Err(Error::Normal("视频不存在".into())),
51 -412 => Err(Error::Normal("请求被拦截,请稍后再试".into())),
52 -10403 => Err(Error::Normal("地区限制,无法观看".into())),
53 _ => Err(Error::Normal(format!("未知错误码: {}", self.code))),
54 }
55 }
56}
57
58#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
59#[serde(rename_all = "camelCase")]
60pub struct VideoData {
61 #[serde(rename = "accept_description")]
62 pub accept_description: Vec<String>,
63 #[serde(rename = "accept_format")]
64 pub accept_format: String,
65 #[serde(rename = "accept_quality")]
66 pub accept_quality: Vec<i64>,
67 #[serde(rename = "auto_qn_resp")]
68 pub auto_qn_resp: AutoQnResp,
69 #[serde(rename = "cur_language")]
70 pub cur_language: String,
71 #[serde(rename = "cur_production_type")]
72 pub cur_production_type: i64,
73 pub dash: Dash,
75 pub format: String,
76 pub from: String,
77 #[serde(rename = "high_format")]
78 pub high_format: Value,
79 #[serde(rename = "last_play_cid")]
80 pub last_play_cid: i64,
81 #[serde(rename = "last_play_time")]
82 pub last_play_time: i64,
83 pub message: String,
84 #[serde(rename = "play_conf")]
85 pub play_conf: PlayConf,
86 pub quality: i64,
87 pub result: String,
88 #[serde(rename = "seek_param")]
89 pub seek_param: String,
90 #[serde(rename = "seek_type")]
91 pub seek_type: String,
92 #[serde(rename = "support_formats")]
93 pub support_formats: Vec<SupportFormat>,
94 pub timelength: i64,
95 #[serde(rename = "video_codecid")]
96 pub video_codecid: i64,
97 #[serde(rename = "view_info")]
98 pub view_info: Value,
99}
100
101impl VideoData {
102 pub fn best_video_quality_url(&self) -> Option<&str> {
103 self.dash
104 .video
105 .iter()
106 .filter(|v| self.accept_quality.contains(&v.id))
107 .max_by_key(|v| v.id)
108 .map(|v| v.base_url.as_str())
109 }
110
111 pub fn get_specified_video_url(
112 &self,
113 video_quality: Option<VideoQuality>,
114 video_encode: Option<VideoEncode>,
115 ) -> Option<&str> {
116 if video_quality.is_none() && video_encode.is_none() {
117 return self.best_video_quality_url();
118 }
119
120 if let Some(url) = self
122 .dash
123 .video
124 .iter()
125 .filter(|v| self.accept_quality.contains(&v.id))
126 .filter(|v| video_quality.is_none_or(|vq| v.id == vq as i64))
127 .find(|v| video_encode.is_none_or(|ve| v.codecs.starts_with(ve.as_str())))
128 .map(|v| v.base_url.as_str())
129 {
130 return Some(url);
131 }
132
133 if let Some(vq) = video_quality {
135 if let Some(url) = self
136 .dash
137 .video
138 .iter()
139 .filter(|v| self.accept_quality.contains(&v.id))
140 .filter(|v| v.id == vq as i64)
141 .map(|v| v.base_url.as_str())
142 .next()
143 {
144 return Some(url);
145 }
146
147 if let Some(url) = self
149 .dash
150 .video
151 .iter()
152 .filter(|v| self.accept_quality.contains(&v.id))
153 .filter(|v| v.id <= vq as i64)
154 .max_by_key(|v| v.id)
155 .map(|v| v.base_url.as_str())
156 {
157 return Some(url);
158 }
159 }
160
161 self.best_video_quality_url()
163 }
164
165 pub fn best_audio_url(&self) -> Option<&str> {
166 if let Some(flac) = &self.dash.flac {
167 return Some(&flac.audio.base_url);
168 }
169 self.dash
170 .audio
171 .iter()
172 .max_by_key(|a| a.id)
173 .map(|v| v.base_url.as_str())
174 }
175
176 pub fn get_specified_audio_url(
177 &self,
178 audio_quality: AudioQuality,
179 ) -> Option<&str> {
180 let direct_match = match audio_quality {
181 AudioQuality::HiRes => self
182 .dash
183 .flac
184 .as_ref()
185 .map(|flac| flac.audio.base_url.as_str()),
186 AudioQuality::Dolby => self
187 .dash
188 .dolby
189 .as_ref()
190 .and_then(|db| db.audio.as_ref())
191 .and_then(|audio_vec| audio_vec.first())
192 .map(|a| a.base_url.as_str()),
193 _ => self
194 .dash
195 .audio
196 .iter()
197 .find(|a| a.id == audio_quality as i64)
198 .map(|a| a.base_url.as_str()),
199 };
200
201 direct_match.or_else(|| self.best_audio_url())
203 }
204}
205
206#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
207#[serde(rename_all = "camelCase")]
208pub struct AutoQnResp {
209 pub dyeid: String,
210}
211
212#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
213#[serde(rename_all = "camelCase")]
214pub struct Dash {
215 pub audio: Vec<Audio>,
216 pub dolby: Option<Dolby>,
217 pub duration: i64,
218 pub flac: Option<Flac>,
219 pub min_buffer_time: f64,
220 #[serde(rename = "min_buffer_time")]
221 pub min_buffer_time2: f64,
222 pub video: Vec<Video>,
223}
224
225#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
226#[serde(rename_all = "camelCase")]
227pub struct Audio {
228 #[serde(rename = "SegmentBase")]
229 pub segment_base: SegmentBase,
230 pub backup_url: Vec<String>,
231 #[serde(rename = "backup_url")]
232 pub backup_url2: Vec<String>,
233 pub bandwidth: i64,
234 pub base_url: String,
235 #[serde(rename = "base_url")]
236 pub base_url2: String,
237 pub codecid: i64,
238 pub codecs: String,
239 pub frame_rate: String,
240 #[serde(rename = "frame_rate")]
241 pub frame_rate2: String,
242 pub height: i64,
243 pub id: i64,
244 pub mime_type: String,
245 #[serde(rename = "mime_type")]
246 pub mime_type2: String,
247 pub sar: String,
248 #[serde(rename = "segment_base")]
249 pub segment_base2: SegmentBase2,
250 pub start_with_sap: i64,
251 #[serde(rename = "start_with_sap")]
252 pub start_with_sap2: i64,
253 pub width: i64,
254}
255
256#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
257#[serde(rename_all = "camelCase")]
258pub struct SegmentBase {
259 #[serde(rename = "Initialization")]
260 pub initialization: String,
261 pub index_range: String,
262}
263
264#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
265#[serde(rename_all = "camelCase")]
266pub struct SegmentBase2 {
267 #[serde(rename = "index_range")]
268 pub index_range: String,
269 pub initialization: String,
270}
271
272#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
273#[serde(rename_all = "camelCase")]
274pub struct Dolby {
275 pub audio: Option<Vec<Audio2>>,
276 #[serde(rename = "type")]
277 pub type_field: i64,
278}
279#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
280#[serde(rename_all = "camelCase")]
281pub struct Audio2 {
282 #[serde(rename = "SegmentBase")]
283 pub segment_base: SegmentBase3,
284 pub backup_url: Vec<String>,
285 #[serde(rename = "backup_url")]
286 pub backup_url2: Vec<String>,
287 pub bandwidth: i64,
288 pub base_url: String,
289 #[serde(rename = "base_url")]
290 pub base_url2: String,
291 pub codecid: i64,
292 pub codecs: String,
293 pub frame_rate: String,
294 #[serde(rename = "frame_rate")]
295 pub frame_rate2: String,
296 pub height: i64,
297 pub id: i64,
298 pub mime_type: String,
299 #[serde(rename = "mime_type")]
300 pub mime_type2: String,
301 pub sar: String,
302 #[serde(rename = "segment_base")]
303 pub segment_base2: SegmentBase4,
304 pub start_with_sap: i64,
305 #[serde(rename = "start_with_sap")]
306 pub start_with_sap2: i64,
307 pub width: i64,
308}
309
310#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
311#[serde(rename_all = "camelCase")]
312pub struct Video {
313 #[serde(rename = "SegmentBase")]
314 pub segment_base: SegmentBase3,
315 pub backup_url: Vec<String>,
316 #[serde(rename = "backup_url")]
317 pub backup_url2: Vec<String>,
318 pub bandwidth: i64,
319 pub base_url: String,
320 #[serde(rename = "base_url")]
321 pub base_url2: String,
322 pub codecid: i64,
323 pub codecs: String,
324 pub frame_rate: String,
325 #[serde(rename = "frame_rate")]
326 pub frame_rate2: String,
327 pub height: i64,
328 pub id: i64,
329 pub mime_type: String,
330 #[serde(rename = "mime_type")]
331 pub mime_type2: String,
332 pub sar: String,
333 #[serde(rename = "segment_base")]
334 pub segment_base2: SegmentBase4,
335 pub start_with_sap: i64,
336 #[serde(rename = "start_with_sap")]
337 pub start_with_sap2: i64,
338 pub width: i64,
339}
340
341#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
342#[serde(rename_all = "camelCase")]
343pub struct SegmentBase3 {
344 #[serde(rename = "Initialization")]
345 pub initialization: String,
346 pub index_range: String,
347}
348
349#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
350#[serde(rename_all = "camelCase")]
351pub struct SegmentBase4 {
352 #[serde(rename = "index_range")]
353 pub index_range: String,
354 pub initialization: String,
355}
356
357#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
358#[serde(rename_all = "camelCase")]
359pub struct Flac {
360 pub audio: Audio3,
361 pub display: bool,
362}
363
364#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
365#[serde(rename_all = "camelCase")]
366pub struct Audio3 {
367 #[serde(rename = "SegmentBase")]
368 pub segment_base: SegmentBase5,
369 pub backup_url: Vec<String>,
370 #[serde(rename = "backup_url")]
371 pub backup_url2: Vec<String>,
372 pub bandwidth: i64,
373 pub base_url: String,
374 #[serde(rename = "base_url")]
375 pub base_url2: String,
376 pub codecid: i64,
377 pub codecs: String,
378 pub frame_rate: String,
379 #[serde(rename = "frame_rate")]
380 pub frame_rate2: String,
381 pub height: i64,
382 pub id: i64,
383 pub mime_type: String,
384 #[serde(rename = "mime_type")]
385 pub mime_type2: String,
386 pub sar: String,
387 #[serde(rename = "segment_base")]
388 pub segment_base2: SegmentBase6,
389 pub start_with_sap: i64,
390 #[serde(rename = "start_with_sap")]
391 pub start_with_sap2: i64,
392 pub width: i64,
393}
394
395#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
396#[serde(rename_all = "camelCase")]
397pub struct SegmentBase5 {
398 #[serde(rename = "Initialization")]
399 pub initialization: String,
400 pub index_range: String,
401}
402
403#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
404#[serde(rename_all = "camelCase")]
405pub struct SegmentBase6 {
406 #[serde(rename = "index_range")]
407 pub index_range: String,
408 pub initialization: String,
409}
410
411#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
412#[serde(rename_all = "camelCase")]
413pub struct PlayConf {
414 #[serde(rename = "is_new_description")]
415 pub is_new_description: bool,
416}
417
418#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
419#[serde(rename_all = "camelCase")]
420pub struct SupportFormat {
421 #[serde(rename = "can_watch_qn_reason")]
422 pub can_watch_qn_reason: i64,
423 pub codecs: Vec<String>,
424 #[serde(rename = "display_desc")]
425 pub display_desc: String,
426 pub format: String,
427 #[serde(rename = "limit_watch_reason")]
428 pub limit_watch_reason: i64,
429 #[serde(rename = "new_description")]
430 pub new_description: String,
431 pub quality: i64,
432 pub report: Report,
433 pub superscript: String,
434}
435
436#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
437#[serde(rename_all = "camelCase")]
438pub struct Report {}