Reduce ffmpeg console output
This commit is contained in:
parent
53b2e5ecca
commit
55ee91cae2
3 changed files with 81 additions and 6 deletions
85
movmaker.py
85
movmaker.py
|
|
@ -35,6 +35,7 @@ BASE_DIR = Path(__file__).resolve().parent
|
|||
FFMPEG = BASE_DIR / "ffmpeg" / "ffmpeg"
|
||||
FFPROBE = BASE_DIR / "ffmpeg" / "ffprobe"
|
||||
PIXABAY_ENV = Path.home() / ".config" / "movmaker" / "pixabay.env"
|
||||
LOG_PATH: Path | None = None
|
||||
|
||||
|
||||
def tool_path(local_path: Path, fallback: str) -> str:
|
||||
|
|
@ -56,9 +57,75 @@ class MediaItem:
|
|||
duration: float
|
||||
|
||||
|
||||
def run(cmd: list[str]) -> None:
|
||||
print("+", shlex.join(cmd))
|
||||
subprocess.run(cmd, check=True)
|
||||
def log_message(message: str) -> None:
|
||||
if LOG_PATH:
|
||||
with LOG_PATH.open("a", encoding="utf-8") as log:
|
||||
log.write(message.rstrip() + "\n")
|
||||
|
||||
|
||||
def ffmpeg_progress_cmd(cmd: list[str], progress: bool) -> list[str]:
|
||||
if not cmd or Path(cmd[0]).name != "ffmpeg":
|
||||
return cmd
|
||||
out = [cmd[0], "-hide_banner", "-nostats"]
|
||||
rest = cmd[1:]
|
||||
if rest and rest[0] == "-y":
|
||||
out.append("-y")
|
||||
rest = rest[1:]
|
||||
if progress:
|
||||
out += ["-progress", "pipe:1"]
|
||||
return out + rest
|
||||
|
||||
|
||||
def format_bytes(value: int) -> str:
|
||||
size = float(max(0, value))
|
||||
for unit in ["B", "KB", "MB", "GB"]:
|
||||
if size < 1024 or unit == "GB":
|
||||
return f"{size:.1f}{unit}"
|
||||
size /= 1024
|
||||
return f"{size:.1f}GB"
|
||||
|
||||
|
||||
def run(cmd: list[str], progress_duration: float | None = None) -> None:
|
||||
progress = progress_duration is not None and progress_duration > 0 and Path(cmd[0]).name == "ffmpeg"
|
||||
run_cmd = ffmpeg_progress_cmd(cmd, progress)
|
||||
log_message("+ " + shlex.join(run_cmd))
|
||||
if not LOG_PATH:
|
||||
subprocess.run(run_cmd, check=True)
|
||||
return
|
||||
|
||||
with LOG_PATH.open("a", encoding="utf-8") as log:
|
||||
if not progress:
|
||||
subprocess.run(run_cmd, stdout=log, stderr=log, check=True)
|
||||
return
|
||||
|
||||
process = subprocess.Popen(run_cmd, stdout=subprocess.PIPE, stderr=log, text=True, bufsize=1)
|
||||
fields: dict[str, str] = {}
|
||||
assert process.stdout is not None
|
||||
for raw_line in process.stdout:
|
||||
line = raw_line.strip()
|
||||
log.write(line + "\n")
|
||||
if "=" not in line:
|
||||
continue
|
||||
key, value = line.split("=", 1)
|
||||
fields[key] = value
|
||||
if key in {"out_time_ms", "total_size", "speed", "fps", "progress"}:
|
||||
try:
|
||||
seconds = int(fields.get("out_time_ms", "0")) / 1_000_000
|
||||
except ValueError:
|
||||
seconds = 0.0
|
||||
try:
|
||||
written = int(fields.get("total_size", "0"))
|
||||
except ValueError:
|
||||
written = 0
|
||||
percent = min(100.0, max(0.0, seconds / progress_duration * 100.0))
|
||||
fps = fields.get("fps", "?")
|
||||
speed = fields.get("speed", "?")
|
||||
sys.stdout.write(f"\rRendering {percent:5.1f}% fps={fps} speed={speed} written={format_bytes(written)}")
|
||||
sys.stdout.flush()
|
||||
rc = process.wait()
|
||||
sys.stdout.write("\n")
|
||||
if rc != 0:
|
||||
raise subprocess.CalledProcessError(rc, run_cmd)
|
||||
|
||||
|
||||
def capture(cmd: list[str]) -> str:
|
||||
|
|
@ -785,7 +852,7 @@ def build_video(
|
|||
cmd += ["-an"]
|
||||
|
||||
cmd += ["-c:v", "libx264", "-preset", preset, "-crf", str(crf), "-r", str(fps), "-pix_fmt", "yuv420p", "-movflags", "+faststart+use_metadata_tags", str(output)]
|
||||
run(cmd)
|
||||
run(cmd, total_duration)
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
|
|
@ -806,6 +873,10 @@ def main(argv: list[str] | None = None) -> int:
|
|||
parser.add_argument("--music-genre", default="cinematic punk rock", help="Pixabay music search query used only when input has no audio files")
|
||||
args = parser.parse_args(argv)
|
||||
|
||||
global LOG_PATH
|
||||
LOG_PATH = args.input / "movmaker.log"
|
||||
LOG_PATH.write_text("movmaker log\n", encoding="utf-8")
|
||||
|
||||
require_tool("ffmpeg")
|
||||
require_tool("ffprobe")
|
||||
|
||||
|
|
@ -852,6 +923,7 @@ def main(argv: list[str] | None = None) -> int:
|
|||
|
||||
print(f"Found {len(media)} media file(s), {len(audio_files)} audio file(s).")
|
||||
print(f"Output: {output}")
|
||||
print(f"Log: {LOG_PATH}")
|
||||
if data.title or data.date or data.place:
|
||||
print("Title data:", " | ".join(x for x in [data.title, data.date, data.place] if x))
|
||||
|
||||
|
|
@ -860,15 +932,16 @@ def main(argv: list[str] | None = None) -> int:
|
|||
jobs = max(1, args.jobs)
|
||||
clips = [tmp / f"clip_{idx:04d}.mp4" for idx in range(len(media))]
|
||||
if jobs == 1:
|
||||
print("Preparing clips...")
|
||||
for item, out in zip(media, clips):
|
||||
print(f"Preparing {item.kind}: {item.path.name}")
|
||||
log_message(f"Preparing {item.kind}: {item.path.name}")
|
||||
normalize_clip(item, out, width, height, fps, preset, crf)
|
||||
else:
|
||||
print(f"Preparing clips with {jobs} parallel jobs")
|
||||
with concurrent.futures.ThreadPoolExecutor(max_workers=jobs) as executor:
|
||||
futures = []
|
||||
for item, out in zip(media, clips):
|
||||
print(f"Preparing {item.kind}: {item.path.name}")
|
||||
log_message(f"Preparing {item.kind}: {item.path.name}")
|
||||
futures.append(executor.submit(normalize_clip, item, out, width, height, fps, preset, crf))
|
||||
for future in concurrent.futures.as_completed(futures):
|
||||
future.result()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue