ffmpeg-update 1.4.1__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.
- ffmpeg_update-1.4.1.dist-info/METADATA +144 -0
- ffmpeg_update-1.4.1.dist-info/RECORD +7 -0
- ffmpeg_update-1.4.1.dist-info/WHEEL +4 -0
- ffmpeg_update-1.4.1.dist-info/entry_points.txt +2 -0
- ffup/__init__.py +1 -0
- ffup/__main__.py +3 -0
- ffup/cli.py +146 -0
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ffmpeg-update
|
|
3
|
+
Version: 1.4.1
|
|
4
|
+
Summary: CLI tool to manage FFmpeg static binaries
|
|
5
|
+
Project-URL: homepage, https://github.com/pantheraleo-7/ffmpeg-update
|
|
6
|
+
Author: Asadullah Shaikh
|
|
7
|
+
Keywords: binaries,ffmpeg,installer,static
|
|
8
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
9
|
+
Classifier: Environment :: Console
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
12
|
+
Classifier: Intended Audience :: Information Technology
|
|
13
|
+
Classifier: Intended Audience :: System Administrators
|
|
14
|
+
Classifier: Operating System :: MacOS
|
|
15
|
+
Classifier: Operating System :: POSIX
|
|
16
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
17
|
+
Classifier: Operating System :: Unix
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Topic :: Multimedia :: Video
|
|
20
|
+
Classifier: Topic :: System :: Installation/Setup
|
|
21
|
+
Classifier: Topic :: System :: Software Distribution
|
|
22
|
+
Classifier: Topic :: Utilities
|
|
23
|
+
Requires-Python: >=3.9
|
|
24
|
+
Requires-Dist: fire
|
|
25
|
+
Requires-Dist: requests
|
|
26
|
+
Requires-Dist: tqdm
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
|
|
29
|
+
# FFUp
|
|
30
|
+
|
|
31
|
+
A Python CLI tool to manage FFmpeg static binaries on Unix-like systems. It fetches the latest builds published by [Martin Riedl](https://ffmpeg.martin-riedl.de/).
|
|
32
|
+
|
|
33
|
+
## Features
|
|
34
|
+
|
|
35
|
+
- Install static builds of FFmpeg, FFprobe, and/or FFplay.
|
|
36
|
+
- Update to the latest version.
|
|
37
|
+
- Check for update.
|
|
38
|
+
- Uninstall from the system.
|
|
39
|
+
- Supports custom installation paths.
|
|
40
|
+
- Supports both **Linux** and **macOS** (Windows builds are not available upstream).
|
|
41
|
+
|
|
42
|
+
## Installation
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
pip install ffmpeg-update
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Usage
|
|
49
|
+
|
|
50
|
+
### With `ffup`:
|
|
51
|
+
|
|
52
|
+
The installation provides the `ffup` command.
|
|
53
|
+
|
|
54
|
+
- **Install**:
|
|
55
|
+
```bash
|
|
56
|
+
ffup install [--dir <custom-path>]
|
|
57
|
+
```
|
|
58
|
+
- **Update**:
|
|
59
|
+
```bash
|
|
60
|
+
ffup update [--dir <custom-path>] [--dry-run]
|
|
61
|
+
```
|
|
62
|
+
- **Check**:
|
|
63
|
+
```bash
|
|
64
|
+
ffup check [--dir <custom-path>]
|
|
65
|
+
```
|
|
66
|
+
- **Uninstall**:
|
|
67
|
+
```bash
|
|
68
|
+
ffup uninstall [--dir <custom-path>]
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### With `python`:
|
|
72
|
+
|
|
73
|
+
Alternatively, the module can be invoked directly.
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
python -m ffup <command>
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Documentation
|
|
80
|
+
|
|
81
|
+
### CLI Commands, Their Flags and Environment Variables
|
|
82
|
+
|
|
83
|
+
1. **Install**:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
ffup install [--dir <custom-path>]
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
- Downloads and installs the binary.
|
|
90
|
+
- Flags and Environment variables:
|
|
91
|
+
- `--dir <custom-path>`: Specifies the installation directory.
|
|
92
|
+
- `$XDG_BIN_HOME`: Used as the installation directory if `--dir` is not specified.
|
|
93
|
+
- Defaults to `~/.local/bin` if none of the above is defined.
|
|
94
|
+
|
|
95
|
+
2. **Update**:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
ffup update [--dir <custom-path>] [--dry-run]
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
- Updates the binary to the latest version.
|
|
102
|
+
- Flags:
|
|
103
|
+
- `--dir <custom-path>`: Specifies the directory where the binary is installed.
|
|
104
|
+
- Defaults to the first executable found on the `$PATH`.
|
|
105
|
+
- `--dry-run`: Only checks for update, skips download and install.
|
|
106
|
+
|
|
107
|
+
3. **Check**:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
ffup check [--dir <custom-path>]
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
- Checks for update.
|
|
114
|
+
- Same as `ffup update [--dir <custom-path>] --dry-run`.
|
|
115
|
+
- Flags:
|
|
116
|
+
- `--dir <custom-path>`: Specifies the directory where the binary is installed.
|
|
117
|
+
- Defaults to the first executable found on the `$PATH`.
|
|
118
|
+
|
|
119
|
+
4. **Uninstall**:
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
ffup uninstall [--dir <custom-path>]
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
- Removes the installed binary.
|
|
126
|
+
- Flags:
|
|
127
|
+
- `--dir <custom-path>`: Specifies the directory where the binary is installed.
|
|
128
|
+
- Defaults to the first executable found on the `$PATH`.
|
|
129
|
+
|
|
130
|
+
### Global Flags and Their Environment Variables
|
|
131
|
+
|
|
132
|
+
- `--sys` or `$FF_SYS`: Specifies the platform name (`macos`, `linux`). Default is to detect using `platform` stdlib.
|
|
133
|
+
- `--arch` or `$FF_ARCH`: Specifies the platform architecture (`arm64`, `amd64`). Default is to detect using `platform` stdlib.
|
|
134
|
+
- `--repo` or `$FF_REPO`: Specifies the static build type (`snapshot`, `release`). Defaults to `snapshot`.
|
|
135
|
+
- `--bin` or `$FF_BIN`: Specifies the binary name (`ffmpeg`, `ffprobe`, `ffplay`). Defaults to `ffmpeg`.
|
|
136
|
+
|
|
137
|
+
> **Note:** Flags have precedence over their respective environment variables.
|
|
138
|
+
|
|
139
|
+
> **Note:** Command arguments may be given positionally. Global arguments are always specified with their respective keywords (flags).
|
|
140
|
+
|
|
141
|
+
### Error Handling
|
|
142
|
+
|
|
143
|
+
- Permission error triggers automatic escalation via `sudo` [and, consequently, prompts the user for password at `stdin`].
|
|
144
|
+
- Path handling checks are exhaustive, with diagnostics logged to `stdout` and `stderr` as appropriate.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
ffup/__init__.py,sha256=JOvFvqYGeEJu1XMhcDSw-tVGZhGxX1AjSoX384iB1VU,30
|
|
2
|
+
ffup/__main__.py,sha256=bYt9eEaoRQWdejEHFD8REx9jxVEdZptECFsV7F49Ink,30
|
|
3
|
+
ffup/cli.py,sha256=JrC2y5YAIhdOLs_YhfbXDb3--p2d2KuTteUD-2U5vvY,4781
|
|
4
|
+
ffmpeg_update-1.4.1.dist-info/METADATA,sha256=OTKG0q1VFXYtAKK7KiyilrmJ3jul9IucFG0pXwA8cKc,4308
|
|
5
|
+
ffmpeg_update-1.4.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
6
|
+
ffmpeg_update-1.4.1.dist-info/entry_points.txt,sha256=1PwlMliyeJa6o_dC6u0A0fPLu7fE72m2LeB671z6_IQ,35
|
|
7
|
+
ffmpeg_update-1.4.1.dist-info/RECORD,,
|
ffup/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .cli import main as main
|
ffup/__main__.py
ADDED
ffup/cli.py
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import platform
|
|
3
|
+
import re
|
|
4
|
+
import shutil
|
|
5
|
+
import subprocess
|
|
6
|
+
import sys
|
|
7
|
+
import tempfile
|
|
8
|
+
import zipfile
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
import fire
|
|
12
|
+
import requests
|
|
13
|
+
from tqdm import tqdm
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class FFUp:
|
|
17
|
+
def __init__(self, sys=None, arch=None, repo=None, bin=None):
|
|
18
|
+
self.sys = sys or os.getenv('FF_SYS') \
|
|
19
|
+
or platform.system().replace('Darwin', 'macOS').lower()
|
|
20
|
+
|
|
21
|
+
self.arch = arch or os.getenv('FF_ARCH') \
|
|
22
|
+
or ('arm64' if platform.machine() in ['arm64', 'aarch64'] else 'amd64')
|
|
23
|
+
|
|
24
|
+
self.repo = repo or os.getenv('FF_REPO') or 'snapshot'
|
|
25
|
+
self.bin = bin or os.getenv('FF_BIN') or 'ffmpeg'
|
|
26
|
+
|
|
27
|
+
self.URL = f'https://ffmpeg.martin-riedl.de/redirect/latest/{self.sys}/{self.arch}/{self.repo}/{self.bin}.zip'
|
|
28
|
+
self._TMPDIR = tempfile.TemporaryDirectory()
|
|
29
|
+
|
|
30
|
+
def __del__(self):
|
|
31
|
+
self._TMPDIR.cleanup()
|
|
32
|
+
|
|
33
|
+
def check(self, dir=None):
|
|
34
|
+
self.update(dir=dir, dry_run=True)
|
|
35
|
+
|
|
36
|
+
def install(self, dir=None):
|
|
37
|
+
path = Path(dir or os.getenv('XDG_BIN_HOME') or os.path.expanduser('~/.local/bin'), self.bin)
|
|
38
|
+
|
|
39
|
+
if shutil.which(self.bin) is not None:
|
|
40
|
+
print('Warning: found an existing installation on the `PATH`.')
|
|
41
|
+
|
|
42
|
+
if path.exists():
|
|
43
|
+
print('Error: found an existing installation at the given path.', file=sys.stderr)
|
|
44
|
+
sys.exit(1)
|
|
45
|
+
|
|
46
|
+
self._latest()
|
|
47
|
+
file = self._download()
|
|
48
|
+
self._install(file, path)
|
|
49
|
+
|
|
50
|
+
def update(self, dir=None, dry_run=False):
|
|
51
|
+
path = self._getpath(dir)
|
|
52
|
+
self._current(path)
|
|
53
|
+
self._latest()
|
|
54
|
+
if self.current_version!=self.latest_version:
|
|
55
|
+
print('Update available.')
|
|
56
|
+
if not dry_run:
|
|
57
|
+
file = self._download()
|
|
58
|
+
self._install(file, path)
|
|
59
|
+
else:
|
|
60
|
+
print('Already up to date.')
|
|
61
|
+
|
|
62
|
+
def uninstall(self, dir=None):
|
|
63
|
+
path = self._getpath(dir)
|
|
64
|
+
self._uninstall(path)
|
|
65
|
+
|
|
66
|
+
def _getpath(self, dir):
|
|
67
|
+
if dir is None:
|
|
68
|
+
path = shutil.which(self.bin)
|
|
69
|
+
if path is None:
|
|
70
|
+
print('Error: no installation found on the `PATH`.', file=sys.stderr)
|
|
71
|
+
sys.exit(1)
|
|
72
|
+
else:
|
|
73
|
+
path = Path(dir, self.bin)
|
|
74
|
+
if not path.exists():
|
|
75
|
+
print('Error: no installation found at the given path.', file=sys.stderr)
|
|
76
|
+
sys.exit(1)
|
|
77
|
+
|
|
78
|
+
return Path(path)
|
|
79
|
+
|
|
80
|
+
def _current(self, path):
|
|
81
|
+
output = subprocess.check_output([path, '-version'], text=True)
|
|
82
|
+
|
|
83
|
+
match = re.search(r'version (N-\d+-\w+|\d\.\d)', output)
|
|
84
|
+
if match is None:
|
|
85
|
+
print(f'Error: failed to parse current version from `{path} -version` output.', file=sys.stderr)
|
|
86
|
+
sys.exit(1)
|
|
87
|
+
|
|
88
|
+
self.current_version = match.group(1)
|
|
89
|
+
print('Current version:', self.current_version)
|
|
90
|
+
|
|
91
|
+
def _latest(self):
|
|
92
|
+
response = requests.get(self.URL, allow_redirects=False)
|
|
93
|
+
response.raise_for_status()
|
|
94
|
+
|
|
95
|
+
if response.status_code==307:
|
|
96
|
+
match = re.search(r'_(N-\d+-\w+|\d\.\d)', response.headers['location'])
|
|
97
|
+
if match is None:
|
|
98
|
+
print('Error: failed to parse latest version from redirected url.', file=sys.stderr)
|
|
99
|
+
sys.exit(1)
|
|
100
|
+
|
|
101
|
+
self.latest_version = match.group(1)
|
|
102
|
+
print('Latest version:', self.latest_version)
|
|
103
|
+
|
|
104
|
+
else:
|
|
105
|
+
print('Error: unexpected', response, file=sys.stderr)
|
|
106
|
+
print('Headers:', response.headers, file=sys.stderr)
|
|
107
|
+
sys.exit(1)
|
|
108
|
+
|
|
109
|
+
def _download(self):
|
|
110
|
+
with requests.get(self.URL, stream=True) as response:
|
|
111
|
+
response.raise_for_status()
|
|
112
|
+
bar = tqdm(
|
|
113
|
+
total=int(response.headers['content-length']),
|
|
114
|
+
unit='B', unit_scale=True,
|
|
115
|
+
desc='Downloading', dynamic_ncols=True
|
|
116
|
+
)
|
|
117
|
+
file = Path(self._TMPDIR.name, 'ff.zip')
|
|
118
|
+
with file.open('wb') as zf:
|
|
119
|
+
for chunk in response.iter_content(chunk_size=4096):
|
|
120
|
+
chunk_size = zf.write(chunk)
|
|
121
|
+
bar.update(chunk_size)
|
|
122
|
+
return file
|
|
123
|
+
|
|
124
|
+
def _install(self, file, path):
|
|
125
|
+
with zipfile.ZipFile(file, 'r') as zf:
|
|
126
|
+
bin = zf.extract(self.bin, self._TMPDIR.name)
|
|
127
|
+
os.chmod(bin, 0o755)
|
|
128
|
+
|
|
129
|
+
try:
|
|
130
|
+
os.replace(bin, path)
|
|
131
|
+
except PermissionError:
|
|
132
|
+
subprocess.run(['sudo', 'mv', bin, path], check=True, capture_output=True)
|
|
133
|
+
|
|
134
|
+
print('Successfully installed:', path)
|
|
135
|
+
|
|
136
|
+
def _uninstall(self, path):
|
|
137
|
+
try:
|
|
138
|
+
os.remove(path)
|
|
139
|
+
except PermissionError:
|
|
140
|
+
subprocess.run(['sudo', 'rm', path], check=True, capture_output=True)
|
|
141
|
+
|
|
142
|
+
print('Successfully uninstalled:', path)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def main():
|
|
146
|
+
fire.Fire(FFUp)
|