Compare commits

...
2 Commits
Author SHA1 Message Date
levogevo b1afb83db4 update efg to use print_opt_map 2026-05-31 16:40:27 -05:00
levogevo 4e1aac1284 generate encode usage programatically 2026-05-31 12:20:49 -05:00
4 changed files with 288 additions and 169 deletions
+12
View File
@@ -0,0 +1,12 @@
# EditorConfig is awesome: https://EditorConfig.org
# top-most EditorConfig file
root = true
[*]
indent_style = space
indent_size = 4
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = false
insert_final_newline = false
+84
View File
@@ -568,3 +568,87 @@ check_for_supmover() {
SUPMOVER="${LOCAL_PREFIX}/bin/supmover" SUPMOVER="${LOCAL_PREFIX}/bin/supmover"
test -f "${SUPMOVER}" || do_build supmover || return 1 test -f "${SUPMOVER}" || do_build supmover || return 1
} }
repeat_character() {
local char="$1"
local count="$2"
local spaces
printf -v spaces "%${count}s" ''
echo "${spaces// /${char}}"
}
# read given varnames to set:
# - index array
# - short option array
# - long option array
# - option description array with
# padding based off of longest option
parse_opt_map() {
local optIndexVarname="$1"
local shortOptVarname="$2"
local longOptVarname="$3"
local descOptVarname="$4"
shift 4
local optMap=("$@")
declare -n optIndex="${optIndexVarname}"
declare -n shortOpt="${shortOptVarname}"
declare -n longOpt="${longOptVarname}"
declare -n descOpt="${descOptVarname}"
local opt index short long desc
local longestOpt=0
for opt in "${optMap[@]}"; do
read -r short long desc <<<"${opt}"
# check for duplicate/overwrites
if line_contains "${shortOpt[*]}" "${short}"; then
echo_fail "${short} already defined"
return 1
fi
if line_contains "${longOpt[*]}" "${long}"; then
echo_fail "${long} already defined"
return 1
fi
# --flag -> flag
index="${long//--/}"
optIndex+=("${index}")
shortOpt["${index}"]="${short}"
longOpt["${index}"]="${long}"
descOpt["${index}"]="${desc}"
# track longest longOpt to pad the description
if [[ ${longestOpt} -lt ${#long} ]]; then
longestOpt=${#long}
fi
done
local longLen
local tab=$'\t\t'
for ind in "${optIndex[@]}"; do
long="${longOpt[${ind}]}"
longLen=${#long}
padding="$(repeat_character ' ' $((longestOpt - longLen)))"
descOpt["${ind}"]="${padding}${tab}${descOpt[${ind}]}"
done
}
print_opt_map() {
local optionMap=("$@")
local OPT_INDS
declare -A SHORT_OPTS LONG_OPTS DESC_OPTS
parse_opt_map \
OPT_INDS \
SHORT_OPTS \
LONG_OPTS \
DESC_OPTS \
"${optionMap[@]}" || return 1
echo -e "\nOPTIONS:"
for ind in "${OPT_INDS[@]}"; do
echo -e " ${SHORT_OPTS[${ind}]}, ${LONG_OPTS[${ind}]}${DESC_OPTS[${ind}]}"
done
echo
}
+80 -61
View File
@@ -2,99 +2,114 @@
efg_usage() { efg_usage() {
echo "efg -i input [options]" echo "efg -i input [options]"
echo -e "\t[-P NUM] set preset (default: ${PRESET})" print_opt_map "${EFG_OPT_MAP[@]}" || return 1
echo -e "\t[-l NUM] low value (default: ${LOW})"
echo -e "\t[-s NUM] step value (default: ${STEP})"
echo -e "\t[-h NUM] high value (default: ${HIGH})"
echo -e "\t[-p] plot bitrates using gnuplot"
echo -e "\n\t[-I] system install at ${EFG_INSTALL_PATH}"
echo -e "\t[-U] uninstall from ${EFG_INSTALL_PATH}"
return 0 return 0
} }
set_efg_opts() { set_efg_opts() {
local opts='P:pl:s:h:i:IU'
local numOpts=${#opts}
# default values # default values
unset INPUT
PRESET=10 PRESET=10
LOW=0 LOW=0
STEP=1 STEP=1
HIGH=30 HIGH=30
PLOT=false PLOT=false
ENCODE_SEGMENTS=5
EFG_INSTALL_PATH='/usr/local/bin/efg' EFG_INSTALL_PATH='/usr/local/bin/efg'
local EFG_OPT_MAP=(
"-i --input input file"
"-P --preset set preset (default: ${PRESET})"
"-l --low set low end value for grain range (default: ${LOW})"
"-s --step set step value for grain range (default: ${STEP})"
"-h --high set high end value for grain range (default: ${HIGH})"
"-n --segments number of segments to analyze from input (default: ${ENCODE_SEGMENTS})"
"-p --plot plot bitrates using gnuplot"
"-I --install system install at ${EFG_INSTALL_PATH}"
"-U --uninstall uninstall from ${EFG_INSTALL_PATH}"
)
# only using -I or -U # only using -I or -U
local minOpt=1 local minOpt=1
# using all
local maxOpt=${numOpts}
test $# -lt ${minOpt} && efg_usage && return 1 test $# -lt ${minOpt} && efg_usage && return 1
test $# -gt ${maxOpt} && efg_usage && return 1
local OPTARG OPTIND local arg value
while getopts "${opts}" flag; do while [[ $# -gt 0 ]]; do
case "${flag}" in arg="$1"
P) value="${2:-}"
if ! is_positive_integer "${OPTARG}"; then case "${arg}" in
-i | --input)
INPUT="${value}"
shift
;;
-P | --preset)
if ! is_positive_integer "${value}"; then
efg_usage efg_usage
return 1 return 1
fi fi
PRESET="${OPTARG}" PRESET="${value}"
shift
;; ;;
I) -l | --low)
if ! is_positive_integer "${value}"; then
efg_usage
return 1
fi
LOW="${value}"
shift
;;
-s | --step)
if ! is_positive_integer "${value}"; then
efg_usage
return 1
fi
STEP="${value}"
shift
;;
-h | --high)
if ! is_positive_integer "${value}"; then
efg_usage
return 1
fi
HIGH="${value}"
shift
;;
-n | --num-segments)
if ! is_positive_integer "${value}"; then
efg_usage
return 1
fi
ENCODE_SEGMENTS="${value}"
shift
;;
-p | --plot)
missing_cmd gnuplot && return 1
PLOT=true
;;
-I | --install)
echo_warn "attempting install" echo_warn "attempting install"
sudo ln -sf "${SCRIPT_DIR}/efg.sh" \ sudo ln -sf "${SCRIPT_DIR}/efg.sh" \
"${EFG_INSTALL_PATH}" || return 1 "${EFG_INSTALL_PATH}" || return 1
echo_pass "succesfull install" echo_pass "succesfull install"
return ${FUNC_EXIT_SUCCESS} return "${FUNC_EXIT_SUCCESS}"
;; ;;
U) -U | --uninstall)
echo_warn "attempting uninstall" echo_warn "attempting uninstall"
sudo rm "${EFG_INSTALL_PATH}" || return 1 sudo rm "${EFG_INSTALL_PATH}" || return 1
echo_pass "succesfull uninstall" echo_pass "succesfull uninstall"
return ${FUNC_EXIT_SUCCESS} return "${FUNC_EXIT_SUCCESS}"
;;
i)
if [[ $# -lt 2 ]]; then
echo_fail "wrong arguments given"
efg_usage
return 1
fi
INPUT="${OPTARG}"
;;
p)
missing_cmd gnuplot && return 1
PLOT=true
;;
l)
if ! is_positive_integer "${OPTARG}"; then
efg_usage
return 1
fi
LOW="${OPTARG}"
;;
s)
if ! is_positive_integer "${OPTARG}"; then
efg_usage
return 1
fi
STEP="${OPTARG}"
;;
h)
if ! is_positive_integer "${OPTARG}"; then
efg_usage
return 1
fi
HIGH="${OPTARG}"
;; ;;
*) *)
echo "wrong flags given" echo_fail "unsupported option: [${arg}]"
efg_usage efg_usage
return 1 return 1
;; ;;
esac esac
shift
done done
if [[ ! -f ${INPUT} ]]; then # validate input
echo "${INPUT} does not exist" if [[ -z ${INPUT} || ! -f ${INPUT} ]]; then
echo_fail "input undefined or does not exist"
efg_usage efg_usage
return 1 return 1
fi fi
@@ -108,7 +123,7 @@ set_efg_opts() {
EFG_DIR+="-${sanitizedInput}" EFG_DIR+="-${sanitizedInput}"
echo_info "estimating film grain for ${INPUT}" echo_info "estimating film grain for ${INPUT}"
echo_info "range: $LOW-$HIGH with $STEP step increments" echo_info "range: ${LOW}-${HIGH} with ${STEP} step increments"
} }
efg_segment() { efg_segment() {
@@ -255,9 +270,13 @@ FB_FUNC_NAMES+=('efg')
# shellcheck disable=SC2034 # shellcheck disable=SC2034
FB_FUNC_DESCS['efg']='estimate the film grain of a given file' FB_FUNC_DESCS['efg']='estimate the film grain of a given file'
efg() { efg() {
# localize variables used by child functions
local PRESET LOW STEP HIGH PLOT \
EFG_INSTALL_PATH EFG_DIR ENCODE_SEGMENTS \
GRAIN_LOG
EFG_DIR="${TMP_DIR}/efg" EFG_DIR="${TMP_DIR}/efg"
# encode N highest-bitrate segments # encode N highest-bitrate segments
ENCODE_SEGMENTS=5
set_efg_opts "$@" set_efg_opts "$@"
local ret=$? local ret=$?
+112 -108
View File
@@ -64,7 +64,7 @@ set_subtitle_params() {
local convertCodec='eia_608' local convertCodec='eia_608'
local defaultTextCodec local defaultTextCodec
if [[ ${SAME_CONTAINER} == false && ${FILE_EXT} == 'mkv' ]]; then if [[ ${SAME_CONTAINER} == false && ${OUTPUT} == *'.mkv' ]]; then
defaultTextCodec='srt' defaultTextCodec='srt'
convertCodec+='|mov_text' convertCodec+='|mov_text'
else else
@@ -324,19 +324,10 @@ setup_pgs_mkv() {
} }
encode_usage() { encode_usage() {
echo "encode -i input [options] output" echo "encode -i input [options] [output]"
echo -e "\t[-P NUM] set preset (default: ${PRESET})" print_opt_map "${ENCODE_OPT_MAP[@]}" || return 1
echo -e "\t[-C NUM] set CRF (default: ${CRF})" echo -e "\n [output] output filename (default: \${PWD}/av1-input-file-name.mkv)\n"
echo -e "\t[-g NUM] set film grain for encode"
echo -e "\t[-p] print the command instead of executing it (default: ${PRINT_OUT})"
echo -e "\t[-c] use cropdetect (default: ${CROP})"
echo -e "\t[-d] enable dolby vision (default: ${DV_TOGGLE})"
echo -e "\t[-v] print relevant version info"
echo -e "\t[-s] use same container as input, default is convert to mkv"
echo -e "\n\t[output] if unset, defaults to \${PWD}/av1-input-file-name.mkv"
echo -e "\n\t[-u] update script (git pull ffmpeg-builder)"
echo -e "\t[-I] system install at ${ENCODE_INSTALL_PATH}"
echo -e "\t[-U] uninstall from ${ENCODE_INSTALL_PATH}"
return 0 return 0
} }
@@ -345,138 +336,146 @@ encode_update() {
} }
set_encode_opts() { set_encode_opts() {
local opts='vi:pcsdg:P:C:uIU'
local numOpts=${#opts}
# default values # default values
PRESET=3 PRESET=3
CRF=25 CRF=25
GRAIN="" GRAIN=''
CROP=false CROP=false
PRINT_OUT=false PRINT_OUT=false
DV_TOGGLE=false DV_TOGGLE=false
ENCODE_INSTALL_PATH='/usr/local/bin/encode' ENCODE_INSTALL_PATH='/usr/local/bin/encode'
SAME_CONTAINER="false" SAME_CONTAINER="false"
local ENCODE_OPT_MAP=(
"-i --input input file"
"-P --preset set preset (default: ${PRESET})"
"-C --crf set CRF (default: ${CRF})"
"-g --grain set film grain (default: disabled)"
"-p --print print the script instead of executing it"
"-c --crop use crop detect to auto-crop"
"-d --dv enable dolby vision"
"-v --version print version info"
"-s --same-container use same container as input (default: mkv)"
"-u --update update script (git pull ffmpeg-builder)"
"-I --install system install at ${ENCODE_INSTALL_PATH}"
"-U --uninstall uninstall from ${ENCODE_INSTALL_PATH}"
)
# only using -I/U # only using -I/U
local minOpt=1 local minOpt=1
# using all + output name
local maxOpt=$((numOpts + 1))
test $# -lt ${minOpt} && encode_usage && return 1 test $# -lt ${minOpt} && encode_usage && return 1
test $# -gt ${maxOpt} && encode_usage && return 1
local optsUsed=0 local arg value
local OPTARG OPTIND while [[ $# -gt 0 ]]; do
while getopts "${opts}" flag; do arg="$1"
case "${flag}" in value="${2:-}"
u) case "${arg}" in
encode_update || return 1 -i | --input)
return ${FUNC_EXIT_SUCCESS} INPUT="${value}"
shift
;; ;;
I) -P | --preset)
if ! is_positive_integer "${value}"; then
encode_usage
return 1
fi
PRESET="${value}"
shift
;;
-C | --crf)
if ! is_positive_integer "${value}" || test "${value}" -gt 63; then
echo_fail "${value} is not a valid CRF value (0-63)"
encode_usage
return 1
fi
CRF="${value}"
shift
;;
-g | --grain)
if ! is_positive_integer "${value}"; then
encode_usage
return 1
fi
GRAIN="film-grain=${value}:film-grain-denoise=1:adaptive-film-grain=1:"
shift
;;
-c | --crop)
CROP=true
;;
-p | --print)
PRINT_OUT=true
;;
-d | --dv)
DV_TOGGLE=true
;;
-v | --version)
get_encode_versions print || return 1
return "${FUNC_EXIT_SUCCESS}"
;;
-s | --same-container)
SAME_CONTAINER=true
;;
-u | --update)
encode_update || return 1
return "${FUNC_EXIT_SUCCESS}"
;;
-I | --install)
echo_warn "attempting install" echo_warn "attempting install"
sudo ln -sf "${SCRIPT_DIR}/encode.sh" \ sudo ln -sf "${SCRIPT_DIR}/encode.sh" \
"${ENCODE_INSTALL_PATH}" || return 1 "${ENCODE_INSTALL_PATH}" || return 1
echo_pass "succesfull install" echo_pass "succesfull install"
return ${FUNC_EXIT_SUCCESS} return "${FUNC_EXIT_SUCCESS}"
;; ;;
U) -U | --uninstall)
echo_warn "attempting uninstall" echo_warn "attempting uninstall"
sudo rm "${ENCODE_INSTALL_PATH}" || return 1 sudo rm "${ENCODE_INSTALL_PATH}" || return 1
echo_pass "succesfull uninstall" echo_pass "succesfull uninstall"
return ${FUNC_EXIT_SUCCESS} return "${FUNC_EXIT_SUCCESS}"
;;
v)
get_encode_versions print || return 1
return ${FUNC_EXIT_SUCCESS}
;;
i)
if [[ $# -lt 2 ]]; then
echo_fail "wrong arguments given"
encode_usage
return 1
fi
INPUT="${OPTARG}"
optsUsed=$((optsUsed + 2))
;;
p)
PRINT_OUT=true
optsUsed=$((optsUsed + 1))
;;
c)
CROP=true
optsUsed=$((optsUsed + 1))
;;
d)
DV_TOGGLE=true
optsUsed=$((optsUsed + 1))
;;
s)
SAME_CONTAINER=true
optsUsed=$((optsUsed + 1))
;;
g)
if ! is_positive_integer "${OPTARG}"; then
encode_usage
return 1
fi
GRAIN="film-grain=${OPTARG}:film-grain-denoise=1:adaptive-film-grain=1:"
optsUsed=$((optsUsed + 2))
;;
P)
if ! is_positive_integer "${OPTARG}"; then
encode_usage
return 1
fi
PRESET="${OPTARG}"
optsUsed=$((optsUsed + 2))
;;
C)
if ! is_positive_integer "${OPTARG}" || test ${OPTARG} -gt 63; then
echo_fail "${OPTARG} is not a valid CRF value (0-63)"
encode_usage
return 1
fi
CRF="${OPTARG}"
OPTS_USED=$((OPTS_USED + 2))
;; ;;
*) *)
echo_fail "wrong flags given" # OUTPUT will be the last (optional) arg
encode_usage if [[ $# -ne 1 ]]; then
return 1 echo_fail "unsupported option: [${arg}]"
encode_usage
return 1
fi
OUTPUT="${arg}"
;; ;;
esac esac
shift
done done
# allow optional output filename # validate input
if [[ $(($# - optsUsed)) == 1 ]]; then if [[ -z ${INPUT} || ! -f ${INPUT} ]]; then
OUTPUT="${*: -1}" echo_fail "input undefined or does not exist"
else encode_usage
local basename="$(bash_basename "${INPUT}")" return 1
fi
# fallback output path
if [[ -z ${OUTPUT} ]]; then
local basename
basename="$(bash_basename "${INPUT}")" || return 1
OUTPUT="${PWD}/av1-${basename}" OUTPUT="${PWD}/av1-${basename}"
fi fi
# use same container for output # use same container for output
if [[ $SAME_CONTAINER == "true" ]]; then if [[ ${SAME_CONTAINER} == true ]]; then
local fileFormat local fileFormat outputSuffix
fileFormat="$(get_file_format "${INPUT}")" || return 1 fileFormat="$(get_file_format "${INPUT}")" || return 1
FILE_EXT=''
if [[ ${fileFormat} == 'MPEG-4' ]]; then if [[ ${fileFormat} == 'MPEG-4' ]]; then
FILE_EXT='mp4' outputSuffix='mp4'
elif [[ ${fileFormat} == 'Matroska' ]]; then elif [[ ${fileFormat} == 'Matroska' ]]; then
FILE_EXT='mkv' outputSuffix='mkv'
else else
echo "unrecognized input format" echo_fail "unrecognized input format"
return 1 return 1
fi fi
else else
FILE_EXT="mkv" outputSuffix='mkv'
fi fi
OUTPUT="${OUTPUT%.*}" OUTPUT="${OUTPUT%.*}"
OUTPUT+=".${FILE_EXT}" OUTPUT+=".${outputSuffix}"
if [[ ! -f ${INPUT} ]]; then
echo "${INPUT} does not exist"
encode_usage
return 1
fi
if [[ ${PRINT_OUT} == false ]]; then if [[ ${PRINT_OUT} == false ]]; then
echo echo
@@ -658,7 +657,7 @@ gen_encode_script() {
echo 'ffmpeg "${ffmpegParams[@]}" -dolbyvision 0 "${OUTPUT}" || exit 1' echo 'ffmpeg "${ffmpegParams[@]}" -dolbyvision 0 "${OUTPUT}" || exit 1'
# track-stats and clear title # track-stats and clear title
if [[ ${FILE_EXT} == 'mkv' ]]; then if [[ ${OUTPUT} == *'.mkv' ]]; then
{ {
# ffmpeg does not copy PGS subtitles without breaking them # ffmpeg does not copy PGS subtitles without breaking them
# use mkvmerge to extract and supmover to crop # use mkvmerge to extract and supmover to crop
@@ -690,6 +689,11 @@ FB_FUNC_NAMES+=('encode')
# shellcheck disable=SC2034 # shellcheck disable=SC2034
FB_FUNC_DESCS['encode']='encode a file using libsvtav1 and libopus' FB_FUNC_DESCS['encode']='encode a file using libsvtav1 and libopus'
encode() { encode() {
# localize variables used by child functions
local PRESET CRF GRAIN CROP PRINT_OUT \
DV_TOGGLE ENCODE_INSTALL_PATH SAME_CONTAINER \
INPUT OUTPUT
set_encode_opts "$@" set_encode_opts "$@"
local ret=$? local ret=$?
if [[ ${ret} -eq ${FUNC_EXIT_SUCCESS} ]]; then if [[ ${ret} -eq ${FUNC_EXIT_SUCCESS} ]]; then