ytextract/video/
related.rs

1//! Recommended/Related items of a video.
2
3use crate::youtube::{
4    self,
5    next::{
6        CompactMovieRenderer, CompactPlaylistRenderer, CompactRadioRenderer, CompactVideoRenderer,
7    },
8    parse_length,
9};
10
11use std::fmt::Debug;
12
13/// A related Video
14#[derive(Clone)]
15pub struct Video(pub(super) CompactVideoRenderer, pub(super) crate::Client);
16
17impl Video {
18    /// The [`Id`](super::Id) of this video.
19    pub fn id(&self) -> super::Id {
20        self.0.video_id
21    }
22
23    /// The title of this video.
24    pub fn title(&self) -> &str {
25        &self.0.title
26    }
27
28    /// The [`Thumbnails`](crate::Thumbnail) of this video.
29    pub fn thumbnails(&self) -> impl Iterator<Item = &crate::Thumbnail> {
30        self.0.thumbnail.thumbnails.iter()
31    }
32
33    /// The amount of views this video has.
34    pub fn views(&self) -> Option<u64> {
35        let s: &str = match self.0.view_count_text.as_ref()? {
36            // "<VIEWS> views"
37            crate::youtube::Text::SimpleText(simple) => {
38                simple
39                    .simple_text
40                    .split_once(' ')
41                    .expect("No space in view_count_text")
42                    .0
43            }
44            // ["<VIEWS>", ..]
45            crate::youtube::Text::Runs(runs) => &runs.runs[0].text,
46        };
47
48        Some(s.replace(',', "").parse().expect("Views were not parsable"))
49    }
50
51    /// The length of this video. [`None`] if this video is a livestream.
52    pub fn length(&self) -> Option<std::time::Duration> {
53        self.0.length_text.as_deref().map(parse_length)
54    }
55
56    /// The [`Channel`] that uploaded this video.
57    pub fn channel(&self) -> Channel<'_> {
58        Channel {
59            id: Some(
60                self.0.short_byline_text.runs[0]
61                    .navigation_endpoint
62                    .browse_endpoint
63                    .browse_id,
64            ),
65            name: &self.0.short_byline_text.runs[0].text,
66            badges: &self.0.owner_badges,
67            client: &self.1,
68        }
69    }
70
71    /// Refetch this video for more information.
72    pub async fn upgrade(&self) -> crate::Result<crate::Video> {
73        self.1.video(self.id()).await
74    }
75
76    /// Get the [`Streams`](crate::Stream) for this video.
77    pub async fn streams(&self) -> crate::Result<impl Iterator<Item = crate::Stream>> {
78        self.1.streams(self.id()).await
79    }
80}
81
82impl std::fmt::Debug for Video {
83    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
84        f.debug_struct("Video")
85            .field("id", &self.id())
86            .field("title", &self.title())
87            .field("thumbnails", &self.thumbnails().collect::<Vec<_>>())
88            .field("views", &self.views())
89            .field("length", &self.length())
90            .field("channel", &self.channel())
91            .finish()
92    }
93}
94
95impl PartialEq for Video {
96    fn eq(&self, other: &Self) -> bool {
97        self.id() == other.id()
98    }
99}
100
101impl Eq for Video {}
102
103/// A related Playlist
104#[derive(Clone)]
105pub struct Playlist(pub(super) CompactPlaylistRenderer, pub(super) crate::Client);
106
107impl Playlist {
108    /// The [`Id`](crate::playlist::Id) of this playlist.
109    pub fn id(&self) -> crate::playlist::Id {
110        crate::playlist::Id(self.0.playlist_id.clone())
111    }
112
113    /// The title of this playlist.
114    pub fn title(&self) -> &str {
115        &self.0.title
116    }
117
118    /// The [`Thumbnails`](crate::Thumbnail) of this playlist.
119    pub fn thumbnails(&self) -> impl Iterator<Item = &crate::Thumbnail> {
120        self.0.thumbnail.thumbnails.iter()
121    }
122
123    /// The [`Channel`] that uploaded this playlist.
124    pub fn channel(&self) -> Channel<'_> {
125        Channel {
126            id: self.0.channel_id(),
127            name: &self.0.channel_name(),
128            badges: &self.0.owner_badges,
129            client: &self.1,
130        }
131    }
132
133    /// Refetch this playlist for more information.
134    pub async fn upgrade(&self) -> crate::Result<crate::Playlist> {
135        self.1.playlist(self.id()).await
136    }
137}
138
139impl Debug for Playlist {
140    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
141        f.debug_struct("Playlist")
142            .field("id", &self.id())
143            .field("title", &self.title())
144            .field("thumbnails", &self.thumbnails().collect::<Vec<_>>())
145            .field("channel", &self.channel())
146            .finish()
147    }
148}
149
150impl PartialEq for Playlist {
151    fn eq(&self, other: &Self) -> bool {
152        self.id() == other.id()
153    }
154}
155
156impl Eq for Playlist {}
157
158/// A related Radio
159#[derive(Clone)]
160pub struct Radio(pub(super) CompactRadioRenderer, pub(super) crate::Client);
161
162impl Radio {
163    /// The [`Id`](crate::playlist::Id) of this radio.
164    pub fn id(&self) -> crate::playlist::Id {
165        crate::playlist::Id(self.0.playlist_id.clone())
166    }
167
168    /// The title of this radio.
169    pub fn title(&self) -> &str {
170        &self.0.title
171    }
172
173    /// The [`Thumbnails`](crate::Thumbnail) of this playlist.
174    pub fn thumbnails(&self) -> impl Iterator<Item = &crate::Thumbnail> {
175        self.0.thumbnail.thumbnails.iter()
176    }
177
178    /// Refetch this radio for more information.
179    pub async fn upgrade(&self) -> crate::Result<crate::Playlist> {
180        self.1.playlist(self.id()).await
181    }
182}
183
184impl Debug for Radio {
185    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
186        f.debug_struct("Radio")
187            .field("id", &self.id())
188            .field("title", &self.title())
189            .field("thumbnails", &self.thumbnails().collect::<Vec<_>>())
190            .finish()
191    }
192}
193
194impl PartialEq for Radio {
195    fn eq(&self, other: &Self) -> bool {
196        self.id() == other.id()
197    }
198}
199
200impl Eq for Radio {}
201
202/// A related Movie
203#[derive(Clone)]
204pub struct Movie(pub(super) CompactMovieRenderer, pub(super) crate::Client);
205
206impl Movie {
207    /// The [`Id`](super::Id) of this movie.
208    pub fn id(&self) -> super::Id {
209        self.0.video_id
210    }
211
212    /// The title of this movie.
213    pub fn title(&self) -> &str {
214        &self.0.title
215    }
216
217    /// The [`Thumbnails`](crate::Thumbnail) of this movie.
218    pub fn thumbnails(&self) -> impl Iterator<Item = &crate::Thumbnail> {
219        self.0.thumbnail.thumbnails.iter()
220    }
221
222    /// The length of this movie.
223    pub fn length(&self) -> std::time::Duration {
224        parse_length(&self.0.length_text)
225    }
226
227    /// Refetch this video for more information.
228    pub async fn upgrade(&self) -> crate::Result<crate::Video> {
229        self.1.video(self.id()).await
230    }
231}
232
233impl std::fmt::Debug for Movie {
234    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
235        f.debug_struct("Movie")
236            .field("id", &self.id())
237            .field("title", &self.title())
238            .field("thumbnails", &self.thumbnails().collect::<Vec<_>>())
239            .field("length", &self.length())
240            .finish()
241    }
242}
243
244impl PartialEq for Movie {
245    fn eq(&self, other: &Self) -> bool {
246        self.id() == other.id()
247    }
248}
249
250impl Eq for Movie {}
251
252/// The uploader of a [`Related`](super::Related) item
253#[derive(Clone)]
254pub struct Channel<'a> {
255    id: Option<crate::channel::Id>,
256    name: &'a str,
257    badges: &'a Vec<youtube::Badge>,
258    client: &'a crate::Client,
259}
260
261impl<'a> Channel<'a> {
262    /// The [`Id`](crate::channel::Id) of this channel.
263    pub fn id(&self) -> Option<crate::channel::Id> {
264        self.id
265    }
266
267    /// The name of this channel.
268    pub fn name(&self) -> &str {
269        self.name
270    }
271
272    /// The [`Badges`](crate::channel::Badge) that this channel has.
273    pub fn badges(&self) -> impl Iterator<Item = crate::channel::Badge> + '_ {
274        self.badges.iter().map(crate::channel::Badge::from)
275    }
276
277    /// Refetch this channel for more information.
278    pub async fn upgrade(&self) -> Option<crate::Result<crate::Channel>> {
279        Some(self.client.channel(self.id?).await)
280    }
281}
282
283impl<'a> std::fmt::Debug for Channel<'a> {
284    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
285        f.debug_struct("Channel")
286            .field("id", &self.id())
287            .field("name", &self.name())
288            .field("badges", &self.badges().collect::<Vec<_>>())
289            .finish()
290    }
291}
292
293impl<'a> PartialEq for Channel<'a> {
294    fn eq(&self, other: &Self) -> bool {
295        self.id() == other.id()
296    }
297}
298
299impl<'a> Eq for Channel<'a> {}