Repository: nathanbabcock/ffmpeg-sidecar (language: rust)

Issue:
ffmpeg won't auto-download for Apple Silicon Mac
When running the auto-download for ffmpeg on an M1 mac it fails because its not compatible with non x86 architectures. Is there a way to add it? 


Discussion / hints:
I'd absolutely be open to a pull request for this if you're up for it.

I found this website for OSX M1 binaries: http://www.osxexperts.net/. Other projects such as [`ffmpeg-static` use this as their source](https://github.com/eugeneware/ffmpeg-static/issues/49).
I'm maintaining home made builds of static ffmpeg builds here: https://github.com/mi-g/ffmpeg-static-builder/releases

These are not builds gathered from all over the place. They are all built statically, with the same dependencies, and tested.
Might be a better fit for ffmpeg-sidecar's download feature.

I'm just missing a Mac Intel build (need access to a older Mac to do that).

This is part of a bigger project and it's supposed to be maintained long term (years).
Thanks @nathanbabcock for building this project! auto-download for Apple Silicon would be great!  

Btw ffmpeg official website https://ffmpeg.org/download.html#build-mac recommends  https://evermeet.cx/ffmpeg/ for macOS static build.

Fix patch:
diff --git a/.github/workflows/mac-m1.yml b/.github/workflows/mac-m1.yml
new file mode 100644
index 0000000..b16969e
--- /dev/null
+++ b/.github/workflows/mac-m1.yml
@@ -0,0 +1,22 @@
+name: Mac M1
+
+on:
+  push:
+    branches: ['main']
+  pull_request:
+    branches: ['main']
+
+env:
+  CARGO_TERM_COLOR: always
+
+jobs:
+  build:
+    # https://github.blog/2023-10-02-introducing-the-new-apple-silicon-powered-m1-macos-larger-runner-for-github-actions/
+    runs-on: macos-13-xlarge
+
+    steps:
+      - uses: actions/checkout@v3
+      - name: Try auto-download
+        run: cargo run --example download_ffmpeg
+      - name: Build
+        run: cargo build --verbose
diff --git a/.github/workflows/mac.yml b/.github/workflows/mac.yml
new file mode 100644
index 0000000..05d8d94
--- /dev/null
+++ b/.github/workflows/mac.yml
@@ -0,0 +1,25 @@
+name: Mac
+
+on:
+  push:
+    branches: [ "main" ]
+  pull_request:
+    branches: [ "main" ]
+
+env:
+  CARGO_TERM_COLOR: always
+
+jobs:
+  build:
+
+    runs-on: macos-latest
+
+    steps:
+    - uses: actions/checkout@v3
+    - uses: FedericoCarboni/setup-ffmpeg@v2
+    - name: Build
+      run: cargo build --verbose
+    - name: Run tests
+      run: cargo test --verbose
+    - name: Try auto-download
+      run: cargo run --example download_ffmpeg
diff --git a/.github/workflows/rust.yml b/.github/workflows/ubuntu.yml
similarity index 75%
rename from .github/workflows/rust.yml
rename to .github/workflows/ubuntu.yml
index c3deddb..f27610e 100644
--- a/.github/workflows/rust.yml
+++ b/.github/workflows/ubuntu.yml
@@ -1,23 +1,25 @@
-name: Rust
-
-on:
-  push:
-    branches: [ "main" ]
-  pull_request:
-    branches: [ "main" ]
-
-env:
-  CARGO_TERM_COLOR: always
-
-jobs:
-  build:
-
-    runs-on: ubuntu-latest
-
-    steps:
-    - uses: actions/checkout@v3
-    - uses: FedericoCarboni/setup-ffmpeg@v2
-    - name: Build
-      run: cargo build --verbose
-    - name: Run tests
-      run: cargo test --verbose
+name: Ubuntu
+
+on:
+  push:
+    branches: [ "main" ]
+  pull_request:
+    branches: [ "main" ]
+
+env:
+  CARGO_TERM_COLOR: always
+
+jobs:
+  build:
+
+    runs-on: ubuntu-latest
+
+    steps:
+    - uses: actions/checkout@v3
+    - uses: FedericoCarboni/setup-ffmpeg@v2
+    - name: Build
+      run: cargo build --verbose
+    - name: Run tests
+      run: cargo test --verbose
+    - name: Try auto-download
+      run: cargo run --example download_ffmpeg
diff --git a/Cargo.toml b/Cargo.toml
index c858170..c5cfb94 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "ffmpeg-sidecar"
-version = "0.4.1"
+version = "0.5.0"
 edition = "2021"
 description = "Wrap a standalone FFmpeg binary in an intuitive Iterator interface."
 authors = ["Nathan Babcock <nathan.r.babcock@gmail.com>"]
