Compare commits

...

2 Commits

Author SHA1 Message Date
f1b2a0d534 small fix for pgs tmpdir 2026-03-03 14:17:14 -06:00
64c7f358fc Crop PGS subtitles to match video dimensions 2026-03-03 10:01:53 -06:00
6 changed files with 335 additions and 99 deletions

View File

@@ -13,7 +13,7 @@ Tested on:
With these scripts you can: With these scripts you can:
1. install required dependencies using `./scripts/install_deps.sh` 1. install required dependencies using `./scripts/install_deps.sh`
2. build ffmpeg with the desired configuration using `./scripts/build.sh` 2. build ffmpeg with the desired configuration using `./scripts/build.sh`
3. encode a file using libsvtav1_psy and libopus using `./scripts/encode.sh` 3. encode a file using libsvtav1 and libopus using `./scripts/encode.sh`
4. estimate the film grain of a given file using `./scripts/efg.sh` 4. estimate the film grain of a given file using `./scripts/efg.sh`
# Building # Building
@@ -24,7 +24,7 @@ Configuration is done through environment variables.
By default, this project will build a static `ffmpeg` binary in `./gitignore/sysroot/bin/ffmpeg`. By default, this project will build a static `ffmpeg` binary in `./gitignore/sysroot/bin/ffmpeg`.
The user-overridable compile options are: The user-overridable compile options are:
- `ENABLE`: configure what ffmpeg enables (default: libaom libass libvpx libxml2 libvmaf libx264 libx265 libwebp libopus librav1e libdav1d libvorbis libmp3lame libfribidi libfreetype libharfbuzz libsvtav1_psy libfontconfig ) - `ENABLE`: configure what ffmpeg enables (default: libaom libass libvpx libxml2 libvmaf libx264 libx265 libwebp libopus librav1e libdav1d libvorbis libmp3lame libfribidi libfreetype libharfbuzz libopenjpeg libsvtav1_psy libfontconfig )
- `PREFIX`: prefix to install to, default is local install in ./gitignore/sysroot (default: local) - `PREFIX`: prefix to install to, default is local install in ./gitignore/sysroot (default: local)
- `STATIC`: static or shared build (default: ON) - `STATIC`: static or shared build (default: ON)
- `LTO`: enable link time optimization (default: ON) - `LTO`: enable link time optimization (default: ON)
@@ -78,6 +78,7 @@ encode -i input [options] output
- Skips re-encoding av1/opus streams. - Skips re-encoding av1/opus streams.
- Only maps audio streams that match the video stream language if the video stream has a defined language. - Only maps audio streams that match the video stream language if the video stream has a defined language.
- Only maps english subtitle streams. - Only maps english subtitle streams.
- Crop PGS subtitles to match video dimensions.
- Adds track statistics to the output mkv file and embeds the encoder versions to the output metadata. For example: - Adds track statistics to the output mkv file and embeds the encoder versions to the output metadata. For example:
``` ```
ENCODE : aa4d7e6 ENCODE : aa4d7e6

View File

@@ -533,3 +533,10 @@ benchmark_command() {
} }
' "${trace}" | sort -rn | head -n 20 ' "${trace}" | sort -rn | head -n 20
} }
# make sure supmover is built
# and set SUPMOVER
check_for_supmover() {
SUPMOVER="${LOCAL_PREFIX}/bin/supmover"
test -f "${SUPMOVER}" || do_build supmover || return 1
}

View File

