110 lines
3.5 KiB
Python
110 lines
3.5 KiB
Python
import argparse
|
|
import os
|
|
import pathlib
|
|
import shlex
|
|
import subprocess
|
|
|
|
import yaml
|
|
|
|
# Hannes' AV1 wrapper script
|
|
# Version 2.0.1
|
|
|
|
# This is a good guide for optimizing AV1 parameters with the AOM reference encoder:
|
|
# https://www.reddit.com/r/AV1/comments/t59j32/encoder_tuning_part_4_a_2nd_generation_guide_to/
|
|
|
|
parser = argparse.ArgumentParser(prog="hav1w")
|
|
parser.add_argument("input")
|
|
parser.add_argument("output")
|
|
parser.add_argument("preset")
|
|
parser.add_argument("--crf")
|
|
parser.add_argument("--ba")
|
|
parser.add_argument("--alayout")
|
|
parser.add_argument("--cpu")
|
|
parser.add_argument("--mf")
|
|
parser.add_argument("--dnl")
|
|
parser.add_argument("--arnr-strength")
|
|
parser.add_argument("--crop")
|
|
parser.add_argument("--no10bit", action="store_true")
|
|
parser.add_argument("--dry", action="store_true")
|
|
args = parser.parse_args()
|
|
|
|
preset_path = os.path.join(pathlib.Path(__file__).parent.resolve(), "av1_presets.yml")
|
|
with open(preset_path, "r") as stream:
|
|
presets = yaml.safe_load(stream)
|
|
preset = presets[args.preset]
|
|
|
|
if args.crf is not None:
|
|
preset["crf"] = args.crf
|
|
if args.ba is not None:
|
|
preset["ba"] = args.ba
|
|
if args.alayout is not None:
|
|
preset["alayout"] = args.alayout
|
|
if args.cpu is not None:
|
|
preset["cpu"] = args.cpu
|
|
if args.mf is not None:
|
|
preset["mf"] = args.mf
|
|
if args.dnl is not None:
|
|
preset["dnl"] = args.dnl
|
|
if args.arnr_strength is not None:
|
|
preset["arnr_strength"] = args.arnr_strength
|
|
|
|
cmd = ["ffmpeg", "-i", args.input, "-c:v", "libaom-av1", "-c:a", "libopus", "-c:s", "copy", "-map", "0"]
|
|
|
|
# AV1 Settings
|
|
|
|
# Constant rate factor, determines target quality
|
|
cmd += ["-crf", preset["crf"], "-b:v", "0"]
|
|
|
|
# Set minimum keyframe interval to 12 to prevent placing too many keyframes... just in case
|
|
cmd += ["-keyint_min", "12"]
|
|
|
|
# Set lookahead to 48 frames
|
|
# Reddit post says it's worth it...
|
|
cmd += ["-lag-in-frames", "48"]
|
|
|
|
# Temporal filtering parameters
|
|
# Set arnr_strength to 0 for animation and low variance stuff, 1 for content with more variance and fast motion scenes
|
|
cmd += ["-arnr-strength", preset["arnr_strength"], "-arnr-max-frames", "3"]
|
|
|
|
# Disable deltaq-mode for higher fidelity
|
|
aom_params = ["deltaq-mode=0"]
|
|
|
|
# Enable quantization matrices
|
|
# The Reddit post told me to do so, no matter what :P
|
|
aom_params += ["enable-qm=1"]
|
|
|
|
# Grain synthesis
|
|
# Set to -1 for disabling grain synthesis: makes sense for animation content (animes, ...) as
|
|
# a) this is not a strength of the aom encoder
|
|
# b) animation content usually does not have a lot of noise
|
|
#
|
|
# If you need to do grain synthesis on animation content, maybe SVT-AV1 is a better choice?
|
|
# And read this: https://www.reddit.com/r/AV1/comments/n4si96/encoder_tuning_part_3_av1_grain_synthesis_how_it/
|
|
# TEMPORARILY DISABLED DUE TO https://bugs.chromium.org/p/aomedia/issues/detail?id=2768
|
|
preset["dnl"] = "-1"
|
|
if preset["dnl"] != "-1":
|
|
aom_params += ["enable-dnl-denoising=0", "denoise-noise-level=" + preset["dnl"]]
|
|
|
|
cmd += ["-aom-params", ":".join(aom_params)]
|
|
|
|
if not args.no10bit: # Encode to 10 bit per default
|
|
cmd += ["-pix_fmt", "yuv420p10le"]
|
|
|
|
if args.crop is not None:
|
|
cmd += ["-vf", "crop=" + args.crop]
|
|
|
|
# Encoding efficiency settings (tiles also affect decoding performance)
|
|
cmd += ["-cpu-used", preset["cpu"], "-row-mt", "1", "-tiles", "2x2"]
|
|
|
|
# Audio/Opus settings
|
|
cmd += ["-b:a", preset["ba"], "-af", f"aformat=channel_layouts={preset['alayout']}", "-mapping_family", preset["mf"]]
|
|
|
|
cmd += [args.output]
|
|
|
|
if args.dry:
|
|
print(" ".join(list(map(lambda x:shlex.quote(x), cmd))))
|
|
exit(0)
|
|
|
|
p = subprocess.run(cmd)
|
|
exit(p.returncode)
|