mirror of
https://github.com/vondas-network/videobeaux.git
synced 2025-12-05 15:30:02 +01:00
added new files, updated gitignore
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -4,7 +4,7 @@ outputs/
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
Examples/
|
Examples/
|
||||||
env/
|
env/
|
||||||
|
ideas/
|
||||||
|
|
||||||
# Byte-compiled / optimized / DLL files
|
# Byte-compiled / optimized / DLL files
|
||||||
__pycache__/
|
__pycache__/
|
||||||
|
|||||||
315
experimental/convertersink.py
Normal file
315
experimental/convertersink.py
Normal file
@@ -0,0 +1,315 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import argparse, os, shlex, subprocess, sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# ------------------------------
|
||||||
|
# Helpers
|
||||||
|
# ------------------------------
|
||||||
|
def run(cmd):
|
||||||
|
print(">>", " ".join(shlex.quote(c) for c in cmd))
|
||||||
|
proc = subprocess.run(cmd)
|
||||||
|
if proc.returncode != 0:
|
||||||
|
sys.exit(proc.returncode)
|
||||||
|
|
||||||
|
def ext_lower(path):
|
||||||
|
return Path(path).suffix.lower().lstrip(".")
|
||||||
|
|
||||||
|
def guess_container(output_path, fmt_override=None):
|
||||||
|
if fmt_override:
|
||||||
|
return fmt_override.lower()
|
||||||
|
ext = ext_lower(output_path)
|
||||||
|
# Map common aliases/extensions to FFmpeg muxer names
|
||||||
|
alias = {
|
||||||
|
"mp4":"mp4", "m4v":"mp4", "mov":"mov", "qt":"mov", "mkv":"matroska",
|
||||||
|
"webm":"webm", "avi":"avi", "wmv":"asf", "asf":"asf",
|
||||||
|
"flv":"flv", "ts":"mpegts", "m2ts":"mpegts", "mpeg":"mpeg", "mpg":"mpeg",
|
||||||
|
"mxf":"mxf", "wav":"wav", "mp3":"mp3", "m4a":"ipod", "aac":"adts", "ogg":"ogg",
|
||||||
|
"oga":"ogg", "opus":"ogg", "flac":"flac", "alac":"ipod",
|
||||||
|
"gif":"gif",
|
||||||
|
# image sequences
|
||||||
|
"jpg":"image2", "jpeg":"image2", "png":"image2", "tif":"image2", "tiff":"image2", "bmp":"image2", "exr":"image2"
|
||||||
|
}
|
||||||
|
return alias.get(ext, ext or "mp4")
|
||||||
|
|
||||||
|
def is_audio_only(fmt):
|
||||||
|
return fmt in {"mp3","ipod","adts","ogg","flac","wav"}
|
||||||
|
|
||||||
|
def default_codecs_for_container(mux):
|
||||||
|
"""
|
||||||
|
Conservative, broadly compatible defaults.
|
||||||
|
Overridden by profiles or explicit --vcodec/--acodec.
|
||||||
|
"""
|
||||||
|
if mux in ("mp4", "mov", "matroska"):
|
||||||
|
return ("libx264", "aac")
|
||||||
|
if mux == "webm":
|
||||||
|
return ("libvpx-vp9", "libopus")
|
||||||
|
if mux == "gif":
|
||||||
|
return (None, None) # handled via palette method
|
||||||
|
if mux in ("mp3","adts","ogg","flac","wav","ipod"):
|
||||||
|
return (None, None) # audio-only handled separately
|
||||||
|
if mux == "mxf":
|
||||||
|
# Default to XDCAM HD 50 unless profile overrides
|
||||||
|
return ("mpeg2video", "pcm_s16le")
|
||||||
|
if mux == "mpegts":
|
||||||
|
return ("libx264", "aac")
|
||||||
|
if mux == "avi":
|
||||||
|
# Legacy default: MPEG-4 ASP + MP3
|
||||||
|
return ("mpeg4", "mp3")
|
||||||
|
return ("libx264", "aac")
|
||||||
|
|
||||||
|
# ------------------------------
|
||||||
|
# Profiles (curated, battle-tested)
|
||||||
|
# ------------------------------
|
||||||
|
PROFILES = {
|
||||||
|
# Web/General delivery
|
||||||
|
"mp4_h264": lambda: [
|
||||||
|
"-c:v","libx264","-preset","veryfast","-crf","18",
|
||||||
|
"-pix_fmt","yuv420p",
|
||||||
|
"-movflags","+faststart",
|
||||||
|
"-c:a","aac","-b:a","192k","-ac","2"
|
||||||
|
],
|
||||||
|
"mp4_hevc": lambda: [
|
||||||
|
"-c:v","libx265","-preset","medium","-crf","22",
|
||||||
|
"-tag:v","hvc1",
|
||||||
|
"-pix_fmt","yuv420p",
|
||||||
|
"-movflags","+faststart",
|
||||||
|
"-c:a","aac","-b:a","192k","-ac","2"
|
||||||
|
],
|
||||||
|
"mp4_av1": lambda: [
|
||||||
|
"-c:v","libaom-av1","-crf","28","-b:v","0",
|
||||||
|
"-pix_fmt","yuv420p",
|
||||||
|
"-movflags","+faststart",
|
||||||
|
"-c:a","aac","-b:a","192k","-ac","2"
|
||||||
|
],
|
||||||
|
|
||||||
|
# WebM
|
||||||
|
"webm_vp9": lambda: [
|
||||||
|
"-c:v","libvpx-vp9","-b:v","0","-crf","30",
|
||||||
|
"-row-mt","1",
|
||||||
|
"-pix_fmt","yuv420p",
|
||||||
|
"-c:a","libopus","-b:a","160k","-ac","2"
|
||||||
|
],
|
||||||
|
"webm_av1": lambda: [
|
||||||
|
"-c:v","libaom-av1","-crf","32","-b:v","0",
|
||||||
|
"-pix_fmt","yuv420p",
|
||||||
|
"-c:a","libopus","-b:a","160k","-ac","2"
|
||||||
|
],
|
||||||
|
|
||||||
|
# Professional mezzanine
|
||||||
|
"prores_422": lambda: [
|
||||||
|
"-c:v","prores_ks","-profile:v","2",
|
||||||
|
"-pix_fmt","yuv422p10le",
|
||||||
|
"-c:a","pcm_s16le"
|
||||||
|
],
|
||||||
|
"prores_4444": lambda: [
|
||||||
|
"-c:v","prores_ks","-profile:v","4",
|
||||||
|
"-pix_fmt","yuva444p10le",
|
||||||
|
"-c:a","pcm_s24le"
|
||||||
|
],
|
||||||
|
"dnxhr_hq": lambda: [
|
||||||
|
"-c:v","dnxhd","-profile:v","dnxhr_hq",
|
||||||
|
"-pix_fmt","yuv422p",
|
||||||
|
"-c:a","pcm_s16le"
|
||||||
|
],
|
||||||
|
|
||||||
|
# Broadcast MXF (OP1a)
|
||||||
|
"mxf_xdcamhd50_1080i59": lambda: [
|
||||||
|
"-c:v","mpeg2video","-b:v","50M","-minrate","50M","-maxrate","50M","-bufsize","17825792",
|
||||||
|
"-r","30000/1001","-flags","+ildct+ilme","-top","1",
|
||||||
|
"-pix_fmt","yuv422p",
|
||||||
|
"-c:a","pcm_s24le","-ar","48000","-ac","2",
|
||||||
|
"-f","mxf"
|
||||||
|
],
|
||||||
|
|
||||||
|
# Archival lossless
|
||||||
|
"lossless_ffv1": lambda: [
|
||||||
|
"-c:v","ffv1","-level","3","-g","1","-slicecrc","1",
|
||||||
|
"-c:a","pcm_s24le"
|
||||||
|
],
|
||||||
|
|
||||||
|
# GIF
|
||||||
|
"gif": lambda: ["-filter_complex","[0:v]fps=15,scale=iw:-2:flags=lanczos"],
|
||||||
|
|
||||||
|
# Image sequences
|
||||||
|
"png_seq": lambda: ["-c:v","png"],
|
||||||
|
"jpg_seq": lambda: ["-qscale:v","2"],
|
||||||
|
|
||||||
|
# Audio-only
|
||||||
|
"mp3_320": lambda: ["-vn","-c:a","libmp3lame","-b:a","320k"],
|
||||||
|
"aac_192": lambda: ["-vn","-c:a","aac","-b:a","192k"],
|
||||||
|
"flac": lambda: ["-vn","-c:a","flac"],
|
||||||
|
|
||||||
|
# --- NEW: AVI speed-focused presets ---
|
||||||
|
# Very fast encode, big files; great for quick turnarounds or NLE ingest
|
||||||
|
"avi_mjpeg_fast": lambda: ["-c:v","mjpeg","-q:v","3","-c:a","pcm_s16le"],
|
||||||
|
# Legacy-compatible, faster-than-default MPEG-4 ASP
|
||||||
|
"avi_mpeg4_fast": lambda: ["-c:v","mpeg4","-qscale:v","3","-bf","0","-mbd","0","-c:a","mp3","-b:a","192k"]
|
||||||
|
}
|
||||||
|
|
||||||
|
# ------------------------------
|
||||||
|
# Command builder
|
||||||
|
# ------------------------------
|
||||||
|
def build_ffmpeg_command(args):
|
||||||
|
in_path = args.input
|
||||||
|
out_path = args.output
|
||||||
|
|
||||||
|
if not out_path:
|
||||||
|
base = str(Path(in_path).with_suffix(""))
|
||||||
|
ext = f".{args.format}" if args.format else ".mp4"
|
||||||
|
out_path = base + ext
|
||||||
|
|
||||||
|
mux = guess_container(out_path, args.format)
|
||||||
|
common = ["ffmpeg", "-hide_banner", "-y" if args.force else "-n", "-i", in_path]
|
||||||
|
|
||||||
|
# Stream copy?
|
||||||
|
if args.copy:
|
||||||
|
return common + ["-c","copy", out_path]
|
||||||
|
|
||||||
|
# Explicit codecs (can be overridden by profile if repeated later)
|
||||||
|
vcodec = args.vcodec
|
||||||
|
acodec = args.acodec
|
||||||
|
|
||||||
|
# Apply profile if chosen
|
||||||
|
profile_args = []
|
||||||
|
if args.profile:
|
||||||
|
prof = PROFILES.get(args.profile)
|
||||||
|
if not prof:
|
||||||
|
sys.exit(f"Unknown profile: {args.profile}")
|
||||||
|
profile_args = prof()
|
||||||
|
|
||||||
|
# If no explicit profile and no explicit codecs, choose container defaults
|
||||||
|
if not args.profile:
|
||||||
|
dv, da = default_codecs_for_container(mux)
|
||||||
|
if not vcodec and dv: vcodec = dv
|
||||||
|
if not acodec and da: acodec = da
|
||||||
|
|
||||||
|
cmd = list(common)
|
||||||
|
|
||||||
|
# Special path: GIF palette method
|
||||||
|
if mux == "gif" or args.profile == "gif":
|
||||||
|
vf = []
|
||||||
|
if args.vf: vf.append(args.vf)
|
||||||
|
else: vf.append("fps=15,scale=iw:-2:flags=lanczos")
|
||||||
|
palette_chain = f"[0:v]{','.join(vf)},palettegen[p];[0:v]{','.join(vf)}[v];[v][p]paletteuse"
|
||||||
|
gif_cmd = list(common) + ["-filter_complex", palette_chain, "-gifflags", "+transdiff", out_path]
|
||||||
|
return gif_cmd
|
||||||
|
|
||||||
|
# Video settings
|
||||||
|
if vcodec:
|
||||||
|
cmd += ["-c:v", vcodec]
|
||||||
|
if args.crf is not None:
|
||||||
|
cmd += ["-crf", str(args.crf)]
|
||||||
|
if args.bitrate:
|
||||||
|
cmd += ["-b:v", args.bitrate]
|
||||||
|
if args.maxrate:
|
||||||
|
cmd += ["-maxrate", args.maxrate]
|
||||||
|
if args.bufsize:
|
||||||
|
cmd += ["-bufsize", args.bufsize]
|
||||||
|
if args.preset:
|
||||||
|
cmd += ["-preset", args.preset]
|
||||||
|
if args.profile_v:
|
||||||
|
cmd += ["-profile:v", args.profile_v]
|
||||||
|
if args.level:
|
||||||
|
cmd += ["-level", args.level]
|
||||||
|
if args.pix_fmt:
|
||||||
|
cmd += ["-pix_fmt", args.pix_fmt]
|
||||||
|
if args.gop:
|
||||||
|
cmd += ["-g", str(args.gop)]
|
||||||
|
if args.r:
|
||||||
|
cmd += ["-r", args.r]
|
||||||
|
if args.vf:
|
||||||
|
cmd += ["-vf", args.vf]
|
||||||
|
if args.tagv:
|
||||||
|
cmd += ["-tag:v", args.tagv]
|
||||||
|
|
||||||
|
# Audio settings
|
||||||
|
if acodec:
|
||||||
|
cmd += ["-c:a", acodec]
|
||||||
|
if args.abitrate:
|
||||||
|
cmd += ["-b:a", args.abitrate]
|
||||||
|
if args.ac:
|
||||||
|
cmd += ["-ac", str(args.ac)]
|
||||||
|
if args.ar:
|
||||||
|
cmd += ["-ar", args.ar]
|
||||||
|
|
||||||
|
# Container niceties
|
||||||
|
if mux == "mp4" and "+faststart" not in " ".join(profile_args):
|
||||||
|
cmd += ["-movflags", "+faststart"]
|
||||||
|
|
||||||
|
# Append profile args last so they can set container-specific flags
|
||||||
|
if profile_args:
|
||||||
|
cmd += profile_args
|
||||||
|
|
||||||
|
# --- NEW: Sensible AVI defaults when user didn't specify quality flags or a profile ---
|
||||||
|
if mux == "avi" and not args.profile:
|
||||||
|
# If using MPEG-4 ASP and no quality flags were set, favor speed with decent visual quality
|
||||||
|
have_quality_flag = any(x in (args.crf, args.bitrate, args.maxrate, args.bufsize) for x in [args.crf, args.bitrate, args.maxrate, args.bufsize])
|
||||||
|
if (vcodec or "").lower() == "mpeg4" and not have_quality_flag:
|
||||||
|
# Avoid B-frames for speed; use qscale for constant quality
|
||||||
|
cmd += ["-qscale:v","3","-bf","0"]
|
||||||
|
# If user picked MJPEG explicitly without profile and no quality, set q:v
|
||||||
|
if (vcodec or "").lower() == "mjpeg" and not have_quality_flag:
|
||||||
|
cmd += ["-q:v","3"]
|
||||||
|
# Reasonable audio default if none provided
|
||||||
|
if not acodec:
|
||||||
|
cmd += ["-c:a","mp3","-b:a","192k"]
|
||||||
|
|
||||||
|
# Raw passthrough flags after '--'
|
||||||
|
if args.ffmpeg_args:
|
||||||
|
cmd += args.ffmpeg_args
|
||||||
|
|
||||||
|
cmd += [out_path]
|
||||||
|
return cmd
|
||||||
|
|
||||||
|
# ------------------------------
|
||||||
|
# CLI
|
||||||
|
# ------------------------------
|
||||||
|
def parse_args():
|
||||||
|
p = argparse.ArgumentParser(
|
||||||
|
description="Black-box FFmpeg converter: any format in → any format out.",
|
||||||
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter
|
||||||
|
)
|
||||||
|
# Globals / required
|
||||||
|
p.add_argument("-i","--input", required=True, help="Input media file")
|
||||||
|
p.add_argument("-o","--output", help="Output path (extension chooses container unless --format)")
|
||||||
|
p.add_argument("-F","--force", action="store_true", help="Overwrite output file")
|
||||||
|
|
||||||
|
# Container/format control
|
||||||
|
p.add_argument("--format", help="Force container/muxer (e.g., mp4, mov, webm, matroska, mxf, gif, image2, avi)")
|
||||||
|
p.add_argument("--profile", choices=sorted(PROFILES.keys()), help="Apply a curated preset")
|
||||||
|
|
||||||
|
# Codecs & quality
|
||||||
|
p.add_argument("--vcodec", help="Video codec (e.g., libx264, libx265, libaom-av1, prores_ks, dnxhd, mpeg2video, mpeg4, mjpeg)")
|
||||||
|
p.add_argument("--acodec", help="Audio codec (e.g., aac, libopus, libmp3lame, mp3, pcm_s16le)")
|
||||||
|
p.add_argument("--crf", type=int, help="Constant Rate Factor (quality target for many codecs)")
|
||||||
|
p.add_argument("--bitrate", help="Video bitrate (e.g., 5M)")
|
||||||
|
p.add_argument("--maxrate", help="Video maxrate")
|
||||||
|
p.add_argument("--bufsize", help="Video VBV buffer size")
|
||||||
|
p.add_argument("--preset", help="Codec speed/efficiency preset (varies by codec)")
|
||||||
|
p.add_argument("--profile-v", dest="profile_v", help="Video codec profile (e.g., high, main, baseline; or prores profile index)")
|
||||||
|
p.add_argument("--level", help="Video level (e.g., 4.1)")
|
||||||
|
p.add_argument("--pix-fmt", dest="pix_fmt", help="Pixel format (e.g., yuv420p, yuv422p10le, yuva444p10le)")
|
||||||
|
p.add_argument("--gop", type=int, help="GOP/keyframe interval (frames)")
|
||||||
|
p.add_argument("-r", help="Output frame rate (e.g., 30000/1001, 25, 24)")
|
||||||
|
p.add_argument("--vf", help="Video filtergraph")
|
||||||
|
p.add_argument("--tagv", help="Force video fourcc/tag (e.g., hvc1)")
|
||||||
|
|
||||||
|
# Audio
|
||||||
|
p.add_argument("--abitrate", help="Audio bitrate (e.g., 192k)")
|
||||||
|
p.add_argument("--ac", type=int, help="Audio channels")
|
||||||
|
p.add_argument("--ar", help="Audio sample rate (e.g., 48000)")
|
||||||
|
|
||||||
|
# Stream copy
|
||||||
|
p.add_argument("--copy", action="store_true", help="Stream copy all streams when compatible (no re-encode)")
|
||||||
|
|
||||||
|
# Pass-through extra ffmpeg args after --
|
||||||
|
p.add_argument("ffmpeg_args", nargs=argparse.REMAINDER, help="Raw args after -- go straight to ffmpeg")
|
||||||
|
return p.parse_args()
|
||||||
|
|
||||||
|
def main():
|
||||||
|
args = parse_args()
|
||||||
|
cmd = build_ffmpeg_command(args)
|
||||||
|
run(cmd)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
87
experimental/convertersink.sh
Normal file
87
experimental/convertersink.sh
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# run_examples.sh
|
||||||
|
# Batch conversions using convertersink.py, pausing 2s between each job.
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# General H.264 MP4 (web-friendly)
|
||||||
|
python3 convertersink.py -i bbb.mov -o out.mp4 --profile mp4_h264 -F
|
||||||
|
sleep 2
|
||||||
|
|
||||||
|
# HEVC in MP4 (Apple-friendly tag)
|
||||||
|
# python3 convertersink.py -i bbb.mkv -o out_hevc.mp4 --profile mp4_hevc -F
|
||||||
|
sleep 2
|
||||||
|
|
||||||
|
# AV1 in MP4 (tiny files, slow encode)
|
||||||
|
python3 convertersink.py -i bbb.mov -o out_av1.mp4 --profile mp4_av1 -F
|
||||||
|
sleep 2
|
||||||
|
|
||||||
|
# WebM VP9 + Opus
|
||||||
|
python3 convertersink.py -i bbb.mp4 -o out.webm --profile webm_vp9 -F
|
||||||
|
sleep 2
|
||||||
|
|
||||||
|
# WebM AV1 + Opus
|
||||||
|
python3 convertersink.py -i bbb.mp4 -o out_av1.webm --profile webm_av1 -F
|
||||||
|
sleep 2
|
||||||
|
|
||||||
|
# ProRes 422 in MOV (mezzanine)
|
||||||
|
python3 convertersink.py -i bbb.mp4 -o out_prores.mov --profile prores_422 -F
|
||||||
|
sleep 2
|
||||||
|
|
||||||
|
# ProRes 4444 in MOV (alpha support)
|
||||||
|
python3 convertersink.py -i in_prores4444_src.mov -o out_4444.mov --profile prores_4444 -F
|
||||||
|
sleep 2
|
||||||
|
|
||||||
|
# DNxHR HQ in MOV
|
||||||
|
python3 convertersink.py -i bbb.mp4 -o out_dnxhr.mov --profile dnxhr_hq -F
|
||||||
|
sleep 2
|
||||||
|
|
||||||
|
# MXF XDCAM HD 50 (1080i59.94 broadcast)
|
||||||
|
python3 convertersink.py -i bbb.mov -o out_xdcamhd50.mxf --profile mxf_xdcamhd50_1080i59 -F
|
||||||
|
sleep 2
|
||||||
|
|
||||||
|
# Lossless archival (FFV1 in MKV)
|
||||||
|
python3 convertersink.py -i bbb.mov -o archive.mkv --profile lossless_ffv1 -F
|
||||||
|
sleep 2
|
||||||
|
|
||||||
|
# High-quality GIF
|
||||||
|
python3 convertersink.py -i bbb.mp4 -o out.gif --profile gif -F
|
||||||
|
sleep 2
|
||||||
|
|
||||||
|
# Image sequence (PNG)
|
||||||
|
python3 convertersink.py -i bbb.mp4 -o frames_%05d.png --format image2 --profile png_seq -F
|
||||||
|
sleep 2
|
||||||
|
|
||||||
|
# Image sequence (JPEG, quality ~2)
|
||||||
|
python3 convertersink.py -i bbb.mp4 -o frames_%05d.jpg --format image2 --profile jpg_seq -F
|
||||||
|
sleep 2
|
||||||
|
|
||||||
|
# Audio-only (MP3 320k)
|
||||||
|
python3 convertersink.py -i bbb.mov -o track.mp3 --profile mp3_320 -F
|
||||||
|
sleep 2
|
||||||
|
|
||||||
|
# Audio-only (AAC 192k in M4A)
|
||||||
|
python3 convertersink.py -i bbb.mov -o track.m4a --profile aac_192 -F
|
||||||
|
sleep 2
|
||||||
|
|
||||||
|
# Quick stream-copy (no re-encode) when compatible
|
||||||
|
python3 convertersink.py -i bbb.mp4 -o just_rewrap.mkv --copy -F
|
||||||
|
sleep 2
|
||||||
|
|
||||||
|
# Custom overrides (manual control)
|
||||||
|
python3 convertersink.py -i bbb.mov -o out_custom.mp4 --vcodec libx264 --crf 20 --preset medium \
|
||||||
|
--pix-fmt yuv420p --acodec aac --abitrate 160k --ac 2 --vf "scale=1280:-2" -F
|
||||||
|
sleep 2
|
||||||
|
|
||||||
|
# Pass raw ffmpeg flags after `--`
|
||||||
|
python3 convertersink.py -i bbb.mov -o out_muxqueue.mp4 --profile mp4_h264 -- --max_muxing_queue_size 9999
|
||||||
|
sleep 2
|
||||||
|
|
||||||
|
# --- New fast AVI presets ---
|
||||||
|
# Fast AVI via MJPEG (very fast encode; large output)
|
||||||
|
python3 convertersink.py -i bbb.mp4 -o fast_review.avi --profile avi_mjpeg_fast -F
|
||||||
|
sleep 2
|
||||||
|
|
||||||
|
# Faster legacy AVI via MPEG-4 ASP
|
||||||
|
python3 convertersink.py -i bbb.mp4 -o legacy_fast.avi --profile avi_mpeg4_fast -F
|
||||||
|
sleep 2
|
||||||
Reference in New Issue
Block a user