fix: Windows环境下进度条因stdout阻塞迟迟未能显示

顺便重构一下`hevc_encode`,这样就“轻”多了。
This commit is contained in:
2026-04-20 13:50:49 +08:00
parent 2013d2e61d
commit ef7965d274
2 changed files with 63 additions and 38 deletions

16
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,16 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Python Debugger: Current File with Arguments",
"type": "debugpy",
"request": "launch",
"program": "${file}",
"console": "integratedTerminal",
"args": "${command:pickArgs}"
}
]
}

View File

@@ -46,70 +46,79 @@ def get_duration(file_path: Path):
raise raise
def read_progress(ostream_line, dur, pbar): def set_progress(line: str, dur, pbar):
if not ostream_line.startswith("out_time_ms="): if not line.startswith("out_time_ms="):
return return
try: try:
us = int(ostream_line.split('=')[1]) us = int(line.split('=')[1])
if us < 0: if us < 0:
return return
secs = us / 1_000_000 current_sec = us / 1_000_000
if secs > dur * 1.1: # Allow 10% overshoot pbar.n = min(current_sec, dur)
pbar.n = dur
else:
pbar.n = secs
pbar.refresh() pbar.refresh()
except (ValueError, OverflowError): except (ValueError, IndexError, OverflowError):
pass return
def start_ffmpeg(infile: Path, outfile: Path):
cmd = [
"ffmpeg", "-y", "-i", str(infile),
"-progress", "pipe:1", "-nostats", "-loglevel", "error",
*COMPRESS_OPTIONS, str(outfile)
]
return subprocess.Popen(
cmd,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
bufsize=1,
text=True,
encoding='utf-8')
def clean_on_failure(proc: subprocess.Popen, outfile: Path):
if proc.poll() is None:
proc.kill()
proc.wait()
if outfile.exists():
try:
outfile.unlink()
except OSError:
pass
def hevc_encode(infile: Path, outfile: Path, progress: bool = True): def hevc_encode(infile: Path, outfile: Path, progress: bool = True):
# too heavy bro.
dur = get_duration(infile) dur = get_duration(infile)
cmd = [
"ffmpeg", "-y", "-i", str(infile),
"-progress", "pipe:1", "-nostats",
*COMPRESS_OPTIONS, str(outfile)
]
logger.debug(f"Duration: {dur:.2f} seconds") logger.debug(f"Duration: {dur:.2f} seconds")
logger.debug(f"Command: {' '.join(cmd)}") proc = start_ffmpeg(infile, outfile)
proc = subprocess.Popen( logger.debug(f"Command: {proc.args}")
cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
text=True, encoding='utf-8', bufsize=1) pbar = get_progress_bar(
progress = progress and is_tty()
pbar = None if not progress else get_progress_bar(
total=dur, unit='s', total=dur, unit='s',
bar_format='{l_bar}{bar}| {n:.2f}/{total:.2f}({unit})') bar_format='{l_bar}{bar}| {n:.2f}/{total:.2f}({unit})'
) if progress and is_tty() else None
try: try:
while True: for line in iter(proc.stdout.readline, ''):
line = proc.stdout.readline() set_progress(line.strip(), dur, pbar)
if not line and proc.poll() is not None: if proc.poll() is not None:
break break
if not pbar:
continue
read_progress(line.strip(), dur, pbar)
proc.wait() proc.wait()
if proc.returncode != 0: if proc.returncode != 0:
raise subprocess.CalledProcessError(proc.returncode, cmd) raise subprocess.CalledProcessError(proc.returncode, "ffmpeg")
except ( except (
KeyboardInterrupt, KeyboardInterrupt,
subprocess.CalledProcessError subprocess.CalledProcessError
) as e: ) as e:
if proc.poll() is None:
proc.kill()
proc.wait()
logger.error( logger.error(
f"Encoding failed with code {proc.returncode}:" f"Encoding failed with code {proc.returncode}:"
f" {e.__class__.__name__}") f" {e.__class__.__name__}")
if outfile.exists(): clean_on_failure(proc, outfile)
try:
outfile.unlink()
except OSError:
pass
raise raise
finally: finally:
if pbar: if pbar:
pbar.close() pbar.close()
proc.stdout.close()
def build_file_list(srcdir, extensions): def build_file_list(srcdir, extensions):