better-ffmpeg-progress 5.1.0__tar.gz → 5.2.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.
Files changed (20) hide show
  1. better_ffmpeg_progress-5.2.0/PKG-INFO +127 -0
  2. better_ffmpeg_progress-5.2.0/README.md +108 -0
  3. {better_ffmpeg_progress-5.1.0 → better_ffmpeg_progress-5.2.0}/better_ffmpeg_progress/better_ffmpeg_progress.py +62 -32
  4. {better_ffmpeg_progress-5.1.0 → better_ffmpeg_progress-5.2.0}/better_ffmpeg_progress/progress_bars.py +5 -13
  5. {better_ffmpeg_progress-5.1.0 → better_ffmpeg_progress-5.2.0}/better_ffmpeg_progress/terminate_process.py +11 -33
  6. {better_ffmpeg_progress-5.1.0 → better_ffmpeg_progress-5.2.0}/better_ffmpeg_progress/utils.py +3 -11
  7. better_ffmpeg_progress-5.2.0/better_ffmpeg_progress.egg-info/PKG-INFO +127 -0
  8. {better_ffmpeg_progress-5.1.0 → better_ffmpeg_progress-5.2.0}/setup.py +1 -1
  9. better_ffmpeg_progress-5.1.0/PKG-INFO +0 -94
  10. better_ffmpeg_progress-5.1.0/README.md +0 -75
  11. better_ffmpeg_progress-5.1.0/better_ffmpeg_progress.egg-info/PKG-INFO +0 -94
  12. {better_ffmpeg_progress-5.1.0 → better_ffmpeg_progress-5.2.0}/LICENSE +0 -0
  13. {better_ffmpeg_progress-5.1.0 → better_ffmpeg_progress-5.2.0}/better_ffmpeg_progress/__init__.py +0 -0
  14. {better_ffmpeg_progress-5.1.0 → better_ffmpeg_progress-5.2.0}/better_ffmpeg_progress/enums.py +0 -0
  15. {better_ffmpeg_progress-5.1.0 → better_ffmpeg_progress-5.2.0}/better_ffmpeg_progress/exceptions.py +0 -0
  16. {better_ffmpeg_progress-5.1.0 → better_ffmpeg_progress-5.2.0}/better_ffmpeg_progress.egg-info/SOURCES.txt +0 -0
  17. {better_ffmpeg_progress-5.1.0 → better_ffmpeg_progress-5.2.0}/better_ffmpeg_progress.egg-info/dependency_links.txt +0 -0
  18. {better_ffmpeg_progress-5.1.0 → better_ffmpeg_progress-5.2.0}/better_ffmpeg_progress.egg-info/requires.txt +0 -0
  19. {better_ffmpeg_progress-5.1.0 → better_ffmpeg_progress-5.2.0}/better_ffmpeg_progress.egg-info/top_level.txt +0 -0
  20. {better_ffmpeg_progress-5.1.0 → better_ffmpeg_progress-5.2.0}/setup.cfg +0 -0
