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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: better-ffmpeg-progress
3
- Version: 2.5.1
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
- self._ffmpeg_args = command + ["-hide_banner", "-loglevel", ffmpeg_loglevel]
60
- self._ffmpeg_stderr: List[str] = []
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._ffmpeg_args.index("-i") + 1
73
- self._input_filepath = Path(self._ffmpeg_args[index_of_filepath])
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._ffmpeg_args:
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._ffmpeg_args.insert(1, "-y")
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
- ) -> None:
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._ffmpeg_args,
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 self._duration_secs is None:
232
- print(
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
- self._ffmpeg_stderr.append(stderr)
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]] = None,
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._ffmpeg_args)}{"\n" if self._duration_secs is None else ""}"
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, return_code, error_handler, success_handler, log_file
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
- ffmpeg_log_file = log_file if log_file else "ffmpeg_log.txt"
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.1
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.1",
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.",