better-ffmpeg-progress 5.4.1__tar.gz → 6.0.0__tar.gz
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.
- {better_ffmpeg_progress-5.4.1/better_ffmpeg_progress.egg-info → better_ffmpeg_progress-6.0.0}/PKG-INFO +51 -13
- better_ffmpeg_progress-5.4.1/PKG-INFO → better_ffmpeg_progress-6.0.0/README.md +40 -24
- {better_ffmpeg_progress-5.4.1 → better_ffmpeg_progress-6.0.0}/better_ffmpeg_progress/__init__.py +3 -0
- {better_ffmpeg_progress-5.4.1 → better_ffmpeg_progress-6.0.0}/better_ffmpeg_progress/better_ffmpeg_progress.py +67 -25
- {better_ffmpeg_progress-5.4.1 → better_ffmpeg_progress-6.0.0}/better_ffmpeg_progress/progress_bars.py +37 -8
- better_ffmpeg_progress-6.0.0/better_ffmpeg_progress/py.typed +0 -0
- {better_ffmpeg_progress-5.4.1 → better_ffmpeg_progress-6.0.0}/better_ffmpeg_progress/terminate_process.py +54 -37
- {better_ffmpeg_progress-5.4.1 → better_ffmpeg_progress-6.0.0}/better_ffmpeg_progress/utils.py +77 -21
- better_ffmpeg_progress-5.4.1/README.md → better_ffmpeg_progress-6.0.0/better_ffmpeg_progress.egg-info/PKG-INFO +63 -6
- {better_ffmpeg_progress-5.4.1 → better_ffmpeg_progress-6.0.0}/better_ffmpeg_progress.egg-info/SOURCES.txt +5 -2
- {better_ffmpeg_progress-5.4.1 → better_ffmpeg_progress-6.0.0}/better_ffmpeg_progress.egg-info/requires.txt +3 -1
- better_ffmpeg_progress-6.0.0/pyproject.toml +39 -0
- better_ffmpeg_progress-6.0.0/tests/test_ffmpeg_process.py +152 -0
- better_ffmpeg_progress-6.0.0/tests/test_utils.py +119 -0
- better_ffmpeg_progress-5.4.1/setup.py +0 -24
- {better_ffmpeg_progress-5.4.1 → better_ffmpeg_progress-6.0.0}/LICENSE +0 -0
- {better_ffmpeg_progress-5.4.1 → better_ffmpeg_progress-6.0.0}/better_ffmpeg_progress/enums.py +0 -0
- {better_ffmpeg_progress-5.4.1 → better_ffmpeg_progress-6.0.0}/better_ffmpeg_progress/exceptions.py +0 -0
- {better_ffmpeg_progress-5.4.1 → better_ffmpeg_progress-6.0.0}/better_ffmpeg_progress.egg-info/dependency_links.txt +0 -0
- {better_ffmpeg_progress-5.4.1 → better_ffmpeg_progress-6.0.0}/better_ffmpeg_progress.egg-info/top_level.txt +0 -0
- {better_ffmpeg_progress-5.4.1 → better_ffmpeg_progress-6.0.0}/setup.cfg +0 -0
|
@@ -1,21 +1,24 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: better-ffmpeg-progress
|
|
3
|
-
Version:
|
|
3
|
+
Version: 6.0.0
|
|
4
4
|
Summary: Run FFmpeg & see percentage progress + ETA.
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
Author-email: "GitHub.com/CrypticSignal" <theaudiophile@outlook.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/CrypticSignal/better-ffmpeg-progress
|
|
8
|
+
Project-URL: Changelog, https://github.com/CrypticSignal/better-ffmpeg-progress/blob/main/CHANGELOG.md
|
|
8
9
|
Keywords: ffmpeg,progress
|
|
9
10
|
Classifier: Programming Language :: Python :: 3
|
|
10
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
11
11
|
Classifier: Operating System :: OS Independent
|
|
12
|
-
|
|
12
|
+
Classifier: Typing :: Typed
|
|
13
|
+
Requires-Python: >=3.9
|
|
13
14
|
Description-Content-Type: text/markdown
|
|
14
15
|
License-File: LICENSE
|
|
15
16
|
Requires-Dist: psutil
|
|
16
|
-
Requires-Dist: requests
|
|
17
17
|
Requires-Dist: rich
|
|
18
18
|
Requires-Dist: tqdm
|
|
19
|
+
Provides-Extra: test
|
|
20
|
+
Requires-Dist: pytest; extra == "test"
|
|
21
|
+
Dynamic: license-file
|
|
19
22
|
|
|
20
23
|
<div align="center">
|
|
21
24
|
|
|
@@ -65,9 +68,8 @@ command = [
|
|
|
65
68
|
]
|
|
66
69
|
|
|
67
70
|
try:
|
|
71
|
+
# Pass use_tqdm=True to use tqdm instead of Rich.
|
|
68
72
|
process = FfmpegProcess(command)
|
|
69
|
-
# Uncomment this line to use tqdm instead of Rich.
|
|
70
|
-
# process.use_tqdm = True
|
|
71
73
|
|
|
72
74
|
process.run()
|
|
73
75
|
except FfmpegProcessError as e:
|
|
@@ -137,14 +139,50 @@ An instance of `FfmpegProcess` accepts the following optional arguments:
|
|
|
137
139
|
* `ffmpeg_log_file` — Filepath or file-like object to which FFmpeg stderr will be written. Default: `<input filename>_ffmpeg_log.txt`.
|
|
138
140
|
* `print_detected_duration` — Print the detected duration. Default: `False`.
|
|
139
141
|
* `duration_override` — Specify input file duration in seconds to use for progress calculation, instead of trying to detect this automatically with FFprobe. Default: `None`.
|
|
142
|
+
* `use_tqdm` — Use tqdm instead of Rich for the progress bar. Default: `False`.
|
|
143
|
+
* `overwrite` — What to do if the output file already exists: `True` overwrites it, `False` raises `FfmpegCommandError`, and `None` asks whether to overwrite it. Ignored if the command contains `-y` or `-n`. Default: `None`.
|
|
140
144
|
|
|
141
145
|
The `run` method accepts the following optional argument:
|
|
142
146
|
* `print_command` — Print the FFmpeg command being executed. Default: `False`.
|
|
143
147
|
|
|
144
148
|
## Using tqdm
|
|
145
|
-
By default, the [Rich](https://github.com/Textualize/rich) library is used to display a progress bar. [tqdm](https://github.com/tqdm/tqdm) can be used instead by
|
|
149
|
+
By default, the [Rich](https://github.com/Textualize/rich) library is used to display a progress bar. [tqdm](https://github.com/tqdm/tqdm) can be used instead by passing `use_tqdm=True`:
|
|
146
150
|
```python
|
|
147
|
-
process = FfmpegProcess(command)
|
|
148
|
-
process.use_tqdm = True
|
|
151
|
+
process = FfmpegProcess(command, use_tqdm=True)
|
|
149
152
|
process.run()
|
|
150
153
|
```
|
|
154
|
+
## Limitations
|
|
155
|
+
- Progress is calculated from the duration of the first input (the first `-i`).
|
|
156
|
+
- The last argument of the command is treated as the output file, e.g. when checking whether it already exists. With multiple outputs, only the last one is checked.
|
|
157
|
+
|
|
158
|
+
## Development
|
|
159
|
+
Install the package in editable mode with the test dependencies, then run the tests (FFmpeg and FFprobe must be installed):
|
|
160
|
+
```bash
|
|
161
|
+
pip install -e ".[test]"
|
|
162
|
+
```
|
|
163
|
+
```bash
|
|
164
|
+
pytest
|
|
165
|
+
```
|
|
166
|
+
`test.py` is a standalone example that encodes a test pattern and shows the progress bar.
|
|
167
|
+
|
|
168
|
+
## Publishing
|
|
169
|
+
Update `__version__` in `better_ffmpeg_progress/__init__.py` and add an entry to `CHANGELOG.md`.
|
|
170
|
+
|
|
171
|
+
One-time setup:
|
|
172
|
+
```bash
|
|
173
|
+
pip install --upgrade build twine pkginfo
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Then, for each release:
|
|
177
|
+
```bash
|
|
178
|
+
rm -rf dist
|
|
179
|
+
```
|
|
180
|
+
```bash
|
|
181
|
+
python -m build
|
|
182
|
+
```
|
|
183
|
+
```bash
|
|
184
|
+
twine check dist/*
|
|
185
|
+
```
|
|
186
|
+
```bash
|
|
187
|
+
twine upload dist/*
|
|
188
|
+
```
|
|
@@ -1,22 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: better-ffmpeg-progress
|
|
3
|
-
Version: 5.4.1
|
|
4
|
-
Summary: Run FFmpeg & see percentage progress + ETA.
|
|
5
|
-
Home-page: https://github.com/CrypticSignal/better-ffmpeg-progress
|
|
6
|
-
Author: GitHub.com/CrypticSignal
|
|
7
|
-
Author-email: theaudiophile@outlook.com
|
|
8
|
-
Keywords: ffmpeg,progress
|
|
9
|
-
Classifier: Programming Language :: Python :: 3
|
|
10
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
-
Classifier: Operating System :: OS Independent
|
|
12
|
-
Requires-Python: >=3.7
|
|
13
|
-
Description-Content-Type: text/markdown
|
|
14
|
-
License-File: LICENSE
|
|
15
|
-
Requires-Dist: psutil
|
|
16
|
-
Requires-Dist: requests
|
|
17
|
-
Requires-Dist: rich
|
|
18
|
-
Requires-Dist: tqdm
|
|
19
|
-
|
|
20
1
|
<div align="center">
|
|
21
2
|
|
|
22
3
|
# Better FFmpeg Progress
|
|
@@ -65,9 +46,8 @@ command = [
|
|
|
65
46
|
]
|
|
66
47
|
|
|
67
48
|
try:
|
|
49
|
+
# Pass use_tqdm=True to use tqdm instead of Rich.
|
|
68
50
|
process = FfmpegProcess(command)
|
|
69
|
-
# Uncomment this line to use tqdm instead of Rich.
|
|
70
|
-
# process.use_tqdm = True
|
|
71
51
|
|
|
72
52
|
process.run()
|
|
73
53
|
except FfmpegProcessError as e:
|
|
@@ -137,14 +117,50 @@ An instance of `FfmpegProcess` accepts the following optional arguments:
|
|
|
137
117
|
* `ffmpeg_log_file` — Filepath or file-like object to which FFmpeg stderr will be written. Default: `<input filename>_ffmpeg_log.txt`.
|
|
138
118
|
* `print_detected_duration` — Print the detected duration. Default: `False`.
|
|
139
119
|
* `duration_override` — Specify input file duration in seconds to use for progress calculation, instead of trying to detect this automatically with FFprobe. Default: `None`.
|
|
120
|
+
* `use_tqdm` — Use tqdm instead of Rich for the progress bar. Default: `False`.
|
|
121
|
+
* `overwrite` — What to do if the output file already exists: `True` overwrites it, `False` raises `FfmpegCommandError`, and `None` asks whether to overwrite it. Ignored if the command contains `-y` or `-n`. Default: `None`.
|
|
140
122
|
|
|
141
123
|
The `run` method accepts the following optional argument:
|
|
142
124
|
* `print_command` — Print the FFmpeg command being executed. Default: `False`.
|
|
143
125
|
|
|
144
126
|
## Using tqdm
|
|
145
|
-
By default, the [Rich](https://github.com/Textualize/rich) library is used to display a progress bar. [tqdm](https://github.com/tqdm/tqdm) can be used instead by
|
|
127
|
+
By default, the [Rich](https://github.com/Textualize/rich) library is used to display a progress bar. [tqdm](https://github.com/tqdm/tqdm) can be used instead by passing `use_tqdm=True`:
|
|
146
128
|
```python
|
|
147
|
-
process = FfmpegProcess(command)
|
|
148
|
-
process.use_tqdm = True
|
|
129
|
+
process = FfmpegProcess(command, use_tqdm=True)
|
|
149
130
|
process.run()
|
|
150
131
|
```
|
|
132
|
+
## Limitations
|
|
133
|
+
- Progress is calculated from the duration of the first input (the first `-i`).
|
|
134
|
+
- The last argument of the command is treated as the output file, e.g. when checking whether it already exists. With multiple outputs, only the last one is checked.
|
|
135
|
+
|
|
136
|
+
## Development
|
|
137
|
+
Install the package in editable mode with the test dependencies, then run the tests (FFmpeg and FFprobe must be installed):
|
|
138
|
+
```bash
|
|
139
|
+
pip install -e ".[test]"
|
|
140
|
+
```
|
|
141
|
+
```bash
|
|
142
|
+
pytest
|
|
143
|
+
```
|
|
144
|
+
`test.py` is a standalone example that encodes a test pattern and shows the progress bar.
|
|
145
|
+
|
|
146
|
+
## Publishing
|
|
147
|
+
Update `__version__` in `better_ffmpeg_progress/__init__.py` and add an entry to `CHANGELOG.md`.
|
|
148
|
+
|
|
149
|
+
One-time setup:
|
|
150
|
+
```bash
|
|
151
|
+
pip install --upgrade build twine pkginfo
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Then, for each release:
|
|
155
|
+
```bash
|
|
156
|
+
rm -rf dist
|
|
157
|
+
```
|
|
158
|
+
```bash
|
|
159
|
+
python -m build
|
|
160
|
+
```
|
|
161
|
+
```bash
|
|
162
|
+
twine check dist/*
|
|
163
|
+
```
|
|
164
|
+
```bash
|
|
165
|
+
twine upload dist/*
|
|
166
|
+
```
|
{better_ffmpeg_progress-5.4.1 → better_ffmpeg_progress-6.0.0}/better_ffmpeg_progress/__init__.py
RENAMED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
__version__ = "6.0.0"
|
|
2
|
+
|
|
1
3
|
from .better_ffmpeg_progress import FfmpegProcess
|
|
2
4
|
from .enums import FfmpegLogLevel
|
|
3
5
|
from .exceptions import (
|
|
@@ -8,6 +10,7 @@ from .exceptions import (
|
|
|
8
10
|
)
|
|
9
11
|
|
|
10
12
|
__all__ = [
|
|
13
|
+
"__version__",
|
|
11
14
|
"FfmpegProcess",
|
|
12
15
|
"FfmpegLogLevel",
|
|
13
16
|
"FfmpegProcessError",
|
|
@@ -4,8 +4,7 @@ import re
|
|
|
4
4
|
import shlex
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
import subprocess
|
|
7
|
-
from
|
|
8
|
-
from typing import List, Optional, Union
|
|
7
|
+
from typing import IO, Any, List, Optional, Union
|
|
9
8
|
|
|
10
9
|
from .enums import FfmpegLogLevel
|
|
11
10
|
from .exceptions import (
|
|
@@ -19,11 +18,19 @@ from .utils import (
|
|
|
19
18
|
get_media_duration,
|
|
20
19
|
get_expected_output_duration,
|
|
21
20
|
is_lavfi_input,
|
|
21
|
+
is_local_file_input,
|
|
22
|
+
is_stdin_input,
|
|
22
23
|
)
|
|
23
24
|
from .terminate_process import terminate_ffmpeg_process
|
|
24
25
|
from .progress_bars import use_rich, use_tqdm
|
|
25
26
|
|
|
26
27
|
_FFMPEG_OVERWRITE_FLAG = "-y"
|
|
28
|
+
_FFMPEG_NO_OVERWRITE_FLAG = "-n"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _safe_filename(name: str) -> str:
|
|
32
|
+
"""Replaces characters that are not valid in filenames on every OS."""
|
|
33
|
+
return re.sub(r"[^\w.,-]", "_", name)
|
|
27
34
|
|
|
28
35
|
|
|
29
36
|
class FfmpegProcess:
|
|
@@ -59,9 +66,11 @@ class FfmpegProcess:
|
|
|
59
66
|
self,
|
|
60
67
|
command: List[str],
|
|
61
68
|
ffmpeg_log_level: Optional[Union[FfmpegLogLevel, str]] = None,
|
|
62
|
-
ffmpeg_log_file: Optional[Union[str, os.PathLike]] = None,
|
|
69
|
+
ffmpeg_log_file: Optional[Union[str, os.PathLike, IO[Any]]] = None,
|
|
63
70
|
print_detected_duration: bool = False,
|
|
64
71
|
duration_override: Optional[float] = None,
|
|
72
|
+
overwrite: Optional[bool] = None,
|
|
73
|
+
use_tqdm: bool = False,
|
|
65
74
|
):
|
|
66
75
|
"""
|
|
67
76
|
Create an FFmpeg process with progress reporting.
|
|
@@ -72,12 +81,19 @@ class FfmpegProcess:
|
|
|
72
81
|
ffmpeg_log_level:
|
|
73
82
|
FFmpeg log level enum or case-insensitive string.
|
|
74
83
|
ffmpeg_log_file:
|
|
75
|
-
File path or file
|
|
84
|
+
File path or file object (with a file descriptor, e.g. one returned by open())
|
|
85
|
+
to which FFmpeg's stderr is written.
|
|
76
86
|
print_detected_duration:
|
|
77
87
|
Print the duration used for progress calculation.
|
|
78
88
|
duration_override:
|
|
79
89
|
Specify input file duration in seconds to use for progress calculation, instead of trying to detect this automatically with FFprobe. This is useful for partial, damaged, streamed, or
|
|
80
90
|
synthetic inputs whose duration cannot be detected correctly.
|
|
91
|
+
overwrite:
|
|
92
|
+
What to do if the output file already exists. True overwrites it, False raises
|
|
93
|
+
FfmpegCommandError, and None (the default) asks the user. Ignored if the command
|
|
94
|
+
contains -y or -n.
|
|
95
|
+
use_tqdm:
|
|
96
|
+
Use tqdm instead of Rich for the progress bar.
|
|
81
97
|
"""
|
|
82
98
|
validate_ffmpeg_command(command)
|
|
83
99
|
|
|
@@ -118,10 +134,17 @@ class FfmpegProcess:
|
|
|
118
134
|
# A filtergraph such as "testsrc2=size=1920x1080" can contain characters that
|
|
119
135
|
# are not valid in filenames (e.g. ":" on Windows), so name the log after the
|
|
120
136
|
# source instead.
|
|
121
|
-
source_name =
|
|
137
|
+
source_name = _safe_filename(input_file_path_str.split("=", 1)[0])
|
|
122
138
|
self._ffmpeg_log_file = Path(f"lavfi_{source_name}_ffmpeg_log.txt")
|
|
123
|
-
|
|
139
|
+
elif input_file_path_str == "-":
|
|
140
|
+
self._ffmpeg_log_file = Path("stdin_ffmpeg_log.txt")
|
|
141
|
+
elif is_local_file_input(command, input_file_index):
|
|
124
142
|
self._ffmpeg_log_file = Path(f"{self._input_filepath.name}_ffmpeg_log.txt")
|
|
143
|
+
else:
|
|
144
|
+
# URLs and other protocols (e.g. "pipe:0") can contain characters that are not
|
|
145
|
+
# valid in filenames, such as ":" or "?".
|
|
146
|
+
name = self._input_filepath.name or input_file_path_str
|
|
147
|
+
self._ffmpeg_log_file = Path(f"{_safe_filename(name)}_ffmpeg_log.txt")
|
|
125
148
|
elif isinstance(ffmpeg_log_file, (str, os.PathLike)):
|
|
126
149
|
self._ffmpeg_log_file = Path(ffmpeg_log_file)
|
|
127
150
|
else:
|
|
@@ -142,6 +165,9 @@ class FfmpegProcess:
|
|
|
142
165
|
raise ValueError("duration_override must be a finite number greater than 0")
|
|
143
166
|
|
|
144
167
|
self._duration_secs = duration_value
|
|
168
|
+
elif is_stdin_input(input_file_path_str):
|
|
169
|
+
# Running FFprobe on standard input would consume data that is meant for FFmpeg.
|
|
170
|
+
self._duration_secs = None
|
|
145
171
|
else:
|
|
146
172
|
self._duration_secs = get_media_duration(input_file_path_str)
|
|
147
173
|
|
|
@@ -178,13 +204,29 @@ class FfmpegProcess:
|
|
|
178
204
|
]
|
|
179
205
|
self._ffmpeg_command.extend(command[1:])
|
|
180
206
|
|
|
181
|
-
|
|
207
|
+
if overwrite is not None and not isinstance(overwrite, bool):
|
|
208
|
+
raise TypeError("overwrite must be True, False or None")
|
|
182
209
|
|
|
183
|
-
|
|
184
|
-
|
|
210
|
+
user_args = command[1:]
|
|
211
|
+
if _FFMPEG_OVERWRITE_FLAG not in user_args and self._output_filepath.exists():
|
|
212
|
+
if _FFMPEG_NO_OVERWRITE_FLAG in user_args or overwrite is False:
|
|
213
|
+
raise FfmpegCommandError(
|
|
214
|
+
f"Output file {self._output_filepath} already exists and overwriting is "
|
|
215
|
+
"disabled."
|
|
216
|
+
)
|
|
217
|
+
elif overwrite:
|
|
218
|
+
self._ffmpeg_command.insert(1, _FFMPEG_OVERWRITE_FLAG)
|
|
219
|
+
elif is_stdin_input(input_file_path_str):
|
|
220
|
+
# Prompting would read from standard input, which carries FFmpeg's input.
|
|
221
|
+
raise FfmpegCommandError(
|
|
222
|
+
f"Output file {self._output_filepath} already exists. Pass overwrite=True "
|
|
223
|
+
"or add -y to the command to overwrite it."
|
|
224
|
+
)
|
|
225
|
+
else:
|
|
226
|
+
self._handle_overwrite_prompt()
|
|
185
227
|
|
|
186
228
|
self._process: Optional[subprocess.Popen] = None
|
|
187
|
-
self.use_tqdm: bool =
|
|
229
|
+
self.use_tqdm: bool = use_tqdm
|
|
188
230
|
|
|
189
231
|
def run(
|
|
190
232
|
self,
|
|
@@ -193,12 +235,15 @@ class FfmpegProcess:
|
|
|
193
235
|
if print_command:
|
|
194
236
|
print(f"Executing: {shlex.join(self._ffmpeg_command)}")
|
|
195
237
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
238
|
+
# Run FFmpeg in its own process group, so that terminating the group on
|
|
239
|
+
# KeyboardInterrupt does not also terminate this Python process.
|
|
240
|
+
popen_kwargs = {}
|
|
241
|
+
if os.name == "nt":
|
|
242
|
+
popen_kwargs["creationflags"] = subprocess.CREATE_NEW_PROCESS_GROUP
|
|
243
|
+
else:
|
|
244
|
+
popen_kwargs["start_new_session"] = True
|
|
201
245
|
|
|
246
|
+
try:
|
|
202
247
|
if isinstance(self._ffmpeg_log_file, Path):
|
|
203
248
|
with open(
|
|
204
249
|
self._ffmpeg_log_file,
|
|
@@ -209,14 +254,14 @@ class FfmpegProcess:
|
|
|
209
254
|
self._ffmpeg_command,
|
|
210
255
|
stdout=subprocess.PIPE,
|
|
211
256
|
stderr=f,
|
|
212
|
-
|
|
257
|
+
**popen_kwargs,
|
|
213
258
|
)
|
|
214
259
|
else:
|
|
215
260
|
self._process = subprocess.Popen(
|
|
216
261
|
self._ffmpeg_command,
|
|
217
262
|
stdout=subprocess.PIPE,
|
|
218
263
|
stderr=self._ffmpeg_log_file,
|
|
219
|
-
|
|
264
|
+
**popen_kwargs,
|
|
220
265
|
)
|
|
221
266
|
except Exception as e:
|
|
222
267
|
raise FfmpegProcessError(f"Error starting FFmpeg process: {e}") from e
|
|
@@ -226,14 +271,11 @@ class FfmpegProcess:
|
|
|
226
271
|
use_tqdm(self, self._process)
|
|
227
272
|
else:
|
|
228
273
|
use_rich(self, self._process)
|
|
229
|
-
except KeyboardInterrupt:
|
|
230
|
-
self.
|
|
274
|
+
except KeyboardInterrupt as e:
|
|
275
|
+
terminate_ffmpeg_process(self._process)
|
|
276
|
+
raise FfmpegProcessInterruptedError(
|
|
277
|
+
"[KeyboardInterrupt] FFmpeg process cancelled."
|
|
278
|
+
) from e
|
|
231
279
|
finally:
|
|
232
280
|
if self._process and self._process.stdout:
|
|
233
281
|
self._process.stdout.close()
|
|
234
|
-
|
|
235
|
-
def _terminate(self):
|
|
236
|
-
if self._process:
|
|
237
|
-
terminate_ffmpeg_process(self._process)
|
|
238
|
-
else:
|
|
239
|
-
exit()
|
|
@@ -13,6 +13,7 @@ from rich.progress import (
|
|
|
13
13
|
from tqdm import tqdm
|
|
14
14
|
|
|
15
15
|
from .utils import (
|
|
16
|
+
build_failure_message,
|
|
16
17
|
estimate_final_size_mb,
|
|
17
18
|
parse_ffmpeg_progress_line,
|
|
18
19
|
parse_ffmpeg_total_size_line,
|
|
@@ -27,8 +28,31 @@ _MAX_FILENAME_LENGTH = 60
|
|
|
27
28
|
|
|
28
29
|
|
|
29
30
|
def _format_size(current_size_mb: float, estimated_size_mb: Optional[float]) -> str:
|
|
30
|
-
|
|
31
|
-
|
|
31
|
+
"""
|
|
32
|
+
Returns e.g. "(3.25 MB / ~14.33 MB)", "(3.25 MB)" while there is no estimate yet, or ""
|
|
33
|
+
if FFmpeg has not reported a size (e.g. at the start, or when the output is "-f null").
|
|
34
|
+
"""
|
|
35
|
+
# Also hide sizes that would display as "0.00 MB", such as a container header of a few bytes.
|
|
36
|
+
if round(current_size_mb, 2) <= 0:
|
|
37
|
+
return ""
|
|
38
|
+
if estimated_size_mb is None:
|
|
39
|
+
return f"({current_size_mb:.2f} MB)"
|
|
40
|
+
return f"({current_size_mb:.2f} MB / ~{estimated_size_mb:.2f} MB)"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _with_size(text: str, current_size_mb: float, estimated_size_mb: Optional[float]) -> str:
|
|
44
|
+
size = _format_size(current_size_mb, estimated_size_mb)
|
|
45
|
+
return f"{text} {size}" if size else text
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _drain(process: subprocess.Popen) -> None:
|
|
49
|
+
"""
|
|
50
|
+
Reads FFmpeg's progress output until it ends. FFmpeg writes it to a pipe, so it must be read
|
|
51
|
+
even when it is not displayed: once the pipe's buffer is full, FFmpeg blocks and never exits.
|
|
52
|
+
"""
|
|
53
|
+
if process.stdout is not None:
|
|
54
|
+
for _ in process.stdout:
|
|
55
|
+
pass
|
|
32
56
|
|
|
33
57
|
|
|
34
58
|
def use_rich(ffmpeg_process_instance: "FfmpegProcess", process: subprocess.Popen) -> None:
|
|
@@ -75,11 +99,13 @@ def use_rich(ffmpeg_process_instance: "FfmpegProcess", process: subprocess.Popen
|
|
|
75
99
|
update_progress(
|
|
76
100
|
task_id,
|
|
77
101
|
completed=progress_val_secs,
|
|
78
|
-
description=
|
|
79
|
-
|
|
102
|
+
description=_with_size(
|
|
103
|
+
f"Processing {input_filename}", current_size_mb, estimated_size_mb
|
|
104
|
+
),
|
|
80
105
|
)
|
|
81
106
|
else:
|
|
82
107
|
print(f"Processing '{input_filename}'...")
|
|
108
|
+
_drain(process)
|
|
83
109
|
|
|
84
110
|
process.wait()
|
|
85
111
|
|
|
@@ -93,13 +119,15 @@ def use_rich(ffmpeg_process_instance: "FfmpegProcess", process: subprocess.Popen
|
|
|
93
119
|
)
|
|
94
120
|
update_progress(
|
|
95
121
|
task_id,
|
|
96
|
-
description=
|
|
122
|
+
description=_with_size(
|
|
123
|
+
f"✓ Processed '{input_filename}'", current_size_mb, None
|
|
124
|
+
),
|
|
97
125
|
)
|
|
98
126
|
else:
|
|
99
127
|
if task_id is not None:
|
|
100
128
|
progress_bar.update(task_id)
|
|
101
129
|
|
|
102
|
-
raise FfmpegProcessError(
|
|
130
|
+
raise FfmpegProcessError(build_failure_message(log_file))
|
|
103
131
|
|
|
104
132
|
|
|
105
133
|
def use_tqdm(ffmpeg_process_instance: "FfmpegProcess", process: subprocess.Popen) -> None:
|
|
@@ -145,6 +173,7 @@ def use_tqdm(ffmpeg_process_instance: "FfmpegProcess", process: subprocess.Popen
|
|
|
145
173
|
progress_bar.set_postfix_str(_format_size(current_size_mb, estimated_size_mb))
|
|
146
174
|
else:
|
|
147
175
|
print(f"Processing '{input_filename}'...")
|
|
176
|
+
_drain(process)
|
|
148
177
|
|
|
149
178
|
process.wait()
|
|
150
179
|
|
|
@@ -152,7 +181,7 @@ def use_tqdm(ffmpeg_process_instance: "FfmpegProcess", process: subprocess.Popen
|
|
|
152
181
|
if progress_bar:
|
|
153
182
|
progress_bar.n = duration_secs
|
|
154
183
|
progress_bar.set_description(f"✓ Processed '{input_filename}'")
|
|
155
|
-
progress_bar.set_postfix_str(
|
|
184
|
+
progress_bar.set_postfix_str(_format_size(current_size_mb, None))
|
|
156
185
|
progress_bar.close()
|
|
157
186
|
else:
|
|
158
187
|
print(f"✓ Processed '{input_filename}'")
|
|
@@ -160,4 +189,4 @@ def use_tqdm(ffmpeg_process_instance: "FfmpegProcess", process: subprocess.Popen
|
|
|
160
189
|
if progress_bar:
|
|
161
190
|
progress_bar.close()
|
|
162
191
|
|
|
163
|
-
raise FfmpegProcessError(
|
|
192
|
+
raise FfmpegProcessError(build_failure_message(log_file))
|
|
File without changes
|