@@ -0,0 +1,127 @@
1
+ Metadata-Version: 2.1
2
+ Name: better-ffmpeg-progress
3
+ Version: 5.2.0
4
+ Summary: Run FFmpeg & see percentage progress + ETA.
5
+ Home-page: https://github.com/CrypticSignal/better-ffmpeg-progress
6
+ Author: GitHub.com/CrypticSignal
7
+ Author-email: theaudiophile@outlook.com
8
+ Keywords: ffmpeg,progress
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Operating System :: OS Independent
12
+ Requires-Python: >=3.7
13
+ Description-Content-Type: text/markdown
14
+ License-File: LICENSE
15
+ Requires-Dist: psutil
16
+ Requires-Dist: requests
17
+ Requires-Dist: rich
18
+ Requires-Dist: tqdm
19
+
20
+ <div align="center">
21
+ [![PyPI downloads](https://img.shields.io/pypi/dm/better-ffmpeg-progress?label=PyPI\&color=blue)](https://pypistats.org/packages/better-ffmpeg-progress)
22
+ [![PyPI downloads](https://img.shields.io/pypi/dd/better-ffmpeg-progress?label=PyPI\&color=blue)](https://pypistats.org/packages/better-ffmpeg-progress)
23
+ ![PyPI - Version](https://img.shields.io/pypi/v/better-ffmpeg-progress)
24
+ [![GitHub](https://img.shields.io/github/license/crypticsignal/better-ffmpeg-progress?label=License\&color=blue)](LICENSE.txt)
25
+
26
+ # Better FFmpeg Progress
27
+ Runs an FFmpeg command and shows a progress bar with percentage progress, elapsed time and ETA.
28
+ </div>
29
+
30
+ FFmpeg outputs something like:
31
+ ```
32
+ frame= 692 fps= 58 q=28.0 size= 5376KiB time=00:00:28.77 bitrate=1530.3kbits/s speed=2.43x
33
+ ```
34
+ Better FFmpeg Progress outputs something like:
35
+ ```
36
+ ⠏ Processing abc.webm ━━━━━━━━━╺━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 23% 0:00:04 00:15
37
+ ```
38
+ Where:
39
+ - `Processing abc.webm` is the description of the progresss bar.
40
+ - `23%` is the percentage progress.
41
+ - `0:00:04` is the time elapsed.
42
+ - `00:15` is the estimated time until the FFmpeg process completes.
43
+
44
+ ## Installation
45
+ ```bash
46
+ pip install better-ffmpeg-progress --upgrade
47
+ ```
48
+
49
+ ## Usage
50
+ Create an instance of the `FfmpegProcess` class and supply a list of arguments like you would when using `subprocess.run()` or `subprocess.Popen()`. Example:
51
+
52
+ ```python
53
+ from better_ffmpeg_progress import FfmpegProcess, FfmpegProcessError
54
+
55
+ command = [
56
+ "ffmpeg",
57
+ "-i",
58
+ "https://media.xiph.org/video/derf/y4m/ducks_take_off_1080p50.y4m",
59
+ "-map",
60
+ "0:V",
61
+ "-c:V",
62
+ "libx264",
63
+ "-preset",
64
+ "ultrafast",
65
+ "-f",
66
+ "null",
67
+ "-",
68
+ ]
69
+
70
+ try:
71
+ process = FfmpegProcess(command)
72
+ # Uncomment this line to use tqdm instead of Rich.
73
+ # process.use_tqdm = True
74
+
75
+ process.run()
76
+ except FfmpegProcessError as e:
77
+ print(
78
+ "An error occurred when running Better FFmpeg Progress:\n"
79
+ f"{e}"
80
+ )
81
+ ```
82
+
83
+ ## Duration Override
84
+ By default, Better FFmpeg Progress detects the duration of the input file and uses it to calculate percentage progress and ETA.
85
+
86
+ For partial, damaged, streamed or synthetic inputs, FFmpeg may be unable to detect the duration correctly. In these cases, you can provide the duration manually using `duration_override`.
87
+
88
+ The value should be the duration of the input file, in seconds:
89
+
90
+ ```python
91
+ from better_ffmpeg_progress import FfmpegProcess
92
+
93
+ command = [
94
+ "ffmpeg",
95
+ "-i",
96
+ "partial-input.flv",
97
+ "-c",
98
+ "copy",
99
+ "recovered-output.mkv",
100
+ ]
101
+
102
+ process = FfmpegProcess(
103
+ command,
104
+ duration_override=1222.64,
105
+ print_detected_duration=True,
106
+ )
107
+
108
+ process.run()
109
+ ```
110
+
111
+ ## Optional Arguments
112
+ An instance of `FfmpegProcess` accepts the following optional arguments:
113
+ * `ffmpeg_log_level` — Set a value for FFmpeg's `-loglevel` option. Default: `"verbose"`.
114
+ * `ffmpeg_log_file` — Filepath or file-like object to which FFmpeg stderr will be written. Default: `<input filename>_ffmpeg_log.txt`.
115
+ * `print_detected_duration` — Print the detected duration. Default: `False`.
116
+ * `duration_override` — Specify input file duration in seconds to use for progress calculation, instead of trying to detect this automatically with FFprobe. Default: `None`.
117
+
118
+ The `run` method accepts the following optional argument:
119
+ * `print_command` — Print the FFmpeg command being executed. Default: `False`.
120
+
121
+ ## Using tqdm
122
+ By default, the [Rich](https://github.com/Textualize/rich) library is used to display a progress bar. [tqdm](https://github.com/tqdm/tqdm) can be used instead by setting `process.use_tqdm = True`:
123
+ ```python
124
+ process = FfmpegProcess(command)
125
+ process.use_tqdm = True
126
+ process.run()
127
+ ```
@@ -0,0 +1,108 @@
1
+ <div align="center">
2
+ [![PyPI downloads](https://img.shields.io/pypi/dm/better-ffmpeg-progress?label=PyPI\&color=blue)](https://pypistats.org/packages/better-ffmpeg-progress)
3
+ [![PyPI downloads](https://img.shields.io/pypi/dd/better-ffmpeg-progress?label=PyPI\&color=blue)](https://pypistats.org/packages/better-ffmpeg-progress)
4
+ ![PyPI - Version](https://img.shields.io/pypi/v/better-ffmpeg-progress)
5
+ [![GitHub](https://img.shields.io/github/license/crypticsignal/better-ffmpeg-progress?label=License\&color=blue)](LICENSE.txt)
6
+
7
+ # Better FFmpeg Progress
8
+ Runs an FFmpeg command and shows a progress bar with percentage progress, elapsed time and ETA.
9
+ </div>
10
+
11
+ FFmpeg outputs something like:
12
+ ```
13
+ frame= 692 fps= 58 q=28.0 size= 5376KiB time=00:00:28.77 bitrate=1530.3kbits/s speed=2.43x
14
+ ```
15
+ Better FFmpeg Progress outputs something like:
16
+ ```
17
+ ⠏ Processing abc.webm ━━━━━━━━━╺━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 23% 0:00:04 00:15
18
+ ```
19
+ Where:
20
+ - `Processing abc.webm` is the description of the progresss bar.
21
+ - `23%` is the percentage progress.
22
+ - `0:00:04` is the time elapsed.
23
+ - `00:15` is the estimated time until the FFmpeg process completes.
24
+
25
+ ## Installation
26
+ ```bash
27
+ pip install better-ffmpeg-progress --upgrade
28
+ ```
29
+
30
+ ## Usage
31
+ Create an instance of the `FfmpegProcess` class and supply a list of arguments like you would when using `subprocess.run()` or `subprocess.Popen()`. Example:
32
+
33
+ ```python
34
+ from better_ffmpeg_progress import FfmpegProcess, FfmpegProcessError
35
+
36
+ command = [
37
+ "ffmpeg",
38
+ "-i",
39
+ "https://media.xiph.org/video/derf/y4m/ducks_take_off_1080p50.y4m",
40
+ "-map",
41
+ "0:V",
42
+ "-c:V",
43
+ "libx264",
44
+ "-preset",
45
+ "ultrafast",
46
+ "-f",
47
+ "null",
48
+ "-",
49
+ ]
50
+
51
+ try:
52
+ process = FfmpegProcess(command)
53
+ # Uncomment this line to use tqdm instead of Rich.
54
+ # process.use_tqdm = True
55
+
56
+ process.run()
57
+ except FfmpegProcessError as e:
58
+ print(
59
+ "An error occurred when running Better FFmpeg Progress:\n"
60
+ f"{e}"
61
+ )
62
+ ```
63
+
64
+ ## Duration Override
65
+ By default, Better FFmpeg Progress detects the duration of the input file and uses it to calculate percentage progress and ETA.
66
+
67
+ For partial, damaged, streamed or synthetic inputs, FFmpeg may be unable to detect the duration correctly. In these cases, you can provide the duration manually using `duration_override`.
68
+
69
+ The value should be the duration of the input file, in seconds:
70
+
71
+ ```python
72
+ from better_ffmpeg_progress import FfmpegProcess
73
+
74
+ command = [
75
+ "ffmpeg",
76
+ "-i",
77
+ "partial-input.flv",
78
+ "-c",
79
+ "copy",
80
+ "recovered-output.mkv",
81
+ ]
82
+
83
+ process = FfmpegProcess(
84
+ command,
85
+ duration_override=1222.64,
86
+ print_detected_duration=True,
87
+ )
88
+
89
+ process.run()
90
+ ```
91
+
92
+ ## Optional Arguments
93
+ An instance of `FfmpegProcess` accepts the following optional arguments:
94
+ * `ffmpeg_log_level` — Set a value for FFmpeg's `-loglevel` option. Default: `"verbose"`.
95
+ * `ffmpeg_log_file` — Filepath or file-like object to which FFmpeg stderr will be written. Default: `<input filename>_ffmpeg_log.txt`.
96
+ * `print_detected_duration` — Print the detected duration. Default: `False`.
97
+ * `duration_override` — Specify input file duration in seconds to use for progress calculation, instead of trying to detect this automatically with FFprobe. Default: `None`.
98
+
99
+ The `run` method accepts the following optional argument:
100
+ * `print_command` — Print the FFmpeg command being executed. Default: `False`.
101
+
102
+ ## Using tqdm
103
+ By default, the [Rich](https://github.com/Textualize/rich) library is used to display a progress bar. [tqdm](https://github.com/tqdm/tqdm) can be used instead by setting `process.use_tqdm = True`:
104
+ ```python
105
+ process = FfmpegProcess(command)
106
+ process.use_tqdm = True
107
+ process.run()
108
+ ```
@@ -1,3 +1,4 @@
1
+ import math
1
2
  import os
2
3
  from pathlib import Path
3
4
  import subprocess
@@ -27,18 +28,16 @@ class FfmpegProcess:
27
28
  """
28
29
  Handles the logic for overwriting an existing output file.
29
30
  Modifies self._ffmpeg_command to include -y if overwrite is confirmed.
30
- Raises FfmpegProcessUserCancelledError or FfmpegProcessInterruptedError if not proceeding.
31
+ Raises FfmpegProcessUserCancelledError or
32
+ FfmpegProcessInterruptedError if not proceeding.
31
33
  """
32
34
  try:
33
35
  answer = (
34
- input(
35
- f"Output file {self._output_filepath} already exists. Overwrite? [y/N]: "
36
- )
36
+ input(f"Output file {self._output_filepath} already exists. Overwrite? [y/N]: ")
37
37
  .strip()
38
38
  .lower()
39
39
  )
40
40
  if answer == "y":
41
- # Insert -y
42
41
  self._ffmpeg_command.insert(1, _FFMPEG_OVERWRITE_FLAG)
43
42
  else:
44
43
  raise FfmpegProcessUserCancelledError(
@@ -59,8 +58,24 @@ class FfmpegProcess:
59
58
  ffmpeg_log_level: Optional[Union[FfmpegLogLevel, str]] = None,
60
59
  ffmpeg_log_file: Optional[Union[str, os.PathLike]] = None,
61
60
  print_detected_duration: bool = False,
61
+ duration_override: Optional[float] = None,
62
62
  ):
63
- # Raises FfmpegCommandError if the command is invalid
63
+ """
64
+ Create an FFmpeg process with progress reporting.
65
+
66
+ Args:
67
+ command:
68
+ FFmpeg command as a list of arguments.
69
+ ffmpeg_log_level:
70
+ FFmpeg log level enum or case-insensitive string.
71
+ ffmpeg_log_file:
72
+ File path or file-like object used for FFmpeg stderr.
73
+ print_detected_duration:
74
+ Print the duration used for progress calculation.
75
+ duration_override:
76
+ Specify input file duration in seconds to use for progress calculation, instead of trying to detect this automatically with FFprobe. This is useful for partial, damaged, streamed, or
77
+ synthetic inputs whose duration cannot be detected correctly.
78
+ """
64
79
  validate_ffmpeg_command(command)
65
80
 
66
81
  ffmpeg_log_level_val: str
@@ -75,20 +90,24 @@ class FfmpegProcess:
75
90
  except ValueError:
76
91
  valid_levels = [e.value for e in FfmpegLogLevel]
77
92
  raise FfmpegCommandError(
78
- f"Invalid ffmpeg_log_level string: '{ffmpeg_log_level}'. "
79
- f"Must be one of {valid_levels} (case-insensitive)."
93
+ f"Invalid ffmpeg_log_level string: "
94
+ f"'{ffmpeg_log_level}'. Must be one of "
95
+ f"{valid_levels} (case-insensitive)."
80
96
  )
81
97
  else:
82
98
  raise TypeError(
83
- f"ffmpeg_log_level must be an FfmpegLogLevel enum instance, a string, or None, "
84
- f"not {type(ffmpeg_log_level).__name__}"
99
+ "ffmpeg_log_level must be an FfmpegLogLevel enum instance, "
100
+ f"a string, or None, not "
101
+ f"{type(ffmpeg_log_level).__name__}"
85
102
  )
103
+
86
104
  self._ffmpeg_log_level_val = ffmpeg_log_level_val
87
105
 
88
106
  input_file_index = command.index("-i")
89
107
  input_file_path_str = command[input_file_index + 1]
90
108
  self._input_filepath = Path(input_file_path_str)
91
- # Assumes last argument is output
109
+
110
+ # Assumes last argument is output.
92
111
  self._output_filepath = Path(command[-1])
93
112
 
94
113
  if ffmpeg_log_file is None:
@@ -99,14 +118,30 @@ class FfmpegProcess:
99
118
  self._ffmpeg_log_file = ffmpeg_log_file
100
119
 
101
120
  self._print_detected_duration = print_detected_duration
102
- self._duration_secs = get_media_duration(input_file_path_str)
103
121
 
104
- if self._print_detected_duration and self._duration_secs is not None:
105
- print(f"Detected duration: {self._duration_secs:.2f} seconds")
106
- elif self._print_detected_duration and self._duration_secs is None:
107
- print(
108
- "Could not detect duration. Progress bar may not show time remaining."
109
- )
122
+ if duration_override is not None:
123
+ if isinstance(duration_override, bool):
124
+ raise TypeError("duration_override must be a number of seconds, not bool")
125
+
126
+ try:
127
+ duration_value = float(duration_override)
128
+ except (TypeError, ValueError) as e:
129
+ raise TypeError("duration_override must be a number of seconds or None") from e
130
+
131
+ if not math.isfinite(duration_value) or duration_value <= 0:
132
+ raise ValueError("duration_override must be a finite number greater than 0")
133
+
134
+ self._duration_secs = duration_value
135
+ else:
136
+ self._duration_secs = get_media_duration(input_file_path_str)
137
+
138
+ if duration_override is not None:
139
+ print(f"Using duration override: {self._duration_secs:.2f} seconds")
140
+ elif self._print_detected_duration:
141
+ if self._duration_secs is None:
142
+ print("Could not detect duration. Progress bar may not show time remaining.")
143
+ else:
144
+ print(f"Detected duration: {self._duration_secs:.2f} seconds")
110
145
 
111
146
  self._ffmpeg_command = [
112
147
  command[0],
@@ -114,14 +149,12 @@ class FfmpegProcess:
114
149
  "-loglevel",
115
150
  self._ffmpeg_log_level_val,
116
151
  "-progress",
117
- "pipe:1", # stdout
152
+ "pipe:1",
118
153
  "-nostats",
119
154
  ]
120
155
  self._ffmpeg_command.extend(command[1:])
121
156
 
122
- is_overwrite_in_user_command = any(
123
- arg == _FFMPEG_OVERWRITE_FLAG for arg in command[1:]
124
- )
157
+ is_overwrite_in_user_command = any(arg == _FFMPEG_OVERWRITE_FLAG for arg in command[1:])
125
158
 
126
159
  if self._output_filepath.exists() and not is_overwrite_in_user_command:
127
160
  self._handle_overwrite_prompt()
@@ -158,7 +191,11 @@ class FfmpegProcess:
158
191
  creationflags = subprocess.CREATE_NEW_PROCESS_GROUP
159
192
 
160
193
  if isinstance(self._ffmpeg_log_file, Path):
161
- with open(self._ffmpeg_log_file, "w", encoding="utf-8") as f:
194
+ with open(
195
+ self._ffmpeg_log_file,
196
+ "w",
197
+ encoding="utf-8",
198
+ ) as f:
162
199
  self._process = subprocess.Popen(
163
200
  current_ffmpeg_command,
164
201
  shell=self._shell_needed,
@@ -177,26 +214,19 @@ class FfmpegProcess:
177
214
  except Exception as e:
178
215
  raise FfmpegProcessError(f"Error starting FFmpeg process: {e}") from e
179
216
 
180
- user_interrupted = False
181
217
  try:
182
218
  if self.use_tqdm:
183
219
  use_tqdm(self, self._process)
184
220
  else:
185
221
  use_rich(self, self._process)
186
222
  except KeyboardInterrupt:
187
- user_interrupted = True
188
223
  self._terminate()
189
224
  finally:
190
- if self._process:
191
- if self._process.stdout:
192
- self._process.stdout.close()
193
-
194
- if not user_interrupted and self._process and self._process.returncode != 0:
195
- raise FfmpegProcessError(f"FFmpeg process failed. Return code: {self._process.returncode}")
225
+ if self._process and self._process.stdout:
226
+ self._process.stdout.close()
196
227
 
197
228
  def _terminate(self):
198
229
  if self._process:
199
230
  terminate_ffmpeg_process(self._process)
200
231
  else:
201
232
  exit()
202
-
@@ -19,9 +19,7 @@ if TYPE_CHECKING:
19
19
  from .better_ffmpeg_progress import FfmpegProcess
20
20
 
21
21
 
22
- def use_rich(
23
- ffmpeg_process_instance: "FfmpegProcess", process: subprocess.Popen
24
- ) -> None:
22
+ def use_rich(ffmpeg_process_instance: "FfmpegProcess", process: subprocess.Popen) -> None:
25
23
  task_id = None
26
24
  progress_bar_instance = Progress(
27
25
  TextColumn("[progress.description]{task.description}"),
@@ -39,16 +37,14 @@ def use_rich(
39
37
  with progress_bar_instance as progress_bar:
40
38
  if duration_secs:
41
39
  task_id = progress_bar.add_task(
42
- f"Processing '{input_filename}'",
40
+ f"Processing {input_filename}",
43
41
  total=duration_secs,
44
42
  )
45
43
  update_progress = progress_bar.update
46
44
 
47
45
  for line_bytes in process.stdout:
48
46
  stripped_line_bytes = line_bytes.strip()
49
- progress_val_secs = parse_ffmpeg_progress_line(
50
- stripped_line_bytes, duration_secs
51
- )
47
+ progress_val_secs = parse_ffmpeg_progress_line(stripped_line_bytes, duration_secs)
52
48
  if progress_val_secs is not None and task_id is not None:
53
49
  update_progress(task_id, completed=progress_val_secs)
54
50
  else:
@@ -75,9 +71,7 @@ def use_rich(
75
71
  raise FfmpegProcessError(f"FFmpeg process failed. Check '{log_file}' for details.")
76
72
 
77
73
 
78
- def use_tqdm(
79
- ffmpeg_process_instance: "FfmpegProcess", process: subprocess.Popen
80
- ) -> None:
74
+ def use_tqdm(ffmpeg_process_instance: "FfmpegProcess", process: subprocess.Popen) -> None:
81
75
  progress_bar = None
82
76
  try:
83
77
  width = os.get_terminal_size().columns
@@ -100,9 +94,7 @@ def use_tqdm(
100
94
 
101
95
  for line_bytes in process.stdout:
102
96
  stripped_line_bytes = line_bytes.strip()
103
- progress_val_secs = parse_ffmpeg_progress_line(
104
- stripped_line_bytes, duration_secs
105
- )
97
+ progress_val_secs = parse_ffmpeg_progress_line(stripped_line_bytes, duration_secs)
106
98
  if progress_val_secs is not None and progress_bar is not None:
107
99
  progress_bar.n = progress_val_secs
108
100
  progress_bar.refresh()
@@ -32,9 +32,7 @@ def _terminate_children_processes(parent_proc: psutil.Process):
32
32
  if not children:
33
33
  return
34
34
 
35
- print(
36
- f"Attempting to terminate {len(children)} child process(es) of {parent_proc.pid}..."
37
- )
35
+ print(f"Attempting to terminate {len(children)} child process(es) of {parent_proc.pid}...")
38
36
  for child in children:
39
37
  try:
40
38
  if child.is_running():
@@ -68,21 +66,15 @@ def _terminate_parent_process_windows(proc: psutil.Process):
68
66
  try:
69
67
  # Check the status after the timeout
70
68
  if proc.is_running():
71
- print(
72
- f"FFmpeg process {proc.pid} still running after {_TERMINATION_TIMEOUT}s."
73
- )
69
+ print(f"FFmpeg process {proc.pid} still running after {_TERMINATION_TIMEOUT}s.")
74
70
  else:
75
- print(
76
- f"FFmpeg process termination took longer than {_TERMINATION_TIMEOUT}s."
77
- )
71
+ print(f"FFmpeg process termination took longer than {_TERMINATION_TIMEOUT}s.")
78
72
  except psutil.NoSuchProcess:
79
73
  print(
80
74
  f"FFmpeg process not found after {_TERMINATION_TIMEOUT}s. It seems like it has terminated."
81
75
  )
82
76
  except psutil.Error as e:
83
- print(
84
- f"Error during status check for process {proc.pid} after timeout:\n{e}"
85
- )
77
+ print(f"Error during status check for process {proc.pid} after timeout:\n{e}")
86
78
  except psutil.AccessDenied:
87
79
  print(f"Access denied when trying to terminate FFmpeg process {proc.pid}.")
88
80
  except psutil.Error as e:
@@ -106,14 +98,10 @@ def _terminate_parent_process_unix(proc: psutil.Process, original_pid: int):
106
98
  if not proc.is_running():
107
99
  terminated_by_pg = True
108
100
  except psutil.TimeoutExpired:
109
- print(
110
- f"Process group {pgid_to_signal} did not exit after SIGTERM, sending SIGKILL..."
111
- )
101
+ print(f"Process group {pgid_to_signal} did not exit after SIGTERM, sending SIGKILL...")
112
102
  os.killpg(pgid_to_signal, SIGKILL)
113
103
  try:
114
- proc.wait(
115
- timeout=_TERMINATION_TIMEOUT
116
- ) # Wait for SIGKILL on process group
104
+ proc.wait(timeout=_TERMINATION_TIMEOUT) # Wait for SIGKILL on process group
117
105
  if not proc.is_running():
118
106
  terminated_by_pg = True
119
107
  except psutil.TimeoutExpired:
@@ -132,13 +120,9 @@ def _terminate_parent_process_unix(proc: psutil.Process, original_pid: int):
132
120
  # os.killpg failed
133
121
  except OSError as e:
134
122
  target_desc = (
135
- f"process group {pgid_to_signal}"
136
- if pgid_to_signal != 0
137
- else f"process {original_pid}"
138
- )
139
- print(
140
- f"Error sending signal to {target_desc}: {e}. Fallback to individual process."
123
+ f"process group {pgid_to_signal}" if pgid_to_signal != 0 else f"process {original_pid}"
141
124
  )
125
+ print(f"Error sending signal to {target_desc}: {e}. Fallback to individual process.")
142
126
 
143
127
  if terminated_by_pg:
144
128
  return
@@ -159,9 +143,7 @@ def _terminate_parent_process_unix(proc: psutil.Process, original_pid: int):
159
143
  f"SIGTERM for FFmpeg process (PID: {proc.pid}) timed out after {_TERMINATION_TIMEOUT}s."
160
144
  )
161
145
  except psutil.Error as e:
162
- print(
163
- f"Error during SIGTERM for FFmpeg process (PID: {proc.pid}):\n{e}"
164
- )
146
+ print(f"Error during SIGTERM for FFmpeg process (PID: {proc.pid}):\n{e}")
165
147
  if not proc.is_running():
166
148
  return
167
149
 
@@ -177,9 +159,7 @@ def _terminate_parent_process_unix(proc: psutil.Process, original_pid: int):
177
159
  proc.kill()
178
160
  print(f"SIGKILL sent to FFmpeg process (PID: {proc.pid}).")
179
161
  except psutil.Error as e:
180
- print(
181
- f"Error during SIGKILL for FFmpeg process (PID: {proc.pid}):\n{e}"
182
- )
162
+ print(f"Error during SIGKILL for FFmpeg process (PID: {proc.pid}):\n{e}")
183
163
  else:
184
164
  print(f"It seems like FFmpeg process {proc.pid} is no longer running.")
185
165
  except psutil.NoSuchProcess:
@@ -239,9 +219,7 @@ def terminate_ffmpeg_process(process: Optional[subprocess.Popen]):
239
219
  f"Main FFmpeg process {original_pid} became inaccessible during termination sequence."
240
220
  )
241
221
  except Exception as e:
242
- print(
243
- f"Unexpected error during main process termination for {original_pid}: {e}"
244
- )
222
+ print(f"Unexpected error during main process termination for {original_pid}: {e}")
245
223
 
246
224
  if process.poll() is None:
247
225
  exit("It seems like the FFmpeg process has not terminated.")
@@ -42,16 +42,10 @@ def validate_ffmpeg_command(command: List[str]) -> None:
42
42
 
43
43
  input_file_idx = input_flag_pos + 1
44
44
  if input_file_idx >= len(command):
45
- raise FfmpegCommandError(
46
- f"No input file specified after '{_FFMPEG_INPUT_FLAG}'."
47
- )
45
+ raise FfmpegCommandError(f"No input file specified after '{_FFMPEG_INPUT_FLAG}'.")
48
46
 
49
47
  input_file = command[input_file_idx]
50
- if (
51
- input_file.startswith("-")
52
- and len(input_file) == 2
53
- and not input_file[1:].isdigit()
54
- ):
48
+ if input_file.startswith("-") and len(input_file) == 2 and not input_file[1:].isdigit():
55
49
  raise FfmpegCommandError(
56
50
  f"Input file path '{input_file}' looks like an option flag. Please check your command."
57
51
  )
@@ -62,9 +56,7 @@ def validate_ffmpeg_command(command: List[str]) -> None:
62
56
  response = requests.head(input_file, timeout=5, allow_redirects=True)
63
57
  response.raise_for_status()
64
58
  except requests.exceptions.RequestException as e:
65
- raise FfmpegCommandError(
66
- f"Input URL not accessible: {input_file}. Error: {e}"
67
- )
59
+ raise FfmpegCommandError(f"Input URL not accessible: {input_file}. Error: {e}")
68
60
  elif not Path(input_file).exists():
69
61
  raise FfmpegCommandError(f"Input file not found: {input_file}")
70
62
 
@@ -0,0 +1,127 @@
1
+ Metadata-Version: 2.1
2
+ Name: better-ffmpeg-progress
3
+ Version: 5.2.0
4
+ Summary: Run FFmpeg & see percentage progress + ETA.
5
+ Home-page: https://github.com/CrypticSignal/better-ffmpeg-progress
6
+ Author: GitHub.com/CrypticSignal
7
+ Author-email: theaudiophile@outlook.com
8
+ Keywords: ffmpeg,progress
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Operating System :: OS Independent
12
+ Requires-Python: >=3.7
13
+ Description-Content-Type: text/markdown
14
+ License-File: LICENSE
15
+ Requires-Dist: psutil
16
+ Requires-Dist: requests
17
+ Requires-Dist: rich
18
+ Requires-Dist: tqdm
19
+
20
+ <div align="center">
21
+ [![PyPI downloads](https://img.shields.io/pypi/dm/better-ffmpeg-progress?label=PyPI\&color=blue)](https://pypistats.org/packages/better-ffmpeg-progress)
22
+ [![PyPI downloads](https://img.shields.io/pypi/dd/better-ffmpeg-progress?label=PyPI\&color=blue)](https://pypistats.org/packages/better-ffmpeg-progress)
23
+ ![PyPI - Version](https://img.shields.io/pypi/v/better-ffmpeg-progress)
24
+ [![GitHub](https://img.shields.io/github/license/crypticsignal/better-ffmpeg-progress?label=License\&color=blue)](LICENSE.txt)
25
+
26
+ # Better FFmpeg Progress
27
+ Runs an FFmpeg command and shows a progress bar with percentage progress, elapsed time and ETA.
28
+ </div>
29
+
30
+ FFmpeg outputs something like:
31
+ ```
32
+ frame= 692 fps= 58 q=28.0 size= 5376KiB time=00:00:28.77 bitrate=1530.3kbits/s speed=2.43x
33
+ ```
34
+ Better FFmpeg Progress outputs something like:
35
+ ```
36
+ ⠏ Processing abc.webm ━━━━━━━━━╺━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 23% 0:00:04 00:15
37
+ ```
38
+ Where:
39
+ - `Processing abc.webm` is the description of the progresss bar.
40
+ - `23%` is the percentage progress.
41
+ - `0:00:04` is the time elapsed.
42
+ - `00:15` is the estimated time until the FFmpeg process completes.
43
+
44
+ ## Installation
45
+ ```bash
46
+ pip install better-ffmpeg-progress --upgrade
47
+ ```
48
+
49
+ ## Usage
50
+ Create an instance of the `FfmpegProcess` class and supply a list of arguments like you would when using `subprocess.run()` or `subprocess.Popen()`. Example:
51
+
52
+ ```python
53
+ from better_ffmpeg_progress import FfmpegProcess, FfmpegProcessError
54
+
55
+ command = [
56
+ "ffmpeg",
57
+ "-i",
58
+ "https://media.xiph.org/video/derf/y4m/ducks_take_off_1080p50.y4m",
59
+ "-map",
60
+ "0:V",
61
+ "-c:V",
62
+ "libx264",
63
+ "-preset",
64
+ "ultrafast",
65
+ "-f",
66
+ "null",
67
+ "-",
68
+ ]
69
+
70
+ try:
71
+ process = FfmpegProcess(command)
72
+ # Uncomment this line to use tqdm instead of Rich.
73
+ # process.use_tqdm = True
74
+
75
+ process.run()
76
+ except FfmpegProcessError as e:
77
+ print(
78
+ "An error occurred when running Better FFmpeg Progress:\n"
79
+ f"{e}"
80
+ )
81
+ ```
82
+
83
+ ## Duration Override
84
+ By default, Better FFmpeg Progress detects the duration of the input file and uses it to calculate percentage progress and ETA.
85
+
86
+ For partial, damaged, streamed or synthetic inputs, FFmpeg may be unable to detect the duration correctly. In these cases, you can provide the duration manually using `duration_override`.
87
+
88
+ The value should be the duration of the input file, in seconds:
89
+
90
+ ```python
91
+ from better_ffmpeg_progress import FfmpegProcess
92
+
93
+ command = [
94
+ "ffmpeg",
95
+ "-i",
96
+ "partial-input.flv",
97
+ "-c",
98
+ "copy",
99
+ "recovered-output.mkv",
100
+ ]
101
+
102
+ process = FfmpegProcess(
103
+ command,
104
+ duration_override=1222.64,
105
+ print_detected_duration=True,
106
+ )
107
+
108
+ process.run()
109
+ ```
110
+
111
+ ## Optional Arguments
112
+ An instance of `FfmpegProcess` accepts the following optional arguments:
113
+ * `ffmpeg_log_level` — Set a value for FFmpeg's `-loglevel` option. Default: `"verbose"`.
114
+ * `ffmpeg_log_file` — Filepath or file-like object to which FFmpeg stderr will be written. Default: `<input filename>_ffmpeg_log.txt`.
115
+ * `print_detected_duration` — Print the detected duration. Default: `False`.
116
+ * `duration_override` — Specify input file duration in seconds to use for progress calculation, instead of trying to detect this automatically with FFprobe. Default: `None`.
117
+
118
+ The `run` method accepts the following optional argument:
119
+ * `print_command` — Print the FFmpeg command being executed. Default: `False`.
120
+
121
+ ## Using tqdm
122
+ By default, the [Rich](https://github.com/Textualize/rich) library is used to display a progress bar. [tqdm](https://github.com/tqdm/tqdm) can be used instead by setting `process.use_tqdm = True`:
123
+ ```python
124
+ process = FfmpegProcess(command)
125
+ process.use_tqdm = True
126
+ process.run()
127
+ ```
@@ -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="5.1.0",
8
+ version="5.2.0",
9
9
  author="GitHub.com/CrypticSignal",
10
10
  author_email="theaudiophile@outlook.com",
11
11
  description="Run FFmpeg & see percentage progress + ETA.",
@@ -1,94 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: better-ffmpeg-progress
3
- Version: 5.1.0
4
- Summary: Run FFmpeg & see percentage progress + ETA.
5
- Home-page: https://github.com/CrypticSignal/better-ffmpeg-progress
6
- Author: GitHub.com/CrypticSignal
7
- Author-email: theaudiophile@outlook.com
8
- Keywords: ffmpeg,progress
9
- Classifier: Programming Language :: Python :: 3
10
- Classifier: License :: OSI Approved :: MIT License
11
- Classifier: Operating System :: OS Independent
12
- Requires-Python: >=3.7
13
- Description-Content-Type: text/markdown
14
- License-File: LICENSE
15
- Requires-Dist: psutil
16
- Requires-Dist: requests
17
- Requires-Dist: rich
18
- Requires-Dist: tqdm
19
-
20
- <div align="center">
21
-
22
- [![PyPI downloads](https://img.shields.io/pypi/dm/better-ffmpeg-progress?label=PyPI&color=blue)](https://pypistats.org/packages/better-ffmpeg-progress)
23
- [![PyPI downloads](https://img.shields.io/pypi/dd/better-ffmpeg-progress?label=PyPI&color=blue)](https://pypistats.org/packages/better-ffmpeg-progress)
24
- ![PyPI - Version](https://img.shields.io/pypi/v/better-ffmpeg-progress)
25
- [![GitHub](https://img.shields.io/github/license/crypticsignal/better-ffmpeg-progress?label=License&color=blue)](LICENSE.txt)
26
-
27
- # Better FFmpeg Progress
28
- Runs an FFmpeg command and shows a progress bar with percentage progress, time elapsed and ETA.
29
-
30
- The [Rich](https://github.com/Textualize/rich) library is used for the progress bar by default, [tqdm](https://github.com/tqdm/tqdm) will be used if you pass `use_tqdm=True` to the `run` method.
31
- </div>
32
-
33
- FFmpeg outputs something like:
34
- ```
35
- frame= 692 fps= 58 q=28.0 size= 5376KiB time=00:00:28.77 bitrate=1530.3kbits/s speed=2.43x
36
- ```
37
- Better FFmpeg Progress outputs something like:
38
- ```
39
- ⠏ Processing abc.webm ━━━━━━━━━╺━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 23% 0:00:04 00:15
40
- ```
41
- Where:
42
- - `Processing abc.webm` is the description of the progresss bar.
43
- - `23%` is the percentage progress.
44
- - `0:00:04` is the time elapsed.
45
- - `00:15` is the estimated time until the FFmpeg process completes.
46
-
47
- As you can see, the output of Better FFmpeg Progress is much more useful.
48
-
49
- ## Installation
50
- ```
51
- pip install better-ffmpeg-progress --upgrade
52
- ```
53
-
54
- ## Usage
55
- Create an instance of the `FfmpegProcess` class and supply a list of arguments like you would to `subprocess.run()` or `subprocess.Popen()`.
56
-
57
- Example:
58
- ```py
59
- from better_ffmpeg_progress import FfmpegProcess, FfmpegProcessError
60
-
61
- command = [
62
- "ffmpeg",
63
- "-i",
64
- "https://media.xiph.org/video/derf/y4m/ducks_take_off_1080p50.y4m",
65
- "-map",
66
- "0:V",
67
- "-c:V",
68
- "libx264",
69
- "-preset",
70
- "ultrafast",
71
- "-f",
72
- "null",
73
- "-",
74
- ]
75
-
76
- try:
77
- process = FfmpegProcess(command)
78
- # Uncomment the line below if you want to use tqdm instead of rich for the progress bar
79
- # process.use_tqdm = True
80
-
81
- process.run()
82
- except FfmpegProcessError as e:
83
- print(f"An error occurred when running the better-ffmpeg-process package:\n{e}")
84
- ```
85
- ## Optional Arguments
86
- An instance of the `FfmpegProcess` class takes the following **optional** arguments:
87
-
88
- - `ffmpeg_log_level` - Desired FFmpeg log level. Default: `"verbose"`
89
- - `ffmpeg_log_file` - The filepath to save the FFmpeg log to. Default: `<input filename>_log.txt`
90
- - `print_detected_duration` - Print the detected duration of the input file. Default: `False`
91
- - `print_stderr_new_line` - If better progress information cannot be shown, print FFmpeg stderr in a new line instead of replacing the current line in the terminal. Default: `False`
92
-
93
- The `run` method takes the following **optional** arguments:
94
- - `print_command` - Print the FFmpeg command being executed. Default: `False`
@@ -1,75 +0,0 @@
1
- <div align="center">
2
-
3
- [![PyPI downloads](https://img.shields.io/pypi/dm/better-ffmpeg-progress?label=PyPI&color=blue)](https://pypistats.org/packages/better-ffmpeg-progress)
4
- [![PyPI downloads](https://img.shields.io/pypi/dd/better-ffmpeg-progress?label=PyPI&color=blue)](https://pypistats.org/packages/better-ffmpeg-progress)
5
- ![PyPI - Version](https://img.shields.io/pypi/v/better-ffmpeg-progress)
6
- [![GitHub](https://img.shields.io/github/license/crypticsignal/better-ffmpeg-progress?label=License&color=blue)](LICENSE.txt)
7
-
8
- # Better FFmpeg Progress
9
- Runs an FFmpeg command and shows a progress bar with percentage progress, time elapsed and ETA.
10
-
11
- The [Rich](https://github.com/Textualize/rich) library is used for the progress bar by default, [tqdm](https://github.com/tqdm/tqdm) will be used if you pass `use_tqdm=True` to the `run` method.
12
- </div>
13
-
14
- FFmpeg outputs something like:
15
- ```
16
- frame= 692 fps= 58 q=28.0 size= 5376KiB time=00:00:28.77 bitrate=1530.3kbits/s speed=2.43x
17
- ```
18
- Better FFmpeg Progress outputs something like:
19
- ```
20
- ⠏ Processing abc.webm ━━━━━━━━━╺━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 23% 0:00:04 00:15
21
- ```
22
- Where:
23
- - `Processing abc.webm` is the description of the progresss bar.
24
- - `23%` is the percentage progress.
25
- - `0:00:04` is the time elapsed.
26
- - `00:15` is the estimated time until the FFmpeg process completes.
27
-
28
- As you can see, the output of Better FFmpeg Progress is much more useful.
29
-
30
- ## Installation
31
- ```
32
- pip install better-ffmpeg-progress --upgrade
33
- ```
34
-
35
- ## Usage
36
- Create an instance of the `FfmpegProcess` class and supply a list of arguments like you would to `subprocess.run()` or `subprocess.Popen()`.
37
-
38
- Example:
39
- ```py
40
- from better_ffmpeg_progress import FfmpegProcess, FfmpegProcessError
41
-
42
- command = [
43
- "ffmpeg",
44
- "-i",
45
- "https://media.xiph.org/video/derf/y4m/ducks_take_off_1080p50.y4m",
46
- "-map",
47
- "0:V",
48
- "-c:V",
49
- "libx264",
50
- "-preset",
51
- "ultrafast",
52
- "-f",
53
- "null",
54
- "-",
55
- ]
56
-
57
- try:
58
- process = FfmpegProcess(command)
59
- # Uncomment the line below if you want to use tqdm instead of rich for the progress bar
60
- # process.use_tqdm = True
61
-
62
- process.run()
63
- except FfmpegProcessError as e:
64
- print(f"An error occurred when running the better-ffmpeg-process package:\n{e}")
65
- ```
66
- ## Optional Arguments
67
- An instance of the `FfmpegProcess` class takes the following **optional** arguments:
68
-
69
- - `ffmpeg_log_level` - Desired FFmpeg log level. Default: `"verbose"`
70
- - `ffmpeg_log_file` - The filepath to save the FFmpeg log to. Default: `<input filename>_log.txt`
71
- - `print_detected_duration` - Print the detected duration of the input file. Default: `False`
72
- - `print_stderr_new_line` - If better progress information cannot be shown, print FFmpeg stderr in a new line instead of replacing the current line in the terminal. Default: `False`
73
-
74
- The `run` method takes the following **optional** arguments:
75
- - `print_command` - Print the FFmpeg command being executed. Default: `False`
@@ -1,94 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: better-ffmpeg-progress
3
- Version: 5.1.0
4
- Summary: Run FFmpeg & see percentage progress + ETA.
5
- Home-page: https://github.com/CrypticSignal/better-ffmpeg-progress
6
- Author: GitHub.com/CrypticSignal
7
- Author-email: theaudiophile@outlook.com
8
- Keywords: ffmpeg,progress
9
- Classifier: Programming Language :: Python :: 3
10
- Classifier: License :: OSI Approved :: MIT License
11
- Classifier: Operating System :: OS Independent
12
- Requires-Python: >=3.7
13
- Description-Content-Type: text/markdown
14
- License-File: LICENSE
15
- Requires-Dist: psutil
16
- Requires-Dist: requests
17
- Requires-Dist: rich
18
- Requires-Dist: tqdm
19
-
20
- <div align="center">
21
-
22
- [![PyPI downloads](https://img.shields.io/pypi/dm/better-ffmpeg-progress?label=PyPI&color=blue)](https://pypistats.org/packages/better-ffmpeg-progress)
23
- [![PyPI downloads](https://img.shields.io/pypi/dd/better-ffmpeg-progress?label=PyPI&color=blue)](https://pypistats.org/packages/better-ffmpeg-progress)
24
- ![PyPI - Version](https://img.shields.io/pypi/v/better-ffmpeg-progress)
25
- [![GitHub](https://img.shields.io/github/license/crypticsignal/better-ffmpeg-progress?label=License&color=blue)](LICENSE.txt)
26
-
27
- # Better FFmpeg Progress
28
- Runs an FFmpeg command and shows a progress bar with percentage progress, time elapsed and ETA.
29
-
30
- The [Rich](https://github.com/Textualize/rich) library is used for the progress bar by default, [tqdm](https://github.com/tqdm/tqdm) will be used if you pass `use_tqdm=True` to the `run` method.
31
- </div>
32
-
33
- FFmpeg outputs something like:
34
- ```
35
- frame= 692 fps= 58 q=28.0 size= 5376KiB time=00:00:28.77 bitrate=1530.3kbits/s speed=2.43x
36
- ```
37
- Better FFmpeg Progress outputs something like:
38
- ```
39
- ⠏ Processing abc.webm ━━━━━━━━━╺━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 23% 0:00:04 00:15
40
- ```
41
- Where:
42
- - `Processing abc.webm` is the description of the progresss bar.
43
- - `23%` is the percentage progress.
44
- - `0:00:04` is the time elapsed.
45
- - `00:15` is the estimated time until the FFmpeg process completes.
46
-
47
- As you can see, the output of Better FFmpeg Progress is much more useful.
48
-
49
- ## Installation
50
- ```
51
- pip install better-ffmpeg-progress --upgrade
52
- ```
53
-
54
- ## Usage
55
- Create an instance of the `FfmpegProcess` class and supply a list of arguments like you would to `subprocess.run()` or `subprocess.Popen()`.
56
-
57
- Example:
58
- ```py
59
- from better_ffmpeg_progress import FfmpegProcess, FfmpegProcessError
60
-
61
- command = [
62
- "ffmpeg",
63
- "-i",
64
- "https://media.xiph.org/video/derf/y4m/ducks_take_off_1080p50.y4m",
65
- "-map",
66
- "0:V",
67
- "-c:V",
68
- "libx264",
69
- "-preset",
70
- "ultrafast",
71
- "-f",
72
- "null",
73
- "-",
74
- ]
75
-
76
- try:
77
- process = FfmpegProcess(command)
78
- # Uncomment the line below if you want to use tqdm instead of rich for the progress bar
79
- # process.use_tqdm = True
80
-
81
- process.run()
82
- except FfmpegProcessError as e:
83
- print(f"An error occurred when running the better-ffmpeg-process package:\n{e}")
84
- ```
85
- ## Optional Arguments
86
- An instance of the `FfmpegProcess` class takes the following **optional** arguments:
87
-
88
- - `ffmpeg_log_level` - Desired FFmpeg log level. Default: `"verbose"`
89
- - `ffmpeg_log_file` - The filepath to save the FFmpeg log to. Default: `<input filename>_log.txt`
90
- - `print_detected_duration` - Print the detected duration of the input file. Default: `False`
91
- - `print_stderr_new_line` - If better progress information cannot be shown, print FFmpeg stderr in a new line instead of replacing the current line in the terminal. Default: `False`
92
-
93
- The `run` method takes the following **optional** arguments:
94
- - `print_command` - Print the FFmpeg command being executed. Default: `False`