better-ffmpeg-progress 2.6.1__tar.gz → 3.0.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.
@@ -0,0 +1,79 @@
1
+ Metadata-Version: 2.1
2
+ Name: better-ffmpeg-progress
3
+ Version: 3.0.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: ffmpeg-python
16
+ Requires-Dist: psutil
17
+ Requires-Dist: rich
18
+
19
+ <div align="center">
20
+
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, time elapsed 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 (H:MM:SS) elapsed.
42
+ - `00:15` is the estimated time until the FFmpeg process completes.
43
+
44
+ As you can see, the output of Better FFmpeg Progress is much more useful.
45
+
46
+ ## Installation
47
+ ```
48
+ pip install better-ffmpeg-progress --upgrade
49
+ ```
50
+
51
+ ## Usage
52
+ Create an instance of the `FfmpegProcess` class and supply a list of arguments like you would to `subprocess.run()` or `subprocess.Popen()`.
53
+
54
+ Here's a simple example:
55
+ ```py
56
+ from better_ffmpeg_progress import FfmpegProcess
57
+
58
+ process = FfmpegProcess(["ffmpeg", "-i", "input.mp4", "-c:v", "libx265", "output.mp4"])
59
+
60
+ # return_code will be 0 if the process was successful
61
+ return_code = process.run()
62
+
63
+ if return_code == 0:
64
+ # Code to run if the process was successful
65
+ pass
66
+ else:
67
+ # Code to run if the process was unsuccessful
68
+ pass
69
+ ```
70
+ ## Optional Arguments
71
+ An instance of the `FfmpegProcess` class takes the following **optional** arguments:
72
+
73
+ - `ffmpeg_log_level` - Desired FFmpeg log level. Default: `"verbose"`
74
+ - `ffmpeg_log_file` - The filepath to save the FFmpeg log to.
75
+ - `print_detected_duration` - Print the detected duration of the input file. Default: `True`
76
+ - `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`
77
+
78
+ The `run` method takes the following **optional** argument:
79
+ - `print_command` - Print the FFmpeg command being executed. Default: `False`
@@ -0,0 +1,61 @@
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
+ </div>
11
+
12
+ FFmpeg outputs something like:
13
+ ```
14
+ frame= 692 fps= 58 q=28.0 size= 5376KiB time=00:00:28.77 bitrate=1530.3kbits/s speed=2.43x
15
+ ```
16
+ Better FFmpeg Progress outputs something like:
17
+ ```
18
+ ⠏ Processing abc.webm ━━━━━━━━━╺━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 23% 0:00:04 00:15
19
+ ```
20
+ Where:
21
+ - `Processing abc.webm` is the description of the progresss bar.
22
+ - `23%` is the percentage progress.
23
+ - `0:00:04` is the time (H:MM:SS) elapsed.
24
+ - `00:15` is the estimated time until the FFmpeg process completes.
25
+
26
+ As you can see, the output of Better FFmpeg Progress is much more useful.
27
+
28
+ ## Installation
29
+ ```
30
+ pip install better-ffmpeg-progress --upgrade
31
+ ```
32
+
33
+ ## Usage
34
+ Create an instance of the `FfmpegProcess` class and supply a list of arguments like you would to `subprocess.run()` or `subprocess.Popen()`.
35
+
36
+ Here's a simple example:
37
+ ```py
38
+ from better_ffmpeg_progress import FfmpegProcess
39
+
40
+ process = FfmpegProcess(["ffmpeg", "-i", "input.mp4", "-c:v", "libx265", "output.mp4"])
41
+
42
+ # return_code will be 0 if the process was successful
43
+ return_code = process.run()
44
+
45
+ if return_code == 0:
46
+ # Code to run if the process was successful
47
+ pass
48
+ else:
49
+ # Code to run if the process was unsuccessful
50
+ pass
51
+ ```
52
+ ## Optional Arguments
53
+ An instance of the `FfmpegProcess` class takes the following **optional** arguments:
54
+
55
+ - `ffmpeg_log_level` - Desired FFmpeg log level. Default: `"verbose"`
56
+ - `ffmpeg_log_file` - The filepath to save the FFmpeg log to.
57
+ - `print_detected_duration` - Print the detected duration of the input file. Default: `True`
58
+ - `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`
59
+
60
+ The `run` method takes the following **optional** argument:
61
+ - `print_command` - Print the FFmpeg command being executed. Default: `False`
@@ -0,0 +1,266 @@
1
+ from enum import Enum
2
+ import os
3
+ from pathlib import Path
4
+ import psutil
5
+ from queue import Empty, Queue
6
+ import shutil
7
+ import subprocess
8
+ from threading import Thread
9
+ from typing import List, Optional, Union
10
+
11
+ from ffmpeg import probe
12
+ from rich.progress import (
13
+ Progress,
14
+ TextColumn,
15
+ BarColumn,
16
+ TaskProgressColumn,
17
+ TimeRemainingColumn,
18
+ TimeElapsedColumn,
19
+ SpinnerColumn,
20
+ )
21
+
22
+
23
+ class FfmpegLogLevel(Enum):
24
+ QUIET = "quiet"
25
+ PANIC = "panic"
26
+ FATAL = "fatal"
27
+ ERROR = "error"
28
+ WARNING = "warning"
29
+ INFO = "info"
30
+ VERBOSE = "verbose"
31
+ DEBUG = "debug"
32
+ TRACE = "trace"
33
+
34
+
35
+ class FfmpegProcess:
36
+ TIME_MS_PREFIX = "out_time_ms="
37
+
38
+ @staticmethod
39
+ def _read_pipe(pipe: subprocess.PIPE, queue: Queue, stdout: bool = False) -> None:
40
+ """Read from pipe and put lines into queue."""
41
+ try:
42
+ with pipe:
43
+ for line in iter(pipe.readline, ""):
44
+ line = line.strip()
45
+ if line:
46
+ if stdout:
47
+ if line.startswith("out_time_ms="):
48
+ queue.put(line)
49
+ else:
50
+ queue.put(line)
51
+ except (IOError, ValueError) as e:
52
+ print(f"Error reading pipe: {e}")
53
+ finally:
54
+ pipe.close()
55
+
56
+ @classmethod
57
+ def _validate_command(cls, command: List[str]) -> bool:
58
+ if not shutil.which("ffmpeg"):
59
+ print("Error: FFmpeg executable not found in PATH")
60
+ return False
61
+
62
+ if "-i" not in command:
63
+ print("Error: FFmpeg command must include '-i'")
64
+ return False
65
+
66
+ input_idx = command.index("-i") + 1
67
+ if input_idx >= len(command):
68
+ print("Error: No input file specified after -i")
69
+ return False
70
+
71
+ input_file = Path(command[input_idx])
72
+ if not input_file.exists():
73
+ print(f"Error: Input file not found: {input_file}")
74
+ return False
75
+
76
+ if input_idx + 1 >= len(command):
77
+ print("Error: No output file specified")
78
+ return False
79
+
80
+ return True
81
+
82
+ def __init__(
83
+ self,
84
+ command: List[str],
85
+ ffmpeg_log_level: Optional[FfmpegLogLevel] = None,
86
+ ffmpeg_log_file: Optional[Union[str, Path]] = None,
87
+ print_detected_duration: bool = False,
88
+ print_stderr_new_line: bool = False,
89
+ ):
90
+ if not self._validate_command(command):
91
+ self.return_code = 1
92
+ return
93
+
94
+ if ffmpeg_log_level and ffmpeg_log_level not in FfmpegLogLevel:
95
+ print(
96
+ f"ffmpeg_log_level must be a lowercase variant of: {list(FfmpegLogLevel.__members__)}"
97
+ )
98
+ self.return_code = 1
99
+ return
100
+
101
+ input_idx = command.index("-i") + 1
102
+ self._input_filepath = Path(command[input_idx])
103
+ self._output_filepath = Path(command[-1])
104
+ self._ffmpeg_log_level = (
105
+ ffmpeg_log_level if ffmpeg_log_level else FfmpegLogLevel.VERBOSE.value
106
+ )
107
+ self._ffmpeg_log_file = Path(
108
+ ffmpeg_log_file
109
+ if ffmpeg_log_file
110
+ else f"{self._input_filepath.name}_log.txt"
111
+ )
112
+ self._print_detected_duration = print_detected_duration
113
+ self._print_stderr_new_line = print_stderr_new_line
114
+
115
+ with open(self._ffmpeg_log_file, "w"):
116
+ pass
117
+
118
+ try:
119
+ probe_data = probe(self._input_filepath)
120
+ self._duration_secs = float(probe_data["format"]["duration"])
121
+ if self._print_detected_duration:
122
+ print(f"Detected duration: {self._duration_secs:.2f} seconds")
123
+ except Exception:
124
+ self._duration_secs = None
125
+
126
+ self._ffmpeg_command = [
127
+ command[0],
128
+ "-hide_banner",
129
+ "-loglevel",
130
+ self._ffmpeg_log_level,
131
+ "-progress",
132
+ "pipe:1",
133
+ "-stats_period",
134
+ "0.1",
135
+ "-nostats",
136
+ *command[1:],
137
+ ]
138
+
139
+ self.return_code = 0
140
+
141
+ def _process_stderr(self, stderr: str) -> None:
142
+ if self._duration_secs is None:
143
+ print(stderr, end="\n" if self._print_stderr_new_line else "\r", flush=True)
144
+
145
+ try:
146
+ with open(self._ffmpeg_log_file, "a", encoding="utf-8") as f:
147
+ f.write(f"{stderr}\n")
148
+ except IOError as e:
149
+ print(f"Error writing to log file: {e}")
150
+ self.return_code = 1
151
+
152
+ def run(self, print_command: bool = False) -> int:
153
+ if hasattr(self, "return_code") and self.return_code != 0:
154
+ return 1
155
+
156
+ if self._output_filepath.exists() and "-y" not in self._ffmpeg_command:
157
+ if (
158
+ input(
159
+ f"{self._output_filepath} already exists. Overwrite? [Y/N]: "
160
+ ).lower()
161
+ != "y"
162
+ ):
163
+ print(
164
+ "FFmpeg process cancelled. Output file exists and overwrite declined."
165
+ )
166
+ return 1
167
+ self._ffmpeg_command.insert(1, "-y")
168
+
169
+ if print_command:
170
+ print(f"Executing: {' '.join(self._ffmpeg_command)}")
171
+
172
+ def _contains_shell_operators(command: List[str]) -> bool:
173
+ shell_operators = {"|", ">", "<", ">>", "&&", "||"}
174
+ return any(op in command for op in shell_operators)
175
+
176
+ # If command contains shell operators, turn the list into a string.
177
+ if _contains_shell_operators(self._ffmpeg_command):
178
+ self._ffmpeg_command = " ".join(self._ffmpeg_command)
179
+
180
+ try:
181
+ process = subprocess.Popen(
182
+ self._ffmpeg_command,
183
+ shell=isinstance(self._ffmpeg_command, str),
184
+ stdout=subprocess.PIPE,
185
+ stderr=subprocess.PIPE,
186
+ text=True,
187
+ creationflags=subprocess.CREATE_NEW_PROCESS_GROUP
188
+ if os.name == "nt"
189
+ else 0,
190
+ )
191
+ except Exception as e:
192
+ print(f"Error starting FFmpeg process: {e}")
193
+ return 1
194
+
195
+ stdout_queue, stderr_queue = Queue(), Queue()
196
+ # Start stdout and stderr pipe readers
197
+ Thread(
198
+ target=self._read_pipe,
199
+ args=(process.stdout, stdout_queue, True),
200
+ daemon=True,
201
+ ).start()
202
+ Thread(
203
+ target=self._read_pipe,
204
+ args=(process.stderr, stderr_queue, False),
205
+ daemon=True,
206
+ ).start()
207
+
208
+ try:
209
+ with Progress(
210
+ SpinnerColumn(),
211
+ TextColumn("[progress.description]{task.description}"),
212
+ BarColumn(),
213
+ TaskProgressColumn(),
214
+ TimeElapsedColumn(),
215
+ TimeRemainingColumn(compact=True),
216
+ refresh_per_second=10,
217
+ ) as progress_bar:
218
+ task_id = None
219
+ if self._duration_secs:
220
+ task_id = progress_bar.add_task(
221
+ f"Processing {self._input_filepath.name}",
222
+ total=self._duration_secs,
223
+ )
224
+
225
+ while process.poll() is None:
226
+ # stdout
227
+ if task_id is not None and not stdout_queue.empty():
228
+ try:
229
+ line = stdout_queue.get_nowait()
230
+ if line.startswith(self.TIME_MS_PREFIX):
231
+ try:
232
+ value = int(line.split("=")[1]) / 1_000_000
233
+ if value <= self._duration_secs:
234
+ progress_bar.update(task_id, completed=value)
235
+ except ValueError:
236
+ pass
237
+ except Empty:
238
+ pass
239
+
240
+ # stderr
241
+ if not stderr_queue.empty():
242
+ try:
243
+ self._process_stderr(stderr_queue.get_nowait())
244
+ except Empty:
245
+ pass
246
+
247
+ # Process remaining stderr
248
+ while not stderr_queue.empty():
249
+ try:
250
+ self._process_stderr(stderr_queue.get_nowait())
251
+ except Empty:
252
+ break
253
+
254
+ if process.returncode == 0 and task_id is not None:
255
+ progress_bar.update(
256
+ task_id, description=f"✓ Processed {self._input_filepath.name}"
257
+ )
258
+
259
+ except KeyboardInterrupt:
260
+ try:
261
+ psutil.Process(process.pid).terminate()
262
+ except psutil.NoSuchProcess:
263
+ pass
264
+ return 1
265
+
266
+ return 0 if process.returncode == 0 else 1
@@ -0,0 +1,79 @@
1
+ Metadata-Version: 2.1
2
+ Name: better-ffmpeg-progress
3
+ Version: 3.0.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: ffmpeg-python
16
+ Requires-Dist: psutil
17
+ Requires-Dist: rich
18
+
19
+ <div align="center">
20
+
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, time elapsed 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 (H:MM:SS) elapsed.
42
+ - `00:15` is the estimated time until the FFmpeg process completes.
43
+
44
+ As you can see, the output of Better FFmpeg Progress is much more useful.
45
+
46
+ ## Installation
47
+ ```
48
+ pip install better-ffmpeg-progress --upgrade
49
+ ```
50
+
51
+ ## Usage
52
+ Create an instance of the `FfmpegProcess` class and supply a list of arguments like you would to `subprocess.run()` or `subprocess.Popen()`.
53
+
54
+ Here's a simple example:
55
+ ```py
56
+ from better_ffmpeg_progress import FfmpegProcess
57
+
58
+ process = FfmpegProcess(["ffmpeg", "-i", "input.mp4", "-c:v", "libx265", "output.mp4"])
59
+
60
+ # return_code will be 0 if the process was successful
61
+ return_code = process.run()
62
+
63
+ if return_code == 0:
64
+ # Code to run if the process was successful
65
+ pass
66
+ else:
67
+ # Code to run if the process was unsuccessful
68
+ pass
69
+ ```
70
+ ## Optional Arguments
71
+ An instance of the `FfmpegProcess` class takes the following **optional** arguments:
72
+
73
+ - `ffmpeg_log_level` - Desired FFmpeg log level. Default: `"verbose"`
74
+ - `ffmpeg_log_file` - The filepath to save the FFmpeg log to.
75
+ - `print_detected_duration` - Print the detected duration of the input file. Default: `True`
76
+ - `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`
77
+
78
+ The `run` method takes the following **optional** argument:
79
+ - `print_command` - Print the FFmpeg command being executed. Default: `False`
@@ -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.6.1",
8
+ version="3.0.0",
9
9
  author="GitHub.com/CrypticSignal",
