bbdown-core 0.1.0

Rust library for resolving Bilibili metadata, download plans, media, subtitles, and danmaku.
Documentation
use serde::{Deserialize, Serialize};

#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ResolvedContent {
    Video(VideoMetadata),
    Season(SeasonResolution),
}

#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct SeasonResolution {
    pub season: SeasonMetadata,
    pub selected_episodes: Vec<EpisodeMetadata>,
}

#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct VideoMetadata {
    pub aid: u64,
    pub bvid: Option<String>,
    pub title: String,
    pub description: String,
    pub cover_url: Option<String>,
    pub pub_time: Option<i64>,
    pub owner: Option<Owner>,
    pub tags: Vec<Tag>,
    pub pages: Vec<PageMetadata>,
}

#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct Owner {
    pub mid: u64,
    pub name: String,
}

#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct PageMetadata {
    pub index: u32,
    pub aid: u64,
    pub cid: u64,
    pub epid: Option<u64>,
    pub title: String,
    pub duration_seconds: Option<u32>,
}

#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct Tag {
    pub id: u64,
    pub name: String,
}

#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct SeasonMetadata {
    pub season_id: Option<u64>,
    pub media_id: Option<u64>,
    pub title: String,
    pub description: String,
    pub cover_url: Option<String>,
    pub main_episode_count: usize,
    pub areas: Vec<String>,
    pub tags: Vec<String>,
    pub episodes: Vec<EpisodeMetadata>,
}

#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct EpisodeMetadata {
    pub index: u32,
    pub aid: u64,
    pub bvid: Option<String>,
    pub cid: u64,
    pub epid: u64,
    pub title: String,
    pub long_title: Option<String>,
    pub pub_time: Option<i64>,
}

#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct DownloadPlan {
    pub title: String,
    pub entries: Vec<DownloadEntry>,
}

#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct DownloadEntry {
    pub index: u32,
    pub aid: u64,
    pub bvid: Option<String>,
    pub cid: u64,
    pub epid: Option<u64>,
    pub title: String,
    pub source: StreamSource,
    pub streams: StreamSet,
    #[serde(default, skip_serializing_if = "StreamDiagnostics::is_empty")]
    pub diagnostics: StreamDiagnostics,
    pub subtitles: Vec<SubtitleTrack>,
    pub danmaku: DanmakuTrack,
}

#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum StreamSource {
    NormalWeb,
    PgcWeb,
    PgcProxy,
    IntlWeb,
}

#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
pub struct StreamDiagnostics {
    pub attempts: Vec<StreamResolverAttempt>,
}

impl StreamDiagnostics {
    #[must_use]
    pub fn is_empty(&self) -> bool {
        self.attempts.is_empty()
    }
}

#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct StreamResolverAttempt {
    pub source: StreamSource,
    pub outcome: StreamResolverOutcome,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub area: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub endpoint: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub message: Option<String>,
}

#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum StreamResolverOutcome {
    Succeeded,
    Failed,
    Skipped,
}

#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct StreamSet {
    pub videos: Vec<MediaStream>,
    pub audios: Vec<MediaStream>,
    pub flv_segments: Vec<FlvSegment>,
    pub accept_quality: Vec<u32>,
    #[serde(default)]
    pub qualities: Vec<StreamQuality>,
    pub duration_seconds: Option<u32>,
}

#[non_exhaustive]
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct StreamQuality {
    pub id: u32,
    pub description: Option<String>,
}

#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct MediaStream {
    pub id: u32,
    pub base_url: String,
    pub backup_urls: Vec<String>,
    pub codecs: Option<String>,
    pub bandwidth: Option<u64>,
    pub width: Option<u32>,
    pub height: Option<u32>,
    pub frame_rate: Option<String>,
    pub mime_type: Option<String>,
    pub size: Option<u64>,
}

#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct FlvSegment {
    pub order: u32,
    pub url: String,
    pub backup_urls: Vec<String>,
    pub size: Option<u64>,
    pub length_ms: Option<u64>,
}

#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct SubtitleTrack {
    pub language: String,
    pub language_doc: Option<String>,
    pub url: String,
    pub format: SubtitleFormat,
}

#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SubtitleFormat {
    Json,
    Ass,
    Unknown,
}

#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct DanmakuTrack {
    pub cid: u64,
    pub xml_url: String,
}