@@ -1,25 +1,27 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# sets unmapStreams DESIRED_SUB_LANG=eng
# sets UNMAP_STREAMS
set_unmap_streams() { set_unmap_streams() {
local file="$1" local file="$1"
local unmapFilter='bin_data|jpeg|png' local unmapFilter='bin_data|jpeg|png'
local streamsStr local streamsStr
unmapStreams=() UNMAP_STREAMS=()
streamsStr="$(get_num_streams "${file}")" || return 1 streamsStr="$(get_num_streams "${file}")" || return 1
mapfile -t streams <<<"${streamsStr}" || return 1 mapfile -t streams <<<"${streamsStr}" || return 1
for stream in "${streams[@]}"; do for stream in "${streams[@]}"; do
if [[ "$(get_stream_codec "${file}" "${stream}")" =~ ${unmapFilter} ]]; then if [[ "$(get_stream_codec "${file}" "${stream}")" =~ ${unmapFilter} ]]; then
unmapStreams+=("-map" "-0:${stream}") UNMAP_STREAMS+=("-map" "-0:${stream}")
fi fi
done done
} }
# sets audioParams # sets AUDIO_PARAMS
set_audio_params() { set_audio_params() {
local file="$1" local file="$1"
local videoLang local videoLang
audioParams=() AUDIO_PARAMS=()
videoLang="$(get_stream_lang "${file}" 'v:0')" || return 1 videoLang="$(get_stream_lang "${file}" 'v:0')" || return 1
for stream in $(get_num_streams "${file}" 'a'); do for stream in $(get_num_streams "${file}" 'a'); do
local numChannels codec lang local numChannels codec lang
@@ -32,18 +34,18 @@ set_audio_params() {
codec="$(get_stream_codec "${file}" "${stream}")" || return 1 codec="$(get_stream_codec "${file}" "${stream}")" || return 1
lang="$(get_stream_lang "${file}" "${stream}")" || return 1 lang="$(get_stream_lang "${file}" "${stream}")" || return 1
if [[ ${videoLang} != '' && ${videoLang} != "${lang}" ]]; then if [[ ${videoLang} != '' && ${videoLang} != "${lang}" ]]; then
audioParams+=( AUDIO_PARAMS+=(
'-map' '-map'
"-0:${stream}" "-0:${stream}"
) )
elif [[ ${codec} == 'opus' ]]; then elif [[ ${codec} == 'opus' ]]; then
audioParams+=( AUDIO_PARAMS+=(
"-c:${OUTPUT_INDEX}" "-c:${OUTPUT_INDEX}"
"copy" "copy"
) )
OUTPUT_INDEX=$((OUTPUT_INDEX + 1)) OUTPUT_INDEX=$((OUTPUT_INDEX + 1))
else else
audioParams+=( AUDIO_PARAMS+=(
"-filter:${OUTPUT_INDEX}" "-filter:${OUTPUT_INDEX}"
"aformat=channel_layouts=7.1|5.1|stereo|mono" "aformat=channel_layouts=7.1|5.1|stereo|mono"
"-c:${OUTPUT_INDEX}" "-c:${OUTPUT_INDEX}"
@@ -56,11 +58,10 @@ set_audio_params() {
done done
} }
# sets subtitleParams # sets SUBTITLE_PARAMS
set_subtitle_params() { set_subtitle_params() {
local file="$1" local file="$1"
local convertCodec='eia_608' local convertCodec='eia_608'
local keepLang='eng'
local defaultTextCodec local defaultTextCodec
if [[ ${SAME_CONTAINER} == false && ${FILE_EXT} == 'mkv' ]]; then if [[ ${SAME_CONTAINER} == false && ${FILE_EXT} == 'mkv' ]]; then
@@ -71,18 +72,27 @@ set_subtitle_params() {
convertCodec+='|srt' convertCodec+='|srt'
fi fi
subtitleParams=() SUBTITLE_PARAMS=()
for stream in $(get_num_streams "${file}" 's'); do for stream in $(get_num_streams "${file}" 's'); do
local codec lang local codec lang
codec="$(get_stream_codec "${file}" "${stream}")" || return 1 codec="$(get_stream_codec "${file}" "${stream}")" || return 1
lang="$(get_stream_lang "${file}" "${stream}")" || return 1 lang="$(get_stream_lang "${file}" "${stream}")" || return 1
if [[ ${lang} != '' && ${keepLang} != "${lang}" ]]; then if [[ ${lang} != '' && ${DESIRED_SUB_LANG} != "${lang}" ]]; then
subtitleParams+=( SUBTITLE_PARAMS+=(
'-map' '-map'
"-0:${stream}" "-0:${stream}"
) )
elif [[ ${codec} =~ ${convertCodec} ]]; then elif [[ ${codec} =~ ${convertCodec} ]]; then
subtitleParams+=("-c:${OUTPUT_INDEX}" "${defaultTextCodec}") SUBTITLE_PARAMS+=("-c:${OUTPUT_INDEX}" "${defaultTextCodec}")
OUTPUT_INDEX=$((OUTPUT_INDEX + 1))
elif [[ ${codec} == 'hdmv_pgs_subtitle' ]]; then
PGS_SUB_STREAMS+=("${stream}")
SUBTITLE_PARAMS+=(
'-map'
"-0:${stream}"
)
else
# map -0 covers the stream but still want to increment the index
OUTPUT_INDEX=$((OUTPUT_INDEX + 1)) OUTPUT_INDEX=$((OUTPUT_INDEX + 1))
fi fi
done done
@@ -91,59 +101,208 @@ set_subtitle_params() {
get_encode_versions() { get_encode_versions() {
action="${1:-}" action="${1:-}"
encodeVersion="encode=$(git -C "${REPO_DIR}" rev-parse --short HEAD)" ENCODE_VERSION="encode=$(git -C "${REPO_DIR}" rev-parse --short HEAD)"
ffmpegVersion='' FFMPEG_VERSION=''
videoEncVersion='' VIDEO_ENC_VERSION=''
audioEncVersion='' AUDIO_ENC_VERSION=''
# shellcheck disable=SC2155 # shellcheck disable=SC2155
local output="$(ffmpeg -version 2>&1)" local output="$(ffmpeg -version 2>&1)"
while read -r line; do while read -r line; do
if line_starts_with "${line}" 'ffmpeg='; then if line_starts_with "${line}" 'ffmpeg='; then
ffmpegVersion="${line}" FFMPEG_VERSION="${line}"
elif line_starts_with "${line}" 'libsvtav1'; then elif line_starts_with "${line}" 'libsvtav1'; then
videoEncVersion="${line}" VIDEO_ENC_VERSION="${line}"
elif line_starts_with "${line}" 'libopus='; then elif line_starts_with "${line}" 'libopus='; then
audioEncVersion="${line}" AUDIO_ENC_VERSION="${line}"
fi fi
done <<<"${output}" done <<<"${output}"
local version local version
if [[ ${ffmpegVersion} == '' ]]; then if [[ ${FFMPEG_VERSION} == '' ]]; then
while read -r line; do while read -r line; do
if line_starts_with "${line}" 'ffmpeg version '; then if line_starts_with "${line}" 'ffmpeg version '; then
read -r _ _ version _ <<<"${line}" read -r _ _ version _ <<<"${line}"
ffmpegVersion="ffmpeg=${version}" FFMPEG_VERSION="ffmpeg=${version}"
break break
fi fi
done <<<"${output}" done <<<"${output}"
fi fi
if [[ ${videoEncVersion} == '' ]]; then if [[ ${VIDEO_ENC_VERSION} == '' ]]; then
version="$(get_pkgconfig_version SvtAv1Enc)" version="$(get_pkgconfig_version SvtAv1Enc)"
test "${version}" == '' && return 1 test "${version}" == '' && return 1
videoEncVersion="libsvtav1=${version}" VIDEO_ENC_VERSION="libsvtav1=${version}"
fi fi
if [[ ${audioEncVersion} == '' ]]; then if [[ ${AUDIO_ENC_VERSION} == '' ]]; then
version="$(get_pkgconfig_version opus)" version="$(get_pkgconfig_version opus)"
test "${version}" == '' && return 1 test "${version}" == '' && return 1
audioEncVersion="libopus=${version}" AUDIO_ENC_VERSION="libopus=${version}"
fi fi
test "${ffmpegVersion}" == '' && return 1 test "${FFMPEG_VERSION}" == '' && return 1
test "${videoEncVersion}" == '' && return 1 test "${VIDEO_ENC_VERSION}" == '' && return 1
test "${audioEncVersion}" == '' && return 1 test "${AUDIO_ENC_VERSION}" == '' && return 1
if [[ ${action} == 'print' ]]; then if [[ ${action} == 'print' ]]; then
echo "${encodeVersion}" echo "${ENCODE_VERSION}"
echo "${ffmpegVersion}" echo "${FFMPEG_VERSION}"
echo "${videoEncVersion}" echo "${VIDEO_ENC_VERSION}"
echo "${audioEncVersion}" echo "${AUDIO_ENC_VERSION}"
fi fi
return 0 return 0
} }
# given an input mkv/sup file,
# output a new mkv file with
# input metadata preserved
replace_mkv_sup() {
local mkvIn="$1"
local supIn="$2"
local mkvOut="$3"
local stream="${4:-0}"
local json
json="$(get_stream_json "${mkvIn}" "${stream}")" || return 1
# x:y
# x = stream json variable name
# y = mkvmerge option name
local optionMap=(
disposition.default:--default-track-flag
disposition.original:--original-flag
disposition.comment:--commentary-flag
disposition.forced:--forced-display-flag
disposition.hearing_impaired:--hearing-impaired-flag
tags.language:--language
tags.title:--track-name
)
# start building mkvmerge command
local mergeCmd=(
mkvmerge
-o "${mkvOut}"
)
for line in "${optionMap[@]}"; do
IFS=: read -r key option <<<"${line}"
local val
val="$(jq -r ".streams[].${key}" <<<"${json}")" || return 1
# skip undefined values
test "${val}" == null && continue
# always track 0 for single track
mergeCmd+=("${option}" "0:${val}")
done
mergeCmd+=("${supIn}")
"${mergeCmd[@]}"
}
# extract PGS_SUB_STREAMS from INPUT
# and crop using CROP_VALUE
setup_pgs_mkv() {
local pgsMkvOut="$1"
if [[ ${#PGS_SUB_STREAMS[@]} -eq 0 ]]; then
return
fi
check_for_supmover || return 1
# setup tempdir
local ogSup cropSup cropMkv tmpdir
tmpdir="${pgsMkvOut}-dir"
recreate_dir "${tmpdir}" || return 1
# get video resolution
local vidRes vidWidth vidHeight
vidRes="$(get_resolution "${INPUT}")"
IFS=x read -r vidWidth vidHeight <<<"${vidRes}"
for stream in "${PGS_SUB_STREAMS[@]}"; do
# extract sup from input
ogSup="${tmpdir}/${stream}.sup"
cropSup="${tmpdir}/${stream}-cropped.sup"
cropMkv="${tmpdir}/${stream}.mkv"
mkvextract "${INPUT}" tracks "${stream}:${ogSup}" || return 1
# check if PGS was uncropped
local supRes
supRes="$(get_sup_resolution "${ogSup}")" || return 1
# crop PGS if either initially cropping or "fixing"
# previously uncropped (relative to video) PGS subs
if [[ ${CROP_VALUE} != '' || ${supRes} != "${vidRes}" ]]; then
local supWidth supHeight
IFS=x read -r supWidth supHeight <<<"${supRes}"
local left top right bottom
# CROP_VALUE gets priority
if [[ ${CROP_VALUE} != '' ]]; then
# determine supmover crop based off of crop
local res w h x y
# extract ffmpeg crop value ("crop=w:h:x:y")
IFS='=' read -r _ res <<<"${CROP_VALUE}"
IFS=':' read -r w h x y <<<"${res}"
# ffmpeg crop value
# is different than supmover crop inputs
left=${x}
top=${y}
right=$((supWidth - w - left))
bottom=$((supHeight - h - top))
else
# determine supmover crop based off video stream
if [[ ${vidWidth} -gt ${supWidth} || ${vidHeight} -gt ${supHeight} ]]; then
echo_warn "PGS sup (stream=${stream}) is somehow smaller than initial video stream"
echo_warn "cropping based off of aspect ratio instead of resolution"
left=0
# (supHeight - ((vidHeight/vidWidth) * supWidth)) / 2
top="$(awk '{ print int(($1 - ($2 / $3 * $4)) / 2) }' <<<"${supHeight} ${vidHeight} ${vidWidth} ${supWidth}")"
else
left=$(((supWidth - vidWidth) / 2))
top=$(((supHeight - vidHeight) / 2))
fi
right=${left}
bottom=${top}
fi
# crop sup
(
set -x
"${SUPMOVER}" \
"${ogSup}" \
"${cropSup}" \
--crop \
"${left}" "${top}" "${right}" "${bottom}" &>"${cropSup}.out" || return 1
)
local cropRet=$?
# supmover does not error for out-of-bounds subtitles
if grep 'Window is outside new screen area' "${cropSup}.out"; then
echo_fail "check ${cropSup}.out for complete logs"
cropRet=1
fi
if [[ ${cropRet} -ne 0 ]]; then
rm -r "${tmpdir}" || return 1
return 1
fi
else
# create placeholder copy for replacement
cp "${ogSup}" "${cropSup}" || return 1
fi
if ! replace_mkv_sup "${INPUT}" "${cropSup}" "${cropMkv}" "${stream}"; then
echo_fail "could not replace mkv sup for ${stream}"
rm -r "${tmpdir}" || return 1
fi
done
# merge all single mkv into one
mkvmerge -o "${pgsMkvOut}" "${tmpdir}/"*.mkv
local mergeRet=$?
rm -r "${tmpdir}" || return 1
return ${mergeRet}
}
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})" echo -e "\t[-P NUM] set preset (default: ${PRESET})"
@@ -312,7 +471,13 @@ set_encode_opts() {
# shellcheck disable=SC2155 # shellcheck disable=SC2155
# shellcheck disable=SC2016 # shellcheck disable=SC2016
gen_encode_script() { gen_encode_script() {
local genScript="${TMP_DIR}/$(bash_basename "${OUTPUT}").sh" if missing_cmd mkvpropedit; then
echo_fail "use: ${REPO_DIR}/scripts/install_deps.sh"
return 1
fi
local outputBasename="$(bash_basename "${OUTPUT}")"
local genScript="${TMP_DIR}/${outputBasename}.sh"
# global output index number to increment # global output index number to increment
OUTPUT_INDEX=0 OUTPUT_INDEX=0
@@ -323,17 +488,15 @@ gen_encode_script() {
OUTPUT OUTPUT
PRESET PRESET
CRF CRF
crop CROP_VALUE
encodeVersion ENCODE_VERSION
ffmpegVersion FFMPEG_VERSION
videoEncVersion VIDEO_ENC_VERSION
audioEncVersion AUDIO_ENC_VERSION
svtAv1Params svtAv1Params
pgsMkv
muxxedPgsMkv
) )
local crop=''
if [[ $CROP == "true" ]]; then
crop="$(get_crop "${INPUT}")" || return 1
fi
svtAv1ParamsArr=( svtAv1ParamsArr=(
"tune=0" "tune=0"
@@ -357,13 +520,16 @@ gen_encode_script() {
# arrays # arrays
local arrays=( local arrays=(
unmapStreams UNMAP_STREAMS
audioParams AUDIO_PARAMS
SUBTITLE_PARAMS
videoParams videoParams
metadata metadata
subtitleParams
ffmpegParams ffmpegParams
PGS_SUB_STREAMS
) )
local "${arrays[@]}"
local videoParams=( local videoParams=(
"-crf" '${CRF}' "-preset" '${PRESET}' "-g" "240" "-crf" '${CRF}' "-preset" '${PRESET}' "-g" "240"
) )
@@ -376,51 +542,72 @@ gen_encode_script() {
) )
# set video params # set video params
get_encode_versions || return 1
local inputVideoCodec="$(get_stream_codec "${INPUT}" 'v:0')" local inputVideoCodec="$(get_stream_codec "${INPUT}" 'v:0')"
if [[ ${inputVideoCodec} == 'av1' ]]; then if [[ ${inputVideoCodec} == 'av1' ]]; then
ffmpegParams+=( ffmpegParams+=(
"-c:v:${OUTPUT_INDEX}" 'copy' "-c:v:${OUTPUT_INDEX}" 'copy'
) )
# can't crop if copying codec
CROP=false
else else
ffmpegParams+=( ffmpegParams+=(
'-pix_fmt' 'yuv420p10le' '-pix_fmt' 'yuv420p10le'
"-c:v:${OUTPUT_INDEX}" 'libsvtav1' '${videoParams[@]}' "-c:v:${OUTPUT_INDEX}" 'libsvtav1' '${videoParams[@]}'
'-svtav1-params' '${svtAv1Params}' '-svtav1-params' '${svtAv1Params}'
) )
metadata+=(
'-metadata' '${VIDEO_ENC_VERSION}'
'-metadata' 'svtav1_params=${svtAv1Params}'
'-metadata' 'video_params=${videoParams[*]}'
)
fi fi
OUTPUT_INDEX=$((OUTPUT_INDEX + 1)) OUTPUT_INDEX=$((OUTPUT_INDEX + 1))
# these values may be empty # these values may be empty
local unmapStr audioParamsStr subtitleParamsStr
set_unmap_streams "${INPUT}" || return 1 set_unmap_streams "${INPUT}" || return 1
set_audio_params "${INPUT}" || return 1 set_audio_params "${INPUT}" || return 1
set_subtitle_params "${INPUT}" || return 1 set_subtitle_params "${INPUT}" || return 1
if [[ ${unmapStreams[*]} != '' ]]; then if [[ ${UNMAP_STREAMS[*]} != '' ]]; then
ffmpegParams+=('${unmapStreams[@]}') ffmpegParams+=('${UNMAP_STREAMS[@]}')
fi fi
if [[ ${audioParams[*]} != '' ]]; then if [[ ${AUDIO_PARAMS[*]} != '' ]]; then
ffmpegParams+=('${audioParams[@]}') ffmpegParams+=('${AUDIO_PARAMS[@]}')
fi fi
if [[ ${subtitleParams[*]} != '' ]]; then if [[ ${SUBTITLE_PARAMS[*]} != '' ]]; then
ffmpegParams+=('${subtitleParams[@]}') ffmpegParams+=('${SUBTITLE_PARAMS[@]}')
fi fi
if [[ ${crop} != '' ]]; then metadata+=(
ffmpegParams+=('-vf' '${crop}') '-metadata' '${ENCODE_VERSION}'
fi '-metadata' '${FFMPEG_VERSION}'
get_encode_versions || return 1
local metadata=(
'-metadata' '${encodeVersion}'
'-metadata' '${ffmpegVersion}'
'-metadata' '${videoEncVersion}'
'-metadata' '${audioEncVersion}'
'-metadata' 'svtav1_params=${svtAv1Params}'
'-metadata' 'video_params=${videoParams[*]}'
) )
# in the case all audio streams are copied,
# don't add libopus metadata
if line_contains "${AUDIO_PARAMS[*]}" 'libopus'; then
metadata+=(
'-metadata' '${AUDIO_ENC_VERSION}')
fi
local CROP_VALUE
if [[ ${CROP} == true ]]; then
CROP_VALUE="$(get_crop "${INPUT}")" || return 1
ffmpegParams+=('-vf' '${CROP_VALUE}')
metadata+=(
'-metadata' '${CROP_VALUE}'
'-metadata' "og_res=$(get_resolution "${INPUT}")"
)
fi
# separate processing step for pkg subs
local pgsMkv="${TMP_DIR}/pgs-${outputBasename// /.}.mkv"
local muxxedPgsMkv='${OUTPUT}.muxxed'
setup_pgs_mkv "${pgsMkv}" 1>&2 || return 1
ffmpegParams+=('${metadata[@]}') ffmpegParams+=('${metadata[@]}')
{ {
@@ -451,27 +638,17 @@ 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' ]] && has_cmd mkvmerge && has_cmd mkvpropedit; then if [[ ${FILE_EXT} == 'mkv' ]]; then
{ {
# ffmpeg does not reliably copy PGS subtitles without breaking # ffmpeg does not copy PGS subtitles without breaking them
# them when cropped, so just use mkvmerge to make sure they get # use mkvmerge to extract and supmover to crop
# copied correctly if [[ ${#PGS_SUB_STREAMS[@]} -gt 0 ]]; then
local muxxed='"${OUTPUT}.muxxed"' echo
local mergeCmd=( echo 'mkvmerge -o "${muxxedPgsMkv}" "${pgsMkv}" "${OUTPUT}" || exit 1'
mkvmerge echo 'rm "${pgsMkv}" || exit 1'
-o "${muxxed}" echo 'mv "${muxxedPgsMkv}" "${OUTPUT}" || exit 1'
--no-subtitles '"${OUTPUT}"' fi
--no-video
--no-audio
--no-chapters
--no-attachments
--no-global-tags
--subtitle-tracks eng
'"${INPUT}"'
)
echo
echo "${mergeCmd[*]} || exit 1"
echo "mv ${muxxed}" '"${OUTPUT}" || exit 1'
echo 'mkvpropedit "${OUTPUT}" --add-track-statistics-tags || exit 1' echo 'mkvpropedit "${OUTPUT}" --add-track-statistics-tags || exit 1'
echo 'mkvpropedit "${OUTPUT}" --edit info --set "title=" || exit 1' echo 'mkvpropedit "${OUTPUT}" --edit info --set "title=" || exit 1'
} }
@@ -491,7 +668,7 @@ gen_encode_script() {
FB_FUNC_NAMES+=('encode') FB_FUNC_NAMES+=('encode')
# shellcheck disable=SC2034 # shellcheck disable=SC2034
FB_FUNC_DESCS['encode']='encode a file using libsvtav1_psy and libopus' FB_FUNC_DESCS['encode']='encode a file using libsvtav1 and libopus'
encode() { encode() {
set_encode_opts "$@" set_encode_opts "$@"
local ret=$? local ret=$?

View File

@@ -1,8 +1,17 @@
#!/usr/bin/env bash #!/usr/bin/env bash
_ffprobe_wrapper() {
local stderr="${TMP_DIR}/ffprobe-stderr"
if ! ffprobe "$@" 2>"${TMP_DIR}/ffprobe-stderr"; then
cat "${stderr}"
return 1
fi
return 0
}
get_duration() { get_duration() {
local file="$1" local file="$1"
ffprobe \ _ffprobe_wrapper \
-v error \ -v error \
-show_entries format=duration \ -show_entries format=duration \
-of default=noprint_wrappers=1:nokey=1 \ -of default=noprint_wrappers=1:nokey=1 \
@@ -11,7 +20,7 @@ get_duration() {
get_avg_bitrate() { get_avg_bitrate() {
local file="$1" local file="$1"
ffprobe \ _ffprobe_wrapper \
-v error \ -v error \
-select_streams v:0 \ -select_streams v:0 \
-show_entries format=bit_rate \ -show_entries format=bit_rate \
@@ -43,7 +52,7 @@ get_crop() {
# don't care about decimal points # don't care about decimal points
IFS='.' read -r duration _ <<<"${duration}" IFS='.' read -r duration _ <<<"${duration}"
# get crop value for first half of input # get crop value for first half of input
local timeEnc=$((duration / 2)) local timeEnc=$((duration / 20))
ffmpeg \ ffmpeg \
-y \ -y \
-hide_banner \ -hide_banner \
@@ -55,7 +64,7 @@ get_crop() {
-filter:v:0 'cropdetect=limit=100:round=16:skip=2:reset_count=0' \ -filter:v:0 'cropdetect=limit=100:round=16:skip=2:reset_count=0' \
-codec:v 'wrapped_avframe' \ -codec:v 'wrapped_avframe' \
-f 'null' '/dev/null' 2>&1 | -f 'null' '/dev/null' 2>&1 |
grep -o crop=.* | grep -o 'crop=.*' |
sort -bh | sort -bh |
uniq -c | uniq -c |
sort -bh | sort -bh |
@@ -63,10 +72,35 @@ get_crop() {
grep -o "crop=.*" grep -o "crop=.*"
} }
# output '1920x1080'
get_resolution() {
local file="$1"
_ffprobe_wrapper \
-v error \
-select_streams v:0 \
-show_entries stream=width,height \
-of csv=s=x:p=0 \
"${file}"
}
# same as get_resolution
get_sup_resolution() {
local supfile="$1"
check_for_supmover || return 1
if [[ "$(get_file_format "${supfile}")" != 'sup' ]]; then
echo_fail "${supfile} is not a sup file"
return 1
fi
local trace res
trace="$("${SUPMOVER}" "${supfile}" --trace)" || return 1
res="$(grep 'Video size' <<<"${trace}" | sort -u | awk '{print $NF}')" || return 1
echo "${res}"
}
get_stream_codec() { get_stream_codec() {
local file="$1" local file="$1"
local stream="$2" local stream="$2"
ffprobe \ _ffprobe_wrapper \
-v error \ -v error \
-select_streams "${stream}" \ -select_streams "${stream}" \
-show_entries stream=codec_name \ -show_entries stream=codec_name \
@@ -74,18 +108,31 @@ get_stream_codec() {
"${file}" "${file}"
} }
get_stream_json() {
local file="$1"
local stream="$2"
_ffprobe_wrapper \
-v error \
-select_streams "${stream}" \
-show_entries stream \
-of json \
"${file}"
}
get_file_format() { get_file_format() {
local file="$1" local file="$1"
local probe local probe
probe="$(ffprobe \ probe="$(_ffprobe_wrapper \
-v error \ -v error \
-show_entries format=format_name \ -show_entries format=format_name \
-of default=noprint_wrappers=1:nokey=1 \ -of default=noprint_wrappers=1:nokey=1 \
"${file}")" || return 1 "${file}")" || return 1
if line_contains "${probe}" 'matroska'; then if line_contains "${probe}" 'matroska'; then
echo mkv echo mkv
else elif line_contains "${probe}" 'mp4'; then
echo mp4 echo mp4
else
echo "${probe}"
fi fi
} }
@@ -98,7 +145,7 @@ get_num_streams() {
select=("-select_streams" "${type}") select=("-select_streams" "${type}")
fi fi
ffprobe \ _ffprobe_wrapper \
-v error "${select[@]}" \ -v error "${select[@]}" \
-show_entries stream=index \ -show_entries stream=index \
-of default=noprint_wrappers=1:nokey=1 \ -of default=noprint_wrappers=1:nokey=1 \
@@ -108,7 +155,7 @@ get_num_streams() {
get_num_audio_channels() { get_num_audio_channels() {
local file="$1" local file="$1"
local stream="$2" local stream="$2"
ffprobe \ _ffprobe_wrapper \
-v error \ -v error \
-select_streams "${stream}" \ -select_streams "${stream}" \
-show_entries stream=channels \ -show_entries stream=channels \
@@ -119,7 +166,7 @@ get_num_audio_channels() {
get_stream_lang() { get_stream_lang() {
local file="$1" local file="$1"
local stream="$2" local stream="$2"
ffprobe \ _ffprobe_wrapper \
-v error \ -v error \
-select_streams "${stream}" \ -select_streams "${stream}" \
-show_entries stream_tags=language \ -show_entries stream_tags=language \

View File

@@ -79,6 +79,7 @@ $(encode)
- Skips re-encoding av1/opus streams. - Skips re-encoding av1/opus streams.
- Only maps audio streams that match the video stream language if the video stream has a defined language. - Only maps audio streams that match the video stream language if the video stream has a defined language.
- Only maps english subtitle streams. - Only maps english subtitle streams.
- Crop PGS subtitles to match video dimensions.
- Adds track statistics to the output mkv file and embeds the encoder versions to the output metadata. For example: - Adds track statistics to the output mkv file and embeds the encoder versions to the output metadata. For example:
\`\`\` \`\`\`
ENCODE : aa4d7e6 ENCODE : aa4d7e6

View File

@@ -88,6 +88,9 @@ $cmd "$@"' >"${ENTRY_SCRIPT}"
} }
gen_links || return 1 gen_links || return 1
# allow calling entry.sh with arguments as execution
entry() { "$@" ; }
set_completions() { set_completions() {
for funcName in "${FB_FUNC_NAMES[@]}"; do for funcName in "${FB_FUNC_NAMES[@]}"; do
complete -W "${FB_FUNC_COMPLETION[${funcName}]}" "${funcName}" complete -W "${FB_FUNC_COMPLETION[${funcName}]}" "${funcName}"