1use ::serde::{Deserialize, Serialize};
32use hyper::{
33 body::{self, HttpBody},
34 Client,
35};
36use hyper_tls::HttpsConnector;
37use std::{
38 error,
39 fmt::{self, Display},
40 str,
41 time::Duration,
42};
43use tokio::{fs::File, io::AsyncWriteExt};
44
45pub mod serde;
46
47const ENDPOINT_URI: &'static str = "https://www.youtube.com/get_video_info";
49
50fn endpoint_from_id<T: Display>(id: T) -> String {
52 format!("{}?video_id={}", ENDPOINT_URI, id)
53}
54
55#[derive(Serialize, Deserialize, Clone, Debug)]
56pub struct Format {
58 itag: u32,
59 url: String,
60
61 width: Option<u32>,
64 height: Option<u32>,
65
66 #[serde(rename = "mimeType")]
67 mime_type: String,
68
69 #[serde(
70 default,
71 rename = "contentLength",
72 deserialize_with = "serde::u32::from_str_option"
73 )]
74 content_length: Option<u32>,
76
77 quality: String,
78 fps: Option<u32>,
79
80 #[serde(
81 default,
82 rename = "approxDurationMs",
83 deserialize_with = "serde::duration::from_millis_option"
84 )]
85 approx_duration: Option<Duration>,
87}
88
89impl Format {
90 pub fn is_video(&self) -> bool {
92 match self.width {
93 Some(_) => true,
94 None => false,
95 }
96 }
97
98 pub fn itag(&self) -> u32 {
99 self.itag
100 }
101
102 pub fn size(&self) -> Option<u32> {
103 self.content_length.clone()
104 }
105
106 pub async fn to_vec(&self) -> Result<Vec<u8>, Box<dyn error::Error + Send + Sync>> {
108 self.to_vec_callback(|_| Ok(())).await
109 }
110
111 pub async fn to_vec_callback<T>(
114 &self,
115 on_chunk: T,
116 ) -> Result<Vec<u8>, Box<dyn error::Error + Send + Sync>>
117 where
118 T: Fn(Vec<u8>) -> Result<(), Box<dyn error::Error + Send + Sync>>,
119 {
120 let https = HttpsConnector::new();
121 let client = Client::builder().build::<_, hyper::Body>(https);
122
123 let mut res = client.get(self.url.parse().unwrap()).await.unwrap();
124
125 let mut v: Vec<u8> = Vec::new();
126 while let Some(chunk) = res.body_mut().data().await {
127 let as_bytes: Vec<u8> = chunk?.iter().cloned().collect();
128 on_chunk(as_bytes.clone())?;
129 v.extend(as_bytes.iter());
130 }
131 Ok(v)
132 }
133
134 pub async fn download(
136 &self,
137 dest: &mut File,
138 ) -> Result<(), Box<dyn error::Error + Send + Sync>> {
139 let https = HttpsConnector::new();
140 let client = Client::builder().build::<_, hyper::Body>(https);
141
142 let mut res = client.get(self.url.parse().unwrap()).await.unwrap();
143
144 while let Some(chunk) = res.body_mut().data().await {
145 dest.write(&chunk?).await?;
146 }
147
148 Ok(())
149 }
150}
151
152impl Display for Format {
153 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
154 write!(
155 f,
156 "itag: {:03}\tQuality: {}\tMime Type: {}",
157 self.itag, self.quality, self.mime_type
158 )
159 }
160}
161
162#[derive(Serialize, Deserialize, Clone, Debug)]
163pub struct StreamingData {
166 #[serde(
167 rename = "expiresInSeconds",
168 deserialize_with = "serde::duration::from_secs"
169 )]
170 expires_in_seconds: Duration,
171
172 formats: Option<Vec<Format>>,
174
175 #[serde(rename = "adaptiveFormats")]
176 adaptive_formats: Vec<Format>,
177}
178
179#[derive(Serialize, Deserialize, Clone, Debug)]
180pub struct VideoDetails {
182 #[serde(rename = "videoId")]
183 video_id: String,
184
185 title: String,
186
187 #[serde(rename = "author")]
188 author: String,
189
190 #[serde(
191 rename = "lengthSeconds",
192 deserialize_with = "serde::duration::from_secs_option"
193 )]
194 approx_length: Option<Duration>,
195
196 #[serde(rename = "viewCount", deserialize_with = "serde::u32::from_str")]
197 views: u32,
198
199 #[serde(rename = "isPrivate")]
200 private: bool,
201
202 #[serde(rename = "isLiveContent")]
203 live: bool,
204}
205
206impl VideoDetails {
207 pub fn id(&self) -> String {
208 self.video_id.clone()
209 }
210}
211
212#[derive(Deserialize, Clone, Debug)]
213pub struct InfoResponse {
215 #[serde(rename = "streamingData")]
216 streaming_data: StreamingData,
217
218 #[serde(rename = "videoDetails")]
219 video_details: VideoDetails,
220}
221
222impl InfoResponse {
223 pub fn formats(&self) -> Option<Vec<Format>> {
224 self.streaming_data.formats.clone()
225 }
226
227 pub fn adaptive_formats(&self) -> Vec<Format> {
228 self.streaming_data.adaptive_formats.clone()
229 }
230
231 pub fn details(&self) -> VideoDetails {
232 self.video_details.clone()
233 }
234
235 pub fn all_formats(&self) -> Vec<Format> {
238 if let Some(fmts) = self.formats() {
239 return fmts
240 .iter()
241 .cloned()
242 .chain(self.adaptive_formats().iter().cloned())
243 .collect();
244 }
245 self.adaptive_formats().iter().cloned().collect()
246 }
247}
248
249#[derive(Serialize, Deserialize, Clone, Debug)]
250struct InfoWrapper {
253 pub player_response: String,
254}
255
256pub async fn get_video_info(id: &str) -> Result<InfoResponse, Box<dyn error::Error>> {
258 let https = HttpsConnector::new();
259 let client = Client::builder().build::<_, hyper::Body>(https);
260
261 let mut res = client
262 .get(endpoint_from_id(id).parse().unwrap())
263 .await
264 .unwrap();
265 let body = body::to_bytes(res.body_mut()).await.unwrap();
266
267 let stream_info: InfoResponse =
268 serde_json::from_str(&serde_urlencoded::from_bytes::<InfoWrapper>(&body)?.player_response)?;
269 Ok(stream_info)
270}