10
10
  author_email="theaudiophile@outlook.com",
11
11
  description="Run FFmpeg & see percentage progress + ETA.",
@@ -1,113 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: better-ffmpeg-progress
3
- Version: 2.6.1
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: ffmpeg-python
16
- Requires-Dist: psutil
17
- Requires-Dist: rich
18
-
19
- <div align="center">
20
-
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, time elapsed 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 (H:MM:SS) elapsed.
42
- - `00:15` is the estimated time until the FFmpeg process completes.
43
-
44
- As you can see, the output of Better FFmpeg Progress is much more useful.
45
-
46
- ## Installation
47
- ```
48
- pip install better-ffmpeg-progress --upgrade
49
- ```
50
-
51
- ## Usage
52
- Create an instance of the `FfmpegProcess` class and supply a list of arguments like you would to `subprocess.run()`.
53
-
54
- Simple Example:
55
- ```py
56
- from better_ffmpeg_progress import FfmpegProcess
57
- # Pass a list of FFmpeg arguments, like you would if using subprocess.run()
58
- process = FfmpegProcess(["ffmpeg", "-i", "input.mp4", "-c:a", "libmp3lame", "output.mp3"])
59
- # Use the run method to run the FFmpeg command.
60
- process.run()
61
- ```
62
-
63
- Advanced Example:
64
- ```py
65
- from better_ffmpeg_progress import FfmpegProcess
66
-
67
- def handle_progress_info(percentage, speed, eta, estimated_filesize):
68
- print(f"Estimated Output Filesize: {estimated_filesize / 1_000_000} MB")
69
-
70
- def handle_success():
71
- # Code to run if the FFmpeg process completes successfully.
72
- pass
73
-
74
- def handle_error():
75
- # Code to run if the FFmpeg process encounters an error.
76
- pass
77
-
78
- # Pass a list of FFmpeg arguments, like you would if using subprocess.run() or subprocess.Popen()
79
- process = FfmpegProcess(["ffmpeg", "-i", "input.mp4", "-c:a", "libmp3lame", "output.mp3"])
80
-
81
- # Use the run method to run the FFmpeg command.
82
- process.run(
83
- progress_bar_description="Converting file...",
84
- progress_handler=handle_progress_info,
85
- log_file="ffmpeg_log.txt",
86
- success_handler=handle_success,
87
- error_handler=handle_error,
88
- )
89
- ```
90
-
91
- ## Optional Arguments
92
- An instance of the `FfmpegProcess` class takes the following **optional** arguments:
93
-
94
- - `ffmpeg_loglevel`: Desired FFmpeg log level. Default: `"verbose"`
95
- - `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
96
- - `print_detected_duration`: Print the detected duration of the input file. Default: True
97
-
98
- The `run` method takes the following **optional** arguments:
99
- - `print_command` - Print the FFmpeg command being executed. Default: `False`
100
- - `progress_bar_description` - An optional string to set a custom description for the progress bar. The default description is `Processing <file>`. This can be an empty string if you don't want the progress bar to have a description.
101
- - `progress_handler`
102
- - You can create a function if you would like to do something with the following values:
103
- - Percentage progress. [float]
104
- - Speed, e.g. `22.3x` which means that 22.3 seconds of the input are processed every second. [string]
105
- - ETA in seconds. [float]
106
- - Estimated output filesize in bytes. [float]
107
- - _Note: This is not accurate. Please take the value with a grain of salt._
108
-
109
- The values will be `None` if unknown. The function will receive the current values of these metrics as arguments, every 0.1s.
110
-
111
- - `log_file` - The filepath to save the FFmpeg log to.
112
- - `success_handler` - A function to run if the FFmpeg process completes successfully.
113
- - `error_handler` - A function to run if the FFmpeg process encounters an error.
@@ -1,95 +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
- </div>
11
-
12
- FFmpeg outputs something like:
13
- ```
14
- frame= 692 fps= 58 q=28.0 size= 5376KiB time=00:00:28.77 bitrate=1530.3kbits/s speed=2.43x
15
- ```
16
- Better FFmpeg Progress outputs something like:
17
- ```
18
- ⠏ Processing abc.webm ━━━━━━━━━╺━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 23% 0:00:04 00:15
19
- ```
20
- Where:
21
- - `Processing abc.webm` is the description of the progresss bar.
22
- - `23%` is the percentage progress.
23
- - `0:00:04` is the time (H:MM:SS) elapsed.
24
- - `00:15` is the estimated time until the FFmpeg process completes.
25
-
26
- As you can see, the output of Better FFmpeg Progress is much more useful.
27
-
28
- ## Installation
29
- ```
30
- pip install better-ffmpeg-progress --upgrade
31
- ```
32
-
33
- ## Usage
34
- Create an instance of the `FfmpegProcess` class and supply a list of arguments like you would to `subprocess.run()`.
35
-
36
- Simple Example:
37
- ```py
38
- from better_ffmpeg_progress import FfmpegProcess
39
- # Pass a list of FFmpeg arguments, like you would if using subprocess.run()
40
- process = FfmpegProcess(["ffmpeg", "-i", "input.mp4", "-c:a", "libmp3lame", "output.mp3"])
41
- # Use the run method to run the FFmpeg command.
42
- process.run()
43
- ```
44
-
45
- Advanced Example:
46
- ```py
47
- from better_ffmpeg_progress import FfmpegProcess
48
-
49
- def handle_progress_info(percentage, speed, eta, estimated_filesize):
50
- print(f"Estimated Output Filesize: {estimated_filesize / 1_000_000} MB")
51
-
52
- def handle_success():
53
- # Code to run if the FFmpeg process completes successfully.
54
- pass
55
-
56
- def handle_error():
57
- # Code to run if the FFmpeg process encounters an error.
58
- pass
59
-
60
- # Pass a list of FFmpeg arguments, like you would if using subprocess.run() or subprocess.Popen()
61
- process = FfmpegProcess(["ffmpeg", "-i", "input.mp4", "-c:a", "libmp3lame", "output.mp3"])
62
-
63
- # Use the run method to run the FFmpeg command.
64
- process.run(
65
- progress_bar_description="Converting file...",
66
- progress_handler=handle_progress_info,
67
- log_file="ffmpeg_log.txt",
68
- success_handler=handle_success,
69
- error_handler=handle_error,
70
- )
71
- ```
72
-
73
- ## Optional Arguments
74
- An instance of the `FfmpegProcess` class takes the following **optional** arguments:
75
-
76
- - `ffmpeg_loglevel`: Desired FFmpeg log level. Default: `"verbose"`
77
- - `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
78
- - `print_detected_duration`: Print the detected duration of the input file. Default: True
79
-
80
- The `run` method takes the following **optional** arguments:
81
- - `print_command` - Print the FFmpeg command being executed. Default: `False`
82
- - `progress_bar_description` - An optional string to set a custom description for the progress bar. The default description is `Processing <file>`. This can be an empty string if you don't want the progress bar to have a description.
83
- - `progress_handler`
84
- - You can create a function if you would like to do something with the following values:
85
- - Percentage progress. [float]
86
- - Speed, e.g. `22.3x` which means that 22.3 seconds of the input are processed every second. [string]
87
- - ETA in seconds. [float]
88
- - Estimated output filesize in bytes. [float]
89
- - _Note: This is not accurate. Please take the value with a grain of salt._
90
-
91
- The values will be `None` if unknown. The function will receive the current values of these metrics as arguments, every 0.1s.
92
-
93
- - `log_file` - The filepath to save the FFmpeg log to.
94
- - `success_handler` - A function to run if the FFmpeg process completes successfully.
95
- - `error_handler` - A function to run if the FFmpeg process encounters an error.
@@ -1,436 +0,0 @@
1
- from dataclasses import dataclass
2
- import os
3
- from pathlib import Path
4
- from queue import Empty, Queue
5
- import subprocess
6
- import sys
7
- from threading import Thread
8
- from typing import Callable, List, Optional, Union
9
-
10
- from .utils import print_with_prefix
11
-
12
- from ffmpeg import probe
13
- import psutil
14
- from rich.progress import (
15
- Progress,
16
- TextColumn,
17
- BarColumn,
18
- TaskProgressColumn,
19
- TimeRemainingColumn,
20
- TimeElapsedColumn,
21
- SpinnerColumn,
22
- )
23
-
24
-
25
- @dataclass
26
- class Metrics:
27
- """Data class to store FFmpeg progress information."""
28
-
29
- percentage: float = 0.0
30
- seconds_processed: float = 0
31
- speed: float = None
32
- eta: Optional[float] = None
33
- estimated_size: Optional[int] = None
34
-
35
-
36
- class FfmpegProcess:
37
- """
38
- Args:
39
- command: A list of arguments to pass to FFmpeg.
40
- ffmpeg_loglevel: Desired FFmpeg log level. Default: "verbose"
41
- 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
42
- print_detected_duration: Print the detected duration of the input file. Default: True
43
-
44
- Raises:
45
- FileNotFoundError: If FFmpeg cannot find the input filepath or filename.
46
- ValueError: If the list of arguments does not include "-i"
47
- """
48
-
49
- WANTED_METRICS = frozenset({"out_time_ms", "total_size", "speed"})
50
-
51
- def __init__(
52
- self,
53
- command: List[str],
54
- ffmpeg_loglevel: str = "verbose",
55
- print_detected_duration: bool = False,
56
- print_stderr_new_line: bool = False,
57
- ):
58
- if "-i" not in command:
59
- raise ValueError("FFmpeg command must include '-i'")
60
-
61
- # -progress pipe:1 sends progress metrics to stdout
62
- # -stats_period sets the period at which encoding progress/statistics are updated
63
- extra_ffmpeg_options = [
64
- "-hide_banner",
65
- "-loglevel",
66
- ffmpeg_loglevel,
67
- "-progress",
68
- "pipe:1",
69
- "-stats_period",
70
- "0.1",
71
- ]
72
-
73
- command[1:1] = extra_ffmpeg_options
74
-
75
- self._ffmpeg_command = command
76
- self._output_filepath = Path(command[-1])
77
- self._print_detected_duration = print_detected_duration
78
- self._print_stderr_new_line = print_stderr_new_line
79
- self._metrics = Metrics()
80
- self._duration_secs: Optional[float]
81
- self._current_size: int = 0
82
- self._ffmpeg_log_file: Optional[Path] = None
83
- self._error_messages: list[str] = []
84
- self._get_input_file_duration()
85
-
86
- def _get_input_file_duration(self) -> None:
87
- """Use ffprobe to get the duration of the input file"""
88
- index_of_filepath = self._ffmpeg_command.index("-i") + 1
89
- self._input_filepath = Path(self._ffmpeg_command[index_of_filepath])
90
-
91
- try:
92
- self._duration_secs = float(
93
- probe(self._input_filepath)["format"]["duration"]
94
- )
95
-
96
- if self._print_detected_duration:
97
- print_with_prefix(
98
- "[Better FFmpeg Progress] ",
99
- f"The duration of {self._input_filepath.name} has been detected as {self._duration_secs} seconds",
100
- )
101
-
102
- # -nostats ensures that progress information is not sent to stderr as this info is not needed in the log file, e.g.
103
- # frame= 1381 fps=254 q=18.0 size= 46592KiB time=00:00:46.07 bitrate=8283.1kbits/s speed=8.47x
104
- self._ffmpeg_command.extend(["-nostats"])
105
- except Exception:
106
- print_with_prefix(
107
- "[Better FFmpeg Progress] ",
108
- f"Could not detect the duration of '{self._input_filepath.name}'. Percentage progress, ETA and estimated filesize will be unavailable.\n",
109
- )
110
- self._duration_secs = None
111
-
112
- def _should_overwrite(self) -> bool:
113
- if self._output_filepath.exists() and "-y" not in self._ffmpeg_command:
114
- choice = input(
115
- f"{self._output_filepath} already exists. Overwrite? [Y/N]: "
116
- ).lower()
117
-
118
- if choice != "y":
119
- print_with_prefix(
120
- "[Better FFmpeg Progress] ",
121
- "FFmpeg process cancelled. Output file exists and overwrite declined.",
122
- )
123
- return False
124
-
125
- self._ffmpeg_command.insert(1, "-y")
126
-
127
- return True
128
-
129
- def _update_metrics(
130
- self, line: str, metric: str, value: str, progress_handler: Optional[Callable]
131
- ) -> None:
132
- """Update progress information based on FFmpeg output."""
133
- if metric == "total_size":
134
- self._current_size = int(value)
135
-
136
- elif metric == "out_time_ms":
137
- self._metrics.seconds_processed = int(value) / 1_000_000
138
-
139
- if self._duration_secs:
140
- self._metrics.percentage = (
141
- self._metrics.seconds_processed / self._duration_secs
142
- ) * 100
143
- if self._current_size and self._metrics.percentage > 0:
144
- self._metrics.estimated_size = int(
145
- self._current_size * (100 / self._metrics.percentage)
146
- )
147
-
148
- elif metric == "speed":
149
- speed_float = float(value.rstrip("x"))
150
- if speed_float > 0:
151
- self._metrics.speed = float(speed_float)
152
- if self._duration_secs:
153
- seconds_remaining = (
154
- self._duration_secs - self._metrics.seconds_processed
155
- )
156
- self._metrics.eta = seconds_remaining / self._metrics.speed
157
-
158
- if line == "progress=end":
159
- self._metrics.percentage = 100
160
- self._metrics.eta = 0
161
-
162
- if progress_handler:
163
- progress_handler(
164
- self._metrics.percentage,
165
- self._metrics.speed,
166
- self._metrics.eta,
167
- self._metrics.estimated_size,
168
- )
169
-
170
- @staticmethod
171
- def _handle_pipe(pipe, queue: Queue) -> None:
172
- """Read from the pipe and put the lines into the queue."""
173
- try:
174
- for line in pipe:
175
- queue.put(line.strip())
176
- finally:
177
- pipe.close()
178
-
179
- def _contains_shell_operators(self, command: List[str]) -> bool:
180
- """Check if the command contains shell operators."""
181
- shell_operators = {"|", ">", "<", ">>", "&&", "||"}
182
- return any(op in command for op in shell_operators)
183
-
184
- def _start_process(
185
- self,
186
- progress_bar: Optional[Progress],
187
- task_id: Optional[int],
188
- progress_handler: Optional[Callable],
189
- ) -> int:
190
- creationflags = subprocess.CREATE_NEW_PROCESS_GROUP if os.name == "nt" else 0
191
-
192
- # If command contains shell operators, turn the list into a string.
193
- if self._contains_shell_operators(self._ffmpeg_command):
194
- final_command = " ".join(self._ffmpeg_command)
195
- else:
196
- final_command = self._ffmpeg_command
197
-
198
- self._write_to_log_file(
199
- f"This file contains the log for the following command:\n{final_command if isinstance(final_command, str) else ' '.join(final_command)}\n",
200
- )
201
-
202
- process = subprocess.Popen(
203
- final_command,
204
- stdout=subprocess.PIPE,
205
- stderr=subprocess.PIPE,
206
- text=True,
207
- shell=isinstance(final_command, str),
208
- creationflags=creationflags,
209
- )
210
-
211
- stdout_queue, stderr_queue = Queue(), Queue()
212
- Thread(
213
- target=self._handle_pipe, args=(process.stdout, stdout_queue), daemon=True
214
- ).start()
215
- Thread(
216
- target=self._handle_pipe, args=(process.stderr, stderr_queue), daemon=True
217
- ).start()
218
-
219
- error_words = [
220
- "error",
221
- "failed",
222
- "invalid",
223
- "trailing",
224
- "unable",
225
- "unknown",
226
- "no such file",
227
- "permission denied",
228
- "not found",
229
- "unrecognized",
230
- "undefined",
231
- "unsupported",
232
- "does not exist",
233
- "missing",
234
- "not available",
235
- "cannot",
236
- "could not",
237
- "bad",
238
- "wrong",
239
- "forbidden",
240
- ]
241
-
242
- try:
243
- while process.poll() is None:
244
- if self._duration_secs is not None:
245
- try:
246
- stdout = stdout_queue.get_nowait()
247
-
248
- if stdout:
249
- metric, value = stdout.split("=")
250
- if (
251
- metric in self.WANTED_METRICS
252
- and value
253
- and "N/A" not in value
254
- ):
255
- self._update_metrics(
256
- stdout, metric, value, progress_handler
257
- )
258
- if (
259
- metric == "out_time_ms"
260
- and progress_bar
261
- and task_id is not None
262
- ):
263
- progress_bar.update(
264
- task_id,
265
- completed=self._metrics.seconds_processed,
266
- speed=self._metrics.speed,
267
- )
268
- except Empty:
269
- pass
270
-
271
- try:
272
- stderr = stderr_queue.get_nowait()
273
-
274
- if stderr:
275
- self._write_to_log_file(stderr)
276
-
277
- if any(keyword in stderr.lower() for keyword in error_words):
278
- self._error_messages.append(stderr)
279
-
280
- if self._duration_secs is None:
281
- print(
282
- stderr,
283
- end="\n" if self._print_stderr_new_line else "\r",
284
- flush=True,
285
- )
286
- except Empty:
287
- pass
288
-
289
- except KeyboardInterrupt:
290
- self._kill_process_and_children(process.pid)
291
- self._write_to_log_file("\n[KeyboardInterrupt] FFmpeg process killed.")
292
- sys.exit("\n[KeyboardInterrupt] FFmpeg process killed.")
293
-
294
- # After the process ends, drain any remaining stderr
295
- while True:
296
- try:
297
- stderr = stderr_queue.get_nowait()
298
- if stderr:
299
- self._write_to_log_file(stderr)
300
-
301
- if any(keyword in stderr.lower() for keyword in error_words):
302
- self._error_messages.append(stderr)
303
-
304
- if self._duration_secs is None:
305
- print(
306
- stderr,
307
- end="\n" if self._print_stderr_new_line else "\r",
308
- flush=True,
309
- )
310
- except Empty:
311
- break
312
-
313
- return process.returncode
314
-
315
- def run(
316
- self,
317
- log_file: Optional[Union[str, Path]] = "ffmpeg_log.txt",
318
- print_command=False,
319
- progress_bar_description: str = None,
320
- progress_handler: Optional[Callable] = None,
321
- success_handler: Optional[Callable] = None,
322
- error_handler: Optional[Callable] = None,
323
- ) -> None:
324
- """
325
- Args:
326
- log_file: Optional filepath to write FFmpeg output to
327
- print_command: Print the FFmpeg command being executed. Default: False
328
- progress_bar_description: Optional string to set a custom description for the progress bar
329
- progress_handler: Optional function which receives progress metrics
330
- success_handler: Optional function to handle successful completion of the FFmpeg process
331
- error_handler: Optional function for FFmpeg process error handling
332
- """
333
- if not self._should_overwrite():
334
- return
335
-
336
- self._ffmpeg_log_file = Path(log_file)
337
-
338
- with open(self._ffmpeg_log_file, "w"):
339
- pass
340
-
341
- if print_command:
342
- print(f"Executing: {' '.join(self._ffmpeg_command)}")
343
-
344
- if self._duration_secs and not progress_handler:
345
- with Progress(
346
- SpinnerColumn(),
347
- TextColumn("[progress.description]{task.description}"),
348
- BarColumn(),
349
- TaskProgressColumn(),
350
- TimeElapsedColumn(),
351
- TimeRemainingColumn(compact=True),
352
- refresh_per_second=10,
353
- ) as progress_bar:
354
- task_id = progress_bar.add_task(
355
- progress_bar_description
356
- if progress_bar_description is not None
357
- else f"Processing {self._input_filepath.name}",
358
- total=self._duration_secs,
359
- )
360
-
361
- return_code = self._start_process(
362
- progress_bar,
363
- task_id,
364
- None,
365
- )
366
-
367
- if return_code == 0:
368
- progress_bar.columns = (
369
- TextColumn("[progress.description]{task.description}"),
370
- )
371
- progress_bar.update(
372
- task_id,
373
- description=f"✓ Processed 100% of {self._input_filepath.name}",
374
- )
375
-
376
- else:
377
- return_code = self._start_process(
378
- None,
379
- None,
380
- progress_handler,
381
- )
382
-
383
- if return_code == 0:
384
- print_with_prefix(
385
- "\n" if self._duration_secs is None else "",
386
- "FFmpeg process completed.",
387
- )
388
-
389
- self._handle_process_ended(return_code, error_handler, success_handler)
390
-
391
- def _handle_process_ended(
392
- self,
393
- return_code: int,
394
- error_handler: Optional[Callable],
395
- success_handler: Optional[Callable],
396
- ) -> None:
397
- """Handle the completion of the FFmpeg process."""
398
- if return_code != 0:
399
- if error_handler:
400
- error_handler()
401
- sys.exit(
402
- "\nThe FFmpeg process encounted an error. Custom error handler executed."
403
- )
404
-
405
- print_with_prefix(
406
- "[Better FFmpeg Progress] ",
407
- f"FFmpeg process failed with return code {return_code}.\nError(s):",
408
- )
409
- print_with_prefix(
410
- "[Better FFmpeg Progress] ", "\n".join(self._error_messages)
411
- )
412
- print_with_prefix(
413
- "[Better FFmpeg Progress] ",
414
- f"Check out '{self._ffmpeg_log_file}' for more details.",
415
- )
416
- sys.exit(1)
417
-
418
- if success_handler:
419
- success_handler()
420
-
421
- def _kill_process_and_children(self, proc_pid: int) -> None:
422
- """Kill the FFmpeg process and its children."""
423
- try:
424
- process = psutil.Process(proc_pid)
425
- for child in process.children(recursive=True):
426
- child.terminate()
427
- # Terminate the main process
428
- process.terminate()
429
- except psutil.NoSuchProcess:
430
- pass
431
-
432
- def _write_to_log_file(self, message: str, mode: str = "a") -> None:
433
- if self._ffmpeg_log_file:
434
- with open(self._ffmpeg_log_file, mode) as f:
435
- f.write(f"{message}\n")
436
- f.flush() # Ensure the message is written immediately
@@ -1,113 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: better-ffmpeg-progress
3
- Version: 2.6.1
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: ffmpeg-python
16
- Requires-Dist: psutil
17
- Requires-Dist: rich
18
-
19
- <div align="center">
20
-
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, time elapsed 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 (H:MM:SS) elapsed.
42
- - `00:15` is the estimated time until the FFmpeg process completes.
43
-
44
- As you can see, the output of Better FFmpeg Progress is much more useful.
45
-
46
- ## Installation
47
- ```
48
- pip install better-ffmpeg-progress --upgrade
49
- ```
50
-
51
- ## Usage
52
- Create an instance of the `FfmpegProcess` class and supply a list of arguments like you would to `subprocess.run()`.
53
-
54
- Simple Example:
55
- ```py
56
- from better_ffmpeg_progress import FfmpegProcess
57
- # Pass a list of FFmpeg arguments, like you would if using subprocess.run()
58
- process = FfmpegProcess(["ffmpeg", "-i", "input.mp4", "-c:a", "libmp3lame", "output.mp3"])
59
- # Use the run method to run the FFmpeg command.
60
- process.run()
61
- ```
62
-
63
- Advanced Example:
64
- ```py
65
- from better_ffmpeg_progress import FfmpegProcess
66
-
67
- def handle_progress_info(percentage, speed, eta, estimated_filesize):
68
- print(f"Estimated Output Filesize: {estimated_filesize / 1_000_000} MB")
69
-
70
- def handle_success():
71
- # Code to run if the FFmpeg process completes successfully.
72
- pass
73
-
74
- def handle_error():
75
- # Code to run if the FFmpeg process encounters an error.
76
- pass
77
-
78
- # Pass a list of FFmpeg arguments, like you would if using subprocess.run() or subprocess.Popen()
79
- process = FfmpegProcess(["ffmpeg", "-i", "input.mp4", "-c:a", "libmp3lame", "output.mp3"])
80
-
81
- # Use the run method to run the FFmpeg command.
82
- process.run(
83
- progress_bar_description="Converting file...",
84
- progress_handler=handle_progress_info,
85
- log_file="ffmpeg_log.txt",
86
- success_handler=handle_success,
87
- error_handler=handle_error,
88
- )
89
- ```
90
-
91
- ## Optional Arguments
92
- An instance of the `FfmpegProcess` class takes the following **optional** arguments:
93
-
94
- - `ffmpeg_loglevel`: Desired FFmpeg log level. Default: `"verbose"`
95
- - `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
96
- - `print_detected_duration`: Print the detected duration of the input file. Default: True
97
-
98
- The `run` method takes the following **optional** arguments:
99
- - `print_command` - Print the FFmpeg command being executed. Default: `False`
100
- - `progress_bar_description` - An optional string to set a custom description for the progress bar. The default description is `Processing <file>`. This can be an empty string if you don't want the progress bar to have a description.
101
- - `progress_handler`
102
- - You can create a function if you would like to do something with the following values:
103
- - Percentage progress. [float]
104
- - Speed, e.g. `22.3x` which means that 22.3 seconds of the input are processed every second. [string]
105
- - ETA in seconds. [float]
106
- - Estimated output filesize in bytes. [float]
107
- - _Note: This is not accurate. Please take the value with a grain of salt._
108
-
109
- The values will be `None` if unknown. The function will receive the current values of these metrics as arguments, every 0.1s.
110
-
111
- - `log_file` - The filepath to save the FFmpeg log to.
112
- - `success_handler` - A function to run if the FFmpeg process completes successfully.
113
- - `error_handler` - A function to run if the FFmpeg process encounters an error.