better-ffmpeg-progress 3.2.0__tar.gz → 3.2.2__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.0 → better_ffmpeg_progress-3.2.2}/PKG-INFO +1 -1
- {better_ffmpeg_progress-3.2.0 → better_ffmpeg_progress-3.2.2}/better_ffmpeg_progress/better_ffmpeg_progress.py +69 -137
- {better_ffmpeg_progress-3.2.0 → better_ffmpeg_progress-3.2.2}/better_ffmpeg_progress.egg-info/PKG-INFO +1 -1
- {better_ffmpeg_progress-3.2.0 → better_ffmpeg_progress-3.2.2}/setup.py +1 -1
- {better_ffmpeg_progress-3.2.0 → better_ffmpeg_progress-3.2.2}/LICENSE +0 -0
- {better_ffmpeg_progress-3.2.0 → better_ffmpeg_progress-3.2.2}/README.md +0 -0
- {better_ffmpeg_progress-3.2.0 → better_ffmpeg_progress-3.2.2}/better_ffmpeg_progress/__init__.py +0 -0
- {better_ffmpeg_progress-3.2.0 → better_ffmpeg_progress-3.2.2}/better_ffmpeg_progress/utils.py +0 -0
- {better_ffmpeg_progress-3.2.0 → better_ffmpeg_progress-3.2.2}/better_ffmpeg_progress.egg-info/SOURCES.txt +0 -0
- {better_ffmpeg_progress-3.2.0 → better_ffmpeg_progress-3.2.2}/better_ffmpeg_progress.egg-info/dependency_links.txt +0 -0
- {better_ffmpeg_progress-3.2.0 → better_ffmpeg_progress-3.2.2}/better_ffmpeg_progress.egg-info/requires.txt +0 -0
- {better_ffmpeg_progress-3.2.0 → better_ffmpeg_progress-3.2.2}/better_ffmpeg_progress.egg-info/top_level.txt +0 -0
- {better_ffmpeg_progress-3.2.0 → better_ffmpeg_progress-3.2.2}/setup.cfg +0 -0
|
@@ -2,10 +2,9 @@ from enum import Enum
|
|
|
2
2
|
import os
|
|
3
3
|
from pathlib import Path
|
|
4
4
|
import psutil
|
|
5
|
-
from queue import
|
|
5
|
+
from queue import Queue
|
|
6
6
|
import shutil
|
|
7
7
|
import subprocess
|
|
8
|
-
from threading import Thread
|
|
9
8
|
from typing import List, Optional, Union
|
|
10
9
|
|
|
11
10
|
from ffmpeg import probe
|
|
@@ -34,26 +33,6 @@ class FfmpegLogLevel(Enum):
|
|
|
34
33
|
|
|
35
34
|
|
|
36
35
|
class FfmpegProcess:
|
|
37
|
-
TIME_MS_PREFIX = "out_time_ms="
|
|
38
|
-
|
|
39
|
-
@staticmethod
|
|
40
|
-
def _read_pipe(pipe: subprocess.PIPE, queue: Queue, stdout: bool = False) -> None:
|
|
41
|
-
"""Read from pipe and put lines into queue."""
|
|
42
|
-
try:
|
|
43
|
-
with pipe:
|
|
44
|
-
for line in iter(pipe.readline, ""):
|
|
45
|
-
line = line.strip()
|
|
46
|
-
if line:
|
|
47
|
-
if stdout:
|
|
48
|
-
if line.startswith("out_time_ms="):
|
|
49
|
-
queue.put(line)
|
|
50
|
-
else:
|
|
51
|
-
queue.put(line)
|
|
52
|
-
except (IOError, ValueError) as e:
|
|
53
|
-
print(f"Error reading pipe: {e}")
|
|
54
|
-
finally:
|
|
55
|
-
pipe.close()
|
|
56
|
-
|
|
57
36
|
@classmethod
|
|
58
37
|
def _validate_command(cls, command: List[str]) -> bool:
|
|
59
38
|
if not shutil.which("ffmpeg"):
|
|
@@ -113,14 +92,11 @@ class FfmpegProcess:
|
|
|
113
92
|
self._print_detected_duration = print_detected_duration
|
|
114
93
|
self._print_stderr_new_line = print_stderr_new_line
|
|
115
94
|
|
|
116
|
-
with open(self._ffmpeg_log_file, "w"):
|
|
117
|
-
pass
|
|
118
|
-
|
|
119
95
|
try:
|
|
120
96
|
probe_data = probe(self._input_filepath)
|
|
121
97
|
self._duration_secs = float(probe_data["format"]["duration"])
|
|
122
98
|
if self._print_detected_duration:
|
|
123
|
-
print(f"Detected duration: {self._duration_secs
|
|
99
|
+
print(f"Detected duration: {self._duration_secs} seconds")
|
|
124
100
|
except Exception:
|
|
125
101
|
self._duration_secs = None
|
|
126
102
|
|
|
@@ -131,26 +107,13 @@ class FfmpegProcess:
|
|
|
131
107
|
self._ffmpeg_log_level,
|
|
132
108
|
"-progress",
|
|
133
109
|
"pipe:1",
|
|
134
|
-
"-stats_period",
|
|
135
|
-
"0.1",
|
|
136
110
|
"-nostats",
|
|
137
111
|
*command[1:],
|
|
138
112
|
]
|
|
139
113
|
|
|
140
114
|
self.return_code = 0
|
|
141
115
|
|
|
142
|
-
def
|
|
143
|
-
if self._duration_secs is None:
|
|
144
|
-
print(stderr, end="\n" if self._print_stderr_new_line else "\r", flush=True)
|
|
145
|
-
|
|
146
|
-
try:
|
|
147
|
-
with open(self._ffmpeg_log_file, "a", encoding="utf-8") as f:
|
|
148
|
-
f.write(f"{stderr}\n")
|
|
149
|
-
except IOError as e:
|
|
150
|
-
print(f"Error writing to log file: {e}")
|
|
151
|
-
self.return_code = 1
|
|
152
|
-
|
|
153
|
-
def _use_rich(self, process, stdout_queue, stderr_queue) -> int:
|
|
116
|
+
def _use_rich(self, process) -> int:
|
|
154
117
|
with Progress(
|
|
155
118
|
SpinnerColumn(),
|
|
156
119
|
TextColumn("[progress.description]{task.description}"),
|
|
@@ -161,46 +124,39 @@ class FfmpegProcess:
|
|
|
161
124
|
refresh_per_second=10,
|
|
162
125
|
) as progress_bar:
|
|
163
126
|
task_id = None
|
|
127
|
+
|
|
164
128
|
if self._duration_secs:
|
|
165
129
|
task_id = progress_bar.add_task(
|
|
166
130
|
f"Processing {self._input_filepath.name}",
|
|
167
131
|
total=self._duration_secs,
|
|
168
132
|
)
|
|
169
133
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
try:
|
|
174
|
-
line = stdout_queue.get_nowait()
|
|
175
|
-
if line.startswith(self.TIME_MS_PREFIX):
|
|
176
|
-
try:
|
|
177
|
-
value = int(line.split("=")[1]) / 1_000_000
|
|
178
|
-
if value <= self._duration_secs:
|
|
179
|
-
progress_bar.update(task_id, completed=value)
|
|
180
|
-
except ValueError:
|
|
181
|
-
pass
|
|
182
|
-
except Empty:
|
|
183
|
-
pass
|
|
184
|
-
|
|
185
|
-
# stderr
|
|
186
|
-
if not stderr_queue.empty():
|
|
187
|
-
try:
|
|
188
|
-
self._process_stderr(stderr_queue.get_nowait())
|
|
189
|
-
except Empty:
|
|
190
|
-
pass
|
|
134
|
+
if task_id is not None:
|
|
135
|
+
while process.poll() is None:
|
|
136
|
+
stdout = process.stdout.readline()
|
|
191
137
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
138
|
+
if not stdout.startswith("out_time_ms"):
|
|
139
|
+
continue
|
|
140
|
+
|
|
141
|
+
try:
|
|
142
|
+
value = int(stdout.split("=")[1]) / 1_000_000
|
|
143
|
+
except ValueError:
|
|
144
|
+
continue
|
|
145
|
+
else:
|
|
146
|
+
if value <= self._duration_secs:
|
|
147
|
+
progress_bar.update(task_id, completed=value)
|
|
148
|
+
|
|
149
|
+
if process.returncode == 0:
|
|
150
|
+
progress_bar.update(task_id, completed=self._duration_secs)
|
|
151
|
+
progress_bar.columns = (
|
|
152
|
+
TextColumn("[progress.description]{task.description}"),
|
|
153
|
+
BarColumn(),
|
|
154
|
+
TaskProgressColumn(),
|
|
155
|
+
)
|
|
156
|
+
progress_bar.update(
|
|
157
|
+
task_id,
|
|
158
|
+
description=f"✓ Processed {self._input_filepath.name}",
|
|
159
|
+
)
|
|
204
160
|
|
|
205
161
|
if process.returncode != 0:
|
|
206
162
|
progress_bar.update(
|
|
@@ -209,19 +165,14 @@ class FfmpegProcess:
|
|
|
209
165
|
)
|
|
210
166
|
return 1
|
|
211
167
|
|
|
212
|
-
progress_bar.update(
|
|
213
|
-
task_id,
|
|
214
|
-
description=f"✓ Processed {self._input_filepath.name}",
|
|
215
|
-
)
|
|
216
|
-
|
|
217
168
|
return 0
|
|
218
169
|
|
|
219
|
-
def _use_tqdm(self, process
|
|
220
|
-
|
|
170
|
+
def _use_tqdm(self, process) -> int:
|
|
171
|
+
progress_bar = None
|
|
221
172
|
width, _ = os.get_terminal_size()
|
|
222
173
|
|
|
223
174
|
if self._duration_secs:
|
|
224
|
-
|
|
175
|
+
progress_bar = tqdm(
|
|
225
176
|
total=self._duration_secs,
|
|
226
177
|
desc=f"Processing {self._input_filepath.name}",
|
|
227
178
|
ncols=80,
|
|
@@ -229,34 +180,27 @@ class FfmpegProcess:
|
|
|
229
180
|
bar_format="{desc} {bar} {percentage:.1f}% {elapsed} {remaining}",
|
|
230
181
|
)
|
|
231
182
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
value = int(line.split("=")[1]) / 1_000_000
|
|
240
|
-
if value <= self._duration_secs:
|
|
241
|
-
pbar.n = value
|
|
242
|
-
pbar.refresh()
|
|
243
|
-
except ValueError:
|
|
244
|
-
pass
|
|
245
|
-
except Empty:
|
|
246
|
-
pass
|
|
247
|
-
# stderr
|
|
248
|
-
if not stderr_queue.empty():
|
|
183
|
+
if progress_bar is not None:
|
|
184
|
+
while process.poll() is None:
|
|
185
|
+
stdout = process.stdout.readline()
|
|
186
|
+
|
|
187
|
+
if not stdout.startswith("out_time_ms"):
|
|
188
|
+
continue
|
|
189
|
+
|
|
249
190
|
try:
|
|
250
|
-
|
|
251
|
-
except
|
|
252
|
-
|
|
191
|
+
value = int(stdout.split("=")[1]) / 1_000_000
|
|
192
|
+
except ValueError:
|
|
193
|
+
continue
|
|
194
|
+
else:
|
|
195
|
+
if value <= self._duration_secs:
|
|
196
|
+
progress_bar.n = value
|
|
197
|
+
progress_bar.refresh()
|
|
253
198
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
break
|
|
199
|
+
if process.returncode == 0:
|
|
200
|
+
progress_bar.n = self._duration_secs
|
|
201
|
+
progress_bar.set_description(f"✓ Processed {self._input_filepath.name}")
|
|
202
|
+
|
|
203
|
+
progress_bar.close()
|
|
260
204
|
|
|
261
205
|
if process.returncode != 0:
|
|
262
206
|
print(
|
|
@@ -264,13 +208,13 @@ class FfmpegProcess:
|
|
|
264
208
|
)
|
|
265
209
|
return 1
|
|
266
210
|
|
|
267
|
-
pbar.set_description(f"✓ Processed {self._input_filepath.name}")
|
|
268
|
-
if pbar:
|
|
269
|
-
pbar.close()
|
|
270
|
-
|
|
271
211
|
return 0
|
|
272
212
|
|
|
273
|
-
def run(
|
|
213
|
+
def run(
|
|
214
|
+
self,
|
|
215
|
+
print_command: bool = False,
|
|
216
|
+
use_tqdm: bool = False,
|
|
217
|
+
) -> int:
|
|
274
218
|
if hasattr(self, "return_code") and self.return_code != 0:
|
|
275
219
|
return 1
|
|
276
220
|
|
|
@@ -299,38 +243,26 @@ class FfmpegProcess:
|
|
|
299
243
|
self._ffmpeg_command = " ".join(self._ffmpeg_command)
|
|
300
244
|
|
|
301
245
|
try:
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
246
|
+
with open(self._ffmpeg_log_file, "w") as f:
|
|
247
|
+
process = subprocess.Popen(
|
|
248
|
+
self._ffmpeg_command,
|
|
249
|
+
shell=isinstance(self._ffmpeg_command, str),
|
|
250
|
+
stdout=subprocess.PIPE,
|
|
251
|
+
stderr=f,
|
|
252
|
+
text=True,
|
|
253
|
+
creationflags=subprocess.CREATE_NEW_PROCESS_GROUP
|
|
254
|
+
if os.name == "nt"
|
|
255
|
+
else 0,
|
|
256
|
+
)
|
|
312
257
|
except Exception as e:
|
|
313
258
|
print(f"Error starting FFmpeg process: {e}")
|
|
314
259
|
return 1
|
|
315
260
|
|
|
316
|
-
stdout_queue, stderr_queue = Queue(), Queue()
|
|
317
|
-
# Start stdout and stderr pipe readers
|
|
318
|
-
Thread(
|
|
319
|
-
target=self._read_pipe,
|
|
320
|
-
args=(process.stdout, stdout_queue, True),
|
|
321
|
-
daemon=True,
|
|
322
|
-
).start()
|
|
323
|
-
Thread(
|
|
324
|
-
target=self._read_pipe,
|
|
325
|
-
args=(process.stderr, stderr_queue, False),
|
|
326
|
-
daemon=True,
|
|
327
|
-
).start()
|
|
328
|
-
|
|
329
261
|
try:
|
|
330
262
|
if use_tqdm:
|
|
331
|
-
return self._use_tqdm(process
|
|
263
|
+
return self._use_tqdm(process)
|
|
332
264
|
else:
|
|
333
|
-
return self._use_rich(process
|
|
265
|
+
return self._use_rich(process)
|
|
334
266
|
except KeyboardInterrupt:
|
|
335
267
|
try:
|
|
336
268
|
psutil.Process(process.pid).terminate()
|
|
@@ -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.
|
|
8
|
+
version="3.2.2",
|
|
9
9
|
author="GitHub.com/CrypticSignal",
|
|
10
10
|
author_email="theaudiophile@outlook.com",
|
|
11
11
|
description="Run FFmpeg & see percentage progress + ETA.",
|
|
File without changes
|
|
File without changes
|
{better_ffmpeg_progress-3.2.0 → better_ffmpeg_progress-3.2.2}/better_ffmpeg_progress/__init__.py
RENAMED
|
File without changes
|
{better_ffmpeg_progress-3.2.0 → better_ffmpeg_progress-3.2.2}/better_ffmpeg_progress/utils.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|