rustube/lib.rs
1// `CHANNEL_NIGHTLY` is passed by `build.rs`
2// Credits: https://blog.wnut.pw/2020/03/24/documentation-and-unstable-rustdoc-features/
3#![cfg_attr(all(doc, CHANNEL_NIGHTLY), feature(doc_auto_cfg))]
4
5#![allow(
6 clippy::nonstandard_macro_braces,
7 clippy::derive_partial_eq_without_eq,
8 unreachable_pub,
9)]
10#![warn(
11 missing_debug_implementations,
12 // missing_docs,
13 rust_2018_idioms,
14 unreachable_pub
15)]
16#![deny(rustdoc::broken_intra_doc_links)]
17#![doc(test(no_crate_inject))]
18
19#![cfg_attr(not(any(feature = "std", feature = "regex")), no_std)]
20
21//! A complete (WIP), and easy to use YouTube downloader.
22//!
23//! ## Just show me the code!
24//! You just want to download a video, and don't care about any intermediate steps and any video
25//! information?
26//!
27//! That's it:
28//! ```no_run
29//!# #[tokio::main]
30//!# async fn main() -> Result<(), Box<dyn std::error::Error>> {
31//! let url = "https://www.youtube.com/watch?v=Edx9D2yaOGs&ab_channel=CollegeHumor";
32//! let path_to_video = rustube::download_best_quality(url).await?;
33//!# Ok(())
34//!# }
35//! ```
36//! And with the `blocking` feature enabled, you don't even have to bring your own runtime:
37//! ```no_run
38//!# fn main() -> Result<(), Box<dyn std::error::Error>> {
39//!# #[cfg(feature = "blocking")]
40//!# {
41//! let url = "https://youtu.be/nv2wQvn6Wxc";
42//! let path_to_video = rustube::blocking::download_best_quality(url)?;
43//!# }
44//!# Ok(())
45//!# }
46//! ```
47//!
48//! ## Getting video information
49//! Of course, there's also the use case, where you want to find out information about a video,
50//! like it's [view count], it's [title], or if it [is_unplugged_corpus] (I mean who of us doesn't
51//! have the desire to find that out).
52//!
53//! In these cases, straigt out using [`download_best_quality`] won't serve you well.
54//! The [`VideoDescrambler`] returned by [`VideoFetcher::fetch`] will probaply fit your usecase a
55//! lot better:
56//! ```no_run
57//!# use rustube::{Id, VideoFetcher};
58//!# #[tokio::main]
59//!# async fn main() -> Result<(), Box<dyn std::error::Error>> {
60//! let id = Id::from_raw("https://www.youtube.com/watch?v=bKldI-XGHIw")?;
61//! let descrambler = VideoFetcher::from_id(id.into_owned())?
62//! .fetch()
63//! .await?;
64//!
65//! let video_info = descrambler.video_info();
66//! let the_only_truth = &video_info.player_response.tracking_params;
67//!# Ok(())
68//!# }
69//! ```
70//! If, after finding out everything about a video, you suddenly decide downloading it is worth it,
71//! you, of curse, can keep using the [`VideoDescrambler`] for that:
72//! ```no_run
73//!# use rustube::{Id, VideoFetcher};
74//!# #[tokio::main]
75//!# async fn main() -> Result<(), Box<dyn std::error::Error>> {
76//!# let id = Id::from_raw("https://www.youtube.com/watch?v=bKldI-XGHIw")?;
77//!# let descrambler = VideoFetcher::from_id(id.into_owned())?
78//!# .fetch()
79//!# .await?;
80//! let video = descrambler.descramble()?;
81//! let path_to_video = video.best_quality().unwrap().download().await?;
82//!# Ok(())
83//!# }
84//! ```
85//!
86//! ## Maybe something in between?
87//! So then, what does `rustube` offer, if I already know, that I want information as well as
88//! downloading the video? That's exactly, what the handy `from_*` methods on [`Video`] are for.
89//! Those methods provide easy to use shortcuts with no need for first fetching and
90//! then descrambeling the video seperatly:
91//!```no_run
92//!# use rustube::{Video, Id};
93//!# #[tokio::main]
94//!# async fn main() -> Result<(), Box<dyn std::error::Error>> {
95//! let id = Id::from_str("hFZFjoX2cGg")?;
96//! let video = Video::from_id(id.into_owned()).await?;
97//!
98//! let the_truth_the_whole_truth_and_nothing_but_the_truth = video.video_info();
99//! let path_to_video = video
100//! .worst_audio()
101//! .unwrap()
102//! .download()
103//! .await?;
104//!# Ok(())
105//!# }
106//!```
107//!
108//! ## Choosing something exotic
109//! Till now, you only saw the methods [`Video::best_quality`] and [`Video::worst_audio`] that
110//! magically tell you which video stream you truly desire. But wait, what's a [`Stream`]? If you
111//! ever watched a video on YouTube, you probably know that most videos come in different
112//! resolutions. So when your internet connection sucks, you may watch the 240p version, instead of
113//! the full fleged 4k variant. Each of those resolutions is a [`Stream`]. Besides those video
114//! [`Stream`]s, there are often also video-only or audio-only [`Stream`]s. The methods we used so
115//! far are actually just a nice shortcut for making your life easier. But since all these success
116//! gurus tell us, we should take the hard road, we will!
117//!
118//! For doing so, and to get a little more control over which [`Stream`] of a [`Video`] to download,
119//! we can use [`Video::streams`], the [`Stream`] attributes, and Rusts amazing [`Iterator`] methods:
120//! ```no_run
121//!# #[tokio::main]
122//!# async fn main() -> Result<(), Box<dyn std::error::Error>> {
123//!# use rustube::{Video, Id};
124//!# let id = Id::from_str("hFZFjoX2cGg")?;
125//!# let video = Video::from_id(id.into_owned()).await?;
126//! let best_quality = video
127//! .streams()
128//! .iter()
129//! .filter(|stream| stream.includes_video_track && stream.includes_audio_track)
130//! .max_by_key(|stream| stream.quality_label);
131//!# Ok(())
132//!# }
133//!```
134//!
135//! Note, that often the video-audio streams have slightly worse quality than the video-only or
136//! audio-only streams. This is not a limitation of rustube, but rather a weird design choice of
137//! YouTube. So if you want the absolute best quality possible, you will sometimes have to download
138//! the best video-only and the best audio-only stream separate. This, however, doesn't affect all
139//! videos.
140//!
141//! ## Different ways of downloading
142//! As you may already have noticed, all the above examples just call [`Stream::download`], and then
143//! get back a path to a video. This path will always point to `<VIDEO_ID>.mp4` in the current
144//! working directory. But what if you want to have a little more control over where
145//! to download the video to?
146//!
147//! [`Stream::download_to_dir`] and [`Stream::download_to`] have your back! Those methods allow you
148//! to specify exactly, where the video should be downloaded too.
149//!
150//! If you want to do something, while the download is progressing, enable the `callback`
151//! feature and use the then availabe `*_with_callback` methods, like
152//! [`Stream::download_with_callback`].
153//!
154//! The [`Callback`] struct can take up to one `on_progress` method and one `on_complete` method.
155//!
156//! For even more control, or when you just want to access the videos URL, have a look at the
157//! [`url`](crate::video_info::player_response::streaming_data::SignatureCipher::url) field inside
158//! of [`Stream::signature_cipher`]. This field contains the video URL of that particular Stream.
159//! You can, i.e., use this URL to watch the video directly in your browser.
160//!
161//! ## Feature flags
162//! One of the goals of `rustube` is to eventually deserialize the complete video information, so
163//! even the weirdest niche cases get all the information they need. Another goal is to become the
164//! fastest and best performing YouTube downloader out there, while also using little resources.
165//! These two goals don't go hand in hand and require `rustube` to offer some kind of feature
166//! system, which allows users to specify precisely, what they need, so they don't have to pay the
167//! price for stuff they don't use.
168//!
169//! When compiling with no features at all, you will be left with only [`Id`]. This is a `no_std`
170//! build. Still, it's highly recommended to at least enable the `regex` feature, which will
171//! currently break `no_std` ([#476](https://github.com/rust-lang/regex/issues/476)), as well as the
172//! `std` feature. This combination enables [`Id::from_raw`], which is the only way of extracting
173//! ids from arbitrary video identifiers, like URLs.
174//!
175//! The feature system is still WIP, and currently, you can just opt-in or opt-out of quite huge
176//! bundles of functionality.
177//!
178//! - `download`: \[default\] Enables all utilities required for downloading videos.
179//! - `regex`: \[default\] Enables [`Id::from_raw`], which extracts valid `Id`s from arbitrary video
180//! identifiers like URLs.
181//! - `serde`: \[default\] Enables [`serde`] support for [`Id`] (Keep in mind, that this feature
182//! does not enable the `regex` automatically).
183//! - `std`: \[default\] Enables `std` usage, which a lot of things depend on.
184//! - `fetch`: \[default\] Enables [`VideoFetcher`], which can be used to fetch video information.
185//! - `descramble`: \[default\] Enables [`VideoDescrambler`], which can decrypt video signatures and is
186//! necessary to extract the individual streams.
187//! - `stream`: \[default\] Enables [`Stream`], a representation of a video stream that can be used to download this particular stream.
188//! - `blocking`: Enables the [`blocking`] API, which internally creates a [`tokio`] runtime for you
189//! , so you don't have to care about it yourself. (Keep in mind, that this feature does not enable
190//! any of the other features above automatically)
191//! - `callback`: Enables to add callbacks to downlaods and the [`Callback`] struct itself
192//!
193//!
194//! [view count]: crate::video_info::player_response::video_details::VideoDetails::view_count
195//! [title]: crate::video_info::player_response::video_details::VideoDetails::title
196//! [is_unplugged_corpus]: crate::video_info::player_response::video_details::VideoDetails::is_unplugged_corpus
197//! [Iterator]: std::iter::Iterator
198
199extern crate alloc;
200
201#[cfg(feature = "tokio")]
202pub use tokio;
203pub use url;
204
205#[cfg(feature = "descramble")]
206pub use crate::descrambler::VideoDescrambler;
207#[cfg(feature = "std")]
208pub use crate::error::Error;
209#[cfg(feature = "fetch")]
210pub use crate::fetcher::VideoFetcher;
211pub use crate::id::{Id, IdBuf};
212#[cfg(feature = "regex")]
213pub use crate::id::{EMBED_URL_PATTERN, ID_PATTERN, ID_PATTERNS, SHARE_URL_PATTERN, WATCH_URL_PATTERN};
214#[cfg(feature = "callback")]
215pub use crate::stream::callback::{Callback, CallbackArguments, OnCompleteType, OnProgressType};
216#[cfg(feature = "stream")]
217pub use crate::stream::Stream;
218#[cfg(feature = "descramble")]
219pub use crate::video::Video;
220#[doc(inline)]
221#[cfg(feature = "fetch")]
222pub use crate::video_info::{
223 player_response::{
224 PlayerResponse,
225 video_details::VideoDetails,
226 },
227 VideoInfo,
228};
229#[doc(inline)]
230#[cfg(feature = "microformat")]
231pub use crate::video_info::player_response::microformat::Microformat;
232
233/// Alias for `Result`, with the default error type [`Error`].
234#[cfg(feature = "std")]
235pub type Result<T, E = Error> = core::result::Result<T, E>;
236
237#[cfg(feature = "blocking")]
238pub mod blocking;
239#[doc(hidden)]
240#[cfg(feature = "std")]
241pub mod error;
242#[doc(hidden)]
243pub mod id;
244#[doc(hidden)]
245#[cfg(feature = "stream")]
246pub mod stream;
247#[cfg(feature = "fetch")]
248pub mod video_info;
249#[doc(hidden)]
250#[cfg(feature = "fetch")]
251pub mod fetcher;
252#[doc(hidden)]
253#[cfg(feature = "descramble")]
254pub mod descrambler;
255#[doc(hidden)]
256#[cfg(feature = "descramble")]
257pub mod video;
258
259#[cfg(feature = "fetch")]
260mod serde_impl;
261
262/// The absolute most straightforward way of downloading a YouTube video in high quality!
263///
264/// Takes an arbitrary video identifier, like any video URL, or the video id, and downloads
265/// the video to `<VIDEO_ID>.mp4` in the current working directory.
266///
267/// For more control over the download process have a look at the [`crate`] level documentation,
268/// or at the [`Video`] struct.
269#[cfg(all(feature = "download", feature = "regex"))]
270pub async fn download_best_quality(video_identifier: &str) -> Result<std::path::PathBuf> {
271 let id = Id::from_raw(video_identifier)?;
272 Video::from_id(id.into_owned())
273 .await?
274 .best_quality()
275 .ok_or(Error::NoStreams)?
276 .download()
277 .await
278}
279
280/// The absolute most straightforward way of downloading a YouTube video in low quality!
281///
282/// Takes an arbitrary video identifier, like any video URL, or the video id, and downloads
283/// the video to `<VIDEO_ID>.mp4` in the current working directory.
284///
285/// For more control over the download process have a look at the [`crate`] level documentation,
286/// or at the [`Video`] struct.
287#[cfg(all(feature = "download", feature = "regex"))]
288pub async fn download_worst_quality(video_identifier: &str) -> Result<std::path::PathBuf> {
289 let id = Id::from_raw(video_identifier)?;
290 Video::from_id(id.into_owned())
291 .await?
292 .worst_quality()
293 .ok_or(Error::NoStreams)?
294 .download()
295 .await
296}
297
298/// A trait for collecting iterators into arbitrary, in particular fixed-sized, types.
299trait TryCollect<T>: Iterator {
300 fn try_collect(self) -> Option<T>;
301 fn try_collect_lossy(self) -> Option<T> where Self: Sized { None }
302}
303
304impl<T> TryCollect<(T::Item, )> for T
305 where T: Iterator {
306 #[inline]
307 fn try_collect(mut self) -> Option<(T::Item, )> {
308 match (self.next(), self.next()) {
309 (Some(item), None) => Some((item, )),
310 _ => None
311 }
312 }
313
314 #[inline]
315 fn try_collect_lossy(mut self) -> Option<(T::Item, )> {
316 self.next().map(|v| (v, ))
317 }
318}
319
320impl<T> TryCollect<(T::Item, T::Item)> for T
321 where T: Iterator {
322 #[inline]
323 fn try_collect(mut self) -> Option<(T::Item, T::Item)> {
324 match (self.next(), self.next(), self.next()) {
325 (Some(item1), Some(item2), None) => Some((item1, item2)),
326 _ => None
327 }
328 }
329
330 #[inline]
331 fn try_collect_lossy(mut self) -> Option<(T::Item, T::Item)> {
332 match (self.next(), self.next()) {
333 (Some(item1), Some(item2)) => Some((item1, item2)),
334 _ => None
335 }
336 }
337}