1pub mod yt_playlist;
2pub mod yt_video;
3pub mod config;
4
5use crate::error::{BlobdlError, BlobResult};
6use dialoguer::console::Term;
7use dialoguer::{theme::ColorfulTheme, Select, Input};
8use serde::{Deserialize, Serialize};
9use serde_json;
10use std::{env, fmt};
11use colored::Colorize;
12
13fn get_media_selection(term: &Term) -> Result<MediaSelection, std::io::Error> {
16    let download_formats = &[
17        "Normal Video",
18        "Audio-only",
19        "Video-only"
20    ];
21
22    let media_selection = Select::with_theme(&ColorfulTheme::default())
24        .with_prompt("What kind of file(s) do you want to download?")
25        .default(0)
26        .items(download_formats)
27        .interact_on(term)?;
28
29    match media_selection {
30        0 => Ok(MediaSelection::FullVideo),
31        1 => Ok(MediaSelection::AudioOnly),
32        _ => Ok(MediaSelection::VideoOnly),
33    }
34}
35
36fn get_output_path(term: &Term) -> BlobResult<String> {
40    let output_path_options = &[
41        "Current directory",
42        "Other [specify]",
43    ];
44
45    let output_path = Select::with_theme(&ColorfulTheme::default())
46        .with_prompt("Where do you want the downloaded file(s) to be saved?")
47        .default(0)
48        .items(output_path_options)
49        .interact_on(term)?;
50
51    match output_path {
52        0 => Ok(env::current_dir()?
54            .as_path()
55            .display()
56            .to_string()),
57
58        _ => Ok(Input::with_theme(&ColorfulTheme::default())
60            .with_prompt("Output path:")
61            .interact_text()?),
62    }
63}
64
65
66use spinoff;
67use std::process;
68use execute::Execute;
70
71fn get_ytdlp_formats(url: &str) -> Result<process::Output, std::io::Error> {
73    let mut sp = spinoff::Spinner::new(spinoff::spinners::Dots10, "Fetching available formats...", spinoff::Color::Cyan);
75
76    let mut command = process::Command::new("yt-dlp");
77    command.arg("-j");
79    command.arg("-i");
81    command.arg(url);
82
83    command.stdout(process::Stdio::piped());
85    command.stderr(process::Stdio::piped());
87    let output = command.execute_output();
88
89    sp.success("Formats downloaded successfully".bold().to_string().as_str());
91
92    output
93}
94
95fn convert_to_format(term: &Term, media_selected: &MediaSelection)
97                     -> BlobResult<VideoQualityAndFormatPreferences>
98{
99    let format_options = match *media_selected {
101        MediaSelection::AudioOnly => vec!["mp3", "m4a", "wav", "aac", "alac", "flac", "opus", "vorbis"],
103        MediaSelection::VideoOnly => vec!["mp4", "mkv", "mov", "avi", "flv", "gif", "webm", "aiff", "mka", "ogg"],
105        MediaSelection::FullVideo => vec!["mp4", "mkv", "mov", "avi", "flv", "gif", "webm", "aac", "aiff",
107                                          "alac", "flac", "m4a", "mka", "mp3", "ogg", "opus", "vorbis", "wav"],
108    };
109
110    let user_selection = Select::with_theme(&ColorfulTheme::default())
111        .with_prompt("Which container do you want the final file to be in?")
112        .default(0)
113        .items(&format_options)
114        .interact_on(term)?;
115
116    Ok(VideoQualityAndFormatPreferences::ConvertTo(format_options[user_selection].to_string()))
117}
118
119fn serialize_formats(json_dump: &str) -> BlobResult<VideoSpecs> {
121    let result = serde_json::from_str(json_dump);
122    match result {
123        Ok(cool) => Ok(cool),
124        Err(err) => Err(BlobdlError::SerdeError(err))
125    }
126}
127
128fn check_format(format: &VideoFormat, media_selected: &MediaSelection) -> bool {
132    if format.filesize.is_none() {
134        return false;
135    }
136    if *media_selected == MediaSelection::FullVideo && format.resolution == "audio only" {
138        return false;
139    }
140    if *media_selected == MediaSelection::AudioOnly && format.resolution != "audio only" {
142        return false;
143    }
144    if let Some(acodec) = &format.acodec {
145        if *media_selected == MediaSelection::FullVideo && acodec == "none" {
147            return false;
148        }
149        if *media_selected == MediaSelection::VideoOnly && acodec != "none" {
151            return false;
152        }
153    }
154    true
155}
156
157#[derive(Debug, Eq, PartialEq, Clone)]
160pub(crate) enum MediaSelection {
161    FullVideo,
162    VideoOnly,
163    AudioOnly,
164}
165
166#[derive(Deserialize, Serialize, Debug, PartialOrd, PartialEq)]
168struct VideoFormat {
169    format_id: String,
170    ext: String,
172    fps: Option<f64>,
174    audio_channels: Option<u64>,
176    resolution: String,
178    filesize: Option<u64>,
180    vcodec: String,
182    acodec: Option<String>,
184    container: Option<String>,
186    tbr: Option<f64>,
188    filesize_approx: Option<u64>,
190}
191
192#[derive(Deserialize, Serialize, Debug)]
194struct VideoSpecs {
195    formats: Vec<VideoFormat>,
196}
197
198#[derive(Debug, Clone)]
199pub(crate) enum VideoQualityAndFormatPreferences {
201    UniqueFormat(String),
203    ConvertTo(String),
205    BestQuality,
206    SmallestSize,
207}
208
209impl fmt::Display for VideoFormat {
210    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
211        let mut result;
212
213        if let Some(tbr) = self.tbr {
214            result = format!("{:<6} ", self.ext);
217
218            if self.resolution != "audio only" {
219                result = format!("{}| {:<13} ", result, self.resolution);
220            }
221
222            let filesize = self.filesize.unwrap_or(0);
224
225            let filesize_section = format!("| filesize: {:<.2}MB", filesize as f32 * 0.000001);
227            result = format!("{}{:<24}", result, filesize_section);
228
229            if let Some(ch) = self.audio_channels {
231                result = format!("{}| {} audio ch ", result, ch);
232            }
233
234            result = format!("{}| tbr: {:<8.2} ", result, tbr);
235
236            if self.vcodec != "none" {
237                result = format!("{}| vcodec: {:<13} ", result, self.vcodec);
238            }
239
240            if let Some(acodec) = &self.acodec {
241                if acodec != "none" {
242                    result = format!("{}| acodec: {:<13} ", result, acodec);
243                }
244            }
245
246            #[cfg(debug_assertions)]
247            return {
248                result = format!("[[DEBUG code: {:<3}]] {} ", self.format_id, result);
249                write!(f, "{}", result)
250            };
251
252            #[cfg(not(debug_assertions))]
253            write!(f, "{}", result)
254        } else {
255            write!(f, "I shouldn't show up because I am a picture format")
256        }
257    }
258}
259
260impl VideoSpecs {
261    fn formats(&self) -> &Vec<VideoFormat> {
262        &self.formats
263    }
264}