better-ffmpeg-progress 3.2.6__tar.gz → 4.0.1__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.
- {better_ffmpeg_progress-3.2.6 → better_ffmpeg_progress-4.0.1}/PKG-INFO +31 -7
- {better_ffmpeg_progress-3.2.6 → better_ffmpeg_progress-4.0.1}/README.md +30 -7
- better_ffmpeg_progress-4.0.1/better_ffmpeg_progress/__init__.py +17 -0
- better_ffmpeg_progress-4.0.1/better_ffmpeg_progress/better_ffmpeg_progress.py +200 -0
- better_ffmpeg_progress-4.0.1/better_ffmpeg_progress/enums.py +13 -0
- better_ffmpeg_progress-4.0.1/better_ffmpeg_progress/exceptions.py +14 -0
- better_ffmpeg_progress-4.0.1/better_ffmpeg_progress/progress_bars.py +130 -0
- better_ffmpeg_progress-4.0.1/better_ffmpeg_progress/terminate_process.py +249 -0
- better_ffmpeg_progress-4.0.1/better_ffmpeg_progress/utils.py +151 -0
- {better_ffmpeg_progress-3.2.6 → better_ffmpeg_progress-4.0.1}/better_ffmpeg_progress.egg-info/PKG-INFO +31 -7
- {better_ffmpeg_progress-3.2.6 → better_ffmpeg_progress-4.0.1}/better_ffmpeg_progress.egg-info/SOURCES.txt +4 -0
- {better_ffmpeg_progress-3.2.6 → better_ffmpeg_progress-4.0.1}/better_ffmpeg_progress.egg-info/requires.txt +1 -0
- {better_ffmpeg_progress-3.2.6 → better_ffmpeg_progress-4.0.1}/setup.py +2 -2
- better_ffmpeg_progress-3.2.6/better_ffmpeg_progress/__init__.py +0 -1
- better_ffmpeg_progress-3.2.6/better_ffmpeg_progress/better_ffmpeg_progress.py +0 -370
- better_ffmpeg_progress-3.2.6/better_ffmpeg_progress/utils.py +0 -10
- {better_ffmpeg_progress-3.2.6 → better_ffmpeg_progress-4.0.1}/LICENSE +0 -0
- {better_ffmpeg_progress-3.2.6 → better_ffmpeg_progress-4.0.1}/better_ffmpeg_progress.egg-info/dependency_links.txt +0 -0
- {better_ffmpeg_progress-3.2.6 → better_ffmpeg_progress-4.0.1}/better_ffmpeg_progress.egg-info/top_level.txt +0 -0
- {better_ffmpeg_progress-3.2.6 → better_ffmpeg_progress-4.0.1}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: better-ffmpeg-progress
|
|
3
|
-
Version:
|
|
3
|
+
Version: 4.0.1
|
|
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: psutil
|
|
16
|
+
Requires-Dist: requests
|
|
16
17
|
Requires-Dist: rich
|
|
17
18
|
Requires-Dist: tqdm
|
|
18
19
|
|
|
@@ -53,13 +54,37 @@ pip install better-ffmpeg-progress --upgrade
|
|
|
53
54
|
## Usage
|
|
54
55
|
Create an instance of the `FfmpegProcess` class and supply a list of arguments like you would to `subprocess.run()` or `subprocess.Popen()`.
|
|
55
56
|
|
|
56
|
-
|
|
57
|
+
Example:
|
|
57
58
|
```py
|
|
58
|
-
from better_ffmpeg_progress import FfmpegProcess
|
|
59
|
+
from better_ffmpeg_progress import FfmpegProcess, FfmpegProcessError
|
|
59
60
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
61
|
+
command = [
|
|
62
|
+
"ffmpeg",
|
|
63
|
+
"-i",
|
|
64
|
+
"https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4",
|
|
65
|
+
"-c:v",
|
|
66
|
+
"libx264",
|
|
67
|
+
"-preset",
|
|
68
|
+
"ultrafast",
|
|
69
|
+
"-c:a",
|
|
70
|
+
"copy",
|
|
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
|
+
# Run the FFmpeg command and show a progress bar
|
|
82
|
+
process.run()
|
|
83
|
+
# The FFmpeg process failed if the return code is not 0
|
|
84
|
+
if process.return_code != 0:
|
|
85
|
+
pass
|
|
86
|
+
except FfmpegProcessError as e:
|
|
87
|
+
print(f"An error occurred when running the better-ffmpeg-process package:\n{e}")
|
|
63
88
|
```
|
|
64
89
|
## Optional Arguments
|
|
65
90
|
An instance of the `FfmpegProcess` class takes the following **optional** arguments:
|
|
@@ -71,4 +96,3 @@ An instance of the `FfmpegProcess` class takes the following **optional** argume
|
|
|
71
96
|
|
|
72
97
|
The `run` method takes the following **optional** arguments:
|
|
73
98
|
- `print_command` - Print the FFmpeg command being executed. Default: `False`
|
|
74
|
-
- `use_tqdm` - Use [tqdm](https://github.com/tqdm/tqdm) instead of [Rich](https://github.com/Textualize/rich) for the progress bar. Default: `False`
|
|
@@ -35,13 +35,37 @@ pip install better-ffmpeg-progress --upgrade
|
|
|
35
35
|
## Usage
|
|
36
36
|
Create an instance of the `FfmpegProcess` class and supply a list of arguments like you would to `subprocess.run()` or `subprocess.Popen()`.
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
Example:
|
|
39
39
|
```py
|
|
40
|
-
from better_ffmpeg_progress import FfmpegProcess
|
|
40
|
+
from better_ffmpeg_progress import FfmpegProcess, FfmpegProcessError
|
|
41
41
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
42
|
+
command = [
|
|
43
|
+
"ffmpeg",
|
|
44
|
+
"-i",
|
|
45
|
+
"https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4",
|
|
46
|
+
"-c:v",
|
|
47
|
+
"libx264",
|
|
48
|
+
"-preset",
|
|
49
|
+
"ultrafast",
|
|
50
|
+
"-c:a",
|
|
51
|
+
"copy",
|
|
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
|
+
# Run the FFmpeg command and show a progress bar
|
|
63
|
+
process.run()
|
|
64
|
+
# The FFmpeg process failed if the return code is not 0
|
|
65
|
+
if process.return_code != 0:
|
|
66
|
+
pass
|
|
67
|
+
except FfmpegProcessError as e:
|
|
68
|
+
print(f"An error occurred when running the better-ffmpeg-process package:\n{e}")
|
|
45
69
|
```
|
|
46
70
|
## Optional Arguments
|
|
47
71
|
An instance of the `FfmpegProcess` class takes the following **optional** arguments:
|
|
@@ -52,5 +76,4 @@ An instance of the `FfmpegProcess` class takes the following **optional** argume
|
|
|
52
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`
|
|
53
77
|
|
|
54
78
|
The `run` method takes the following **optional** arguments:
|
|
55
|
-
- `print_command` - Print the FFmpeg command being executed. Default: `False`
|
|
56
|
-
- `use_tqdm` - Use [tqdm](https://github.com/tqdm/tqdm) instead of [Rich](https://github.com/Textualize/rich) for the progress bar. Default: `False`
|
|
79
|
+
- `print_command` - Print the FFmpeg command being executed. Default: `False`
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from .better_ffmpeg_progress import FfmpegProcess
|
|
2
|
+
from .enums import FfmpegLogLevel
|
|
3
|
+
from .exceptions import (
|
|
4
|
+
FfmpegProcessError,
|
|
5
|
+
FfmpegCommandError,
|
|
6
|
+
FfmpegProcessUserCancelledError,
|
|
7
|
+
FfmpegProcessInterruptedError,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"FfmpegProcess",
|
|
12
|
+
"FfmpegLogLevel",
|
|
13
|
+
"FfmpegProcessError",
|
|
14
|
+
"FfmpegCommandError",
|
|
15
|
+
"FfmpegProcessUserCancelledError",
|
|
16
|
+
"FfmpegProcessInterruptedError",
|
|
17
|
+
]
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
import subprocess
|
|
4
|
+
from sys import exit
|
|
5
|
+
from typing import List, Optional, Union
|
|
6
|
+
|
|
7
|
+
from .enums import FfmpegLogLevel
|
|
8
|
+
from .exceptions import (
|
|
9
|
+
FfmpegProcessError,
|
|
10
|
+
FfmpegCommandError,
|
|
11
|
+
FfmpegProcessUserCancelledError,
|
|
12
|
+
FfmpegProcessInterruptedError,
|
|
13
|
+
)
|
|
14
|
+
from .utils import (
|
|
15
|
+
validate_ffmpeg_command,
|
|
16
|
+
get_media_duration,
|
|
17
|
+
check_shell_needed_for_command,
|
|
18
|
+
)
|
|
19
|
+
from .terminate_process import terminate_ffmpeg_process
|
|
20
|
+
from .progress_bars import use_rich, use_tqdm
|
|
21
|
+
|
|
22
|
+
_FFMPEG_OVERWRITE_FLAG = "-y"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class FfmpegProcess:
|
|
26
|
+
def _handle_overwrite_prompt(self) -> None:
|
|
27
|
+
"""
|
|
28
|
+
Handles the logic for overwriting an existing output file.
|
|
29
|
+
Modifies self._ffmpeg_command to include -y if overwrite is confirmed.
|
|
30
|
+
Raises FfmpegProcessUserCancelledError or FfmpegProcessInterruptedError if not proceeding.
|
|
31
|
+
"""
|
|
32
|
+
try:
|
|
33
|
+
answer = (
|
|
34
|
+
input(
|
|
35
|
+
f"Output file {self._output_filepath} already exists. Overwrite? [y/N]: "
|
|
36
|
+
)
|
|
37
|
+
.strip()
|
|
38
|
+
.lower()
|
|
39
|
+
)
|
|
40
|
+
if answer == "y":
|
|
41
|
+
# Insert -y
|
|
42
|
+
self._ffmpeg_command.insert(1, _FFMPEG_OVERWRITE_FLAG)
|
|
43
|
+
else:
|
|
44
|
+
raise FfmpegProcessUserCancelledError(
|
|
45
|
+
"FFmpeg process cancelled. Output file exists and overwrite declined."
|
|
46
|
+
)
|
|
47
|
+
except KeyboardInterrupt as e:
|
|
48
|
+
raise FfmpegProcessInterruptedError(
|
|
49
|
+
"[KeyboardInterrupt] FFmpeg process cancelled during overwrite prompt."
|
|
50
|
+
) from e
|
|
51
|
+
except EOFError as e:
|
|
52
|
+
raise FfmpegProcessInterruptedError(
|
|
53
|
+
"Input error (EOF) during overwrite prompt. FFmpeg process cancelled."
|
|
54
|
+
) from e
|
|
55
|
+
|
|
56
|
+
def __init__(
|
|
57
|
+
self,
|
|
58
|
+
command: List[str],
|
|
59
|
+
ffmpeg_log_level: Optional[Union[FfmpegLogLevel, str]] = None,
|
|
60
|
+
ffmpeg_log_file: Optional[Union[str, Path]] = None,
|
|
61
|
+
print_detected_duration: bool = False,
|
|
62
|
+
):
|
|
63
|
+
# Raises FfmpegCommandError if the command is invalid
|
|
64
|
+
validate_ffmpeg_command(command)
|
|
65
|
+
|
|
66
|
+
ffmpeg_log_level_val: str
|
|
67
|
+
|
|
68
|
+
if ffmpeg_log_level is None:
|
|
69
|
+
ffmpeg_log_level_val = FfmpegLogLevel.VERBOSE.value
|
|
70
|
+
elif isinstance(ffmpeg_log_level, FfmpegLogLevel):
|
|
71
|
+
ffmpeg_log_level_val = ffmpeg_log_level.value
|
|
72
|
+
elif isinstance(ffmpeg_log_level, str):
|
|
73
|
+
try:
|
|
74
|
+
ffmpeg_log_level_val = FfmpegLogLevel(ffmpeg_log_level.lower()).value
|
|
75
|
+
except ValueError:
|
|
76
|
+
valid_levels = [e.value for e in FfmpegLogLevel]
|
|
77
|
+
raise FfmpegCommandError(
|
|
78
|
+
f"Invalid ffmpeg_log_level string: '{ffmpeg_log_level}'. "
|
|
79
|
+
f"Must be one of {valid_levels} (case-insensitive)."
|
|
80
|
+
)
|
|
81
|
+
else:
|
|
82
|
+
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__}"
|
|
85
|
+
)
|
|
86
|
+
self._ffmpeg_log_level_val = ffmpeg_log_level_val
|
|
87
|
+
|
|
88
|
+
input_file_index = command.index("-i")
|
|
89
|
+
input_file_path_str = command[input_file_index + 1]
|
|
90
|
+
self._input_filepath = Path(input_file_path_str)
|
|
91
|
+
# Assumes last argument is output
|
|
92
|
+
self._output_filepath = Path(command[-1])
|
|
93
|
+
|
|
94
|
+
self._ffmpeg_log_file = Path(
|
|
95
|
+
ffmpeg_log_file or f"{self._input_filepath.name}_ffmpeg_log.txt"
|
|
96
|
+
)
|
|
97
|
+
self._print_detected_duration = print_detected_duration
|
|
98
|
+
self._duration_secs = get_media_duration(input_file_path_str)
|
|
99
|
+
|
|
100
|
+
if self._print_detected_duration and self._duration_secs is not None:
|
|
101
|
+
print(f"Detected duration: {self._duration_secs:.2f} seconds")
|
|
102
|
+
elif self._print_detected_duration and self._duration_secs is None:
|
|
103
|
+
print(
|
|
104
|
+
"Could not detect duration. Progress bar may not show time remaining."
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
self._ffmpeg_command = [
|
|
108
|
+
command[0],
|
|
109
|
+
"-hide_banner",
|
|
110
|
+
"-loglevel",
|
|
111
|
+
self._ffmpeg_log_level_val,
|
|
112
|
+
"-progress",
|
|
113
|
+
"pipe:1", # stdout
|
|
114
|
+
"-nostats",
|
|
115
|
+
]
|
|
116
|
+
self._ffmpeg_command.extend(command[1:])
|
|
117
|
+
|
|
118
|
+
is_overwrite_in_user_command = any(
|
|
119
|
+
arg == _FFMPEG_OVERWRITE_FLAG for arg in command[1:]
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
if self._output_filepath.exists() and not is_overwrite_in_user_command:
|
|
123
|
+
self._handle_overwrite_prompt()
|
|
124
|
+
|
|
125
|
+
self._process: Optional[subprocess.Popen] = None
|
|
126
|
+
self._return_code: Optional[int] = None
|
|
127
|
+
self.use_tqdm: bool = False
|
|
128
|
+
|
|
129
|
+
def run(
|
|
130
|
+
self,
|
|
131
|
+
print_command: bool = False,
|
|
132
|
+
) -> int:
|
|
133
|
+
if print_command:
|
|
134
|
+
cmd_str = (
|
|
135
|
+
" ".join(self._ffmpeg_command)
|
|
136
|
+
if isinstance(self._ffmpeg_command, list)
|
|
137
|
+
else self._ffmpeg_command
|
|
138
|
+
)
|
|
139
|
+
print(f"Executing: {cmd_str}")
|
|
140
|
+
|
|
141
|
+
self._shell_needed = check_shell_needed_for_command(
|
|
142
|
+
self._ffmpeg_command
|
|
143
|
+
if isinstance(self._ffmpeg_command, list)
|
|
144
|
+
else [self._ffmpeg_command]
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
current_ffmpeg_command = self._ffmpeg_command
|
|
148
|
+
if self._shell_needed and isinstance(current_ffmpeg_command, list):
|
|
149
|
+
current_ffmpeg_command = " ".join(current_ffmpeg_command)
|
|
150
|
+
|
|
151
|
+
self._return_code = 1
|
|
152
|
+
|
|
153
|
+
try:
|
|
154
|
+
with open(self._ffmpeg_log_file, "w", encoding="utf-8") as f:
|
|
155
|
+
creationflags = 0
|
|
156
|
+
|
|
157
|
+
if os.name == "nt":
|
|
158
|
+
creationflags = subprocess.CREATE_NEW_PROCESS_GROUP
|
|
159
|
+
|
|
160
|
+
self._process = subprocess.Popen(
|
|
161
|
+
current_ffmpeg_command,
|
|
162
|
+
shell=self._shell_needed,
|
|
163
|
+
stdout=subprocess.PIPE,
|
|
164
|
+
stderr=f,
|
|
165
|
+
creationflags=creationflags,
|
|
166
|
+
)
|
|
167
|
+
except Exception as e:
|
|
168
|
+
# self._return_code remains None as the process didn't start
|
|
169
|
+
raise FfmpegProcessError(f"Error starting FFmpeg process: {e}") from e
|
|
170
|
+
|
|
171
|
+
try:
|
|
172
|
+
if self.use_tqdm:
|
|
173
|
+
self._return_code = use_tqdm(self, self._process)
|
|
174
|
+
else:
|
|
175
|
+
self._return_code = use_rich(self, self._process)
|
|
176
|
+
except KeyboardInterrupt:
|
|
177
|
+
self._terminate()
|
|
178
|
+
self._return_code = 1
|
|
179
|
+
finally:
|
|
180
|
+
if self._process:
|
|
181
|
+
self._return_code = self._process.returncode
|
|
182
|
+
|
|
183
|
+
if self._process.stdout:
|
|
184
|
+
self._process.stdout.close()
|
|
185
|
+
|
|
186
|
+
return self._return_code
|
|
187
|
+
|
|
188
|
+
def _terminate(self):
|
|
189
|
+
if self._process:
|
|
190
|
+
terminate_ffmpeg_process(self._process)
|
|
191
|
+
else:
|
|
192
|
+
exit()
|
|
193
|
+
|
|
194
|
+
@property
|
|
195
|
+
def return_code(self) -> Optional[int]:
|
|
196
|
+
"""
|
|
197
|
+
The return code of the FFmpeg process.
|
|
198
|
+
None if the process has not started or failed to start.
|
|
199
|
+
"""
|
|
200
|
+
return self._return_code
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import subprocess
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
from rich.progress import (
|
|
6
|
+
Progress,
|
|
7
|
+
TextColumn,
|
|
8
|
+
BarColumn,
|
|
9
|
+
TaskProgressColumn,
|
|
10
|
+
TimeRemainingColumn,
|
|
11
|
+
TimeElapsedColumn,
|
|
12
|
+
)
|
|
13
|
+
from tqdm import tqdm
|
|
14
|
+
|
|
15
|
+
from .utils import parse_ffmpeg_progress_line
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from .better_ffmpeg_progress import FfmpegProcess
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def use_rich(
|
|
22
|
+
ffmpeg_process_instance: "FfmpegProcess", process: subprocess.Popen
|
|
23
|
+
) -> int:
|
|
24
|
+
task_id = None
|
|
25
|
+
progress_bar_instance = Progress(
|
|
26
|
+
TextColumn("[progress.description]{task.description}"),
|
|
27
|
+
BarColumn(),
|
|
28
|
+
TaskProgressColumn(),
|
|
29
|
+
TimeElapsedColumn(),
|
|
30
|
+
TimeRemainingColumn(compact=True),
|
|
31
|
+
refresh_per_second=2,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
duration_secs = ffmpeg_process_instance._duration_secs
|
|
35
|
+
input_filename = ffmpeg_process_instance._input_filepath.name
|
|
36
|
+
log_file = ffmpeg_process_instance._ffmpeg_log_file
|
|
37
|
+
|
|
38
|
+
with progress_bar_instance as progress_bar:
|
|
39
|
+
if duration_secs:
|
|
40
|
+
task_id = progress_bar.add_task(
|
|
41
|
+
f"Processing {input_filename}",
|
|
42
|
+
total=duration_secs,
|
|
43
|
+
)
|
|
44
|
+
update_progress = progress_bar.update
|
|
45
|
+
|
|
46
|
+
for line_bytes in process.stdout:
|
|
47
|
+
stripped_line_bytes = line_bytes.strip()
|
|
48
|
+
progress_val_secs = parse_ffmpeg_progress_line(
|
|
49
|
+
stripped_line_bytes, duration_secs
|
|
50
|
+
)
|
|
51
|
+
if progress_val_secs is not None and task_id is not None:
|
|
52
|
+
update_progress(task_id, completed=progress_val_secs)
|
|
53
|
+
else:
|
|
54
|
+
print(f"Processing {input_filename}...")
|
|
55
|
+
|
|
56
|
+
process.wait()
|
|
57
|
+
|
|
58
|
+
if task_id is not None:
|
|
59
|
+
if process.returncode == 0:
|
|
60
|
+
update_progress(task_id, completed=duration_secs)
|
|
61
|
+
progress_bar.columns = (
|
|
62
|
+
TextColumn("[progress.description]{task.description}"),
|
|
63
|
+
BarColumn(),
|
|
64
|
+
TaskProgressColumn(),
|
|
65
|
+
)
|
|
66
|
+
update_progress(
|
|
67
|
+
task_id,
|
|
68
|
+
description=f"✓ Processed {input_filename}",
|
|
69
|
+
)
|
|
70
|
+
else:
|
|
71
|
+
progress_bar.update(
|
|
72
|
+
task_id,
|
|
73
|
+
description=f"FFmpeg process failed. Check {log_file} for details.",
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
if process.returncode != 0 and task_id is None:
|
|
77
|
+
print(f"FFmpeg process failed. Check {log_file} for details.")
|
|
78
|
+
|
|
79
|
+
return 1 if process.returncode != 0 else 0
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def use_tqdm(
|
|
83
|
+
ffmpeg_process_instance: "FfmpegProcess", process: subprocess.Popen
|
|
84
|
+
) -> int:
|
|
85
|
+
progress_bar = None
|
|
86
|
+
try:
|
|
87
|
+
width = os.get_terminal_size().columns
|
|
88
|
+
except OSError:
|
|
89
|
+
width = 80
|
|
90
|
+
|
|
91
|
+
duration_secs = ffmpeg_process_instance._duration_secs
|
|
92
|
+
input_filename = ffmpeg_process_instance._input_filepath.name
|
|
93
|
+
log_file = ffmpeg_process_instance._ffmpeg_log_file
|
|
94
|
+
|
|
95
|
+
if duration_secs:
|
|
96
|
+
progress_bar = tqdm(
|
|
97
|
+
mininterval=0.5,
|
|
98
|
+
total=duration_secs,
|
|
99
|
+
desc=f"Processing {input_filename}",
|
|
100
|
+
ncols=80,
|
|
101
|
+
dynamic_ncols=True if width < 80 else False,
|
|
102
|
+
bar_format="{desc} {bar} {percentage:.1f}% [{elapsed}<{remaining}, {rate_fmt}{postfix}]",
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
for line_bytes in process.stdout:
|
|
106
|
+
stripped_line_bytes = line_bytes.strip()
|
|
107
|
+
progress_val_secs = parse_ffmpeg_progress_line(
|
|
108
|
+
stripped_line_bytes, duration_secs
|
|
109
|
+
)
|
|
110
|
+
if progress_val_secs is not None and progress_bar is not None:
|
|
111
|
+
progress_bar.n = progress_val_secs
|
|
112
|
+
progress_bar.refresh()
|
|
113
|
+
else:
|
|
114
|
+
print(f"Processing {input_filename}...")
|
|
115
|
+
|
|
116
|
+
process.wait()
|
|
117
|
+
|
|
118
|
+
if process.returncode == 0:
|
|
119
|
+
if progress_bar:
|
|
120
|
+
progress_bar.n = duration_secs
|
|
121
|
+
progress_bar.set_description(f"✓ Processed {input_filename}")
|
|
122
|
+
progress_bar.close()
|
|
123
|
+
else:
|
|
124
|
+
print(f"✓ Processed {input_filename}")
|
|
125
|
+
else:
|
|
126
|
+
if progress_bar:
|
|
127
|
+
progress_bar.close()
|
|
128
|
+
print(f"FFmpeg process failed. Check {log_file} for details.")
|
|
129
|
+
|
|
130
|
+
return 1 if process.returncode != 0 else 0
|