better-ffmpeg-progress 2.5.2__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.2 → better_ffmpeg_progress-2.5.3}/PKG-INFO +2 -1
- {better_ffmpeg_progress-2.5.2 → better_ffmpeg_progress-2.5.3}/README.md +2 -1
- {better_ffmpeg_progress-2.5.2 → better_ffmpeg_progress-2.5.3}/better_ffmpeg_progress/better_ffmpeg_progress.py +55 -46
- {better_ffmpeg_progress-2.5.2 → better_ffmpeg_progress-2.5.3}/better_ffmpeg_progress.egg-info/PKG-INFO +2 -1
- {better_ffmpeg_progress-2.5.2 → better_ffmpeg_progress-2.5.3}/setup.py +1 -1
- {better_ffmpeg_progress-2.5.2 → better_ffmpeg_progress-2.5.3}/LICENSE +0 -0
- {better_ffmpeg_progress-2.5.2 → better_ffmpeg_progress-2.5.3}/better_ffmpeg_progress/__init__.py +0 -0
- {better_ffmpeg_progress-2.5.2 → better_ffmpeg_progress-2.5.3}/better_ffmpeg_progress.egg-info/SOURCES.txt +0 -0
- {better_ffmpeg_progress-2.5.2 → better_ffmpeg_progress-2.5.3}/better_ffmpeg_progress.egg-info/dependency_links.txt +0 -0
- {better_ffmpeg_progress-2.5.2 → better_ffmpeg_progress-2.5.3}/better_ffmpeg_progress.egg-info/requires.txt +0 -0
- {better_ffmpeg_progress-2.5.2 → better_ffmpeg_progress-2.5.3}/better_ffmpeg_progress.egg-info/top_level.txt +0 -0
- {better_ffmpeg_progress-2.5.2 → 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
|
|
@@ -170,3 +170,4 @@ The `run` method takes the following **optional** arguments:
|
|
|
170
170
|
|
|
171
171
|
[12/11/2024]
|
|
172
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.
|
|
@@ -151,4 +151,5 @@ The `run` method takes the following **optional** arguments:
|
|
|
151
151
|
- [v2.5.1] Print the correct log file.
|
|
152
152
|
|
|
153
153
|
[12/11/2024]
|
|
154
|
-
- [v2.5.2] Drain any remaining FFmpeg stderr after the process ends.
|
|
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,
|
|
@@ -229,7 +230,7 @@ class FfmpegProcess:
|
|
|
229
230
|
stderr = stderr_queue.get_nowait()
|
|
230
231
|
|
|
231
232
|
if stderr:
|
|
232
|
-
self.
|
|
233
|
+
self._write_to_log_file(stderr)
|
|
233
234
|
|
|
234
235
|
if self._duration_secs is None:
|
|
235
236
|
print(
|
|
@@ -242,6 +243,7 @@ class FfmpegProcess:
|
|
|
242
243
|
|
|
243
244
|
except KeyboardInterrupt:
|
|
244
245
|
self._kill_process_and_children(process.pid)
|
|
246
|
+
self._write_to_log_file("\n[KeyboardInterrupt] FFmpeg process(es) killed.")
|
|
245
247
|
sys.exit(
|
|
246
248
|
f"{"\n" if self._duration_secs is None else ""}[KeyboardInterrupt] FFmpeg process(es) killed."
|
|
247
249
|
)
|
|
@@ -252,7 +254,7 @@ class FfmpegProcess:
|
|
|
252
254
|
stderr = stderr_queue.get_nowait()
|
|
253
255
|
|
|
254
256
|
if stderr:
|
|
255
|
-
self.
|
|
257
|
+
self._write_to_log_file(stderr)
|
|
256
258
|
|
|
257
259
|
if self._duration_secs is None:
|
|
258
260
|
print(
|
|
@@ -267,7 +269,7 @@ class FfmpegProcess:
|
|
|
267
269
|
|
|
268
270
|
def run(
|
|
269
271
|
self,
|
|
270
|
-
log_file: Optional[Union[str, Path]] =
|
|
272
|
+
log_file: Optional[Union[str, Path]] = "ffmpeg_log.txt",
|
|
271
273
|
progress_bar_description: str = None,
|
|
272
274
|
progress_handler: Optional[Callable] = None,
|
|
273
275
|
success_handler: Optional[Callable] = None,
|
|
@@ -284,8 +286,13 @@ class FfmpegProcess:
|
|
|
284
286
|
if not self._check_output_exists():
|
|
285
287
|
return
|
|
286
288
|
|
|
289
|
+
self._ffmpeg_log_file = Path(log_file)
|
|
290
|
+
|
|
291
|
+
with open(self._ffmpeg_log_file, "w"):
|
|
292
|
+
pass
|
|
293
|
+
|
|
287
294
|
print(
|
|
288
|
-
f"Executing: {' '.join(self.
|
|
295
|
+
f"Executing: {' '.join(self._ffmpeg_command)}{"\n" if self._duration_secs is None else ""}"
|
|
289
296
|
)
|
|
290
297
|
|
|
291
298
|
if self._duration_secs and not progress_handler:
|
|
@@ -317,28 +324,23 @@ class FfmpegProcess:
|
|
|
317
324
|
progress_handler,
|
|
318
325
|
)
|
|
319
326
|
|
|
320
|
-
self._handle_process_ended(
|
|
321
|
-
return_code, error_handler, success_handler, log_file
|
|
322
|
-
)
|
|
327
|
+
self._handle_process_ended(return_code, error_handler, success_handler)
|
|
323
328
|
|
|
324
329
|
def _handle_process_ended(
|
|
325
|
-
self,
|
|
326
|
-
|
|
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."""
|
|
327
336
|
if return_code != 0:
|
|
328
337
|
if error_handler:
|
|
329
338
|
error_handler()
|
|
330
339
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
self._write_ffmpeg_output(
|
|
334
|
-
"\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."
|
|
335
342
|
)
|
|
336
343
|
|
|
337
|
-
sys.exit(f"FFmpeg process failed. Check out {ffmpeg_log_file} for details")
|
|
338
|
-
|
|
339
|
-
if log_file:
|
|
340
|
-
self._write_ffmpeg_output("\n".join(self._ffmpeg_stderr), log_file)
|
|
341
|
-
|
|
342
344
|
if success_handler:
|
|
343
345
|
success_handler()
|
|
344
346
|
else:
|
|
@@ -346,7 +348,8 @@ class FfmpegProcess:
|
|
|
346
348
|
f"{"\n" if self._duration_secs is None else ""}FFmpeg process completed."
|
|
347
349
|
)
|
|
348
350
|
|
|
349
|
-
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."""
|
|
350
353
|
try:
|
|
351
354
|
process = psutil.Process(proc_pid)
|
|
352
355
|
for child in process.children(recursive=True):
|
|
@@ -355,3 +358,9 @@ class FfmpegProcess:
|
|
|
355
358
|
process.terminate()
|
|
356
359
|
except psutil.NoSuchProcess:
|
|
357
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
|
|
@@ -170,3 +170,4 @@ The `run` method takes the following **optional** arguments:
|
|
|
170
170
|
|
|
171
171
|
[12/11/2024]
|
|
172
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.2 → 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
|