blob_dl/assembling/youtube/
config.rs

1use crate::assembling::youtube;
2use crate::analyzer;
3use std::process;
4
5/// Contains all the information needed to download a youtube video or playlist
6#[derive(Debug, Clone)]
7pub struct DownloadConfig {
8    url: String,
9
10    output_path: String,
11    /// Whether to include a file's index (in the playlist it is downloaded from) in its name
12    include_indexes: bool,
13    /// The quality and format the user wants the downloaded files to be in
14    chosen_format: youtube::VideoQualityAndFormatPreferences,
15    /// Whether the downloaded files have to be audio-only/video-only/normal video
16    media_selected: youtube::MediaSelection,
17    /// Whether the link refers to a playlist or a single video
18    pub download_target: analyzer::DownloadOption,
19}
20
21impl DownloadConfig {
22    pub(crate) fn new_playlist (
23        url: &str,
24        output_path: String,
25        include_indexes: bool,
26        chosen_format: youtube::VideoQualityAndFormatPreferences,
27        media_selected: youtube::MediaSelection,
28    )
29        -> DownloadConfig
30    {
31        DownloadConfig { url: url.to_string(), output_path, include_indexes, chosen_format, media_selected,
32            download_target: analyzer::DownloadOption::YtPlaylist }
33    }
34
35    pub(crate) fn new_video (
36        url: &str,
37        chosen_format: youtube::VideoQualityAndFormatPreferences,
38        output_path: String,
39        media_selected: youtube::MediaSelection,
40    )
41        -> DownloadConfig
42    {
43        DownloadConfig { url: url.to_string(), chosen_format, output_path, media_selected,
44            include_indexes: false, download_target: analyzer::DownloadOption::YtVideo(0) }
45    }
46}
47
48// Command generation
49impl DownloadConfig {
50    /// Builds a command according to the current configuration, which is also returned
51    ///
52    /// This function is meant for the main video-downloading task
53    pub(crate) fn build_command(&self) -> (process::Command, DownloadConfig) {
54        (
55            match self.download_target {
56                analyzer::DownloadOption::YtVideo(_) => self.build_yt_video_command(),
57                analyzer::DownloadOption::YtPlaylist => self.build_yt_playlist_command(),
58            },
59
60            self.clone()
61        )
62    }
63
64    fn build_yt_playlist_command(&self) -> process::Command {
65        let mut command = process::Command::new("yt-dlp");
66
67        // Continue even when errors are encountered
68        command.arg("-i");
69
70        // If the url refers to a video in a playlist, download the whole playlist
71        command.arg("--yes-playlist");
72
73        // Setup output directory and naming scheme
74        self.choose_output_path(&mut command);
75
76        // Makes the id live long enough to be used as an arg for command.
77        // If it was fetched from the next match arm the temporary &str would not outlive command
78        let id = match &self.chosen_format {
79            youtube::VideoQualityAndFormatPreferences::UniqueFormat(id) => id.to_string(),
80            _ => String::new(),
81        };
82
83        // Quality and format selection
84        self.choose_format(&mut command, id.as_str());
85
86        // Add the playlist's url
87        command.arg(self.url.clone());
88
89        command
90    }
91
92    fn build_yt_video_command(&self) -> process::Command {
93        let mut command = process::Command::new("yt-dlp");
94
95        self.choose_output_path(&mut command);
96
97        // Makes the id live long enough to be used as an arg for command.
98        // If it was fetched from the next match arm the temporary &str would not outlive command
99        let id = match &self.chosen_format {
100            youtube::VideoQualityAndFormatPreferences::UniqueFormat(id) => id.to_string(),
101            _ => String::new(),
102        };
103
104        self.choose_format(&mut command, &id);
105
106        command.arg("--no-playlist");
107
108        command.arg(self.url.clone());
109
110        command
111    }
112
113    /// Downloads a new video while keeping the current preferences.
114    ///
115    /// This function is meant to be used to re-download videos which failed because of issues like bad internet
116    pub fn build_command_for_video(&self, video_id: &str) -> process::Command {
117        let mut command = process::Command::new("yt-dlp");
118
119        self.choose_output_path(&mut command);
120
121        // Makes the id live long enough to be used as an arg for command.
122        // If it was fetched from the next match arm the temporary &str would not outlive command
123        let id = match &self.chosen_format {
124            youtube::VideoQualityAndFormatPreferences::UniqueFormat(id) => id.to_string(),
125            _ => String::new(),
126        };
127
128        self.choose_format(&mut command, id.as_str());
129
130        command.arg("--no-playlist");
131
132        command.arg(video_id);
133
134        command
135    }
136
137    fn choose_output_path(&self, command: &mut process::Command) {
138        command.arg("-o");
139        command.arg(
140            {
141                let mut path_and_scheme = String::new();
142                // Add the user's output path (empty string for current directory)
143                path_and_scheme.push_str(self.output_path.as_str());
144
145                if self.download_target == analyzer::DownloadOption::YtPlaylist {
146                    // Create a directory named after the playlist
147                    #[cfg(target_os = "windows")]
148                    path_and_scheme.push_str("\\%(playlist)s\\");
149
150                    #[cfg(not(target_os = "windows"))]
151                    path_and_scheme.push_str("/%(playlist)s/");
152
153                    if self.include_indexes {
154                        path_and_scheme.push_str("%(playlist_index)s_");
155                    };
156                    path_and_scheme.push_str("%(title)s");
157                } else {
158                    // Downloading a yt_video
159                    #[cfg(target_os = "windows")]
160                    path_and_scheme.push_str("\\%(title)s.%(ext)s");
161
162                    #[cfg(not(target_os = "windows"))]
163                    path_and_scheme.push_str("/%(title)s.%(ext)s");
164                }
165
166                path_and_scheme
167            }
168        );
169    }
170
171    fn choose_format(&self, command: &mut process::Command, format_id: &str) {
172        match self.media_selected {
173            youtube::MediaSelection::FullVideo => {
174                match &self.chosen_format {
175                    youtube::VideoQualityAndFormatPreferences::BestQuality => {}
176
177                    youtube::VideoQualityAndFormatPreferences::SmallestSize => {
178                        command.arg("-S").arg("+size,+br");
179                    }
180
181                    youtube::VideoQualityAndFormatPreferences::UniqueFormat(_) => {
182                        command.arg("-f").arg(format_id);
183                    }
184                    youtube::VideoQualityAndFormatPreferences::ConvertTo(f) => {
185                        command.arg("--recode-video").arg(f.as_str());
186                    }
187                }
188            }
189
190            youtube::MediaSelection::AudioOnly => {
191                match &self.chosen_format {
192                    youtube::VideoQualityAndFormatPreferences::BestQuality => {
193                        command.arg("-f").arg("bestaudio");
194                    }
195
196                    youtube::VideoQualityAndFormatPreferences::SmallestSize => {
197                        command.arg("-f").arg("worstaudio");
198                    }
199
200                    youtube::VideoQualityAndFormatPreferences::UniqueFormat(_) => {
201                        command.arg("-f").arg(format_id);
202                    }
203                    youtube::VideoQualityAndFormatPreferences::ConvertTo(f) => {
204                        command.arg("-x").arg("--audio-format").arg(f.as_str());
205                    }
206                }
207            }
208
209            youtube::MediaSelection::VideoOnly => {
210                match &self.chosen_format {
211                    youtube::VideoQualityAndFormatPreferences::BestQuality => {
212                        command.arg("-f").arg("bestvideo");
213                    }
214
215                    youtube::VideoQualityAndFormatPreferences::SmallestSize => {
216                        command.arg("-f").arg("worstvideo");
217                    }
218
219                    youtube::VideoQualityAndFormatPreferences::UniqueFormat(_) => {
220                        command.arg("-f").arg(format_id);
221                    }
222                    youtube::VideoQualityAndFormatPreferences::ConvertTo(f) => {
223                        command.arg("--recode-video").arg(f.as_str());
224                    }
225                }
226            }
227        };
228    }
229}