Skip to main content

bpi_rs/video/info/
view.rs

1//! 获取视频详细信息 (Web端)
2//!
3//! [查看 API 文档](https://github.com/SocialSisterYi/bilibili-API-collect/tree/master/docs/video)
4
5use crate::models::Account;
6use serde::{Deserialize, Serialize};
7
8/// 视频分辨率信息
9#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct Dimension {
11    pub width: u32,
12    pub height: u32,
13    pub rotate: u8,
14}
15
16/// 视频状态统计信息
17#[derive(Debug, Clone, Serialize, Deserialize)]
18pub struct Stat {
19    pub aid: u64,
20    pub view: u64,
21    pub danmaku: u64,
22    pub reply: u64,
23    #[serde(rename = "favorite")]
24    pub favorite: Option<u64>,
25    #[serde(rename = "fav")]
26    pub fav: Option<u64>,
27    pub coin: u64,
28    pub share: u64,
29    pub now_rank: i64,
30    pub his_rank: i64,
31    pub like: u64,
32    pub dislike: u64,
33    pub evaluation: String,
34    #[serde(rename = "argue_msg")]
35    pub argue_msg: Option<String>,
36    pub vt: u64,
37    pub vv: Option<u64>,
38}
39
40/// 视频争议/警告信息
41#[derive(Debug, Clone, Serialize, Deserialize)]
42pub struct ArgueInfo {
43    pub argue_link: String,
44    pub argue_msg: String,
45    pub argue_type: i32,
46}
47
48/// UP主信息
49#[derive(Debug, Clone, Serialize, Deserialize)]
50pub struct Owner {
51    pub mid: u64,
52    pub name: String,
53    pub face: String,
54}
55
56/// rights对象
57#[derive(Debug, Clone, Serialize, Deserialize)]
58pub struct Rights {
59    pub bp: u8,
60    pub elec: u8,
61    pub download: u8,
62    pub movie: u8,
63    pub pay: u8,
64    pub hd5: u8,
65    pub no_reprint: u8,
66    pub autoplay: u8,
67    pub ugc_pay: u8,
68    pub is_cooperation: u8,
69    pub ugc_pay_preview: u8,
70    pub no_background: Option<u8>,
71    pub clean_mode: Option<u8>,
72    pub is_stein_gate: Option<u8>,
73    pub arc_pay: u8,
74    pub free_watch: u8,
75    // 新增字段
76    pub is_360: Option<u8>,
77    pub no_share: Option<u8>,
78}
79
80/// 视频每P信息
81#[derive(Debug, Clone, Serialize, Deserialize)]
82pub struct Page {
83    pub cid: u64,
84    pub page: u32,
85    pub from: String,
86    pub part: String,
87    pub duration: u64,
88    pub vid: String,
89    pub weblink: String,
90    pub dimension: Dimension,
91    pub first_frame: Option<String>,
92    pub ctime: Option<u64>,
93}
94
95/// 字幕上传者信息
96///
97/// 字幕列表
98#[derive(Debug, Clone, Serialize, Deserialize)]
99pub struct SubtitleListItem {
100    pub id: u64,
101    pub lan: String,
102    pub lan_doc: String,
103    pub is_lock: bool,
104    pub subtitle_url: String,
105    #[serde(rename = "type")]
106    pub sub_type: u8,
107    pub id_str: String,
108    pub ai_type: u8,
109    pub ai_status: u8,
110    pub author: Account,
111}
112
113/// 字幕信息
114#[derive(Debug, Clone, Serialize, Deserialize)]
115pub struct Subtitle {
116    pub allow_submit: bool,
117    pub list: Vec<SubtitleListItem>,
118}
119
120/// staff成员大会员状态
121#[derive(Debug, Clone, Serialize, Deserialize)]
122pub struct StaffVip {
123    #[serde(rename = "type")]
124    pub type_: u8,
125    pub status: u8,
126    pub due_date: u64,
127    pub vip_pay_type: u8,
128    pub theme_type: u8,
129    pub label: serde_json::Value,
130}
131
132/// staff成员认证信息
133#[derive(Debug, Clone, Serialize, Deserialize)]
134pub struct StaffOfficial {
135    pub role: i32,
136    pub title: String,
137    pub desc: String,
138    #[serde(rename = "type")]
139    pub type_: i32,
140}
141
142/// staff成员信息
143#[derive(Debug, Clone, Serialize, Deserialize)]
144pub struct StaffItem {
145    pub mid: u64,
146    pub title: String,
147    pub name: String,
148    pub face: String,
149    pub vip: StaffVip,
150    pub official: StaffOfficial,
151    pub follower: u64,
152    pub label_style: u8,
153}
154
155/// honor_reply信息
156#[derive(Debug, Clone, Serialize, Deserialize)]
157pub struct HonorItem {
158    pub aid: u64,
159    pub hover_type: u8,
160    pub desc: String,
161    pub weekly_recommend_num: u64,
162}
163
164#[derive(Debug, Clone, Serialize, Deserialize)]
165pub struct HonorReply {
166    pub honor: Vec<HonorItem>,
167}
168
169/// 用户装扮信息
170#[derive(Debug, Clone, Serialize, Deserialize)]
171pub struct UserGarb {
172    pub url_image_ani_cut: String,
173}
174
175/// ugc_season中的episodes
176#[derive(Debug, Clone, Serialize, Deserialize)]
177pub struct EpisodeArc {
178    pub aid: u64,
179    pub videos: u32,
180    pub type_id: u32,
181    pub type_name: String,
182    pub copyright: u8,
183    pub pic: String,
184    pub title: String,
185    pub pubdate: u64,
186    pub ctime: u64,
187    pub desc: String,
188    pub state: u8,
189    pub duration: u64,
190    pub rights: Rights,
191    pub author: Owner,
192    pub stat: Stat,
193    pub dynamic: String,
194    pub dimension: Dimension,
195    pub desc_v2: serde_json::Value,
196    pub is_chargeable_season: bool,
197    pub is_blooper: bool,
198    pub enable_vt: u8,
199    pub vt_display: String,
200    pub type_id_v2: u32,
201    pub type_name_v2: String,
202    pub is_lesson_video: u8,
203}
204
205/// ugc_season中的section中的episodes
206#[derive(Debug, Clone, Serialize, Deserialize)]
207pub struct SectionEpisode {
208    pub season_id: u64,
209    pub section_id: u64,
210    pub id: u64,
211    pub aid: u64,
212    pub cid: u64,
213    pub title: String,
214    pub attribute: u32,
215    pub arc: EpisodeArc,
216    pub page: Page,
217    pub bvid: String,
218    pub pages: Vec<Page>,
219}
220
221/// ugc_season section
222#[derive(Debug, Clone, Serialize, Deserialize)]
223pub struct Section {
224    pub season_id: u64,
225    pub id: u64,
226    pub title: String,
227    #[serde(rename = "type")]
228    pub type_: u8,
229    pub episodes: Vec<SectionEpisode>,
230}
231
232/// ugc_season
233#[derive(Debug, Clone, Serialize, Deserialize)]
234pub struct UgcSeasonStat {
235    pub season_id: u64,
236    pub view: u64,
237    pub danmaku: u64,
238    pub reply: u64,
239    pub fav: u64,
240    pub coin: u64,
241    pub share: u64,
242    pub now_rank: i64,
243    pub his_rank: i64,
244    pub like: u64,
245    pub vt: u64,
246    pub vv: u64,
247}
248
249#[derive(Debug, Clone, Serialize, Deserialize)]
250pub struct UgcSeason {
251    pub id: u64,
252    pub title: String,
253    pub cover: String,
254    pub mid: u64,
255    pub intro: String,
256    pub sign_state: u8,
257    pub attribute: u32,
258    pub sections: Vec<Section>,
259    pub stat: UgcSeasonStat,
260    pub ep_count: u32,
261    pub season_type: u8,
262    pub is_pay_season: bool,
263    pub enable_vt: u8,
264}
265
266/// video data完整结构体
267#[derive(Debug, Clone, Serialize, Deserialize)]
268pub struct VideoData {
269    pub aid: u64,
270    pub bvid: String,
271    pub videos: u32,
272    pub tid: u32,
273    pub tid_v2: u32,
274    pub tname: String,
275    pub tname_v2: String,
276    pub copyright: u8,
277    pub pic: String,
278    pub title: String,
279    pub pubdate: u64,
280    pub ctime: u64,
281    pub desc: String,
282    pub desc_v2: Option<Vec<serde_json::Value>>,
283    pub state: u32,
284    pub duration: u64, // 单位为秒
285    #[serde(default = "default_forward")] // 仅撞车视频存在此字段
286    pub forward: u64,
287    pub mission_id: Option<u64>,
288    #[serde(default = "default_redirect_url")] // 用于番剧&影视的av/bv->ep
289    pub redirect_url: String,
290    pub rights: Rights,
291    pub owner: Owner,
292    pub stat: Stat,
293    pub argue_info: ArgueInfo,
294    pub dynamic: String,
295    pub cid: u64,
296    pub dimension: Dimension,
297    pub premiere: serde_json::Value,
298    pub teenage_mode: u8,
299    pub is_chargeable_season: bool,
300    pub is_story: bool,
301    pub is_upower_exclusive: bool,
302    pub is_upower_play: bool,
303    pub is_upower_preview: bool,
304    pub no_cache: bool,
305    pub pages: Vec<Page>,
306    pub subtitle: Subtitle,
307    pub ugc_season: Option<UgcSeason>, // 不在合集中的视频无此项
308    #[serde(default = "default_staff")]
309    pub staff: Vec<StaffItem>, // 非合作视频无此项
310    pub is_season_display: bool,
311    pub user_garb: UserGarb,
312    pub honor_reply: serde_json::Value,
313    pub like_icon: String,
314    pub need_jump_bv: bool,
315    pub disable_show_up_info: bool,
316    pub is_story_play: u32,
317    pub is_view_self: bool,
318    pub is_upower_exclusive_with_qa: bool,
319}
320
321fn default_forward() -> u64 {
322    0
323}
324
325fn default_redirect_url() -> String {
326    String::new()
327}
328
329fn default_staff() -> Vec<StaffItem> {
330    let v: Vec<StaffItem> = Vec::new();
331    v
332}
333
334#[cfg(test)]
335mod tests {
336    use crate::ids::Aid;
337    use crate::video::params::VideoViewParams;
338    use crate::{BpiClient, BpiError};
339
340    #[ignore = "legacy live API test; requires explicit BPI_LIVE_TEST review"]
341    #[tokio::test]
342    async fn test_video_info() -> Result<(), BpiError> {
343        let bpi = BpiClient::new().expect("client should build");
344
345        let data = bpi
346            .video()
347            .view(VideoViewParams::from_aid(Aid::new(10001)?))
348            .await?;
349
350        assert!(!data.title.is_empty());
351
352        Ok(())
353    }
354}