better-ffmpeg-progress 2.5.2__tar.gz → 2.5.4__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.2
3
+ Version: 2.5.4
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,5 @@ 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.
174
+ - [v2.5.4] Include progress info in stderr if the duration of the input file cannot be detected.
@@ -151,4 +151,6 @@ 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.
156
+ - [v2.5.4] Include progress info in stderr if the duration of the input file cannot be detected.
@@ -56,21 +56,34 @@ 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
+ command.extend(
62
+ [
63
+ "-loglevel",
64
+ ffmpeg_loglevel,
65
+ "-progress",
66
+ "pipe:1",
67
+ "-stats_period",
68
+ "0.1",
69
+ ]
70
+ )
71
+
72
+ self._ffmpeg_command = command
61
73
  self._output_filepath = Path(command[-1])
62
74
  self._print_detected_duration = print_detected_duration
63
75
  self._print_stderr_new_line = print_stderr_new_line
64
76
  self._metrics = Metrics()
65
77
  self._duration_secs: Optional[float] = None
66
78
  self._current_size: int = 0
79
+ self._ffmpeg_log_file: Optional[Path] = None
67
80
 
68
81
  self._get_input_file_duration()
69
82
 
70
83
  def _get_input_file_duration(self) -> None:
71
84
  """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])
85
+ index_of_filepath = self._ffmpeg_command.index("-i") + 1
86
+ self._input_filepath = Path(self._ffmpeg_command[index_of_filepath])
74
87
 
75
88
  try:
76
89
  self._duration_secs = float(
@@ -81,9 +94,9 @@ class FfmpegProcess:
81
94
  print(
82
95
  f"The duration of {self._input_filepath.name} has been detected as {self._duration_secs} seconds"
83
96
  )
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"])
97
+ # -nostats ensures that progress information is not sent to stderr as this info is not needed in the log file, e.g.
98
+ # frame= 1381 fps=254 q=18.0 size= 46592KiB time=00:00:46.07 bitrate=8283.1kbits/s speed=8.47x
99
+ self._ffmpeg_command.extend(["-nostats"])
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,
@@ -229,7 +230,7 @@ class FfmpegProcess:
229
230
  stderr = stderr_queue.get_nowait()
230
231
 
231
232
  if stderr:
232
- self._ffmpeg_stderr.append(stderr)
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._ffmpeg_stderr.append(stderr)
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]] = None,
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._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 ""}"
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, return_code, error_handler, success_handler, log_file
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
- ffmpeg_log_file = log_file if log_file else "ffmpeg_log.txt"
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.2
3
+ Version: 2.5.4
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,5 @@ 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.
174
+ - [v2.5.4] Include progress info in stderr if the duration of the input file cannot be detected.
@@ -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.2",
8
+ version="2.5.4",
9
9
  author="GitHub.com/CrypticSignal",
10
10
  author_email="theaudiophile@outlook.com",
11
11
  description="Run FFmpeg & see percentage progress + ETA.",