diff --git a/examples/download_ffmpeg.rs b/examples/download_ffmpeg.rs
index 6a97434..4c38dc4 100644
--- a/examples/download_ffmpeg.rs
+++ b/examples/download_ffmpeg.rs
@@ -1,46 +1,49 @@
-use ffmpeg_sidecar::{
-  command::ffmpeg_is_installed,
-  download::{check_latest_version, download_ffmpeg_package, ffmpeg_download_url, unpack_ffmpeg},
-  error::Result,
-  paths::sidecar_dir,
-  version::ffmpeg_version,
-};
-
-fn main() -> Result<()> {
-  if ffmpeg_is_installed() {
-    println!("FFmpeg is already installed! 🎉");
-    println!("For demo purposes, we'll re-download and unpack it anyway.");
-    println!("TIP: Use `auto_download()` to skip manual customization.");
-  }
-
-  // Short version without customization:
-  // ```rust
-  // ffmpeg_sidecar::download::auto_download().unwrap();
-  // ```
-
-  // Checking the version number before downloading is actually not necessary,
-  // but it's a good way to check that the download URL is correct.
-  let latest_version = check_latest_version()?;
-  println!("Latest available version: {}", latest_version);
-
-  // These defaults will automatically select the correct download URL for your
-  // platform.
-  let download_url = ffmpeg_download_url()?;
-  let destination = sidecar_dir()?;
-
-  // By default the download will use a `curl` command. You could also write
-  // your own download function and use another package like `reqwest` instead.
-  let archive_path = download_ffmpeg_package(download_url, &destination)?;
-  println!("Downloaded package: {:?}", archive_path);
-
-  // Extraction uses `tar` on all platforms (available in Windows since version 1803)
-  println!("Extracting...");
-  unpack_ffmpeg(&archive_path, &destination)?;
-
-  // Use the freshly installed FFmpeg to check the version number
-  let version = ffmpeg_version()?;
-  println!("FFmpeg version: {}", version);
-
-  println!("Done! 🏁");
-  Ok(())
-}
+use ffmpeg_sidecar::{
+  command::ffmpeg_is_installed,
+  download::{check_latest_version, download_ffmpeg_package, ffmpeg_download_url, unpack_ffmpeg},
+  error::Result,
+  paths::sidecar_dir,
+  version::ffmpeg_version,
+};
+
+fn main() -> Result<()> {
+  if ffmpeg_is_installed() {
+    println!("FFmpeg is already installed! 🎉");
+    println!("For demo purposes, we'll re-download and unpack it anyway.");
+    println!("TIP: Use `auto_download()` to skip manual customization.");
+  }
+
+  // Short version without customization:
+  // ```rust
+  // ffmpeg_sidecar::download::auto_download().unwrap();
+  // ```
+
+  // Checking the version number before downloading is actually not necessary,
+  // but it's a good way to check that the download URL is correct.
+  match check_latest_version() {
+    Ok(version) => println!("Latest available version: {}", version),
+    Err(_) => println!("Skipping version check on this platform."),
+  }
+
+  // These defaults will automatically select the correct download URL for your
+  // platform.
+  let download_url = ffmpeg_download_url()?;
+  let destination = sidecar_dir()?;
+
+  // By default the download will use a `curl` command. You could also write
+  // your own download function and use another package like `reqwest` instead.
+  println!("Downloading from: {:?}", download_url);
+  let archive_path = download_ffmpeg_package(download_url, &destination)?;
+  println!("Downloaded package: {:?}", archive_path);
+
+  // Extraction uses `tar` on all platforms (available in Windows since version 1803)
+  println!("Extracting...");
+  unpack_ffmpeg(&archive_path, &destination)?;
+
+  // Use the freshly installed FFmpeg to check the version number
+  let version = ffmpeg_version()?;
+  println!("FFmpeg version: {}", version);
+
+  println!("Done! 🏁");
+  Ok(())
+}
diff --git a/src/download.rs b/src/download.rs
index 0d8a081..d4901fb 100644
--- a/src/download.rs
+++ b/src/download.rs
@@ -1,245 +1,247 @@
-use std::{
-  fs::{create_dir_all, read_dir, remove_dir_all, remove_file, rename},
-  io::Read,
-  path::{Path, PathBuf},
-  process::{Command, ExitStatus, Stdio},
-};
-
-use crate::{
-  command::ffmpeg_is_installed,
-  error::{Error, Result},
-  paths::sidecar_dir,
-};
-
-pub const UNPACK_DIRNAME: &str = "ffmpeg_release_temp";
-
-/// URL of a manifest file containing the latest published build of FFmpeg. The
-/// correct URL for the target platform is baked in at compile time.
-pub fn ffmpeg_manifest_url() -> Result<&'static str> {
-  if cfg!(not(target_arch = "x86_64")) {
-    return Err(Error::msg(
-      "Downloads must be manually provided for non-x86_64 architectures",
-    ));
-  }
-
-  if cfg!(target_os = "windows") {
-    Ok("https://www.gyan.dev/ffmpeg/builds/release-version")
-  } else if cfg!(target_os = "macos") {
-    Ok("https://evermeet.cx/ffmpeg/info/ffmpeg/release")
-  } else if cfg!(target_os = "linux") {
-    Ok("https://johnvansickle.com/ffmpeg/release-readme.txt")
-  } else {
-    Err(Error::msg("Unsupported platform"))
-  }
-}
-
-/// URL for the latest published FFmpeg release. The correct URL for the target
-/// platform is baked in at compile time.
-pub fn ffmpeg_download_url() -> Result<&'static str> {
-  if cfg!(not(target_arch = "x86_64")) {
-    return Err(Error::msg(
-      "Downloads must be manually provided for non-x86_64 architectures",
-    ));
-  }
-
-  if cfg!(target_os = "windows") {
-    Ok("https://www.gyan.dev/ffmpeg/builds/ffmpeg-release-essentials.zip")
-  } else if cfg!(target_os = "macos") {
-    Ok("https://evermeet.cx/ffmpeg/getrelease")
-  } else if cfg!(target_os = "linux") {
-    Ok("https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz")
-  } else {
-    Err(Error::msg("Unsupported platform"))
-  }
-}
-
-/// Check if FFmpeg is installed, and if it's not, download and unpack it.
-/// Automatically selects the correct binaries for Windows, Linux, and MacOS.
-/// The binaries will be placed in the same directory as the Rust executable.
-///
-/// If FFmpeg is already installed, the method exits early without downloading
-/// anything.
-pub fn auto_download() -> Result<()> {
-  if ffmpeg_is_installed() {
-    return Ok(());
-  }
-
-  let download_url = ffmpeg_download_url()?;
-  let destination = sidecar_dir()?;
-  let archive_path = download_ffmpeg_package(download_url, &destination)?;
-  unpack_ffmpeg(&archive_path, &destination)?;
-
-  match ffmpeg_is_installed() {
-    false => Err(Error::msg(
-      "FFmpeg failed to install, please install manually.",
-    )),
-    true => Ok(()),
-  }
-}
-
-/// Parse the the MacOS version number from a JSON string manifest file.
-///
-/// Example input: https://evermeet.cx/ffmpeg/info/ffmpeg/release
-///
-/// ```rust
-/// use ffmpeg_sidecar::download::parse_macos_version;
-/// let json_string = "{\"name\":\"ffmpeg\",\"type\":\"release\",\"version\":\"6.0\",...}";
-/// let parsed = parse_macos_version(&json_string).unwrap();
-/// assert!(parsed == "6.0");
-/// ```
-pub fn parse_macos_version(version: &str) -> Option<String> {
-  version
-    .split("\"version\":")
-    .nth(1)?
-    .trim()
-    .split('\"')
-    .nth(1)
-    .map(|s| s.to_string())
-}
-
-/// Parse the the Linux version number from a long manifest text file.
-///
-/// Example input: https://johnvansickle.com/ffmpeg/release-readme.txt
-///
-/// ```rust
-/// use ffmpeg_sidecar::download::parse_linux_version;
-/// let json_string = "build: ffmpeg-5.1.1-amd64-static.tar.xz\nversion: 5.1.1\n\ngcc: 8.3.0";
-/// let parsed = parse_linux_version(&json_string).unwrap();
-/// assert!(parsed == "5.1.1");
-/// ```
-pub fn parse_linux_version(version: &str) -> Option<String> {
-  version
-    .split("version:")
-    .nth(1)?
-    .split_whitespace()
-    .next()
-    .map(|s| s.to_string())
-}
-
-/// Invoke cURL on the command line to download a file, returning it as a string.
-pub fn curl(url: &str) -> Result<String> {
-  let mut child = Command::new("curl")
-    .args(["-L", url])
-    .stderr(Stdio::null())
-    .stdout(Stdio::piped())
-    .spawn()?;
-
-  let stdout = child
-    .stdout
-    .take()
-    .ok_or_else(|| Error::msg("Failed to get stdout"))?;
-
-  let mut string = String::new();
-  std::io::BufReader::new(stdout).read_to_string(&mut string)?;
-  Ok(string)
-}
-
-/// Invoke cURL on the command line to download a file, writing to a file.
-pub fn curl_to_file(url: &str, destination: &str) -> Result<ExitStatus> {
-  Command::new("curl")
-    .args(["-L", url])
-    .args(["-o", destination])
-    .status()
-    .map_err(Error::from)
-}
-
-/// Makes an HTTP request to obtain the latest version available online,
-/// automatically choosing the correct URL for the current platform.
-pub fn check_latest_version() -> Result<String> {
-  let string = curl(ffmpeg_manifest_url()?)?;
-
-  if cfg!(target_os = "windows") {
-    Ok(string)
-  } else if cfg!(target_os = "macos") {
-    Ok(parse_macos_version(&string).ok_or("failed to parse version number (macos variant)")?)
-  } else if cfg!(target_os = "linux") {
-    Ok(parse_linux_version(&string).ok_or("failed to parse version number (linux variant)")?)
-  } else {
-    Err(Error::msg("Unsupported platform"))
-  }
-}
-
-/// Invoke `curl` to download an archive (ZIP on windows, TAR on linux and mac)
-/// from the latest published release online.
-pub fn download_ffmpeg_package(url: &str, download_dir: &Path) -> Result<PathBuf> {
-  let filename = Path::new(url)
-    .file_name()
-    .ok_or_else(|| Error::msg("Failed to get filename"))?;
-
-  let archive_path = download_dir.join(filename);
-
-  let archive_filename = archive_path.to_str().ok_or("invalid download path")?;
-
-  let exit_status = curl_to_file(url, archive_filename)?;
-
-  if !exit_status.success() {
-    return Err(Error::msg("Failed to download ffmpeg"));
-  }
-
-  Ok(archive_path)
-}
-
-/// After downloading, unpacks the archive to a folder, moves the binaries to
-/// their final location, and deletes the archive and temporary folder.
-pub fn unpack_ffmpeg(from_archive: &PathBuf, binary_folder: &Path) -> Result<()> {
-  let temp_dirname = UNPACK_DIRNAME;
-  let temp_folder = binary_folder.join(temp_dirname);
-  create_dir_all(&temp_folder)?;
-
-  // Extract archive
-  Command::new("tar")
-    .arg("-xf")
-    .arg(from_archive)
-    .current_dir(&temp_folder)
-    .status()?
-    .success()
-    .then_some(())
-    .ok_or("Failed to unpack ffmpeg")?;
-
-  // Move binaries
-  let inner_folder = read_dir(&temp_folder)?
-    .next()
-    .ok_or("Failed to get inner folder")??;
-
-  if !inner_folder.file_type()?.is_dir() {
-    return Err(Error::msg("No top level directory inside archive"));
-  }
-
-  let (ffmpeg, ffplay, ffprobe) = if cfg!(target_os = "windows") {
-    (
-      inner_folder.path().join("bin/ffmpeg.exe"),
-      inner_folder.path().join("bin/ffplay.exe"),
-      inner_folder.path().join("bin/ffprobe.exe"),
-    )
-  } else if cfg!(any(target_os = "linux", target_os = "macos")) {
-    (
-      inner_folder.path().join("./ffmpeg"),
-      inner_folder.path().join("./ffplay"), // <- this typically only exists in Windows builds
-      inner_folder.path().join("./ffprobe"),
-    )
-  } else {
-    return Err(Error::msg("Unsupported platform"));
-  };
-
-  // Move binaries
-  rename(&ffmpeg, binary_folder.join(ffmpeg.file_name().ok_or(())?))?;
-
-  if ffprobe.exists() {
-    rename(&ffprobe, binary_folder.join(ffprobe.file_name().ok_or(())?))?;
-  }
-
-  if ffplay.exists() {
-    rename(&ffplay, binary_folder.join(ffplay.file_name().ok_or(())?))?;
-  }
-
-  // Delete archive and unpacked files
-  if temp_folder.exists() {
-    remove_dir_all(&temp_folder)?;
-  }
-
-  if from_archive.exists() {
-    remove_file(from_archive)?;
-  }
-
-  Ok(())
-}
+use std::{
+  fs::{create_dir_all, read_dir, remove_dir_all, remove_file, rename},
+  io::Read,
+  path::{Path, PathBuf},
+  process::{Command, ExitStatus, Stdio},
+};
+
+use crate::{
+  command::ffmpeg_is_installed,
+  error::{Error, Result},
+  paths::sidecar_dir,
+};
+
+pub const UNPACK_DIRNAME: &str = "ffmpeg_release_temp";
+
+/// URL of a manifest file containing the latest published build of FFmpeg. The
+/// correct URL for the target platform is baked in at compile time.
+pub fn ffmpeg_manifest_url() -> Result<&'static str> {
+  if cfg!(not(target_arch = "x86_64")) {
+    return Err(Error::msg(
+      "Downloads must be manually provided for non-x86_64 architectures",
+    ));
+  }
+
+  if cfg!(target_os = "windows") {
+    Ok("https://www.gyan.dev/ffmpeg/builds/release-version")
+  } else if cfg!(target_os = "macos") {
+    Ok("https://evermeet.cx/ffmpeg/info/ffmpeg/release")
+  } else if cfg!(target_os = "linux") {
+    Ok("https://johnvansickle.com/ffmpeg/release-readme.txt")
+  } else {
+    Err(Error::msg("Unsupported platform"))
+  }
+}
+
+/// URL for the latest published FFmpeg release. The correct URL for the target
+/// platform is baked in at compile time.
+pub fn ffmpeg_download_url() -> Result<&'static str> {
+  if cfg!(all(target_os = "windows", target_arch = "x86_64")) {
+    Ok("https://www.gyan.dev/ffmpeg/builds/ffmpeg-release-essentials.zip")
+  } else if cfg!(all(target_os = "linux", target_arch = "x86_64")) {
+    Ok("https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz")
+  } else if cfg!(all(target_os = "macos", target_arch = "x86_64")) {
+    Ok("https://evermeet.cx/ffmpeg/getrelease")
+  } else if cfg!(all(target_os = "macos", target_arch = "aarch64")) {
+    Ok("https://www.osxexperts.net/ffmpeg6arm.zip") // Mac M1
+  } else {
+    Err(Error::msg(
+      "Unsupported platform; you can provide your own URL instead and call download_ffmpeg_package directly.",
+    ))
+  }
+}
+
+/// Check if FFmpeg is installed, and if it's not, download and unpack it.
+/// Automatically selects the correct binaries for Windows, Linux, and MacOS.
+/// The binaries will be placed in the same directory as the Rust executable.
+///
+/// If FFmpeg is already installed, the method exits early without downloading
+/// anything.
+pub fn auto_download() -> Result<()> {
+  if ffmpeg_is_installed() {
+    return Ok(());
+  }
+
+  let download_url = ffmpeg_download_url()?;
+  let destination = sidecar_dir()?;
+  let archive_path = download_ffmpeg_package(download_url, &destination)?;
+  unpack_ffmpeg(&archive_path, &destination)?;
+
+  match ffmpeg_is_installed() {
+    false => Err(Error::msg(
+      "FFmpeg failed to install, please install manually.",
+    )),
+    true => Ok(()),
+  }
+}
+
+/// Parse the the MacOS version number from a JSON string manifest file.
+///
+/// Example input: https://evermeet.cx/ffmpeg/info/ffmpeg/release
+///
+/// ```rust
+/// use ffmpeg_sidecar::download::parse_macos_version;
+/// let json_string = "{\"name\":\"ffmpeg\",\"type\":\"release\",\"version\":\"6.0\",...}";
+/// let parsed = parse_macos_version(&json_string).unwrap();
+/// assert!(parsed == "6.0");
+/// ```
+pub fn parse_macos_version(version: &str) -> Option<String> {
+  version
+    .split("\"version\":")
+    .nth(1)?
+    .trim()
+    .split('\"')
+    .nth(1)
+    .map(|s| s.to_string())
+}
+
+/// Parse the the Linux version number from a long manifest text file.
+///
+/// Example input: https://johnvansickle.com/ffmpeg/release-readme.txt
+///
+/// ```rust
+/// use ffmpeg_sidecar::download::parse_linux_version;
+/// let json_string = "build: ffmpeg-5.1.1-amd64-static.tar.xz\nversion: 5.1.1\n\ngcc: 8.3.0";
+/// let parsed = parse_linux_version(&json_string).unwrap();
+/// assert!(parsed == "5.1.1");
+/// ```
+pub fn parse_linux_version(version: &str) -> Option<String> {
+  version
+    .split("version:")
+    .nth(1)?
+    .split_whitespace()
+    .next()
+    .map(|s| s.to_string())
+}
+
+/// Invoke cURL on the command line to download a file, returning it as a string.
+pub fn curl(url: &str) -> Result<String> {
+  let mut child = Command::new("curl")
+    .args(["-L", url])
+    .stderr(Stdio::null())
+    .stdout(Stdio::piped())
+    .spawn()?;
+
+  let stdout = child
+    .stdout
+    .take()
+    .ok_or_else(|| Error::msg("Failed to get stdout"))?;
+
+  let mut string = String::new();
+  std::io::BufReader::new(stdout).read_to_string(&mut string)?;
+  Ok(string)
+}
+
+/// Invoke cURL on the command line to download a file, writing to a file.
+pub fn curl_to_file(url: &str, destination: &str) -> Result<ExitStatus> {
+  Command::new("curl")
+    .args(["-L", url])
+    .args(["-o", destination])
+    .status()
+    .map_err(Error::from)
+}
+
+/// Makes an HTTP request to obtain the latest version available online,
+/// automatically choosing the correct URL for the current platform.
+pub fn check_latest_version() -> Result<String> {
+  let string = curl(ffmpeg_manifest_url()?)?;
+
+  if cfg!(target_os = "windows") {
+    Ok(string)
+  } else if cfg!(target_os = "macos") {
+    Ok(parse_macos_version(&string).ok_or("failed to parse version number (macos variant)")?)
+  } else if cfg!(target_os = "linux") {
+    Ok(parse_linux_version(&string).ok_or("failed to parse version number (linux variant)")?)
+  } else {
+    Err(Error::msg("Unsupported platform"))
+  }
+}
+
+/// Invoke `curl` to download an archive (ZIP on windows, TAR on linux and mac)
+/// from the latest published release online.
+pub fn download_ffmpeg_package(url: &str, download_dir: &Path) -> Result<PathBuf> {
+  let filename = Path::new(url)
+    .file_name()
+    .ok_or_else(|| Error::msg("Failed to get filename"))?;
+
+  let archive_path = download_dir.join(filename);
+
+  let archive_filename = archive_path.to_str().ok_or("invalid download path")?;
+
+  let exit_status = curl_to_file(url, archive_filename)?;
+
+  if !exit_status.success() {
+    return Err(Error::msg("Failed to download ffmpeg"));
+  }
+
+  Ok(archive_path)
+}
+
+/// After downloading, unpacks the archive to a folder, moves the binaries to
+/// their final location, and deletes the archive and temporary folder.
+pub fn unpack_ffmpeg(from_archive: &PathBuf, binary_folder: &Path) -> Result<()> {
+  let temp_dirname = UNPACK_DIRNAME;
+  let temp_folder = binary_folder.join(temp_dirname);
+  create_dir_all(&temp_folder)?;
+
+  // Extract archive
+  Command::new("tar")
+    .arg("-xf")
+    .arg(from_archive)
+    .current_dir(&temp_folder)
+    .status()?
+    .success()
+    .then_some(())
+    .ok_or("Failed to unpack ffmpeg")?;
+
+  // Move binaries
+  let (ffmpeg, ffplay, ffprobe) = if cfg!(target_os = "windows") {
+    let inner_folder = read_dir(&temp_folder)?
+      .next()
+      .ok_or("Failed to get inner folder")??;
+    (
+      inner_folder.path().join("bin/ffmpeg.exe"),
+      inner_folder.path().join("bin/ffplay.exe"),
+      inner_folder.path().join("bin/ffprobe.exe"),
+    )
+  } else if cfg!(target_os = "linux") {
+    let inner_folder = read_dir(&temp_folder)?
+      .next()
+      .ok_or("Failed to get inner folder")??;
+    (
+      inner_folder.path().join("./ffmpeg"),
+      inner_folder.path().join("./ffplay"), // <- no ffplay on linux
+      inner_folder.path().join("./ffprobe"),
+    )
+  } else if cfg!(target_os = "macos") {
+    (
+      temp_folder.join("ffmpeg"),
+      temp_folder.join("ffplay"),  // <-- no ffplay on mac
+      temp_folder.join("ffprobe"), // <-- no ffprobe on mac
+    )
+  } else {
+    return Err(Error::msg("Unsupported platform"));
+  };
+
+  // Move binaries
+  rename(&ffmpeg, binary_folder.join(ffmpeg.file_name().ok_or(())?))?;
+
+  if ffprobe.exists() {
+    rename(&ffprobe, binary_folder.join(ffprobe.file_name().ok_or(())?))?;
+  }
+
+  if ffplay.exists() {
+    rename(&ffplay, binary_folder.join(ffplay.file_name().ok_or(())?))?;
+  }
+
+  // Delete archive and unpacked files
+  if temp_folder.exists() && temp_folder.is_dir() {
+    remove_dir_all(&temp_folder)?;
+  }
+
+  if from_archive.exists() {
+    remove_file(from_archive)?;
+  }
+
+  Ok(())
+}


Test patch: