better-ffmpeg-progress 2.3.0__tar.gz → 2.4.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: better-ffmpeg-progress
3
- Version: 2.3.0
3
+ Version: 2.4.0
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
@@ -13,6 +13,7 @@ Requires-Python: >=3.7
13
13
  Description-Content-Type: text/markdown
14
14
  License-File: LICENSE
15
15
  Requires-Dist: ffmpeg-python
16
+ Requires-Dist: psutil
16
17
  Requires-Dist: rich
17
18
 
18
19
  <div align="center">
@@ -166,3 +167,7 @@ The `run` method takes the following **optional** arguments:
166
167
 
167
168
  [29/10/2024]
168
169
  - [v2.3.0] Set `shell=True` to support piping.
170
+ - [v2.3.1] Kill FFmpeg process(es) on KeyboardInterrupt
171
+
172
+ [31/10/2024]
173
+ - [v2.4.0] Print FFmpeg stderr if better progress information cannot be shown.
@@ -148,4 +148,8 @@ The `run` method takes the following **optional** arguments:
148
148
  - [v2.2.2] Make printing the detected duration of the input file optional.
149
149
 
150
150
  [29/10/2024]
151
- - [v2.3.0] Set `shell=True` to support piping.
151
+ - [v2.3.0] Set `shell=True` to support piping.
152
+ - [v2.3.1] Kill FFmpeg process(es) on KeyboardInterrupt
153
+
154
+ [31/10/2024]
155
+ - [v2.4.0] Print FFmpeg stderr if better progress information cannot be shown.
@@ -1,4 +1,5 @@
1
1
  from dataclasses import dataclass
2
+ import os
2
3
  from pathlib import Path
3
4
  from queue import Empty, Queue
4
5
  import subprocess
@@ -7,6 +8,7 @@ from threading import Thread
7
8
  from typing import Callable, List, Optional, Union
8
9
 
9
10
  from ffmpeg import probe
11
+ import psutil
10
12
  from rich.progress import (
11
13
  Progress,
12
14
  TextColumn,
@@ -77,15 +79,11 @@ class FfmpegProcess:
77
79
  )
78
80
 
79
81
  # -progress pipe:1 sends progress metrics to stdout AND -stats_period sets the period at which encoding progress/statistics are updated
80
- # -nostats disables encoding progress/statistics in stderr
81
- self._ffmpeg_args.extend(
82
- ["-progress", "pipe:1", "-stats_period", "0.1", "-nostats"]
83
- )
82
+ self._ffmpeg_args.extend(["-progress", "pipe:1", "-stats_period", "0.1"])
84
83
  except Exception:
85
84
  print(
86
- f"Could not detect the duration of {self._input_filepath.name}. Percentage progress, ETA and estimated filesize will be unavailable."
85
+ f"Could not detect the duration of '{self._input_filepath.name}'. Percentage progress, ETA and estimated filesize will be unavailable.\n",
87
86
  )
88
- self._duration_secs = None
89
87
 
90
88
  def _check_output_exists(self) -> bool:
91
89
  """Check if output file exists and handle overwrite prompt."""
@@ -163,7 +161,9 @@ class FfmpegProcess:
163
161
  if is_error:
164
162
  return
165
163
 
166
- print(f"FFmpeg log written to: {log_file}")
164
+ print(
165
+ f"{"\n\n" if self._duration_secs is None else ""}FFmpeg log filename: {log_file}"
166
+ )
167
167
 
168
168
  def _start_process(
169
169
  self,
@@ -171,12 +171,15 @@ class FfmpegProcess:
171
171
  task_id: Optional[int],
172
172
  progress_handler: Optional[Callable],
173
173
  ) -> None:
174
+ creationflags = subprocess.CREATE_NEW_PROCESS_GROUP if os.name == "nt" else 0
175
+
174
176
  process = subprocess.Popen(
175
177
  self._ffmpeg_args,
176
178
  stdout=subprocess.PIPE,
177
179
  stderr=subprocess.PIPE,
178
180
  text=True,
179
181
  shell=True,
182
+ creationflags=creationflags,
180
183
  )
181
184
 
182
185
  stdout_queue, stderr_queue = Queue(), Queue()
@@ -187,40 +190,50 @@ class FfmpegProcess:
187
190
  target=self._handle_pipe, args=(process.stderr, stderr_queue), daemon=True
188
191
  ).start()
189
192
 
