diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..e0a93b9 --- /dev/null +++ b/.vscode/launch.json @@ -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}" + } + ] +} \ No newline at end of file diff --git a/cmps_hevc_crf18.py b/cmps_hevc_crf18.py index 9ca597b..205372f 100755 --- a/cmps_hevc_crf18.py +++ b/cmps_hevc_crf18.py @@ -46,70 +46,79 @@ def get_duration(file_path: Path): raise -def read_progress(ostream_line, dur, pbar): - if not ostream_line.startswith("out_time_ms="): +def set_progress(line: str, dur, pbar): + if not line.startswith("out_time_ms="): return try: - us = int(ostream_line.split('=')[1]) + us = int(line.split('=')[1]) if us < 0: return - secs = us / 1_000_000 - if secs > dur * 1.1: # Allow 10% overshoot - pbar.n = dur - else: - pbar.n = secs + current_sec = us / 1_000_000 + pbar.n = min(current_sec, dur) pbar.refresh() - except (ValueError, OverflowError): - pass + except (ValueError, IndexError, OverflowError): + 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): - # too heavy bro. 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"Command: {' '.join(cmd)}") - proc = subprocess.Popen( - cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, - text=True, encoding='utf-8', bufsize=1) - progress = progress and is_tty() - pbar = None if not progress else get_progress_bar( + proc = start_ffmpeg(infile, outfile) + logger.debug(f"Command: {proc.args}") + + pbar = get_progress_bar( 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: - while True: - line = proc.stdout.readline() - if not line and proc.poll() is not None: + for line in iter(proc.stdout.readline, ''): + set_progress(line.strip(), dur, pbar) + if proc.poll() is not None: break - if not pbar: - continue - read_progress(line.strip(), dur, pbar) + proc.wait() if proc.returncode != 0: - raise subprocess.CalledProcessError(proc.returncode, cmd) + raise subprocess.CalledProcessError(proc.returncode, "ffmpeg") except ( KeyboardInterrupt, subprocess.CalledProcessError ) as e: - if proc.poll() is None: - proc.kill() - proc.wait() logger.error( f"Encoding failed with code {proc.returncode}:" f" {e.__class__.__name__}") - if outfile.exists(): - try: - outfile.unlink() - except OSError: - pass + clean_on_failure(proc, outfile) raise finally: if pbar: pbar.close() + proc.stdout.close() def build_file_list(srcdir, extensions):