1use std::collections::BTreeMap;
4use std::path::PathBuf;
5use std::time::Duration;
6
7use time::{Date, Month, OffsetDateTime, PrimitiveDateTime, Time};
8
9use crate::error::{Error, Result};
10use crate::manifest::RoleType;
11use crate::mux::{MuxFormat, MuxImport};
12
13#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
15pub enum CompatibilityProfile {
16 #[default]
18 CliCompatible,
19 ApiSafe,
21}
22
23#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
25pub enum LogLevel {
26 Debug,
27 #[default]
28 Info,
29 Warn,
30 Error,
31 Off,
32}
33
34#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
36pub enum DecryptionEngine {
37 #[default]
38 Mp4forge,
39 Mp4decrypt,
40 ShakaPackager,
41 Ffmpeg,
42}
43
44#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
46pub enum SubtitleFormat {
47 #[default]
49 Srt,
50 Vtt,
52}
53
54#[derive(Clone, Copy, Debug, Eq, PartialEq)]
56pub enum UiLanguage {
57 EnUs,
59}
60
61#[derive(Clone, Debug, Eq, PartialEq)]
63pub enum HlsMethod {
64 None,
65 Aes128,
66 Aes128Ecb,
67 Cenc,
68 SampleAes,
69 SampleAesCtr,
70 Chacha20,
71 Unknown,
72}
73
74#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
76pub enum MuxerKind {
77 #[default]
79 Ffmpeg,
80 Mkvmerge,
82 Mp4forge,
84}
85
86#[derive(Clone, Debug, PartialEq)]
88pub enum CustomRange {
89 Segment {
90 input: String,
91 start_index: i64,
92 end_index: i64,
93 },
94 Time {
95 input: String,
96 start_seconds: f64,
97 end_seconds: f64,
98 },
99}
100
101#[derive(Clone, Debug, Eq, PartialEq)]
103pub struct TaskStartAt {
104 raw: String,
105}
106
107impl TaskStartAt {
108 pub fn new(raw: String) -> Self {
110 Self { raw }
111 }
112
113 pub fn as_str(&self) -> &str {
115 &self.raw
116 }
117
118 pub fn duration_until_now(&self) -> Result<Option<Duration>> {
120 self.duration_until_datetime(current_local_datetime())
121 }
122
123 pub fn duration_until_raw(&self, now_raw: &str) -> Result<Option<Duration>> {
125 self.duration_until_datetime(parse_task_start_datetime(now_raw)?)
126 }
127
128 fn duration_until_datetime(&self, now: PrimitiveDateTime) -> Result<Option<Duration>> {
129 let target = parse_task_start_datetime(&self.raw)?;
130 if target <= now {
131 return Ok(None);
132 }
133 Ok(Some((target - now).unsigned_abs()))
134 }
135}
136
137fn current_local_datetime() -> PrimitiveDateTime {
138 let now = OffsetDateTime::now_local().unwrap_or_else(|_| OffsetDateTime::now_utc());
139 PrimitiveDateTime::new(now.date(), now.time())
140}
141
142fn parse_task_start_datetime(value: &str) -> Result<PrimitiveDateTime> {
143 if value.len() != 14 || !value.chars().all(|ch| ch.is_ascii_digit()) {
144 return Err(Error::config("task start time must use yyyyMMddHHmmss"));
145 }
146 let year = parse_task_start_part(value, 0, 4)?;
147 let month = parse_task_start_part(value, 4, 6)?;
148 let day = parse_task_start_part(value, 6, 8)?;
149 let hour = parse_task_start_part(value, 8, 10)?;
150 let minute = parse_task_start_part(value, 10, 12)?;
151 let second = parse_task_start_part(value, 12, 14)?;
152 let month = Month::try_from(
153 u8::try_from(month)
154 .map_err(|_| Error::config("task start time contains an out-of-range month"))?,
155 )
156 .map_err(|_| Error::config("task start time contains an out-of-range month"))?;
157 let date = Date::from_calendar_date(
158 i32::try_from(year)
159 .map_err(|_| Error::config("task start time contains an out-of-range year"))?,
160 month,
161 u8::try_from(day)
162 .map_err(|_| Error::config("task start time contains an out-of-range day"))?,
163 )
164 .map_err(|_| Error::config("task start time contains an out-of-range date"))?;
165 let time = Time::from_hms(
166 u8::try_from(hour)
167 .map_err(|_| Error::config("task start time contains an out-of-range hour"))?,
168 u8::try_from(minute)
169 .map_err(|_| Error::config("task start time contains an out-of-range minute"))?,
170 u8::try_from(second)
171 .map_err(|_| Error::config("task start time contains an out-of-range second"))?,
172 )
173 .map_err(|_| Error::config("task start time contains an out-of-range time"))?;
174 Ok(PrimitiveDateTime::new(date, time))
175}
176
177fn parse_task_start_part(value: &str, start: usize, end: usize) -> Result<u32> {
178 value
179 .get(start..end)
180 .ok_or_else(|| Error::config("task start time field is invalid"))?
181 .parse::<u32>()
182 .map_err(|_| Error::config("task start time field is invalid"))
183}
184
185#[derive(Clone, Debug, Eq, PartialEq)]
187pub enum CustomKey {
188 Track { track_id: u32, key_hex: String },
189 Kid { kid_hex: String, key_hex: String },
190 Key { key_hex: String },
191}
192
193#[derive(Clone, Debug, Default, PartialEq)]
196pub struct StreamFilter {
197 pub for_choice: String,
198 pub id: Option<String>,
199 pub language: Option<String>,
200 pub name: Option<String>,
201 pub codecs: Option<String>,
202 pub resolution: Option<String>,
203 pub frame_rate: Option<String>,
204 pub channels: Option<String>,
205 pub range: Option<String>,
206 pub url: Option<String>,
207 pub segment_count_min: Option<i64>,
208 pub segment_count_max: Option<i64>,
209 pub playlist_duration_min: Option<f64>,
210 pub playlist_duration_max: Option<f64>,
211 pub bandwidth_min: Option<i64>,
212 pub bandwidth_max: Option<i64>,
213 pub role: Option<RoleType>,
214}
215
216#[derive(Clone, Debug, Eq, PartialEq)]
218pub struct MuxAfterDoneOptions {
219 pub format: MuxFormat,
221 pub muxer: MuxerKind,
223 pub fallback_muxer: Option<MuxerKind>,
228 pub bin_path: Option<PathBuf>,
230 pub keep: bool,
232 pub skip_sub: bool,
234}
235
236impl Default for MuxAfterDoneOptions {
237 fn default() -> Self {
238 Self {
239 format: MuxFormat::Mp4,
240 muxer: MuxerKind::Ffmpeg,
241 fallback_muxer: None,
242 bin_path: None,
243 keep: false,
244 skip_sub: false,
245 }
246 }
247}
248
249#[derive(Clone, Debug)]
251pub struct DownloadOptions {
252 pub compatibility_profile: CompatibilityProfile,
254 pub log_level: LogLevel,
255 pub tmp_dir: Option<PathBuf>,
257 pub save_dir: Option<PathBuf>,
259 pub save_name: Option<String>,
261 pub save_pattern: Option<String>,
263 pub log_file_path: Option<PathBuf>,
264 pub ui_language: Option<UiLanguage>,
265 pub urlprocessor_args: Option<String>,
266 pub base_url: Option<String>,
268 pub headers: BTreeMap<String, String>,
269 pub custom_proxy: Option<String>,
270 pub use_system_proxy: bool,
271 pub allow_insecure_tls: bool,
273 pub append_url_params: bool,
274 pub custom_range: Option<CustomRange>,
275 pub keys: Vec<CustomKey>,
276 pub key_text_file: Option<PathBuf>,
277 pub decryption_engine: DecryptionEngine,
278 pub decryption_binary_path: Option<PathBuf>,
279 pub mp4_real_time_decryption: bool,
280 pub use_shaka_packager: bool,
281 pub custom_hls_method: Option<HlsMethod>,
282 pub custom_hls_key: Option<Vec<u8>>,
283 pub custom_hls_iv: Option<Vec<u8>>,
284 pub allow_hls_multi_ext_map: bool,
285 pub thread_count: i32,
287 pub download_retry_count: i32,
289 pub http_request_timeout: Duration,
291 pub max_speed: Option<u64>,
292 pub auto_select: bool,
293 pub concurrent_download: bool,
295 pub sub_only: bool,
297 pub select_video: Vec<StreamFilter>,
298 pub select_audio: Vec<StreamFilter>,
299 pub select_subtitle: Vec<StreamFilter>,
300 pub drop_video: Vec<StreamFilter>,
301 pub drop_audio: Vec<StreamFilter>,
302 pub drop_subtitle: Vec<StreamFilter>,
303 pub ad_keywords: Vec<String>,
304 pub skip_merge: bool,
306 pub skip_download: bool,
308 pub check_segments_count: bool,
310 pub binary_merge: bool,
312 pub use_ffmpeg_concat_demuxer: bool,
314 pub del_after_done: bool,
316 pub no_date_info: bool,
318 pub no_log: bool,
320 pub write_meta_json: bool,
322 pub auto_subtitle_fix: bool,
324 pub sub_format: SubtitleFormat,
326 pub ffmpeg_binary_path: Option<PathBuf>,
328 pub mkvmerge_binary_path: Option<PathBuf>,
330 pub mux_after_done: Option<MuxAfterDoneOptions>,
332 pub mux_imports: Vec<MuxImport>,
333 pub live_perform_as_vod: bool,
335 pub live_real_time_merge: bool,
337 pub live_keep_segments: bool,
339 pub live_pipe_mux: bool,
341 pub live_record_limit: Option<Duration>,
342 pub live_wait_time: Option<i32>,
343 pub live_take_count: i32,
345 pub live_fix_vtt_by_audio: bool,
346 pub task_start_at: Option<TaskStartAt>,
347 pub force_ansi_console: bool,
348 pub no_ansi_color: bool,
349 pub disable_update_check: bool,
350}
351
352impl Default for DownloadOptions {
353 fn default() -> Self {
354 Self {
355 compatibility_profile: CompatibilityProfile::CliCompatible,
356 log_level: LogLevel::Info,
357 tmp_dir: None,
358 save_dir: None,
359 save_name: None,
360 save_pattern: None,
361 log_file_path: None,
362 ui_language: None,
363 urlprocessor_args: None,
364 base_url: None,
365 headers: BTreeMap::new(),
366 custom_proxy: None,
367 use_system_proxy: true,
368 allow_insecure_tls: false,
369 append_url_params: false,
370 custom_range: None,
371 keys: Vec::new(),
372 key_text_file: None,
373 decryption_engine: DecryptionEngine::Mp4forge,
374 decryption_binary_path: None,
375 mp4_real_time_decryption: false,
376 use_shaka_packager: false,
377 custom_hls_method: None,
378 custom_hls_key: None,
379 custom_hls_iv: None,
380 allow_hls_multi_ext_map: false,
381 thread_count: default_thread_count(),
382 download_retry_count: 3,
383 http_request_timeout: Duration::from_secs(100),
384 max_speed: None,
385 auto_select: false,
386 concurrent_download: false,
387 sub_only: false,
388 select_video: Vec::new(),
389 select_audio: Vec::new(),
390 select_subtitle: Vec::new(),
391 drop_video: Vec::new(),
392 drop_audio: Vec::new(),
393 drop_subtitle: Vec::new(),
394 ad_keywords: Vec::new(),
395 skip_merge: false,
396 skip_download: false,
397 check_segments_count: true,
398 binary_merge: false,
399 use_ffmpeg_concat_demuxer: false,
400 del_after_done: true,
401 no_date_info: false,
402 no_log: false,
403 write_meta_json: true,
404 auto_subtitle_fix: true,
405 sub_format: SubtitleFormat::Srt,
406 ffmpeg_binary_path: None,
407 mkvmerge_binary_path: None,
408 mux_after_done: None,
409 mux_imports: Vec::new(),
410 live_perform_as_vod: false,
411 live_real_time_merge: false,
412 live_keep_segments: true,
413 live_pipe_mux: false,
414 live_record_limit: None,
415 live_wait_time: None,
416 live_take_count: 16,
417 live_fix_vtt_by_audio: false,
418 task_start_at: None,
419 force_ansi_console: false,
420 no_ansi_color: false,
421 disable_update_check: false,
422 }
423 }
424}
425
426const fn fallback_thread_count() -> i32 {
427 1
428}
429
430fn default_thread_count() -> i32 {
431 match std::thread::available_parallelism() {
432 Ok(count) => i32::try_from(count.get()).unwrap_or(i32::MAX),
433 Err(_) => fallback_thread_count(),
434 }
435}