215 lines
8.8 KiB
Bash
Executable File
215 lines
8.8 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
#
|
|
# v2022-06-13
|
|
#
|
|
# I run it like this (using zsh syntax):
|
|
#
|
|
# $ for video (~/Pictures/2021/**/*.mp4(ND.)); do ~/Downloads/av1-tests/convert-libaom.sh "$video"; done
|
|
#
|
|
# Reference:
|
|
# - https://ffmpeg.org/ffmpeg-codecs.html#libaom_002dav1
|
|
# - https://trac.ffmpeg.org/wiki/Encode/AV1
|
|
#
|
|
# Changes:
|
|
# 2022-06-13: use _max_crf in mkvpropedit if we don't find an acceptable vmaf
|
|
# 2022-04-05: update VMAF log parsing
|
|
# 2021-10-21: I tested 2-pass and found it doesn't do anything for constant
|
|
# quality mode. cpu-used 2 still takes ~5x longer than cpu-used 4
|
|
# at all tiling levels. 4x4 tiles much faster than 2x1 and 2x2 on
|
|
# all cpu-used levels, with quality less than 0.5 VMAF difference.
|
|
# Using 4x4 creates a grid of 16 tiles which just seems wrong...
|
|
# especially if you don't have many threads (I was using 12), so
|
|
# I will default to 2x2 as a compromise.
|
|
# 2021-10-20: use tiles instead of columns and rows
|
|
# 2021-10-16: use tile-columns 2 and tile-rows 2 (oops, this is 4x4 tiles!)
|
|
# 2021-08-31: strip spaces and commas from VMAF log file name as well
|
|
# 2021-05-14: benchmarked two pass and file sizes are larger by .5-1MB
|
|
# 2021-04-29: make sure input file exists
|
|
# 2021-03-07: benchmarked 10-bit and it's only .5 or less improvment to VMAF
|
|
# 2021-02-22: allow overriding threads
|
|
# 2021-02-15: fix handling of apostrophes in ffmpeg's log_path
|
|
# 2021-01-27: round VMAF before comparing, simplifies code a bit
|
|
# 2021-01-25: stop trying lower CRFs if VMAF is not likely to improve
|
|
# 2021-01-19: embed encoding parameters in webm metadata with mkvpropedit
|
|
# 2021-01-18: use 1-pass encoding (2-pass is only for trying to hit a target
|
|
# bitrate!)
|
|
# 2021-01-18: allow overriding variables
|
|
# 2021-01-17: detect if video already processed
|
|
# 2021-01-08: use cpu-used 4
|
|
# 2021-01-07: use tile-columns 2 and tile-rows 2 with cpu-used 4
|
|
# 2021-01-07: use tile-columns 2 and tile-rows 2 with cpu-used 3
|
|
# 2021-01-07: use -g 300 (30fps x 10)
|
|
|
|
# exit on first error
|
|
set -o errexit
|
|
|
|
if [[ -z "$1" ]]; then
|
|
echo "No input file specified."
|
|
|
|
exit 1
|
|
elif [[ ! -r "$1" ]]; then
|
|
echo "Input file missing or unreadable: $1"
|
|
|
|
exit 1
|
|
fi
|
|
|
|
INPUT_FILE_BASENAME=$(basename "$1")
|
|
INPUT_FILE_EXTENSION=${INPUT_FILE_BASENAME##*.}
|
|
INPUT_FILE_DIRNAME=$(dirname "$1")
|
|
|
|
# Check if an output file name was specified (like if we are calling from the
|
|
# benchmarking script, in which case we want to exit as soon as possible). If
|
|
# not then we can continue with a simple filename based on the input file's.
|
|
if [[ -z $_output_file ]]; then
|
|
_output_file="${INPUT_FILE_BASENAME/.*/}.webm";
|
|
_benchmark_mode="false"
|
|
else
|
|
_benchmark_mode="true"
|
|
fi
|
|
|
|
# aq-mode not recommended in AV1 yet
|
|
_aq_mode=${_aq_mode:-0}
|
|
# 2021-01-08 (libaom 2.0.1): realistic range between 3 and 5, lower takes *much*
|
|
# longer with very little boost to VMAF. In my experience, cpu-used 2 is *three*
|
|
# times slower than cpu-used 4 at the same CRF for only a 0.5% boost in VMAF. 6
|
|
# is the same as 5 in all my tests *shrug*.
|
|
_cpu_used=${_cpu_used:-4}
|
|
# 2021-10-20 -tile-columns and -tile-rows are for compatibility with libvpx. The
|
|
# new option for libaom is -tiles. Convert any columns/rows to tiles using 2^n.
|
|
if [[ ! -z $_tile_columns || ! -z $_tile_rows ]]; then
|
|
_tile_columns=${_tile_columns:-1}
|
|
_tile_rows=${_tile_rows:-1}
|
|
|
|
# Compute 2^n tile columns and tile rows (the power operator is ** in bash)
|
|
_tiles="$((2 ** $_tile_columns))x$((2 ** $_tile_rows))"
|
|
else
|
|
_tiles=${_tiles:-2x2}
|
|
fi
|
|
|
|
# 2021-02-22 use nproc number of threads unless threads is already set.
|
|
_nproc=$(nproc)
|
|
_threads=${_threads:-$_nproc}
|
|
|
|
# Range of CRFs to try, where higher is faster, but lower quality. Based on my
|
|
# testing 52 to 40 should cover most crappy phone videos.
|
|
_crf_max=${_crf_max:-52}
|
|
_crf_min=${_crf_min:-40}
|
|
|
|
_libaom_version=$(pacman -Qi aom | grep Version | awk '{print $3}')
|
|
|
|
# For grainy mobile phone videos 90 is fine
|
|
_target_vmaf=${_target_vmaf:-90}
|
|
# Don't bother trying lower CRF values if the CRF score is more than five points
|
|
# above the target. In my experience each successively lower CRF step gives you
|
|
# ~1 VMAF point. In these cases it isn't likely we'll ever reach the target, so
|
|
# we might as well just settle on the current CRF straight away.
|
|
_target_vmaf_threshold=${_target_vmaf_threshold:-5}
|
|
|
|
_acceptable_output_found="no"
|
|
|
|
# Change to the input file's directory
|
|
pushd "$INPUT_FILE_DIRNAME" >/dev/null
|
|
|
|
# We want the highest VMAF score possible with the highest CRF possible. Start
|
|
# with high CRF first to see if we can get an acceptable VMAF score as soon as
|
|
# possible. Step through values by 2.
|
|
for _crf in $(seq $_crf_max -2 $_crf_min); do
|
|
_processed=$(find . -maxdepth 1 -type f -iname "$_output_file" | wc -l)
|
|
if [[ $_processed -gt 0 ]]; then
|
|
echo "${INPUT_FILE_BASENAME}: already processed"
|
|
|
|
exit 0
|
|
fi
|
|
|
|
if [[ $_benchmark_mode == "false" ]]; then
|
|
echo "Processing ${INPUT_FILE_BASENAME} with libaom $_libaom_version CRF ${_crf}..."
|
|
fi
|
|
|
|
chrt -b 0 nice ffmpeg -hide_banner -y -i "$INPUT_FILE_BASENAME" -c:v libaom-av1 -b:v 0 -crf $_crf \
|
|
-aq-mode $_aq_mode -c:a libopus -b:a 16k \
|
|
-sc_threshold 0 -cpu-used $_cpu_used \
|
|
-tiles $_tiles -row-mt 1 \
|
|
-auto-alt-ref 1 -lag-in-frames 25 \
|
|
-g 300 -threads $_threads \
|
|
-f webm "$_output_file" 2>/dev/null
|
|
|
|
# Return quickly if we are in benchmark mode so the benchmark script can get
|
|
# an accurate time and compute its own VMAF score.
|
|
if [[ $_benchmark_mode == "true" ]]; then
|
|
exit 0
|
|
fi
|
|
|
|
_vmaf_log="${_output_file/.*/}.log"
|
|
|
|
# strip apostrophes, spaces, and commas (if any) from log file name because
|
|
# ffmpeg's log_path doesn't seem to be able to handle them. The // performs
|
|
# a global replace.
|
|
_vmaf_log="${_vmaf_log//\'}"
|
|
_vmaf_log="${_vmaf_log//\,}"
|
|
_vmaf_log="${_vmaf_log// }"
|
|
|
|
# Get VMAF score with harmonic mean to emphasize small outliers
|
|
# See: https://netflixtechblog.com/vmaf-the-journey-continues-44b51ee9ed12
|
|
chrt -b 0 nice ffmpeg -hide_banner -y -i "$_output_file" -i "$INPUT_FILE_BASENAME" \
|
|
-filter_complex "libvmaf=pool=harmonic_mean:log_path=${_vmaf_log}:log_fmt=json" \
|
|
-f null - 2>/dev/null
|
|
|
|
_vmaf_score=$(jq '.["pooled_metrics"]["vmaf"]["harmonic_mean"]' "$_vmaf_log")
|
|
# bash can't do floating point so we use bc
|
|
# See: https://stackoverflow.com/questions/8654051/how-to-compare-two-floating-point-numbers-in-bash
|
|
_vmaf_score_round=$(printf %.$2f $(echo "scale=0;(((10^0)*$_vmaf_score)+0.5)/(10^0)" | bc))
|
|
|
|
rm "$_vmaf_log"
|
|
|
|
# Check if rounded VMAF score is >= target VMAF
|
|
if [[ $_vmaf_score_round -ge $_target_vmaf ]]; then
|
|
printf "$INPUT_FILE_BASENAME: acceptable VMAF (%.3f) at AV1 CRF ${_crf}.\n" $_vmaf_score
|
|
|
|
# Set the title with information about the encoding in the MKV title
|
|
mkvpropedit --edit info --set "title=${INPUT_FILE_BASENAME/.*/}-libaom_${_libaom_version}-crf${_crf}-cpu${_cpu_used}-${_tiles}-tiles-vmaf_${_vmaf_score}" "$_output_file" >/dev/null
|
|
|
|
_acceptable_output_found="yes"
|
|
|
|
# Break from the for loop because we have an acceptable output
|
|
break
|
|
# Check if the VMAF score is anywhere near our target, otherwise just keep
|
|
# current output.
|
|
elif (($_target_vmaf - $_vmaf_score_round >= $_target_vmaf_threshold)); then
|
|
printf "$INPUT_FILE_BASENAME: unacceptable VMAF (%.3f) at AV1 CRF ${_crf}, unlikely to reach target soon.\n" $_vmaf_score
|
|
|
|
_acceptable_output_found="yes"
|
|
|
|
# Break from the for loop and keep the current output, even though it's
|
|
# unacceptable.
|
|
break
|
|
else
|
|
printf "$INPUT_FILE_BASENAME: unacceptable VMAF (%.3f) at AV1 CRF ${_crf}, continuing.\n" $_vmaf_score
|
|
|
|
# Clean up the unacceptable output
|
|
rm "$_output_file"
|
|
fi
|
|
done
|
|
|
|
# If we finished going over all the CRFs and still didn't get an acceptable VMAF
|
|
# score then the video is probably just really low quality so let's just convert
|
|
# using our max CRF and be done with it.
|
|
if [[ $_acceptable_output_found == "no" ]]; then
|
|
echo "${INPUT_FILE_BASENAME}: no acceptable output found, settling on AV1 CRF ${_crf_max}."
|
|
|
|
chrt -b 0 nice ffmpeg -hide_banner -y -i "$INPUT_FILE_BASENAME" -c:v libaom-av1 -b:v 0 -crf $_crf_max \
|
|
-aq-mode $_aq_mode -c:a libopus -b:a 16k \
|
|
-sc_threshold 0 -cpu-used $_cpu_used \
|
|
-tiles $_tiles -row-mt 1 \
|
|
-auto-alt-ref 1 -lag-in-frames 25 \
|
|
-g 300 -threads $_threads \
|
|
-f webm "$_output_file" 2>/dev/null
|
|
|
|
# Set the title with information about the encoding in the MKV title
|
|
mkvpropedit --edit info --set "title=${INPUT_FILE_BASENAME/.*/}-libaom_${_libaom_version}-crf${_max_crf}-cpu${_cpu_used}-${_tiles}-tiles" "$_output_file" >/dev/null
|
|
fi
|
|
|
|
# Change back to our starting directory
|
|
popd >/dev/null
|
|
|
|
exit 0
|