portable-ffmpeg 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,29 @@
1
+ """Static FFmpeg binaries for Windows, macOS, and Linux.
2
+
3
+ This package automatically downloads and manages static FFmpeg binaries
4
+ for cross-platform use without requiring system installation.
5
+ """
6
+
7
+ from .core import (
8
+ add_to_path,
9
+ clear_cache,
10
+ get_ffmpeg,
11
+ print_paths,
12
+ remove_from_path,
13
+ run_ffmpeg,
14
+ run_ffprobe,
15
+ )
16
+ from .enums import Architectures, FFmpegVersions, OperatingSystems
17
+
18
+ __all__ = [
19
+ "Architectures",
20
+ "FFmpegVersions",
21
+ "OperatingSystems",
22
+ "add_to_path",
23
+ "clear_cache",
24
+ "get_ffmpeg",
25
+ "print_paths",
26
+ "remove_from_path",
27
+ "run_ffmpeg",
28
+ "run_ffprobe",
29
+ ]
@@ -0,0 +1,106 @@
1
+ """Configuration for FFmpeg download URLs by platform and architecture."""
2
+
3
+ from .downloaders import (
4
+ BaseFFmpegDownloader,
5
+ FFmpegDownloadSingleTar,
6
+ FFmpegDownloadSingleZip,
7
+ FFmpegDownloadTwoZips,
8
+ )
9
+ from .enums import Architectures, FFmpegVersions, OperatingSystems
10
+
11
+ # Download URLs for different platforms, architectures, and versions
12
+ DOWNLOAD_URLS: dict[
13
+ OperatingSystems,
14
+ dict[Architectures, dict[FFmpegVersions, BaseFFmpegDownloader]],
15
+ ] = {
16
+ OperatingSystems.WINDOWS: {
17
+ Architectures.AMD64: {
18
+ FFmpegVersions.LATEST: FFmpegDownloadSingleZip(
19
+ url="https://www.gyan.dev/ffmpeg/builds/ffmpeg-release-essentials.zip",
20
+ ffmpeg_name="ffmpeg.exe",
21
+ ffprobe_name="ffprobe.exe",
22
+ ),
23
+ FFmpegVersions.V7: FFmpegDownloadSingleZip(
24
+ url="https://www.gyan.dev/ffmpeg/builds/packages/ffmpeg-7.1.1-essentials_build.zip",
25
+ ffmpeg_name="ffmpeg.exe",
26
+ ffprobe_name="ffprobe.exe",
27
+ ),
28
+ },
29
+ },
30
+ OperatingSystems.OSX: {
31
+ Architectures.AMD64: {
32
+ FFmpegVersions.LATEST: FFmpegDownloadTwoZips(
33
+ ffmpeg_url="https://www.osxexperts.net/ffmpeg71intel.zip",
34
+ ffprobe_url="https://www.osxexperts.net/ffprobe71intel.zip",
35
+ ffmpeg_name="ffmpeg",
36
+ ffprobe_name="ffprobe",
37
+ ),
38
+ FFmpegVersions.V7: FFmpegDownloadTwoZips(
39
+ ffmpeg_url="https://www.osxexperts.net/ffmpeg71intel.zip",
40
+ ffprobe_url="https://www.osxexperts.net/ffprobe71intel.zip",
41
+ ffmpeg_name="ffmpeg",
42
+ ffprobe_name="ffprobe",
43
+ ),
44
+ },
45
+ Architectures.ARM64: {
46
+ FFmpegVersions.LATEST: FFmpegDownloadTwoZips(
47
+ ffmpeg_url="https://www.osxexperts.net/ffmpeg711arm.zip",
48
+ ffprobe_url="https://www.osxexperts.net/ffprobe711arm.zip",
49
+ ffmpeg_name="ffmpeg",
50
+ ffprobe_name="ffprobe",
51
+ ),
52
+ FFmpegVersions.V7: FFmpegDownloadTwoZips(
53
+ ffmpeg_url="https://www.osxexperts.net/ffmpeg711arm.zip",
54
+ ffprobe_url="https://www.osxexperts.net/ffprobe711arm.zip",
55
+ ffmpeg_name="ffmpeg",
56
+ ffprobe_name="ffprobe",
57
+ ),
58
+ },
59
+ },
60
+ OperatingSystems.LINUX: {
61
+ Architectures.AMD64: {
62
+ FFmpegVersions.LATEST: FFmpegDownloadSingleTar(
63
+ url="https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz",
64
+ ffmpeg_name="ffmpeg",
65
+ ffprobe_name="ffprobe",
66
+ ),
67
+ FFmpegVersions.V7: FFmpegDownloadSingleTar(
68
+ url="https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz",
69
+ ffmpeg_name="ffmpeg",
70
+ ffprobe_name="ffprobe",
71
+ ),
72
+ FFmpegVersions.V6: FFmpegDownloadSingleTar(
73
+ url="https://johnvansickle.com/ffmpeg/old-releases/ffmpeg-6.0.1-amd64-static.tar.xz",
74
+ ffmpeg_name="ffmpeg",
75
+ ffprobe_name="ffprobe",
76
+ ),
77
+ FFmpegVersions.V5: FFmpegDownloadSingleTar(
78
+ url="https://johnvansickle.com/ffmpeg/old-releases/ffmpeg-5.1.1-amd64-static.tar.xz",
79
+ ffmpeg_name="ffmpeg",
80
+ ffprobe_name="ffprobe",
81
+ ),
82
+ },
83
+ Architectures.ARM64: {
84
+ FFmpegVersions.LATEST: FFmpegDownloadSingleTar(
85
+ url="https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-arm64-static.tar.xz",
86
+ ffmpeg_name="ffmpeg",
87
+ ffprobe_name="ffprobe",
88
+ ),
89
+ FFmpegVersions.V7: FFmpegDownloadSingleTar(
90
+ url="https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-arm64-static.tar.xz",
91
+ ffmpeg_name="ffmpeg",
92
+ ffprobe_name="ffprobe",
93
+ ),
94
+ FFmpegVersions.V6: FFmpegDownloadSingleTar(
95
+ url="https://johnvansickle.com/ffmpeg/old-releases/ffmpeg-6.0.1-arm64-static.tar.xz",
96
+ ffmpeg_name="ffmpeg",
97
+ ffprobe_name="ffprobe",
98
+ ),
99
+ FFmpegVersions.V5: FFmpegDownloadSingleTar(
100
+ url="https://johnvansickle.com/ffmpeg/old-releases/ffmpeg-5.1.1-arm64-static.tar.xz",
101
+ ffmpeg_name="ffmpeg",
102
+ ffprobe_name="ffprobe",
103
+ ),
104
+ },
105
+ },
106
+ }
@@ -0,0 +1,149 @@
1
+ """Core functionality for managing FFmpeg binaries."""
2
+
3
+ import os
4
+ import shutil
5
+ import subprocess
6
+ import sys
7
+ import threading
8
+ from pathlib import Path
9
+
10
+ from .config import DOWNLOAD_URLS
11
+ from .enums import Architectures, FFmpegVersions, OperatingSystems
12
+
13
+ CACHE_DIR = Path(__file__).parent / "binaries"
14
+ _download_lock = threading.Lock()
15
+ _path_lock = threading.Lock()
16
+
17
+
18
+ def get_ffmpeg(version: FFmpegVersions = FFmpegVersions.LATEST) -> tuple[Path, Path]:
19
+ """Download and return paths to static ffmpeg and ffprobe binaries.
20
+
21
+ Args:
22
+ version: FFmpeg major version to download (default: latest).
23
+
24
+ Returns:
25
+ Tuple containing paths to ffmpeg and ffprobe binaries.
26
+
27
+ """
28
+ system = OperatingSystems.from_current_system()
29
+ arch = Architectures.from_current_architecture()
30
+
31
+ # Create cache directory inside the package
32
+ CACHE_DIR.mkdir(parents=True, exist_ok=True)
33
+
34
+ # Get platform-specific configuration
35
+ if system not in DOWNLOAD_URLS:
36
+ msg = f"Unsupported operating system: {system}"
37
+ raise ValueError(msg)
38
+
39
+ if arch not in DOWNLOAD_URLS[system]:
40
+ msg = f"Unsupported {system} architecture: {arch}"
41
+ raise ValueError(msg)
42
+
43
+ if version not in DOWNLOAD_URLS[system][arch]:
44
+ msg = f"Unsupported FFmpeg version {version} for {system} {arch}"
45
+ raise ValueError(msg)
46
+
47
+ config = DOWNLOAD_URLS[system][arch][version]
48
+
49
+ # Check if binaries already exist in cache
50
+ cache_subdir = f"{system.value}-{arch.value}-{version.value}"
51
+ platform_cache_dir = CACHE_DIR / cache_subdir
52
+ ffmpeg_path = platform_cache_dir / config.ffmpeg_name
53
+ ffprobe_path = platform_cache_dir / config.ffprobe_name
54
+
55
+ with _download_lock:
56
+ if not ffmpeg_path.exists() or not ffprobe_path.exists():
57
+ # Use lock to prevent concurrent downloads
58
+ print(f"Downloading FFmpeg {version.value} binaries for {system} {arch}...")
59
+ # Create platform-specific cache directory
60
+
61
+ # Handle corrupted cache (file exists where directory should be)
62
+ if platform_cache_dir.exists() and not platform_cache_dir.is_dir():
63
+ platform_cache_dir.unlink()
64
+
65
+ platform_cache_dir.mkdir(parents=True, exist_ok=True)
66
+
67
+ # Download and extract binaries using dataclass method
68
+ ffmpeg_path, ffprobe_path = config.download_files(platform_cache_dir)
69
+
70
+ return ffmpeg_path, ffprobe_path
71
+
72
+
73
+ def clear_cache() -> None:
74
+ """Clear the cached FFmpeg binaries.
75
+
76
+ This removes all downloaded binaries from the cache directory,
77
+ forcing them to be re-downloaded on the next call to get_ffmpeg().
78
+ """
79
+ if CACHE_DIR.exists():
80
+ shutil.rmtree(CACHE_DIR)
81
+
82
+
83
+ def add_to_path(*, weak: bool = False, version: FFmpegVersions = FFmpegVersions.LATEST) -> None:
84
+ """Add FFmpeg binaries to system PATH.
85
+
86
+ Args:
87
+ weak: If True, only add to PATH if ffmpeg is not already available.
88
+ If False, always add to PATH (taking precedence).
89
+ version: FFmpeg major version to use (default: latest).
90
+
91
+ """
92
+ with _path_lock:
93
+ if weak and shutil.which("ffmpeg") is not None:
94
+ return # ffmpeg already available, don't override
95
+
96
+ ffmpeg_path, _ = get_ffmpeg(version)
97
+ bin_dir = str(ffmpeg_path.parent)
98
+
99
+ current_path = os.environ.get("PATH", "")
100
+ if bin_dir not in current_path:
101
+ os.environ["PATH"] = f"{bin_dir}{os.pathsep}{current_path}"
102
+
103
+
104
+ def remove_from_path(version: FFmpegVersions = FFmpegVersions.LATEST) -> None:
105
+ """Remove FFmpeg binaries from system PATH.
106
+
107
+ Args:
108
+ version: FFmpeg major version to use (default: latest).
109
+
110
+ """
111
+ with _path_lock:
112
+ ffmpeg_path, _ = get_ffmpeg(version)
113
+ bin_dir = str(ffmpeg_path.parent)
114
+
115
+ current_path = os.environ.get("PATH", "")
116
+ path_parts = current_path.split(os.pathsep)
117
+
118
+ # Remove our binary directory from PATH
119
+ new_path_parts = [part for part in path_parts if part != bin_dir]
120
+ os.environ["PATH"] = os.pathsep.join(new_path_parts)
121
+
122
+
123
+ def run_ffmpeg(version: FFmpegVersions = FFmpegVersions.LATEST) -> None:
124
+ """Entry point to run ffmpeg binary directly.
125
+
126
+ Args:
127
+ version: FFmpeg major version to use (default: latest).
128
+
129
+ """
130
+ ffmpeg_path, _ = get_ffmpeg(version)
131
+ subprocess.run([str(ffmpeg_path)] + sys.argv[1:], check=False)
132
+
133
+
134
+ def run_ffprobe(version: FFmpegVersions = FFmpegVersions.LATEST) -> None:
135
+ """Entry point to run ffprobe binary directly.
136
+
137
+ Args:
138
+ version: FFmpeg major version to use (default: latest).
139
+
140
+ """
141
+ _, ffprobe_path = get_ffmpeg(version)
142
+ subprocess.run([str(ffprobe_path)] + sys.argv[1:], check=False)
143
+
144
+
145
+ def print_paths() -> None:
146
+ """Print the paths to the FFmpeg and FFprobe binaries."""
147
+ ffmpeg_path, ffprobe_path = get_ffmpeg()
148
+ print(f"FFmpeg: {ffmpeg_path}")
149
+ print(f"FFprobe: {ffprobe_path}")
@@ -0,0 +1,155 @@
1
+ """Download classes for different FFmpeg binary formats."""
2
+
3
+ import sys
4
+ import tarfile
5
+ import tempfile
6
+ import urllib.request
7
+ import zipfile
8
+ from abc import ABC, abstractmethod
9
+ from dataclasses import dataclass
10
+ from pathlib import Path
11
+
12
+
13
+ def _download_file(url: str, file_path: Path | str) -> None:
14
+ """Download a file from a URL."""
15
+ print(f"🔽 Downloading from {url}")
16
+ last_value = 0
17
+
18
+ def reporthook(block_num: int, block_size: int, total_size: int) -> None:
19
+ nonlocal last_value
20
+ if total_size > 0:
21
+ downloaded = block_num * block_size
22
+ percent = downloaded * 100 // total_size
23
+ if percent != last_value:
24
+ last_value = percent
25
+ if percent == 100:
26
+ sys.stdout.write("\r✅ Download complete!\n")
27
+ else:
28
+ sys.stdout.write(f"\r🔽 {percent:3d}%")
29
+ sys.stdout.flush()
30
+ else:
31
+ total = block_num * block_size
32
+ if last_value != total:
33
+ last_value = total
34
+ sys.stdout.write(f"\r🔽 {block_num * block_size} bytes")
35
+ sys.stdout.flush()
36
+
37
+ # Kick off download
38
+ urllib.request.urlretrieve(url, file_path, reporthook)
39
+
40
+
41
+ def _extract_zip_files(zip_file: Path, outfolder: Path, target_names: list[str]) -> list[Path]:
42
+ """Extract specific files from a zip archive."""
43
+ extracted_files = []
44
+ with zipfile.ZipFile(zip_file, "r") as zip_ref:
45
+ for member in zip_ref.namelist():
46
+ filename = Path(member).name
47
+ if filename in target_names:
48
+ # Extract the file to outfolder with just the filename
49
+ member_data = zip_ref.read(member)
50
+ output_path = outfolder / filename
51
+ output_path.write_bytes(member_data)
52
+ # Set executable permissions on Unix systems
53
+ if sys.platform != "win32":
54
+ output_path.chmod(0o755)
55
+ extracted_files.append(output_path)
56
+ return extracted_files
57
+
58
+
59
+ def _extract_tar_files(tar_file: Path, outfolder: Path, target_names: list[str]) -> list[Path]:
60
+ """Extract specific files from a tar archive."""
61
+ extracted_files = []
62
+ with tarfile.open(tar_file, "r:*") as tar_ref:
63
+ for member in tar_ref.getmembers():
64
+ if member.isfile():
65
+ filename = Path(member.name).name
66
+ if filename in target_names:
67
+ # Extract the file to outfolder with just the filename
68
+ extracted_file = tar_ref.extractfile(member)
69
+ if extracted_file:
70
+ output_path = outfolder / filename
71
+ output_path.write_bytes(extracted_file.read())
72
+ # Set executable permissions on Unix systems
73
+ if sys.platform != "win32":
74
+ output_path.chmod(0o755)
75
+ extracted_files.append(output_path)
76
+ return extracted_files
77
+
78
+
79
+ @dataclass
80
+ class BaseFFmpegDownloader(ABC):
81
+ """Base class for downloading FFmpeg binaries."""
82
+
83
+ ffmpeg_name: str
84
+ ffprobe_name: str
85
+
86
+ @abstractmethod
87
+ def download_files(self, outfolder: Path) -> tuple[Path, Path]:
88
+ """Download and extract the FFmpeg and FFprobe binaries."""
89
+
90
+
91
+ @dataclass
92
+ class FFmpegDownloadSingleZip(BaseFFmpegDownloader):
93
+ """Configuration for downloading ffmpeg and ffprobe binaries."""
94
+
95
+ url: str
96
+
97
+ def download_files(self, outfolder: Path) -> tuple[Path, Path]:
98
+ """Download and extract zip archive containing both binaries."""
99
+ with tempfile.TemporaryDirectory() as tmp_dir:
100
+ tmp_file = Path(tmp_dir) / "download.zip"
101
+ _download_file(self.url, tmp_file)
102
+
103
+ _extract_zip_files(
104
+ zip_file=tmp_file,
105
+ outfolder=outfolder,
106
+ target_names=[self.ffmpeg_name, self.ffprobe_name],
107
+ )
108
+
109
+ return outfolder / self.ffmpeg_name, outfolder / self.ffprobe_name
110
+
111
+
112
+ @dataclass
113
+ class FFmpegDownloadSingleTar(BaseFFmpegDownloader):
114
+ """Configuration for downloading ffmpeg and ffprobe binaries as a single tar archive."""
115
+
116
+ url: str
117
+
118
+ def download_files(self, outfolder: Path) -> tuple[Path, Path]:
119
+ """Download and extract tar archive containing both binaries."""
120
+ with tempfile.TemporaryDirectory() as tmp_dir:
121
+ tmp_file = Path(tmp_dir) / "download.tar.xz"
122
+ _download_file(self.url, tmp_file)
123
+
124
+ _extract_tar_files(
125
+ tar_file=tmp_file,
126
+ outfolder=outfolder,
127
+ target_names=[self.ffmpeg_name, self.ffprobe_name],
128
+ )
129
+
130
+ return outfolder / self.ffmpeg_name, outfolder / self.ffprobe_name
131
+
132
+
133
+ @dataclass
134
+ class FFmpegDownloadTwoZips(BaseFFmpegDownloader):
135
+ """Configuration for downloading ffmpeg and ffprobe binaries as separate zip files."""
136
+
137
+ ffmpeg_url: str
138
+ ffprobe_url: str
139
+
140
+ def download_files(self, outfolder: Path) -> tuple[Path, Path]:
141
+ """Download and extract two separate zip files for ffmpeg and ffprobe."""
142
+ target_binaries = {self.ffmpeg_url: self.ffmpeg_name, self.ffprobe_url: self.ffprobe_name}
143
+
144
+ for url, target_name in target_binaries.items():
145
+ with tempfile.TemporaryDirectory() as tmp_dir:
146
+ tmp_file = Path(tmp_dir) / f"{target_name}.zip"
147
+ _download_file(url, tmp_file)
148
+
149
+ _extract_zip_files(
150
+ zip_file=tmp_file,
151
+ outfolder=outfolder,
152
+ target_names=[target_name],
153
+ )
154
+
155
+ return outfolder / self.ffmpeg_name, outfolder / self.ffprobe_name
@@ -0,0 +1,54 @@
1
+ """Operating system and architecture enums for FFmpeg binary selection."""
2
+
3
+ import platform
4
+ from enum import Enum
5
+
6
+
7
+ class OperatingSystems(Enum):
8
+ """Supported operating systems for ffmpeg binaries."""
9
+
10
+ WINDOWS = "windows"
11
+ OSX = "osx"
12
+ LINUX = "linux"
13
+
14
+ @classmethod
15
+ def from_current_system(cls) -> "OperatingSystems":
16
+ """Get the current operating system."""
17
+ system = platform.system().lower()
18
+ if system == "windows":
19
+ return cls.WINDOWS
20
+ if system == "darwin":
21
+ return cls.OSX
22
+ if system == "linux":
23
+ return cls.LINUX
24
+
25
+ msg = f"Unsupported operating system: {system}"
26
+ raise ValueError(msg)
27
+
28
+
29
+ class Architectures(Enum):
30
+ """Supported architectures for ffmpeg binaries."""
31
+
32
+ AMD64 = "amd64"
33
+ ARM64 = "arm64"
34
+
35
+ @classmethod
36
+ def from_current_architecture(cls) -> "Architectures":
37
+ """Get the current architecture."""
38
+ machine = platform.machine().lower()
39
+ if machine in ["x86_64", "amd64"]:
40
+ return cls.AMD64
41
+ if machine in ["aarch64", "arm64"]:
42
+ return cls.ARM64
43
+
44
+ msg = f"Unsupported architecture: {machine}"
45
+ raise ValueError(msg)
46
+
47
+
48
+ class FFmpegVersions(Enum):
49
+ """Supported FFmpeg major versions."""
50
+
51
+ LATEST = "latest"
52
+ V7 = "7"
53
+ V6 = "6"
54
+ V5 = "5"
File without changes
@@ -0,0 +1,168 @@
1
+ Metadata-Version: 2.4
2
+ Name: portable-ffmpeg
3
+ Version: 0.1.0
4
+ Summary: Downloads static ffmpeg builds for Windows, macOS, and Linux
5
+ Project-URL: Homepage, https://github.com/mrkbac/portable-ffmpeg
6
+ Project-URL: Repository, https://github.com/mrkbac/portable-ffmpeg
7
+ Project-URL: Issues, https://github.com/mrkbac/portable-ffmpeg/issues
8
+ Author-email: Marko Bausch <60338487+mrkbac@users.noreply.github.com>
9
+ License: MIT
10
+ Keywords: audio,ffmpeg,ffprobe,multimedia,portable,static,video
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: Multimedia :: Sound/Audio
21
+ Classifier: Topic :: Multimedia :: Video
22
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
+ Requires-Python: >=3.10
24
+ Provides-Extra: dev
25
+ Requires-Dist: pre-commit; extra == 'dev'
26
+ Requires-Dist: pyright; extra == 'dev'
27
+ Requires-Dist: pytest-cov; extra == 'dev'
28
+ Requires-Dist: pytest>=8.4.0; extra == 'dev'
29
+ Requires-Dist: ruff; extra == 'dev'
30
+ Description-Content-Type: text/markdown
31
+
32
+ # Portable FFmpeg
33
+
34
+ Downloads static ffmpeg builds for Windows, macOS, and Linux.
35
+
36
+ - Supports Windows, macOS, and Linux
37
+ - Supports x86_64 architecture on all platforms, ARM64 on macOS and Linux
38
+ - Automatic platform detection and binary caching
39
+
40
+ ## Usage
41
+
42
+ ### Python API
43
+
44
+ ```python
45
+ from portable_ffmpeg import get_ffmpeg
46
+ ffmpeg_path, ffprobe_path = get_ffmpeg()
47
+ print(ffmpeg_path) # Path to ffmpeg executable
48
+ ```
49
+
50
+ ### Command Line Interface
51
+
52
+ After installation, you can use `static_ffmpeg` and `static_ffprobe` directly from the command line:
53
+
54
+ ```bash
55
+ # Use static ffmpeg
56
+ static_ffmpeg -i input.mp4 output.mp3
57
+
58
+ # Use static ffprobe
59
+ static_ffprobe -v quiet -print_format json -show_format input.mp4
60
+
61
+ # Print paths to the binaries
62
+ print_paths
63
+ ```
64
+
65
+ The CLI commands automatically download and cache the appropriate static binaries for your platform.
66
+
67
+ ### PATH Management
68
+
69
+ You can also programmatically manage your PATH:
70
+
71
+ ```python
72
+ from portable_ffmpeg import add_to_path, remove_from_path
73
+
74
+ # Add FFmpeg binaries to PATH
75
+ add_to_path()
76
+
77
+ # Add only if FFmpeg is not already available (weak mode)
78
+ add_to_path(weak=True)
79
+
80
+ # Remove from PATH
81
+ remove_from_path()
82
+ ```
83
+
84
+ ## Sources of Static Builds
85
+
86
+ This package downloads static FFmpeg binaries from the following trusted sources:
87
+
88
+ ### Windows (x86_64 only)
89
+
90
+ - **Source**: [CODEX FFMPEG @ gyan.dev](https://www.gyan.dev/ffmpeg/builds/)
91
+ - **URL**: `https://www.gyan.dev/ffmpeg/builds/ffmpeg-release-essentials.zip`
92
+ - **Description**: Official Windows builds, 64-bit static binaries licensed as GPLv3
93
+
94
+ ### macOS
95
+
96
+ #### Intel (x86_64)
97
+
98
+ - **Source**: [OSXExperts.net](http://www.osxexperts.net/)
99
+ - **URLs**:
100
+ - FFmpeg: `https://www.osxexperts.net/ffmpeg71intel.zip`
101
+ - FFprobe: `https://www.osxexperts.net/ffprobe71intel.zip`
102
+ - **Description**: Static FFmpeg 7.1 binaries for macOS Intel processors
103
+
104
+ #### Apple Silicon (arm64)
105
+
106
+ - **Source**: [OSXExperts.net](http://www.osxexperts.net/)
107
+ - **URLs**:
108
+ - FFmpeg: `https://www.osxexperts.net/ffmpeg711arm.zip`
109
+ - FFprobe: `https://www.osxexperts.net/ffprobe711arm.zip`
110
+ - **Description**: Static FFmpeg 7.1.1 binaries optimized for Apple Silicon processors
111
+
112
+ ### Linux (x86_64 and arm64)
113
+
114
+ - **Source**: [John Van Sickle - FFmpeg Static Builds](https://johnvansickle.com/ffmpeg/)
115
+ - **URLs**:
116
+ - x86_64: `https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz`
117
+ - arm64: `https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-arm64-static.tar.xz`
118
+ - **Description**: Latest versions of FFmpeg for Linux kernels 3.2.0+, statically linked
119
+
120
+
121
+ ### Alternative Sources
122
+
123
+ The following sources provide FFmpeg static builds but are not currently supported by this package. They could be considered for future releases:
124
+
125
+ - **[FFmpeg Martin Riedl](https://ffmpeg.martin-riedl.de/)**
126
+ - **Platforms**: macOS, Linux
127
+ - **Architectures**: x86_64, ARM64
128
+ - **Description**: Regularly updated static builds with comprehensive codec support
129
+
130
+ - **[BtbN FFmpeg Builds](https://github.com/BtbN/FFmpeg-Builds)**
131
+ - **Platforms**: Windows, Linux
132
+ - **Architectures**: x86_64, ARM64, x86 (Windows only)
133
+ - **Description**: GitHub Actions-built binaries with automated releases
134
+
135
+ - **[EverMeet FFmpeg](https://evermeet.cx/ffmpeg/)**
136
+ - **Platforms**: macOS only
137
+ - **Architectures**: x86_64
138
+ - **Description**: Long-standing macOS static builds provider
139
+
140
+ - **[FreeBSD FFmpeg Static](https://github.com/Thefrank/ffmpeg-static-freebsd/)**
141
+ - **Platforms**: FreeBSD only
142
+ - **Architectures**: Various
143
+ - **Description**: Static builds specifically for FreeBSD systems
144
+
145
+ - **[John Van Sickle Extended](https://johnvansickle.com/ffmpeg/)**
146
+ - **Additional Architectures**: i686, armhf, armel (beyond the currently supported x86_64 and ARM64)
147
+ - **Description**: Comprehensive Linux support including older and embedded architectures
148
+
149
+ ## Caching
150
+
151
+ Downloaded binaries are cached in the package's `binaries/` directory to avoid re-downloading. Each virtual environment gets its own copy of the binaries.
152
+
153
+ The cache is organized by platform and architecture (e.g., `linux-amd64`, `windows-amd64`, `osx-arm64`).
154
+
155
+ ### Cache Management
156
+
157
+ ```python
158
+ from portable_ffmpeg import clear_cache
159
+
160
+ # Clear all cached binaries
161
+ clear_cache()
162
+ ```
163
+
164
+ Or use the command line:
165
+
166
+ ```bash
167
+ clear_cache
168
+ ```
@@ -0,0 +1,10 @@
1
+ portable_ffmpeg/__init__.py,sha256=krQpv5t46LGAk9_lwbpGjViMbbnJkyaSPaeq-6TwUqs,626
2
+ portable_ffmpeg/config.py,sha256=jvLmGk0NWVzflZd8xgslIqWjU06bw7XUGmwqiYGM5AQ,4509
3
+ portable_ffmpeg/core.py,sha256=H6Q1XhgE1D87UwiuSSBZeg4J2t8EWN6juLl9cXcLyCc,4918
4
+ portable_ffmpeg/downloaders.py,sha256=a6NX9FPODujrtpCvZloTOqPv9Th32FqjaYeCgAH9z44,5706
5
+ portable_ffmpeg/enums.py,sha256=FxA4jboyEpBvtwsLAgRaWScToyMttPlyZCGwQZ2uYEY,1353
6
+ portable_ffmpeg/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
+ portable_ffmpeg-0.1.0.dist-info/METADATA,sha256=xlW8wlDUt4zWLgB-blONcOWAVv21rSxbtZCnkDUiSyE,5629
8
+ portable_ffmpeg-0.1.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
9
+ portable_ffmpeg-0.1.0.dist-info/entry_points.txt,sha256=fUJF_ovTj115pznpHnUAkHqY1kUuV2gniiNJqJGxTs4,190
10
+ portable_ffmpeg-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.27.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,5 @@
1
+ [console_scripts]
2
+ clear_cache = portable_ffmpeg:clear_cache
3
+ print_paths = portable_ffmpeg:print_paths
4
+ static_ffmpeg = portable_ffmpeg:run_ffmpeg
5
+ static_ffprobe = portable_ffmpeg:run_ffprobe