better-ffmpeg-progress 2.5.1__tar.gz → 2.5.3__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-2.5.1 → better_ffmpeg_progress-2.5.3}/PKG-INFO +5 -1
- {better_ffmpeg_progress-2.5.1 → better_ffmpeg_progress-2.5.3}/README.md +5 -1
- {better_ffmpeg_progress-2.5.1 → better_ffmpeg_progress-2.5.3}/better_ffmpeg_progress/better_ffmpeg_progress.py +78 -51
- {better_ffmpeg_progress-2.5.1 → better_ffmpeg_progress-2.5.3}/better_ffmpeg_progress.egg-info/PKG-INFO +5 -1
- {better_ffmpeg_progress-2.5.1 → better_ffmpeg_progress-2.5.3}/setup.py +1 -1
- {better_ffmpeg_progress-2.5.1 → better_ffmpeg_progress-2.5.3}/LICENSE +0 -0
- {better_ffmpeg_progress-2.5.1 → better_ffmpeg_progress-2.5.3}/better_ffmpeg_progress/__init__.py +0 -0
- {better_ffmpeg_progress-2.5.1 → better_ffmpeg_progress-2.5.3}/better_ffmpeg_progress.egg-info/SOURCES.txt +0 -0
- {better_ffmpeg_progress-2.5.1 → better_ffmpeg_progress-2.5.3}/better_ffmpeg_progress.egg-info/dependency_links.txt +0 -0
- {better_ffmpeg_progress-2.5.1 → better_ffmpeg_progress-2.5.3}/better_ffmpeg_progress.egg-info/requires.txt +0 -0
- {better_ffmpeg_progress-2.5.1 → better_ffmpeg_progress-2.5.3}/better_ffmpeg_progress.egg-info/top_level.txt +0 -0
- {better_ffmpeg_progress-2.5.1 → better_ffmpeg_progress-2.5.3}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: better-ffmpeg-progress
|
|
3
|
-
Version: 2.5.
|
|
3
|
+
Version: 2.5.3
|
|
4
4
|
Summary: Run FFmpeg & see percentage progress + ETA.
|
|
5
5
|
Home-page: https://github.com/CrypticSignal/better-ffmpeg-progress
|
|
6
6
|
Author: GitHub.com/CrypticSignal
|
|
@@ -167,3 +167,7 @@ The `run` method takes the following **optional** arguments:
|
|
|
167
167
|
|
|
168
168
|
[11/11/2024]
|
|
169
169
|
- [v2.5.1] Print the correct log file.
|
|
170
|
+
|
|
171
|
+
[12/11/2024]
|
|
172
|
+
- [v2.5.2] Drain any remaining FFmpeg stderr after the process ends.
|
|
173
|
+
- [v2.5.3] Write FFmpeg stderr to the log file in real time.
|
|
@@ -148,4 +148,8 @@ The `run` method takes the following **optional** arguments:
|
|
|
148
148
|
- [v2.5.0] Add an option to print FFmpeg stderr on a new line if better progress information cannot be shown.
|
|
149
149
|
|
|
150
150
|
[11/11/2024]
|
|
151
|
-
- [v2.5.1] Print the correct log file.
|
|
151
|
+
- [v2.5.1] Print the correct log file.
|
|
152
|
+
|
|
153
|
+
[12/11/2024]
|
|
154
|
+
- [v2.5.2] Drain any remaining FFmpeg stderr after the process ends.
|
|
155
|
+
- [v2.5.3] Write FFmpeg stderr to the log file in real time.
|
|
@@ -56,21 +56,37 @@ class FfmpegProcess:
|
|
|
56
56
|
if "-i" not in command:
|
|
57
57
|
raise ValueError("FFmpeg command must include '-i'")
|
|
58
58
|
|
|
59
|
-
|
|
60
|
-
|
|
59
|
+
# -progress pipe:1 sends progress metrics to stdout
|
|
60
|
+
# -stats_period sets the period at which encoding progress/statistics are updated
|
|
61
|
+
# -nostats ensures that progress information is not sent to stderr as this info is not needed in the log file, e.g.
|
|
62
|
+
# frame= 1381 fps=254 q=18.0 size= 46592KiB time=00:00:46.07 bitrate=8283.1kbits/s speed=8.47x
|
|
63
|
+
command.extend(
|
|
64
|
+
[
|
|
65
|
+
"-loglevel",
|
|
66
|
+
ffmpeg_loglevel,
|
|
67
|
+
"-progress",
|
|
68
|
+
"pipe:1",
|
|
69
|
+
"-stats_period",
|
|
70
|
+
"0.1",
|
|
71
|
+
"-nostats",
|
|
72
|
+
]
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
self._ffmpeg_command = command
|
|
61
76
|
self._output_filepath = Path(command[-1])
|
|
62
77
|
self._print_detected_duration = print_detected_duration
|
|
63
78
|
self._print_stderr_new_line = print_stderr_new_line
|
|
64
79
|
self._metrics = Metrics()
|
|
65
80
|
self._duration_secs: Optional[float] = None
|
|
66
81
|
self._current_size: int = 0
|
|
82
|
+
self._ffmpeg_log_file: Optional[Path] = None
|
|
67
83
|
|
|
68
84
|
self._get_input_file_duration()
|
|
69
85
|
|
|
70
86
|
def _get_input_file_duration(self) -> None:
|
|
71
87
|
"""Use ffprobe to get the duration of the input file"""
|
|
72
|
-
index_of_filepath = self.
|
|
73
|
-
self._input_filepath = Path(self.
|
|
88
|
+
index_of_filepath = self._ffmpeg_command.index("-i") + 1
|
|
89
|
+
self._input_filepath = Path(self._ffmpeg_command[index_of_filepath])
|
|
74
90
|
|
|
75
91
|
try:
|
|
76
92
|
self._duration_secs = float(
|
|
@@ -81,9 +97,6 @@ class FfmpegProcess:
|
|
|
81
97
|
print(
|
|
82
98
|
f"The duration of {self._input_filepath.name} has been detected as {self._duration_secs} seconds"
|
|
83
99
|
)
|
|
84
|
-
|
|
85
|
-
# -progress pipe:1 sends progress metrics to stdout AND -stats_period sets the period at which encoding progress/statistics are updated
|
|
86
|
-
self._ffmpeg_args.extend(["-progress", "pipe:1", "-stats_period", "0.1"])
|
|
87
100
|
except Exception:
|
|
88
101
|
print(
|
|
89
102
|
f"Could not detect the duration of '{self._input_filepath.name}'. Percentage progress, ETA and estimated filesize will be unavailable.\n",
|
|
@@ -91,7 +104,7 @@ class FfmpegProcess:
|
|
|
91
104
|
|
|
92
105
|
def _check_output_exists(self) -> bool:
|
|
93
106
|
"""Check if output file exists and handle overwrite prompt."""
|
|
94
|
-
if self._output_filepath.exists() and "-y" not in self.
|
|
107
|
+
if self._output_filepath.exists() and "-y" not in self._ffmpeg_command:
|
|
95
108
|
choice = input(
|
|
96
109
|
f"{self._output_filepath} already exists. Overwrite? [Y/N]: "
|
|
97
110
|
).lower()
|
|
@@ -100,7 +113,7 @@ class FfmpegProcess:
|
|
|
100
113
|
"FFmpeg process cancelled. Output file exists and overwrite declined."
|
|
101
114
|
)
|
|
102
115
|
return False
|
|
103
|
-
self.
|
|
116
|
+
self._ffmpeg_command.insert(1, "-y")
|
|
104
117
|
return True
|
|
105
118
|
|
|
106
119
|
def _update_metrics(
|
|
@@ -153,32 +166,20 @@ class FfmpegProcess:
|
|
|
153
166
|
finally:
|
|
154
167
|
pipe.close()
|
|
155
168
|
|
|
156
|
-
def _write_ffmpeg_output(
|
|
157
|
-
self,
|
|
158
|
-
stderr_output: str,
|
|
159
|
-
log_file: Optional[Union[str, Path]],
|
|
160
|
-
is_error: bool = False,
|
|
161
|
-
) -> None:
|
|
162
|
-
"""Write FFmpeg stderr to a txt file."""
|
|
163
|
-
Path(log_file).write_text(stderr_output)
|
|
164
|
-
|
|
165
|
-
if is_error:
|
|
166
|
-
return
|
|
167
|
-
|
|
168
|
-
print(
|
|
169
|
-
f"{"\n\n" if self._duration_secs is None else ""}FFmpeg log filename: {log_file}"
|
|
170
|
-
)
|
|
171
|
-
|
|
172
169
|
def _start_process(
|
|
173
170
|
self,
|
|
174
171
|
progress_bar: Optional[Progress],
|
|
175
172
|
task_id: Optional[int],
|
|
176
173
|
progress_handler: Optional[Callable],
|
|
177
|
-
) ->
|
|
174
|
+
) -> int:
|
|
178
175
|
creationflags = subprocess.CREATE_NEW_PROCESS_GROUP if os.name == "nt" else 0
|
|
179
176
|
|
|
177
|
+
self._write_to_log_file(
|
|
178
|
+
f"This file contains the log for the following command:\n{' '.join(self._ffmpeg_command)}\n",
|
|
179
|
+
)
|
|
180
|
+
|
|
180
181
|
process = subprocess.Popen(
|
|
181
|
-
self.
|
|
182
|
+
self._ffmpeg_command,
|
|
182
183
|
stdout=subprocess.PIPE,
|
|
183
184
|
stderr=subprocess.PIPE,
|
|
184
185
|
text=True,
|
|
@@ -228,28 +229,47 @@ class FfmpegProcess:
|
|
|
228
229
|
try:
|
|
229
230
|
stderr = stderr_queue.get_nowait()
|
|
230
231
|
|
|
231
|
-
if
|
|
232
|
-
|
|
233
|
-
stderr,
|
|
234
|
-
end="\n" if self._print_stderr_new_line else "\r",
|
|
235
|
-
flush=True,
|
|
236
|
-
)
|
|
232
|
+
if stderr:
|
|
233
|
+
self._write_to_log_file(stderr)
|
|
237
234
|
|
|
238
|
-
|
|
235
|
+
if self._duration_secs is None:
|
|
236
|
+
print(
|
|
237
|
+
stderr,
|
|
238
|
+
end="\n" if self._print_stderr_new_line else "\r",
|
|
239
|
+
flush=True,
|
|
240
|
+
)
|
|
239
241
|
except Empty:
|
|
240
242
|
pass
|
|
241
243
|
|
|
242
244
|
except KeyboardInterrupt:
|
|
243
245
|
self._kill_process_and_children(process.pid)
|
|
246
|
+
self._write_to_log_file("\n[KeyboardInterrupt] FFmpeg process(es) killed.")
|
|
244
247
|
sys.exit(
|
|
245
248
|
f"{"\n" if self._duration_secs is None else ""}[KeyboardInterrupt] FFmpeg process(es) killed."
|
|
246
249
|
)
|
|
247
250
|
|
|
251
|
+
# After the process ends, drain any remaining stderr
|
|
252
|
+
while True:
|
|
253
|
+
try:
|
|
254
|
+
stderr = stderr_queue.get_nowait()
|
|
255
|
+
|
|
256
|
+
if stderr:
|
|
257
|
+
self._write_to_log_file(stderr)
|
|
258
|
+
|
|
259
|
+
if self._duration_secs is None:
|
|
260
|
+
print(
|
|
261
|
+
stderr,
|
|
262
|
+
end="\n" if self._print_stderr_new_line else "\r",
|
|
263
|
+
flush=True,
|
|
264
|
+
)
|
|
265
|
+
except Empty:
|
|
266
|
+
break
|
|
267
|
+
|
|
248
268
|
return process.returncode
|
|
249
269
|
|
|
250
270
|
def run(
|
|
251
271
|
self,
|
|
252
|
-
log_file: Optional[Union[str, Path]] =
|
|
272
|
+
log_file: Optional[Union[str, Path]] = "ffmpeg_log.txt",
|
|
253
273
|
progress_bar_description: str = None,
|
|
254
274
|
progress_handler: Optional[Callable] = None,
|
|
255
275
|
success_handler: Optional[Callable] = None,
|
|
@@ -266,8 +286,13 @@ class FfmpegProcess:
|
|
|
266
286
|
if not self._check_output_exists():
|
|
267
287
|
return
|
|
268
288
|
|
|
289
|
+
self._ffmpeg_log_file = Path(log_file)
|
|
290
|
+
|
|
291
|
+
with open(self._ffmpeg_log_file, "w"):
|
|
292
|
+
pass
|
|
293
|
+
|
|
269
294
|
print(
|
|
270
|
-
f"Executing: {' '.join(self.
|
|
295
|
+
f"Executing: {' '.join(self._ffmpeg_command)}{"\n" if self._duration_secs is None else ""}"
|
|
271
296
|
)
|
|
272
297
|
|
|
273
298
|
if self._duration_secs and not progress_handler:
|
|
@@ -299,28 +324,23 @@ class FfmpegProcess:
|
|
|
299
324
|
progress_handler,
|
|
300
325
|
)
|
|
301
326
|
|
|
302
|
-
self._handle_process_ended(
|
|
303
|
-
return_code, error_handler, success_handler, log_file
|
|
304
|
-
)
|
|
327
|
+
self._handle_process_ended(return_code, error_handler, success_handler)
|
|
305
328
|
|
|
306
329
|
def _handle_process_ended(
|
|
307
|
-
self,
|
|
308
|
-
|
|
330
|
+
self,
|
|
331
|
+
return_code: int,
|
|
332
|
+
error_handler: Optional[Callable],
|
|
333
|
+
success_handler: Optional[Callable],
|
|
334
|
+
) -> None:
|
|
335
|
+
"""Handle the completion of the FFmpeg process."""
|
|
309
336
|
if return_code != 0:
|
|
310
337
|
if error_handler:
|
|
311
338
|
error_handler()
|
|
312
339
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
self._write_ffmpeg_output(
|
|
316
|
-
"\n".join(self._ffmpeg_stderr), ffmpeg_log_file, is_error=True
|
|
340
|
+
sys.exit(
|
|
341
|
+
f"FFmpeg process failed. Check out '{self._ffmpeg_log_file}' for details."
|
|
317
342
|
)
|
|
318
343
|
|
|
319
|
-
sys.exit(f"FFmpeg process failed. Check out {ffmpeg_log_file} for details")
|
|
320
|
-
|
|
321
|
-
if log_file:
|
|
322
|
-
self._write_ffmpeg_output("\n".join(self._ffmpeg_stderr), log_file)
|
|
323
|
-
|
|
324
344
|
if success_handler:
|
|
325
345
|
success_handler()
|
|
326
346
|
else:
|
|
@@ -328,7 +348,8 @@ class FfmpegProcess:
|
|
|
328
348
|
f"{"\n" if self._duration_secs is None else ""}FFmpeg process completed."
|
|
329
349
|
)
|
|
330
350
|
|
|
331
|
-
def _kill_process_and_children(self, proc_pid):
|
|
351
|
+
def _kill_process_and_children(self, proc_pid: int) -> None:
|
|
352
|
+
"""Kill the FFmpeg process and its children."""
|
|
332
353
|
try:
|
|
333
354
|
process = psutil.Process(proc_pid)
|
|
334
355
|
for child in process.children(recursive=True):
|
|
@@ -337,3 +358,9 @@ class FfmpegProcess:
|
|
|
337
358
|
process.terminate()
|
|
338
359
|
except psutil.NoSuchProcess:
|
|
339
360
|
pass
|
|
361
|
+
|
|
362
|
+
def _write_to_log_file(self, message: str, mode: str = "a") -> None:
|
|
363
|
+
if self._ffmpeg_log_file:
|
|
364
|
+
with open(self._ffmpeg_log_file, mode) as f:
|
|
365
|
+
f.write(f"{message}\n")
|
|
366
|
+
f.flush() # Ensure the message is written immediately
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: better-ffmpeg-progress
|
|
3
|
-
Version: 2.5.
|
|
3
|
+
Version: 2.5.3
|
|
4
4
|
Summary: Run FFmpeg & see percentage progress + ETA.
|
|
5
5
|
Home-page: https://github.com/CrypticSignal/better-ffmpeg-progress
|
|
6
6
|
Author: GitHub.com/CrypticSignal
|
|
@@ -167,3 +167,7 @@ The `run` method takes the following **optional** arguments:
|
|
|
167
167
|
|
|
168
168
|
[11/11/2024]
|
|
169
169
|
- [v2.5.1] Print the correct log file.
|
|
170
|
+
|
|
171
|
+
[12/11/2024]
|
|
172
|
+
- [v2.5.2] Drain any remaining FFmpeg stderr after the process ends.
|
|
173
|
+
- [v2.5.3] Write FFmpeg stderr to the log file in real time.
|
|
@@ -5,7 +5,7 @@ with open("README.md", "r", encoding="utf-8") as f:
|
|
|
5
5
|
|
|
6
6
|
setuptools.setup(
|
|
7
7
|
name="better-ffmpeg-progress",
|
|
8
|
-
version="2.5.
|
|
8
|
+
version="2.5.3",
|
|
9
9
|
author="GitHub.com/CrypticSignal",
|
|
10
10
|
author_email="theaudiophile@outlook.com",
|
|
11
11
|
description="Run FFmpeg & see percentage progress + ETA.",
|
|
File without changes
|
{better_ffmpeg_progress-2.5.1 → better_ffmpeg_progress-2.5.3}/better_ffmpeg_progress/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|