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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: better-ffmpeg-progress
3
- Version: 3.2.0
3
+ Version: 3.2.2
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
@@ -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 Empty, Queue
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:.2f} seconds")
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 _process_stderr(self, stderr: str) -> None:
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
- while process.poll() is None:
171
- # stdout
172
- if task_id is not None and not stdout_queue.empty():
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
- # Process remaining stderr
193
- while not stderr_queue.empty():
194
- try:
195
- self._process_stderr(stderr_queue.get_nowait())
196
- except Empty:
197
- break
198
-
199
- progress_bar.columns = (
200
- TextColumn("[progress.description]{task.description}"),
201
- BarColumn(),
202
- TaskProgressColumn(),
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, stdout_queue, stderr_queue) -> int:
220
- pbar = None
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
- pbar = tqdm(
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
- while process.poll() is None:
233
- # stdout
234
- if pbar and not stdout_queue.empty():
235
- try:
236
- line = stdout_queue.get_nowait()
237
- if line.startswith(self.TIME_MS_PREFIX):
238
- try:
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
- self._process_stderr(stderr_queue.get_nowait())
251
- except Empty:
252
- pass
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
- # Process remaining stderr
255
- while not stderr_queue.empty():
256
- try:
257
- self._process_stderr(stderr_queue.get_nowait())
258
- except Empty:
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(self, print_command: bool = False, use_tqdm: bool = False) -> int:
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
- process = subprocess.Popen(
303
- self._ffmpeg_command,
304
- shell=isinstance(self._ffmpeg_command, str),
305
- stdout=subprocess.PIPE,
306
- stderr=subprocess.PIPE,
307
- text=True,
308
- creationflags=subprocess.CREATE_NEW_PROCESS_GROUP
309
- if os.name == "nt"
310
- else 0,
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, stdout_queue, stderr_queue)
263
+ return self._use_tqdm(process)
332
264
  else:
333
- return self._use_rich(process, stdout_queue, stderr_queue)
265
+ return self._use_rich(process)
334
266
  except KeyboardInterrupt:
335
267
  try:
336
268
  psutil.Process(process.pid).terminate()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: better-ffmpeg-progress
3
- Version: 3.2.0
3
+ Version: 3.2.2
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
@@ -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.0",
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.",