use std::fmt;
use std::path::PathBuf;
use crate::client::deps::WantedRelease;
use crate::client::deps::github::GitHubFetcher;
use crate::error::{Error, Result};
use crate::utils::fs;
use crate::utils::platform::{Architecture, Platform};
#[derive(Debug, Clone)]
struct Extraction {
executable_path: PathBuf,
binary_extension: String,
}
#[derive(Clone, Debug, Default)]
pub struct BuildFetcher;
impl BuildFetcher {
pub fn new() -> Self {
Self
}
pub async fn fetch_binary(&self) -> Result<WantedRelease> {
tracing::debug!(
platform = ?Platform::detect(),
architecture = ?Architecture::detect(),
"📦 Fetching ffmpeg binary for current platform"
);
let platform = Platform::detect();
let architecture = Architecture::detect();
self.fetch_binary_for_platform(platform, architecture).await
}
pub async fn fetch_binary_for_platform(
&self,
platform: Platform,
architecture: Architecture,
) -> Result<WantedRelease> {
tracing::debug!(
platform = ?platform,
architecture = ?architecture,
repo = "boul2gom/ffmpeg-builds",
"📦 Fetching ffmpeg binary from GitHub"
);
match platform {
Platform::Windows | Platform::Linux | Platform::Mac => {
let fetcher = GitHubFetcher::new("boul2gom", "ffmpeg-builds");
fetcher
.fetch_release_for_platform(platform, architecture, None, |release, platform, architecture| {
let os_str = platform.as_str();
let arch_str = architecture.as_str();
let target_name = format!("ffmpeg-{}-{}.zip", os_str, arch_str);
release.assets.iter().find(|asset| asset.name == target_name)
})
.await
}
_ => Err(Error::NoBinaryRelease {
binary: "ffmpeg".to_string(),
platform,
architecture,
}),
}
}
pub async fn extract_binary(&self, archive: impl Into<PathBuf>) -> Result<PathBuf> {
let archive: PathBuf = archive.into();
tracing::debug!(
archive = ?archive,
platform = ?Platform::detect(),
architecture = ?Architecture::detect(),
"⚙️ Extracting ffmpeg binary from archive"
);
let platform = Platform::detect();
let architecture = Architecture::detect();
self.extract_binary_for_platform(archive, platform, architecture).await
}
pub async fn extract_binary_for_platform(
&self,
archive: impl Into<PathBuf>,
platform: Platform,
architecture: Architecture,
) -> Result<PathBuf> {
let archive: PathBuf = archive.into();
tracing::debug!(
archive = ?archive,
platform = ?platform,
architecture = ?architecture,
"⚙️ Extracting ffmpeg binary for specified platform"
);
let archive_path = archive.clone();
let destination = archive_path.with_extension("");
let extraction_info = self
.get_extraction_info(&platform, &architecture)
.ok_or(Error::NoBinaryRelease {
binary: "ffmpeg".to_string(),
platform: platform.clone(),
architecture: architecture.clone(),
})?;
self.extract_archive(archive_path, destination, extraction_info, platform)
.await
}
fn get_extraction_info(&self, platform: &Platform, architecture: &Architecture) -> Option<Extraction> {
match (platform, architecture) {
(Platform::Windows, _) => Some(Extraction {
executable_path: PathBuf::from("ffmpeg.exe"),
binary_extension: "exe".to_string(),
}),
(Platform::Mac, _) => Some(Extraction {
executable_path: PathBuf::from("ffmpeg"),
binary_extension: "".to_string(),
}),
(Platform::Linux, _) => Some(Extraction {
executable_path: PathBuf::from("ffmpeg"),
binary_extension: "".to_string(),
}),
_ => None,
}
}
async fn extract_archive(
&self,
archive: PathBuf,
destination: PathBuf,
extraction_info: Extraction,
platform: Platform,
) -> Result<PathBuf> {
tracing::debug!(
archive = ?archive,
destination = ?destination,
executable_path = ?extraction_info.executable_path,
platform = ?platform,
"⚙️ Extracting archive and locating ffmpeg binary"
);
fs::extract_zip(&archive, &destination).await?;
let parent = fs::try_parent(&destination)?;
let binary_name = format!(
"ffmpeg{}",
if !extraction_info.binary_extension.is_empty() {
format!(".{}", extraction_info.binary_extension)
} else {
"".to_string()
}
);
let final_binary_path = parent.join(&binary_name);
let extracted_binary = destination.join(&extraction_info.executable_path);
if !extracted_binary.exists() {
return Err(Error::BinaryNotFound {
binary: "ffmpeg".to_string(),
path: extracted_binary,
});
}
tokio::fs::copy(&extracted_binary, &final_binary_path).await?;
tokio::fs::remove_dir_all(destination).await?;
tokio::fs::remove_file(archive).await?;
if matches!(platform, Platform::Mac | Platform::Linux) {
fs::set_executable(final_binary_path.clone()).await?;
}
Ok(final_binary_path)
}
}
impl fmt::Display for BuildFetcher {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "BuildFetcher")
}
}