190
- while process.poll() is None:
191
- try:
192
- stdout = stdout_queue.get_nowait()
193
-
194
- if stdout:
195
- metric, value = stdout.split("=")
196
-
197
- if metric in self.WANTED_METRICS and value and "N/A" not in value:
198
- self._update_metrics(stdout, metric, value, progress_handler)
199
-
200
- if (
201
- metric == "out_time_ms"
202
- and progress_bar
203
- and task_id is not None
204
- ):
205
- progress_bar.update(
206
- task_id,
207
- completed=self._metrics.seconds_processed,
208
- speed=self._metrics.speed,
209
- )
210
- except Empty:
211
- pass
212
-
213
- try:
214
- stderr = stderr_queue.get_nowait()
215
-
216
- if "No such file" in stderr:
217
- raise FileNotFoundError(
218
- f"Input file not found: {self._input_filepath}"
219
- )
220
-
221
- self._ffmpeg_stderr.append(stderr)
222
- except Empty:
223
- pass
193
+ try:
194
+ while process.poll() is None:
195
+ if self._duration_secs is not None:
196
+ try:
197
+ stdout = stdout_queue.get_nowait()
198
+
199
+ if stdout:
200
+ metric, value = stdout.split("=")
201
+
202
+ if (
203
+ metric in self.WANTED_METRICS
204
+ and value
205
+ and "N/A" not in value
206
+ ):
207
+ self._update_metrics(
208
+ stdout, metric, value, progress_handler
209
+ )
210
+
211
+ if (
212
+ metric == "out_time_ms"
213
+ and progress_bar
214
+ and task_id is not None
215
+ ):
216
+ progress_bar.update(
217
+ task_id,
218
+ completed=self._metrics.seconds_processed,
219
+ speed=self._metrics.speed,
220
+ )
221
+ except Empty:
222
+ pass
223
+
224
+ try:
225
+ stderr = stderr_queue.get_nowait()
226
+
227
+ if self._duration_secs is None:
228
+ print(stderr, end="\r")
229
+
230
+ self._ffmpeg_stderr.append(stderr)
231
+ except Empty:
232
+ pass
233
+
234
+ except KeyboardInterrupt:
235
+ self._kill_process_and_children(process.pid)
236
+ sys.exit("[KeyboardInterrupt] FFmpeg process(es) killed.")
224
237
 
225
238
  return process.returncode
226
239
 
@@ -243,7 +256,9 @@ class FfmpegProcess:
243
256
  if not self._check_output_exists():
244
257
  return
245
258
 
246
- print(f"Executing: {' '.join(self._ffmpeg_args)}")
259
+ print(
260
+ f"Executing: {' '.join(self._ffmpeg_args)}{"\n" if self._duration_secs is None else ""}"
261
+ )
247
262
 
248
263
  if self._duration_secs and not progress_handler:
249
264
  with Progress(
@@ -299,4 +314,16 @@ class FfmpegProcess:
299
314
  if success_handler:
300
315
  success_handler()
301
316
  else:
302
- print("FFmpeg process completed.")
317
+ print(
318
+ f"{"\n" if self._duration_secs is None else ""}FFmpeg process completed."
319
+ )
320
+
321
+ def _kill_process_and_children(self, proc_pid):
322
+ try:
323
+ process = psutil.Process(proc_pid)
324
+ for child in process.children(recursive=True):
325
+ child.terminate()
326
+ # Terminate the main process
327
+ process.terminate()
328
+ except psutil.NoSuchProcess:
329
+ pass
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: better-ffmpeg-progress
3
- Version: 2.3.0
3
+ Version: 2.4.0
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
@@ -13,6 +13,7 @@ Requires-Python: >=3.7
13
13
  Description-Content-Type: text/markdown
14
14
  License-File: LICENSE
15
15
  Requires-Dist: ffmpeg-python
16
+ Requires-Dist: psutil
16
17
  Requires-Dist: rich
17
18
 
18
19
  <div align="center">
@@ -166,3 +167,7 @@ The `run` method takes the following **optional** arguments:
166
167
 
167
168
  [29/10/2024]
168
169
  - [v2.3.0] Set `shell=True` to support piping.
170
+ - [v2.3.1] Kill FFmpeg process(es) on KeyboardInterrupt
171
+
172
+ [31/10/2024]
173
+ - [v2.4.0] Print FFmpeg stderr if better progress information cannot be shown.
@@ -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.3.0",
8
+ version="2.4.0",
9
9
  author="GitHub.com/CrypticSignal",
10
10
  author_email="theaudiophile@outlook.com",
11
11
  description="Run FFmpeg & see percentage progress + ETA.",
@@ -13,7 +13,7 @@ setuptools.setup(
13
13
  long_description_content_type="text/markdown",
14
14
  url="https://github.com/CrypticSignal/better-ffmpeg-progress",
15
15
  packages=["better_ffmpeg_progress"],
16
- install_requires=["ffmpeg-python", "rich"],
16
+ install_requires=["ffmpeg-python", "psutil", "rich"],
17
17
  python_requires=">=3.7",
18
18
  keywords=["ffmpeg", "progress"],
19
19
  classifiers=[