refactor!: refactored response models

doc: documented all public methods
This commit is contained in:
ThetaDev 2022-12-09 01:01:25 +01:00
parent 4c1876cb55
commit f526ab38eb
37 changed files with 600 additions and 255 deletions

View file

@ -3,10 +3,19 @@ use super::{
MusicPlaylistItem, PlaylistItem, TrackItem, VideoItem, YouTubeItem,
};
/// Trait for casting generic YouTube/YouTube music items to a specific kind.
///
/// Returns [`None`] if the item does not match.
pub trait FromYtItem: Sized {
/// Casting from a generic YouTube item to a specific kind
///
/// Returns [`None`] if the item does not match.
fn from_yt_item(_item: YouTubeItem) -> Option<Self> {
None
}
/// Casting from a generic YouTube Music item to a specific kind
///
/// Returns [`None`] if the item does not match.
fn from_ytm_item(_item: MusicItem) -> Option<Self> {
None
}

View file

@ -1,13 +1,13 @@
//! YouTube API request and response models
//! YouTube API response models
mod convert;
mod ordering;
mod paginator;
pub mod paginator;
pub mod richtext;
pub use convert::FromYtItem;
pub use ordering::QualityOrd;
pub use paginator::Paginator;
pub mod traits;
use serde_with::serde_as;
use std::{collections::BTreeSet, ops::Range};
@ -17,7 +17,7 @@ use time::{Date, OffsetDateTime};
use crate::{error::Error, param::Country, serializer::DateYmd, util};
use self::richtext::RichText;
use self::{paginator::Paginator, richtext::RichText};
/*
#COMMON
@ -38,10 +38,36 @@ pub struct Thumbnail {
/// Entities extracted from a YouTube URL
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub enum UrlTarget {
Video { id: String, start_time: u32 },
Channel { id: String },
Playlist { id: String },
Album { id: String },
/// YouTube video
///
/// Example: <youtube.com/watch?v=ZeerrnuLi5E>
Video {
/// Unique YouTube video ID
id: String,
/// Video start time in seconds
start_time: u32,
},
/// YouTube channel
///
/// Example: <https://www.youtube.com/channel/UC2DjFE7Xf11URZqWBigcVOQ>
Channel {
/// Unique YouTube channel ID
id: String,
},
/// YouTube playlist
///
/// Example: <https://www.youtube.com/playlist?list=PLKUA473MWUv2jmkqIxzQR3YL4kuPArj4G>
Playlist {
/// Unique YouTube playlist ID
id: String,
},
/// YouTube Music album
///
/// Example: <https://music.youtube.com/browse/MPREb_nlBWQROfvjo>
Album {
/// Unique YouTube album ID
id: String,
},
}
impl ToString for UrlTarget {
@ -51,10 +77,19 @@ impl ToString for UrlTarget {
}
impl UrlTarget {
/// Convert the URL target to a YouTube URL
///
/// Is equivalent to `url_target.to_string()`
pub fn to_url(&self) -> String {
self.to_url_yt_host("https://www.youtube.com")
}
/// Convert the URL target to a YouTube URL with a specified YouTube host.
///
/// Used to redirect to alternative YouTube frontends like Piped or Invidious.
///
/// **Note:** Music album URL targets are still converted to `music.youtube.com/browse/*`,
/// since these URLs are not supported by Piped or Invidious.
pub fn to_url_yt_host(&self, yt_host: &str) -> String {
match self {
UrlTarget::Video { id, start_time, .. } => match start_time {
@ -73,6 +108,7 @@ impl UrlTarget {
}
}
/// Validate the YouTube ID from the URL target
pub(crate) fn validate(&self) -> Result<(), Error> {
match self {
UrlTarget::Video { id, .. } => {
@ -107,11 +143,6 @@ impl UrlTarget {
#PLAYER
*/
pub trait FileFormat {
/// Get the file extension (".xyz") of the file format
fn extension(&self) -> &str;
}
/// Video player data
#[derive(Clone, Debug, Serialize, Deserialize)]
#[non_exhaustive]
@ -172,13 +203,17 @@ pub struct VideoStream {
pub url: String,
/// YouTube stream format identifier
pub itag: u32,
/// Stream bitrate (in bits/second)
pub bitrate: u32,
/// Average stream bitrate (in bits/second)
pub average_bitrate: u32,
/// Video file size in bytes
pub size: Option<u64>,
/// Index range (used for DASH streaming)
pub index_range: Option<Range<u32>>,
/// Init range (used for DASH streaming)
pub init_range: Option<Range<u32>>,
// Video duration in milliseconds
/// Video duration in milliseconds
pub duration_ms: Option<u32>,
/// Video width in pixels
pub width: u32,
@ -209,13 +244,17 @@ pub struct AudioStream {
pub url: String,
/// YouTube stream format identifier
pub itag: u32,
/// Stream bitrate (in bits/second)
pub bitrate: u32,
/// Average stream bitrate (in bits/second)
pub average_bitrate: u32,
/// Audio file size in bytes
pub size: u64,
/// Index range (used for DASH streaming)
pub index_range: Option<Range<u32>>,
/// Init range (used for DASH streaming)
pub init_range: Option<Range<u32>>,
// Audio duration in milliseconds
/// Audio duration in milliseconds
pub duration_ms: Option<u32>,
/// MIME file type
pub mime: String,
@ -241,94 +280,6 @@ pub struct AudioStream {
pub track: Option<AudioTrack>,
}
pub trait YtStream {
fn url(&self) -> &str;
fn itag(&self) -> u32;
fn bitrate(&self) -> u32;
fn averate_bitrate(&self) -> u32;
fn size(&self) -> Option<u64>;
fn index_range(&self) -> Option<Range<u32>>;
fn init_range(&self) -> Option<Range<u32>>;
fn duration_ms(&self) -> Option<u32>;
fn mime(&self) -> &str;
}
impl YtStream for VideoStream {
fn url(&self) -> &str {
&self.url
}
fn itag(&self) -> u32 {
self.itag
}
fn bitrate(&self) -> u32 {
self.bitrate
}
fn averate_bitrate(&self) -> u32 {
self.average_bitrate
}
fn size(&self) -> Option<u64> {
self.size
}
fn index_range(&self) -> Option<Range<u32>> {
self.index_range.clone()
}
fn init_range(&self) -> Option<Range<u32>> {
self.init_range.clone()
}
fn duration_ms(&self) -> Option<u32> {
self.duration_ms
}
fn mime(&self) -> &str {
&self.mime
}
}
impl YtStream for AudioStream {
fn url(&self) -> &str {
&self.url
}
fn itag(&self) -> u32 {
self.itag
}
fn bitrate(&self) -> u32 {
self.bitrate
}
fn averate_bitrate(&self) -> u32 {
self.average_bitrate
}
fn size(&self) -> Option<u64> {
Some(self.size)
}
fn index_range(&self) -> Option<Range<u32>> {
self.index_range.clone()
}
fn init_range(&self) -> Option<Range<u32>> {
self.init_range.clone()
}
fn duration_ms(&self) -> Option<u32> {
self.duration_ms
}
fn mime(&self) -> &str {
&self.mime
}
}
/// Video codec
#[derive(
Default, Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash,
@ -336,6 +287,7 @@ impl YtStream for AudioStream {
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum VideoCodec {
/// Unknown codec
#[default]
Unknown,
/// MPEG-4 Part 14 <https://en.wikipedia.org/wiki/MPEG-4_Part_14>
@ -355,6 +307,7 @@ pub enum VideoCodec {
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum AudioCodec {
/// Unknown codec
#[default]
Unknown,
/// MP4A aka AAC: <https://en.wikipedia.org/wiki/Advanced_Audio_Coding>
@ -396,16 +349,6 @@ pub struct AudioTrack {
pub is_default: bool,
}
impl FileFormat for VideoFormat {
fn extension(&self) -> &str {
match self {
VideoFormat::ThreeGp => ".3gp",
VideoFormat::Mp4 => ".mp4",
VideoFormat::Webm => ".webm",
}
}
}
/// Audio file type
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[serde(rename_all = "snake_case")]
@ -417,15 +360,6 @@ pub enum AudioFormat {
Webm,
}
impl FileFormat for AudioFormat {
fn extension(&self) -> &str {
match self {
AudioFormat::M4a => ".m4a",
AudioFormat::Webm => ".webm",
}
}
}
/// YouTube provides subtitles in different formats.
///
/// srv1 (XML) is the default format, to request a different format you have
@ -1023,7 +957,9 @@ pub struct ArtistItem {
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[non_exhaustive]
pub struct ArtistId {
/// Unique YouTube channel ID
pub id: Option<String>,
/// Artist name
pub name: String,
}
@ -1203,10 +1139,12 @@ pub struct MusicSearchResult {
pub corrected_query: Option<String>,
/// Order of the item sections of the search page, starting with
/// the most relevant.
pub order: Vec<MusicEntityType>,
pub order: Vec<MusicItemType>,
}
/// Generic YouTube Music item
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[allow(missing_docs)]
pub enum MusicItem {
Track(TrackItem),
Album(AlbumItem),
@ -1214,18 +1152,21 @@ pub enum MusicItem {
Playlist(MusicPlaylistItem),
}
/// YouTube Music item type
#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum MusicEntityType {
#[allow(missing_docs)]
pub enum MusicItemType {
Track,
Album,
Artist,
Playlist,
}
/// Filtered YouTube Music search result
/// Filtered YouTube Music search result (one item type)
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[non_exhaustive]
pub struct MusicSearchFiltered<T> {
/// Search items
pub items: Paginator<T>,
/// Corrected search query
///
@ -1239,8 +1180,11 @@ pub struct MusicSearchFiltered<T> {
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[non_exhaustive]
pub struct TrackDetails {
/// Track metadata
pub track: TrackItem,
/// ID to fetch lyrics
pub lyrics_id: Option<String>,
/// ID to fetch related tracks
pub related_id: Option<String>,
}
@ -1254,7 +1198,7 @@ pub struct Lyrics {
pub footer: String,
}
/// YouTube Music entities related to a track
/// YouTube Music items related to a track
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[non_exhaustive]
pub struct MusicRelated {

View file

@ -4,7 +4,13 @@ use crate::model::AudioCodec;
use super::{AudioStream, VideoStream};
/// Trait for ordering YouTube video/audio streams by quality
///
/// analogous to [`std::cmp::Ord`]
pub trait QualityOrd {
/// Compare two streams by quality
///
/// analogous to [`std::cmp::Ord::cmp`]
fn quality_cmp(&self, other: &Self) -> Ordering;
}

View file

@ -1,9 +1,9 @@
//! Wrapper model for progressively fetched items
use std::convert::TryInto;
use serde::{Deserialize, Serialize};
use crate::param::ContinuationEndpoint;
/// Wrapper around progressively fetched items
///
/// The paginator is a wrapper around a list of items that are fetched
@ -34,7 +34,7 @@ pub struct Paginator<T> {
#[serde(skip_serializing_if = "Option::is_none")]
pub visitor_data: Option<String>,
/// YouTube API endpoint to fetch continuations from
pub endpoint: ContinuationEndpoint,
pub(crate) endpoint: ContinuationEndpoint,
}
impl<T> Default for Paginator<T> {
@ -49,6 +49,39 @@ impl<T> Default for Paginator<T> {
}
}
/// YouTube API endpoint to fetch continuations from
#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
#[allow(missing_docs)]
pub enum ContinuationEndpoint {
Browse,
Search,
Next,
MusicBrowse,
MusicSearch,
MusicNext,
}
impl ContinuationEndpoint {
pub(crate) fn as_str(self) -> &'static str {
match self {
ContinuationEndpoint::Browse | ContinuationEndpoint::MusicBrowse => "browse",
ContinuationEndpoint::Search | ContinuationEndpoint::MusicSearch => "search",
ContinuationEndpoint::Next | ContinuationEndpoint::MusicNext => "next",
}
}
pub(crate) fn is_music(self) -> bool {
matches!(
self,
ContinuationEndpoint::MusicBrowse
| ContinuationEndpoint::MusicSearch
| ContinuationEndpoint::MusicNext
)
}
}
impl<T> Paginator<T> {
pub(crate) fn new(count: Option<u64>, items: Vec<T>, ctoken: Option<String>) -> Self {
Self::new_ext(count, items, ctoken, None, ContinuationEndpoint::Browse)

View file

@ -6,19 +6,31 @@ use crate::util;
use super::UrlTarget;
/// Text content with links
#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[non_exhaustive]
pub struct RichText(pub Vec<TextComponent>);
/// Text component forming a [`RichText`] object
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[non_exhaustive]
pub enum TextComponent {
/// Plain text
Text(String),
/// Web link
Web { text: String, url: String },
/// Link to a YouTube entity
YouTube { text: String, target: UrlTarget },
Web {
/// Link text
text: String,
/// Link URL
url: String,
},
/// Link to a YouTube item
YouTube {
/// Link text
text: String,
/// YouTube URL target
target: UrlTarget,
},
}
/// Trait for converting rich text to plain text.
@ -46,6 +58,7 @@ pub trait ToHtml {
}
impl TextComponent {
/// Get the text from the component
pub fn get_text(&self) -> &str {
match self {
TextComponent::Text(text) => text,
@ -54,9 +67,12 @@ impl TextComponent {
}
}
/// Get the link URL from the component
///
/// Returns an empty string if the component is not a link.
pub fn get_url(&self, yt_host: &str) -> String {
match self {
TextComponent::Text(_) => "".to_owned(),
TextComponent::Text(_) => String::new(),
TextComponent::Web { url, .. } => url.to_owned(),
TextComponent::YouTube { target, .. } => target.to_url_yt_host(yt_host),
}

130
src/model/traits.rs Normal file
View file

@ -0,0 +1,130 @@
//! Traits for working with response models
use std::ops::Range;
pub use super::{convert::FromYtItem, ordering::QualityOrd};
use super::{AudioFormat, AudioStream, VideoFormat, VideoStream};
/// Trait for YouTube streams (video and audio)
pub trait YtStream {
/// Stream URL
fn url(&self) -> &str;
/// YouTube stream format identifier
fn itag(&self) -> u32;
/// Stream bitrate (in bits/second)
fn bitrate(&self) -> u32;
/// Average stream bitrate (in bits/second)
fn averate_bitrate(&self) -> u32;
/// File size in bytes
fn size(&self) -> Option<u64>;
/// Index range (used for DASH streaming)
fn index_range(&self) -> Option<Range<u32>>;
/// Init range (used for DASH streaming)
fn init_range(&self) -> Option<Range<u32>>;
/// Stream duration in milliseconds
fn duration_ms(&self) -> Option<u32>;
/// MIME file type
fn mime(&self) -> &str;
}
impl YtStream for VideoStream {
fn url(&self) -> &str {
&self.url
}
fn itag(&self) -> u32 {
self.itag
}
fn bitrate(&self) -> u32 {
self.bitrate
}
fn averate_bitrate(&self) -> u32 {
self.average_bitrate
}
fn size(&self) -> Option<u64> {
self.size
}
fn index_range(&self) -> Option<Range<u32>> {
self.index_range.clone()
}
fn init_range(&self) -> Option<Range<u32>> {
self.init_range.clone()
}
fn duration_ms(&self) -> Option<u32> {
self.duration_ms
}
fn mime(&self) -> &str {
&self.mime
}
}
impl YtStream for AudioStream {
fn url(&self) -> &str {
&self.url
}
fn itag(&self) -> u32 {
self.itag
}
fn bitrate(&self) -> u32 {
self.bitrate
}
fn averate_bitrate(&self) -> u32 {
self.average_bitrate
}
fn size(&self) -> Option<u64> {
Some(self.size)
}
fn index_range(&self) -> Option<Range<u32>> {
self.index_range.clone()
}
fn init_range(&self) -> Option<Range<u32>> {
self.init_range.clone()
}
fn duration_ms(&self) -> Option<u32> {
self.duration_ms
}
fn mime(&self) -> &str {
&self.mime
}
}
/// Trait for file types
pub trait FileFormat {
/// Get the file extension (".xyz") of the file format
fn extension(&self) -> &str;
}
impl FileFormat for VideoFormat {
fn extension(&self) -> &str {
match self {
VideoFormat::ThreeGp => ".3gp",
VideoFormat::Mp4 => ".mp4",
VideoFormat::Webm => ".webm",
}
}
}
impl FileFormat for AudioFormat {
fn extension(&self) -> &str {
match self {
AudioFormat::M4a => ".m4a",
AudioFormat::Webm => ".webm",
}
}
}