better-ffmpeg-progress 3.2.1__tar.gz → 3.2.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: better-ffmpeg-progress
3
- Version: 3.2.1
3
+ Version: 3.2.3
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
@@ -12,7 +12,6 @@ Classifier: Operating System :: OS Independent
12
12
  Requires-Python: >=3.7
13
13
  Description-Content-Type: text/markdown
14
14
  License-File: LICENSE
15
- Requires-Dist: ffmpeg-python
16
15
  Requires-Dist: psutil
17
16
  Requires-Dist: rich
18
17
  Requires-Dist: tqdm
@@ -0,0 +1,350 @@
1
+ from enum import Enum
2
+ import os
3
+ from pathlib import Path
4
+ import psutil
5
+ import shutil
6
+ from signal import CTRL_BREAK_EVENT
7
+ import subprocess
8
+ from typing import List, Optional, Union
9
+
10
+ from rich.progress import (
11
+ Progress,
12
+ TextColumn,
13
+ BarColumn,
14
+ TaskProgressColumn,
15
+ TimeRemainingColumn,
16
+ TimeElapsedColumn,
17
+ )
18
+ from tqdm import tqdm
19
+
20
+ OUT_TIME_PREFIX = b"out_time_"
21
+ # Convert microseconds to seconds
22
+ MULTIPLIER = 1e-6
23
+
24
+
25
+ class FfmpegLogLevel(Enum):
26
+ QUIET = "quiet"
27
+ PANIC = "panic"
28
+ FATAL = "fatal"
29
+ ERROR = "error"
30
+ WARNING = "warning"
31
+ INFO = "info"
32
+ VERBOSE = "verbose"
33
+ DEBUG = "debug"
34
+ TRACE = "trace"
35
+
36
+
37
+ class FfmpegProcess:
38
+ @staticmethod
39
+ def _validate_command(command: List[str]) -> bool:
40
+ if not shutil.which("ffmpeg"):
41
+ print("Error: FFmpeg executable not found in PATH")
42
+ return False
43
+
44
+ if "-i" not in command:
45
+ print("Error: FFmpeg command must include '-i'")
46
+ return False
47
+
48
+ input_idx = command.index("-i") + 1
49
+ if input_idx >= len(command):
50
+ print("Error: No input file specified after -i")
51
+ return False
52
+
53
+ input_file = Path(command[input_idx])
54
+ if not input_file.exists():
55
+ print(f"Error: Input file not found: {input_file}")
56
+ return False
57
+
58
+ if input_idx + 1 >= len(command):
59
+ print("Error: No output file specified")
60
+ return False
61
+
62
+ return True
63
+
64
+ def _should_overwrite(self):
65
+ if self._output_filepath.exists() and "-y" not in self._ffmpeg_command:
66
+ try:
67
+ if (
68
+ input(
69
+ f"{self._output_filepath} already exists. Overwrite? [Y/N]: "
70
+ ).lower()
71
+ != "y"
72
+ ):
73
+ print(
74
+ "FFmpeg process cancelled. Output file exists and overwrite declined."
75
+ )
76
+ return False
77
+ except EOFError:
78
+ print("Input error. FFmpeg process cancelled.")
79
+ return False
80
+ except KeyboardInterrupt:
81
+ print("[KeyboardInterrupt] FFmpeg process cancelled.")
82
+ return False
83
+
84
+ self._ffmpeg_command.insert(1, "-y")
85
+ return True
86
+
87
+ @staticmethod
88
+ def _get_duration(filepath: Path) -> Optional[float]:
89
+ try:
90
+ output = subprocess.check_output(
91
+ [
92
+ "ffprobe",
93
+ "-v",
94
+ "error",
95
+ "-show_entries",
96
+ "format=duration",
97
+ "-of",
98
+ "default=noprint_wrappers=1:nokey=1",
99
+ "-i",
100
+ str(filepath),
101
+ ],
102
+ text=True,
103
+ )
104
+ return float(output)
105
+ except (subprocess.CalledProcessError, ValueError, FileNotFoundError):
106
+ return None
107
+
108
+ @staticmethod
109
+ def _check_shell_needed(command):
110
+ shell_operators = {"|", ">", "<", ">>", "&&", "||"}
111
+ return any(op in item for item in command for op in shell_operators)
112
+
113
+ def __init__(
114
+ self,
115
+ command: List[str],
116
+ ffmpeg_log_level: Optional[Union[FfmpegLogLevel, str]] = None,
117
+ ffmpeg_log_file: Optional[Union[str, Path]] = None,
118
+ print_detected_duration: bool = False,
119
+ print_stderr_new_line: bool = False,
120
+ ):
121
+ if not self._validate_command(command):
122
+ self._return_code = 1
123
+ return
124
+
125
+ if ffmpeg_log_level and ffmpeg_log_level not in FfmpegLogLevel:
126
+ print(
127
+ f"ffmpeg_log_level must be a lowercase variant of: {list(FfmpegLogLevel.__members__)}"
128
+ )
129
+ self._return_code = 1
130
+ return
131
+
132
+ input_idx = command.index("-i") + 1
133
+ self._input_filepath = Path(command[input_idx])
134
+ self._output_filepath = Path(command[-1])
135
+ self._ffmpeg_log_level = (
136
+ ffmpeg_log_level.value if ffmpeg_log_level else FfmpegLogLevel.VERBOSE.value
137
+ )
138
+ self._ffmpeg_log_file = Path(
139
+ ffmpeg_log_file or f"{self._input_filepath.name}_log.txt"
140
+ )
141
+ self._print_detected_duration = print_detected_duration
142
+ self._print_stderr_new_line = print_stderr_new_line
143
+ self._duration_secs = self._get_duration(self._input_filepath)
144
+
145
+ if self._print_detected_duration and self._duration_secs is not None:
146
+ print(f"Detected duration: {self._duration_secs} seconds")
147
+
148
+ self._ffmpeg_command = [
149
+ command[0],
150
+ "-hide_banner",
151
+ "-loglevel",
152
+ self._ffmpeg_log_level,
153
+ "-progress",
154
+ "pipe:1",
155
+ "-nostats",
156
+ ]
157
+ self._ffmpeg_command.extend(command[1:])
158
+
159
+ if not self._should_overwrite():
160
+ self._return_code = 1
161
+ return
162
+
163
+ def _use_rich(self, process) -> int:
164
+ task_id = None
165
+
166
+ with Progress(
167
+ TextColumn("[progress.description]{task.description}"),
168
+ BarColumn(),
169
+ TaskProgressColumn(),
170
+ TimeElapsedColumn(),
171
+ TimeRemainingColumn(compact=True),
172
+ refresh_per_second=2,
173
+ ) as progress_bar:
174
+ if self._duration_secs:
175
+ task_id = progress_bar.add_task(
176
+ f"Processing {self._input_filepath.name}",
177
+ total=self._duration_secs,
178
+ )
179
+
180
+ update_progress = progress_bar.update
181
+ duration = self._duration_secs
182
+
183
+ for line in process.stdout:
184
+ if line[:9] == OUT_TIME_PREFIX:
185
+ value = line[12:-1]
186
+ # Check if the first byte is a digit
187
+ try:
188
+ if 48 <= value[0] <= 57:
189
+ value_int = int(value) * MULTIPLIER
190
+ if value_int <= duration:
191
+ update_progress(task_id, completed=value_int)
192
+ except (IndexError, ValueError):
193
+ pass
194
+
195
+ process.wait()
196
+
197
+ if process.returncode == 0 and task_id is not None:
198
+ update_progress(task_id, completed=duration)
199
+ progress_bar.columns = (
200
+ TextColumn("[progress.description]{task.description}"),
201
+ BarColumn(),
202
+ TaskProgressColumn(),
203
+ )
204
+ update_progress(
205
+ task_id,
206
+ description=f"✓ Processed {self._input_filepath.name}",
207
+ )
208
+
209
+ if process.returncode != 0:
210
+ if task_id is not None:
211
+ progress_bar.update(
212
+ task_id,
213
+ description=f"FFmpeg process failed. Check out {self._ffmpeg_log_file} for details.",
214
+ )
215
+ return 1
216
+
217
+ return 0
218
+
219
+ def _use_tqdm(self, process) -> int:
220
+ progress_bar = None
221
+
222
+ try:
223
+ width = os.get_terminal_size().columns
224
+ except OSError:
225
+ width = 80
226
+
227
+ if self._duration_secs:
228
+ progress_bar = tqdm(
229
+ mininterval=0.5,
230
+ total=self._duration_secs,
231
+ desc=f"Processing {self._input_filepath.name}",
232
+ ncols=80,
233
+ dynamic_ncols=True if width < 80 else False,
234
+ bar_format="{desc} {bar} {percentage:.1f}% {elapsed} {remaining}",
235
+ )
236
+
237
+ duration = self._duration_secs
238
+
239
+ for line in process.stdout:
240
+ if line[:9] == OUT_TIME_PREFIX:
241
+ value = line[12:-1]
242
+ try:
243
+ # Check if the first byte is a digit
244
+ if 48 <= value[0] <= 57:
245
+ value_int = int(value) * MULTIPLIER
246
+ if value_int <= duration:
247
+ progress_bar.n = value_int
248
+ progress_bar.refresh()
249
+ except (IndexError, ValueError):
250
+ pass
251
+
252
+ process.wait()
253
+
254
+ if process.returncode == 0 and progress_bar is not None:
255
+ progress_bar.n = duration
256
+ progress_bar.set_description(f"✓ Processed {self._input_filepath.name}")
257
+ progress_bar.close()
258
+
259
+ if process.returncode != 0:
260
+ print(
261
+ f"FFmpeg process failed. Check out {self._ffmpeg_log_file} for details."
262
+ )
263
+ return 1
264
+
265
+ return 0
266
+
267
+ def run(
268
+ self,
269
+ print_command: bool = False,
270
+ use_tqdm: bool = False,
271
+ ) -> int:
272
+ if hasattr(self, "_return_code") and self._return_code != 0:
273
+ return 1
274
+
275
+ if print_command:
276
+ print(f"Executing: {' '.join(self._ffmpeg_command)}")
277
+
278
+ self._shell_needed = self._check_shell_needed(self._ffmpeg_command)
279
+
280
+ if self._shell_needed:
281
+ self._ffmpeg_command = " ".join(self._ffmpeg_command)
282
+
283
+ try:
284
+ with open(self._ffmpeg_log_file, "w") as f:
285
+ self._process = subprocess.Popen(
286
+ self._ffmpeg_command,
287
+ shell=self._shell_needed,
288
+ stdout=subprocess.PIPE,
289
+ stderr=f,
290
+ creationflags=subprocess.CREATE_NEW_PROCESS_GROUP
291
+ if os.name == "nt"
292
+ else 0,
293
+ )
294
+ except Exception as e:
295
+ print(f"Error starting FFmpeg process: {e}")
296
+ return 1
297
+
298
+ try:
299
+ return (
300
+ self._use_tqdm(self._process)
301
+ if use_tqdm
302
+ else self._use_rich(self._process)
303
+ )
304
+ except KeyboardInterrupt:
305
+ print("\n[KeyboardInterrupt] Terminating FFmpeg process...")
306
+ self._terminate()
307
+ print("Done!")
308
+ return 1
309
+ finally:
310
+ if self._process and self._process.poll() is None:
311
+ self._terminate()
312
+
313
+ def _terminate(self):
314
+ if not self._process or self._process.poll() is not None:
315
+ return
316
+
317
+ try:
318
+ proc = psutil.Process(self._process.pid)
319
+ children = proc.children(recursive=True)
320
+
321
+ # Windows. Use CTRL_BREAK_EVENT to terminate entire process group
322
+ if os.name == "nt":
323
+ if self._shell_needed:
324
+ # If running under a shell, terminate shell first
325
+ proc.terminate()
326
+ else:
327
+ # Send CTRL_BREAK_EVENT to entire process group
328
+ os.kill(self._process.pid, CTRL_BREAK_EVENT)
329
+ # Unix
330
+ else:
331
+ for child in children:
332
+ try:
333
+ child.terminate()
334
+ except psutil.NoSuchProcess:
335
+ pass
336
+ proc.terminate()
337
+
338
+ # Wait up to 1 second for processes to exit gracefully
339
+ gone, still_alive = psutil.wait_procs(children + [proc], timeout=1)
340
+
341
+ # Force kill any remaining processes
342
+ for p in still_alive:
343
+ try:
344
+ p.kill()
345
+ except psutil.NoSuchProcess:
346
+ pass
347
+
348
+ except (psutil.NoSuchProcess, ProcessLookupError, KeyboardInterrupt):
349
+ # As a last resort, force kill the main process
350
+ self._process.kill()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: better-ffmpeg-progress
3
- Version: 3.2.1
3
+ Version: 3.2.3
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
@@ -12,7 +12,6 @@ Classifier: Operating System :: OS Independent
12
12
  Requires-Python: >=3.7
