Skip to main content

mangofetch_core/core/
media_processor.rs

1use crate::core::hls_downloader::HlsDownloadResult;
2use tokio_util::sync::CancellationToken;
3
4pub struct MediaProcessor;
5
6impl MediaProcessor {
7    #[allow(clippy::too_many_arguments)]
8    pub async fn download_hls(
9        m3u8_url: &str,
10        output: &str,
11        referer: &str,
12        bytes_tx: Option<tokio::sync::mpsc::UnboundedSender<u64>>,
13        cancel_token: CancellationToken,
14        max_concurrent: u32,
15        max_retries: u32,
16        client: Option<reqwest::Client>,
17    ) -> anyhow::Result<HlsDownloadResult> {
18        let downloader = match client {
19            Some(c) => crate::core::hls_downloader::HlsDownloader::with_client(c),
20            None => crate::core::hls_downloader::HlsDownloader::new(),
21        };
22        downloader
23            .download(
24                m3u8_url,
25                output,
26                referer,
27                bytes_tx,
28                cancel_token,
29                max_concurrent,
30                max_retries,
31            )
32            .await
33    }
34
35    #[allow(clippy::too_many_arguments)]
36    pub async fn download_hls_with_quality(
37        m3u8_url: &str,
38        output: &str,
39        referer: &str,
40        bytes_tx: Option<tokio::sync::mpsc::UnboundedSender<u64>>,
41        cancel_token: CancellationToken,
42        max_concurrent: u32,
43        max_retries: u32,
44        client: Option<reqwest::Client>,
45        max_height: Option<u32>,
46    ) -> anyhow::Result<HlsDownloadResult> {
47        let downloader = match client {
48            Some(c) => crate::core::hls_downloader::HlsDownloader::with_client(c),
49            None => crate::core::hls_downloader::HlsDownloader::new(),
50        };
51        downloader
52            .download_with_quality(
53                m3u8_url,
54                output,
55                referer,
56                bytes_tx,
57                cancel_token,
58                max_concurrent,
59                max_retries,
60                max_height,
61            )
62            .await
63    }
64
65    pub async fn remux(input: &str, output: &str) -> anyhow::Result<()> {
66        let status = crate::core::process::command("ffmpeg")
67            .args(["-y", "-i", input, "-c", "copy", output])
68            .stdout(std::process::Stdio::null())
69            .stderr(std::process::Stdio::piped())
70            .status()
71            .await?;
72
73        if !status.success() {
74            anyhow::bail!("FFmpeg remux falhou com status {}", status);
75        }
76
77        Ok(())
78    }
79
80    pub async fn merge_audio_video(video: &str, audio: &str, output: &str) -> anyhow::Result<()> {
81        let status = crate::core::process::command("ffmpeg")
82            .args([
83                "-y", "-i", video, "-i", audio, "-map", "0:v", "-map", "1:a", "-c", "copy", output,
84            ])
85            .stdout(std::process::Stdio::null())
86            .stderr(std::process::Stdio::piped())
87            .status()
88            .await?;
89
90        if !status.success() {
91            anyhow::bail!("FFmpeg merge falhou com status {}", status);
92        }
93
94        Ok(())
95    }
96
97    pub async fn download_direct(
98        url: &str,
99        output: &str,
100        headers: &[(&str, &str)],
101    ) -> anyhow::Result<()> {
102        let mut args = vec!["-y".to_string()];
103
104        if !headers.is_empty() {
105            let header_str: String = headers
106                .iter()
107                .map(|(k, v)| format!("{}: {}\r\n", k, v))
108                .collect();
109            args.extend(["-headers".to_string(), header_str]);
110        }
111
112        args.extend([
113            "-i".to_string(),
114            url.to_string(),
115            "-c".to_string(),
116            "copy".to_string(),
117            output.to_string(),
118        ]);
119
120        let status = crate::core::process::command("ffmpeg")
121            .args(&args)
122            .stdout(std::process::Stdio::null())
123            .stderr(std::process::Stdio::piped())
124            .status()
125            .await?;
126
127        if !status.success() {
128            anyhow::bail!("FFmpeg download_direct falhou com status {}", status);
129        }
130
131        Ok(())
132    }
133}
134
135pub fn check_ffmpeg() -> bool {
136    crate::core::process::std_command("ffmpeg")
137        .arg("-version")
138        .stdout(std::process::Stdio::null())
139        .stderr(std::process::Stdio::null())
140        .status()
141        .map(|s| s.success())
142        .unwrap_or(false)
143}
144
145pub fn check_ytdlp() -> bool {
146    crate::core::process::std_command("yt-dlp")
147        .arg("--version")
148        .stdout(std::process::Stdio::null())
149        .stderr(std::process::Stdio::null())
150        .status()
151        .map(|s| s.success())
152        .unwrap_or(false)
153}
154
155pub fn check_dependencies() -> Vec<String> {
156    let mut missing = Vec::new();
157    if !check_ytdlp() {
158        missing.push("yt-dlp".into());
159    }
160    if !check_ffmpeg() {
161        missing.push("ffmpeg".into());
162    }
163    missing
164}