13
13
  Description-Content-Type: text/markdown
14
14
  License-File: LICENSE
15
- Requires-Dist: ffmpeg-python
16
15
  Requires-Dist: psutil
17
16
  Requires-Dist: rich
18
17
  Requires-Dist: tqdm
@@ -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="3.2.1",
8
+ version="3.2.3",
9
9
  author="GitHub.com/CrypticSignal",
10
10
  author_email="theaudiophile@outlook.com",
11
11
  description="Run FFmpeg & see percentage progress + ETA.",
@@ -13,7 +13,7 @@ setuptools.setup(
13
13
  long_description_content_type="text/markdown",
14
14
  url="https://github.com/CrypticSignal/better-ffmpeg-progress",
15
15
  packages=["better_ffmpeg_progress"],
16
- install_requires=["ffmpeg-python", "psutil", "rich", "tqdm"],
16
+ install_requires=["psutil", "rich", "tqdm"],
17
17
  python_requires=">=3.7",
18
18
  keywords=["ffmpeg", "progress"],
19
19
  classifiers=[
@@ -1,328 +0,0 @@
1
- from enum import Enum
2
- import os
3
- from pathlib import Path
4
- import psutil
5
- from queue import Queue
6
- import shutil
7
- import subprocess
8
- from threading import Thread
9
- from time import sleep
10
- from typing import List, Optional, Union
11
-
12
- from ffmpeg import probe
13
- from rich.progress import (
14
- Progress,
15
- TextColumn,
16
- BarColumn,
17
- TaskProgressColumn,
18
- TimeRemainingColumn,
19
- TimeElapsedColumn,
20
- SpinnerColumn,
21
- )
22
- from tqdm import tqdm
23
-
24
-
25
- class FfmpegLogLevel(Enum):
26
- QUIET = "quiet"
27
- PANIC = "panic"
28
- FATAL = "fatal"
29
- ERROR = "error"
30
- WARNING = "warning"
31
- INFO = "info"
32
- VERBOSE = "verbose"
33
- DEBUG = "debug"
34
- TRACE = "trace"
35
-
36
-
37
- class FfmpegProcess:
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} 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
- "-nostats",
134
- *command[1:],
135
- ]
136
-
137
- self.return_code = 0
138
-
139
- def _process_stderr(self, stderr: str) -> None:
140
- if self._duration_secs is None:
141
- print(stderr, end="\n" if self._print_stderr_new_line else "\r", flush=True)
142
-
143
- try:
144
- with open(self._ffmpeg_log_file, "a", encoding="utf-8") as f:
145
- f.write(f"{stderr}\n")
146
- except IOError as e:
147
- print(f"Error writing to log file: {e}")
148
- self.return_code = 1
149
-
150
- def _use_rich(self, process, stdout_queue, stderr_queue) -> int:
151
- with Progress(
152
- SpinnerColumn(),
153
- TextColumn("[progress.description]{task.description}"),
154
- BarColumn(),
155
- TaskProgressColumn(),
156
- TimeElapsedColumn(),
157
- TimeRemainingColumn(compact=True),
158
- refresh_per_second=10,
159
- ) as progress_bar:
160
- task_id = None
161
-
162
- if self._duration_secs:
163
- task_id = progress_bar.add_task(
164
- f"Processing {self._input_filepath.name}",
165
- total=self._duration_secs,
166
- )
167
-
168
- while process.poll() is None:
169
- # FFmpeg only updates encoding progress/statistics every 0.5 seconds
170
- sleep(0.5)
171
- # stdout
172
- if task_id is not None and not stdout_queue.empty():
173
- line = stdout_queue.get()
174
- try:
175
- value = int(line.split("=")[1]) / 1_000_000
176
- if value <= self._duration_secs:
177
- progress_bar.update(task_id, completed=value)
178
- except ValueError:
179
- pass
180
- # stderr
181
- if not stderr_queue.empty():
182
- self._process_stderr(stderr_queue.get())
183
-
184
- if process.returncode == 0 and task_id is not None:
185
- progress_bar.update(task_id, completed=self._duration_secs)
186
- progress_bar.columns = (
187
- TextColumn("[progress.description]{task.description}"),
188
- BarColumn(),
189
- TaskProgressColumn(),
190
- )
191
- progress_bar.update(
192
- task_id,
193
- description=f"✓ Processed {self._input_filepath.name}",
194
- )
195
-
196
- # Process remaining stderr
197
- while not stderr_queue.empty():
198
- self._process_stderr(stderr_queue.get())
199
-
200
- if process.returncode != 0:
201
- progress_bar.update(
202
- task_id,
203
- description=f"The FFmpeg process did not complete successfully. Check out {self._ffmpeg_log_file} for details.",
204
- )
205
- return 1
206
-
207
- return 0
208
-
209
- def _use_tqdm(self, process, stdout_queue, stderr_queue) -> int:
210
- pbar = None
211
- width, _ = os.get_terminal_size()
212
-
213
- if self._duration_secs:
214
- pbar = tqdm(
215
- total=self._duration_secs,
216
- desc=f"Processing {self._input_filepath.name}",
217
- ncols=80,
218
- dynamic_ncols=True if width < 80 else False,
219
- bar_format="{desc} {bar} {percentage:.1f}% {elapsed} {remaining}",
220
- )
221
-
222
- while process.poll() is None:
223
- # FFmpeg only updates encoding progress/statistics every 0.5 seconds
224
- sleep(0.5)
225
- # stdout
226
- if pbar and not stdout_queue.empty():
227
- line = stdout_queue.get()
228
- try:
229
- value = int(line.split("=")[1]) / 1_000_000
230
- if value <= self._duration_secs:
231
- pbar.n = value
232
- pbar.refresh()
233
- except ValueError:
234
- pass
235
- # stderr
236
- if not stderr_queue.empty():
237
- self._process_stderr(stderr_queue.get())
238
-
239
- if process.returncode == 0 and pbar:
240
- pbar.n = self._duration_secs
241
- pbar.set_description(f"✓ Processed {self._input_filepath.name}")
242
-
243
- if pbar:
244
- pbar.close()
245
-
246
- # Process remaining stderr
247
- while not stderr_queue.empty():
248
- self._process_stderr(stderr_queue.get())
249
-
250
- if process.returncode != 0:
251
- print(
252
- f"The FFmpeg process did not complete successfully. Check out {self._ffmpeg_log_file} for details."
253
- )
254
- return 1
255
-
256
- return 0
257
-
258
- def run(
259
- self,
260
- print_command: bool = False,
261
- use_tqdm: bool = False,
262
- ) -> int:
263
- if hasattr(self, "return_code") and self.return_code != 0:
264
- return 1
265
-
266
- if self._output_filepath.exists() and "-y" not in self._ffmpeg_command:
267
- if (
268
- input(
269
- f"{self._output_filepath} already exists. Overwrite? [Y/N]: "
270
- ).lower()
271
- != "y"
272
- ):
273
- print(
274
- "FFmpeg process cancelled. Output file exists and overwrite declined."
275
- )
276
- return 1
277
- self._ffmpeg_command.insert(1, "-y")
278
-
279
- if print_command:
280
- print(f"Executing: {' '.join(self._ffmpeg_command)}")
281
-
282
- def _contains_shell_operators(command: List[str]) -> bool:
283
- shell_operators = {"|", ">", "<", ">>", "&&", "||"}
284
- return any(op in command for op in shell_operators)
285
-
286
- # If command contains shell operators, turn the list into a string.
287
- if _contains_shell_operators(self._ffmpeg_command):
288
- self._ffmpeg_command = " ".join(self._ffmpeg_command)
289
-
290
- try:
291
- process = subprocess.Popen(
292
- self._ffmpeg_command,
293
- shell=isinstance(self._ffmpeg_command, str),
294
- stdout=subprocess.PIPE,
295
- stderr=subprocess.PIPE,
296
- text=True,
297
- creationflags=subprocess.CREATE_NEW_PROCESS_GROUP
298
- if os.name == "nt"
299
- else 0,
300
- )
301
- except Exception as e:
302
- print(f"Error starting FFmpeg process: {e}")
303
- return 1
304
-
305
- stdout_queue, stderr_queue = Queue(), Queue()
306
- # Start stdout and stderr pipe readers
307
- Thread(
308
- target=self._read_pipe,
309
- args=(process.stdout, stdout_queue, True),
310
- daemon=True,
311
- ).start()
312
- Thread(
313
- target=self._read_pipe,
314
- args=(process.stderr, stderr_queue, False),
315
- daemon=True,
316
- ).start()
317
-
318
- try:
319
- if use_tqdm:
320
- return self._use_tqdm(process, stdout_queue, stderr_queue)
321
- else:
322
- return self._use_rich(process, stdout_queue, stderr_queue)
323
- except KeyboardInterrupt:
324
- try:
325
- psutil.Process(process.pid).terminate()
326
- except psutil.NoSuchProcess:
327
- pass
328
- return 1