Compare commits

..

36 Commits

Author SHA1 Message Date
38c160e2bd improve add_project_versioning_to_ffmpeg and remove HEADLESS 2026-01-11 09:56:35 -06:00
47e287ef7f update svtav1params 2026-01-09 20:28:02 -06:00
691d0f857a libass 2026-01-06 17:48:09 -06:00
3c3fc6164c add -P to efg and some improvements 2026-01-03 15:31:09 -06:00
1adcaa15a4 tabs to spaces and make utils load first 2026-01-02 17:48:27 -06:00
108561a002 fix lack of which and rpath for darwin again 2025-12-23 20:37:14 -06:00
ff1b0c2304 fixes for rpath and libogg 2025-12-23 17:19:51 -06:00
ff656d477f small fixes for macos rpath 2025-12-23 09:20:44 -06:00
7ce42985e2 update DEFAULT_ENABLE 2025-12-22 18:33:50 -06:00
8feeccc46a add libvpx/vorbis 2025-12-22 18:33:15 -06:00
87f65e48f4 bandaid OOM, ensure mkvtoolnix 2025-12-22 09:21:09 -06:00
200d4c4a6c don't really care about pgo for rust 2025-12-21 11:26:05 -06:00
71c7266661 fix Jenkins syntax error 2025-12-20 23:11:26 -06:00
69ada12884 working PGO 2025-12-20 23:07:30 -06:00
30ddc39ed5 update README and init PGO 2025-12-20 12:02:06 -06:00
25093463af fix darwin not packaging 2025-12-20 11:57:18 -06:00
2356e05ad5 slight oversight 2025-12-19 23:14:36 -06:00
7419027a80 use alternative method to cache images 2025-12-19 23:01:29 -06:00
71a35d1320 flto=full 2025-12-19 15:27:58 -06:00
cf27104939 small mistake on LTO_FLAG 2025-12-19 15:27:28 -06:00
fac5951fc9 fix lto for rust and lame_init_old 2025-12-19 09:56:24 -06:00
ff7921c45d remove docker pruning 2025-12-18 14:50:41 -06:00
65fb35877e improvements to spinner 2025-12-18 13:24:58 -06:00
ad5b8e4482 fix syntax error 2025-12-18 09:02:48 -06:00
e6ab74c351 more fixes for android, and no more cmake3 2025-12-18 08:59:47 -06:00
1b8c411458 fix LTO_FLAG usage 2025-12-18 08:12:33 -06:00
5c9e97e0a2 update rustc 1.88->1.90 2025-12-17 19:01:11 -06:00
687a8473b5 more android fixes 2025-12-17 18:15:02 -06:00
c07b52f5c2 update docker base images shas 2025-12-17 18:11:12 -06:00
e1d5595b06 more fixing 2025-12-17 15:01:47 -06:00
c7c855a35a more small fixes 2025-12-17 13:39:50 -06:00
1b03235761 more small fixes 2025-12-16 17:48:07 -06:00
0b35c8b94a init llvm and lld 2025-12-16 17:01:18 -06:00
c038798d2c only build cargo-c if required 2025-12-12 14:28:54 -06:00
23d05a8c3a more windows fixes 2025-12-12 14:19:40 -06:00
c399c008ba disable lame frontend 2025-12-12 13:46:24 -06:00
19 changed files with 2833 additions and 2313 deletions

20
Jenkinsfile vendored
View File

@@ -13,7 +13,9 @@ def withDockerCreds(body) {
pipeline { pipeline {
agent none agent none
environment { DEBUG = "1" } environment {
DEBUG = "1"
}
options { buildDiscarder logRotator(numToKeepStr: '4') } options { buildDiscarder logRotator(numToKeepStr: '4') }
stages { stages {
stage('build docker image') { stage('build docker image') {
@@ -36,14 +38,16 @@ pipeline {
stage('build ffmpeg on darwin') { stage('build ffmpeg on darwin') {
matrix { matrix {
axes { axes {
axis { name 'OPT_LTO'; values 'OPT=0 LTO=OFF', 'OPT=3 LTO=ON' } axis {
axis { name 'STATIC'; values 'ON', 'OFF' } name 'COMP_OPTS';
values 'OPT=0 LTO=OFF STATIC=OFF', 'OPT=2 LTO=OFF STATIC=ON', 'OPT=3 LTO=ON STATIC=ON PGO=ON'
}
} }
stages { stages {
stage('build on darwin ') { stage('build on darwin ') {
agent { label "darwin" } agent { label "darwin" }
steps { steps {
sh "${OPT_LTO} ./scripts/build.sh" sh "${COMP_OPTS} ./scripts/build.sh"
archiveArtifacts allowEmptyArchive: true, artifacts: 'gitignore/package/*.tar.xz', defaultExcludes: false archiveArtifacts allowEmptyArchive: true, artifacts: 'gitignore/package/*.tar.xz', defaultExcludes: false
} }
} }
@@ -55,15 +59,17 @@ pipeline {
axes { axes {
axis { name 'ARCH'; values 'armv8-a', 'x86-64-v3' } axis { name 'ARCH'; values 'armv8-a', 'x86-64-v3' }
axis { name 'DISTRO'; values 'ubuntu', 'fedora', 'debian', 'archlinux' } axis { name 'DISTRO'; values 'ubuntu', 'fedora', 'debian', 'archlinux' }
axis { name 'OPT_LTO'; values 'OPT=0 LTO=OFF', 'OPT=3 LTO=ON' } axis {
axis { name 'STATIC'; values 'ON', 'OFF' } name 'COMP_OPTS';
values 'OPT=0 LTO=OFF STATIC=OFF', 'OPT=2 LTO=OFF STATIC=ON', 'OPT=3 LTO=ON STATIC=ON PGO=ON'
}
} }
stages { stages {
stage('build ffmpeg on linux using docker') { stage('build ffmpeg on linux using docker') {
agent { label "linux && ${ARCH}" } agent { label "linux && ${ARCH}" }
steps { steps {
withDockerCreds { withDockerCreds {
sh "${OPT_LTO} ./scripts/build_with_docker.sh ${DISTRO}" sh "${COMP_OPTS} ./scripts/build_with_docker.sh ${DISTRO}"
archiveArtifacts allowEmptyArchive: true, artifacts: 'gitignore/package/*.tar.xz', defaultExcludes: false archiveArtifacts allowEmptyArchive: true, artifacts: 'gitignore/package/*.tar.xz', defaultExcludes: false
} }
} }

View File

@@ -32,16 +32,20 @@ The default enabled libraries included in the `ffmpeg` build are:
- libx264 - libx264
- libx265 - libx265
- libwebp - libwebp
- libvpx
- libass
- libvorbis
- libmp3lame - libmp3lame
The user-overridable compile options are: The user-overridable compile options are:
- `CLEAN`: clean build directories before building (default: ON) - `ENABLE`: configure what ffmpeg enables (default: libsvtav1_psy libopus libdav1d libaom librav1e libvmaf libx264 libx265 libwebp libvpx libass libvorbis libmp3lame)
- `LTO`: enable link time optimization (default: ON)
- `OPT`: optimization level (0-3) (default: 3)
- `STATIC`: static or shared build (default: ON)
- `ARCH`: architecture type (x86-64-v{1,2,3,4}, armv8-a, etc) (default: native)
- `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)
- `ENABLE`: configure what ffmpeg enables (default: libsvtav1_psy libopus libdav1d libaom librav1e libvmaf libx264 libx265 libwebp libmp3lame) - `STATIC`: static or shared build (default: ON)
- `LTO`: enable link time optimization (default: ON)
- `CLEAN`: clean build directories before building (default: ON)
- `PGO`: enable profile guided optimization (default: OFF)
- `ARCH`: architecture type (x86-64-v{1,2,3,4}, armv8-a, etc) (default: native)
- `OPT`: optimization level (0-3) (default: 3)
Examples: Examples:
- only build libsvtav1_psy and libopus: `ENABLE='libsvtav1_psy libopus' ./scripts/build.sh` - only build libsvtav1_psy and libopus: `ENABLE='libsvtav1_psy libopus' ./scripts/build.sh`
@@ -77,7 +81,7 @@ encode -i input [options] output
[-v] print relevant version info [-v] print relevant version info
[-s] use same container as input, default is convert to mkv [-s] use same container as input, default is convert to mkv
[output] if unset, defaults to ${HOME}/av1-input-file-name.mkv [output] if unset, defaults to ${PWD}/av1-input-file-name.mkv
[-u] update script (git pull ffmpeg-builder) [-u] update script (git pull ffmpeg-builder)
[-I] system install at /usr/local/bin/encode [-I] system install at /usr/local/bin/encode
@@ -106,6 +110,7 @@ Example usage:
## Estimate film-grain ## Estimate film-grain
```bash ```bash
efg -i input [options] efg -i input [options]
[-P NUM] set preset (default: 10)
[-l NUM] low value (default: 0) [-l NUM] low value (default: 0)
[-s NUM] step value (default: 1) [-s NUM] step value (default: 1)
[-h NUM] high value (default: 30) [-h NUM] high value (default: 30)
@@ -120,33 +125,41 @@ efg -i input [options]
Example usage: Example usage:
- `efg -i input.mkv -p` - `efg -i input.mkv -p`
``` ```
1 +------------------------------------------------------------------------------------------------------+ 10000 +------------------------------------------------------------------------------------------------------------------------------------------+
| *****G***** + + + | | **G* + + + + + |
| *****G** '/tmp/plot.dat' ***G*** | | ** /Volumes/External/ffmpeg-builder/gitignore/tmp/efg-matrix-reloaded.mkv/plot.dat ***G*** |
0.95 |-+ ***** +-| | *G** |
| **G* | | **G |
| *** | 9000 |-+ ** +-|
| **** | | *G** |
0.9 |-+ *G* +-| | **G |
| **** | | ** |
| **** | | *G* |
0.85 |-+ *G* +-| 8000 |-+ ** +-|
| *** | | *G** |
| **** | | **G |
0.8 |-+ *G* +-| | ** |
| *** | | *G** |
| **** | 7000 |-+ **G* +-|
| *G* | | ** |
0.75 |-+ *** +-| | *G* |
| **** | | **G** |
| *G* | 6000 |-+ **G* +-|
0.7 |-+ *** +-| | ** |
| ** | | *G* |
| *** | | **G** |
| *G* | | **G* |
0.65 |-+ *** +-| 5000 |-+ **G** +-|
| **** | | **G****G* |
| + + + *| | **G** |
0.6 +------------------------------------------------------------------------------------------------------+ | **G****G* |
| **G****G* |
4000 |-+ **G****G****G* +-|
| **G****|
| |
| |
| + + + + + |
3000 +------------------------------------------------------------------------------------------------------------------------------------------+
0 5 10 15 20 25 30
``` ```

419
lib/0-utils.sh Normal file
View File

@@ -0,0 +1,419 @@
#!/usr/bin/env bash
# shellcheck disable=SC2034
# ANSI colors
RED='\e[0;31m'
CYAN='\e[0;36m'
GREEN='\e[0;32m'
YELLOW='\e[0;33m'
NC='\e[0m'
# echo wrappers
echo_wrapper() {
local args
if [[ $1 == '-n' ]]; then
args=("$1")
shift
fi
# COLOR is override for using ${color}
# shellcheck disable=SC2153
if [[ ${COLOR} == 'OFF' ]]; then
color=''
endColor=''
else
endColor="${NC}"
fi
echo -e "${args[@]}" "${color}${word:-''}${endColor}" "$@"
}
echo_fail() { color="${RED}" word="FAIL" echo_wrapper "$@"; }
echo_info() { color="${CYAN}" word="INFO" echo_wrapper "$@"; }
echo_pass() { color="${GREEN}" word="PASS" echo_wrapper "$@"; }
echo_warn() { color="${YELLOW}" word="WARN" echo_wrapper "$@"; }
echo_exit() {
echo_fail "$@"
exit 1
}
void() { echo "$@" >/dev/null; }
echo_if_fail() {
local cmd=("$@")
local logName="${LOGNAME:-${RANDOM}}-"
local out="${TMP_DIR}/${logName}stdout"
local err="${TMP_DIR}/${logName}stderr"
# set trace to the cmdEvalTrace and open file descriptor
local cmdEvalTrace="${TMP_DIR}/${logName}cmdEvalTrace"
exec 5>"${cmdEvalTrace}"
export BASH_XTRACEFD=5
set -x
"${cmd[@]}" >"${out}" 2>"${err}"
local retval=$?
# unset and close file descriptor
set +x
exec 5>&-
# parse out relevant part of the trace
local cmdEvalLines=()
while IFS= read -r line; do
line="${line/${PS4}/}"
test "${line}" == 'set +x' && continue
test "${line}" == '' && continue
cmdEvalLines+=("${line}")
done <"${cmdEvalTrace}"
if ! test ${retval} -eq 0; then
echo
echo_fail "command failed with ${retval}:"
printf "%s\n" "${cmdEvalLines[@]}"
echo_warn "command stdout:"
tail -n 32 "${out}"
echo_warn "command stderr:"
tail -n 32 "${err}"
echo
fi
if [[ -z ${LOGNAME} ]]; then
rm "${out}" "${err}" "${cmdEvalTrace}"
fi
return ${retval}
}
is_root_owned() {
local path=$1
local uid
if stat --version >/dev/null 2>&1; then
# GNU coreutils (Linux)
uid=$(stat -c '%u' "$path")
else
# BSD/macOS
uid=$(stat -f '%u' "$path")
fi
test "$uid" -eq 0
}
dump_arr() {
local arrayNames=("$@")
for arrayName in "${arrayNames[@]}"; do
declare -n array="${arrayName}"
arrayExpanded=("${array[@]}")
# skip showing single element arrays by default
if [[ ! ${#arrayExpanded[@]} -gt 1 ]]; then
if [[ ${SHOW_SINGLE} == true ]]; then
echo_info "${arrayName}='${arrayExpanded[*]}'"
else
continue
fi
fi
echo
# don't care that the variable has "ARR"
echo_info "${arrayName//"_ARR"/}"
printf "\t%s\n" "${arrayExpanded[@]}"
done
}
has_cmd() {
local cmds=("$@")
local rv=0
for cmd in "${cmds[@]}"; do
command -v "${cmd}" >/dev/null 2>&1 || rv=1
done
return ${rv}
}
missing_cmd() {
local cmds=("$@")
local rv=1
for cmd in "${cmds[@]}"; do
if ! has_cmd "${cmd}"; then
echo_warn "missing ${cmd}"
rv=0
fi
done
return ${rv}
}
bash_dirname() {
local tmp=${1:-.}
[[ $tmp != *[!/]* ]] && {
printf '/\n'
return
}
tmp=${tmp%%"${tmp##*[!/]}"}
[[ $tmp != */* ]] && {
printf '.\n'
return
}
tmp=${tmp%/*}
tmp=${tmp%%"${tmp##*[!/]}"}
printf '%s\n' "${tmp:-/}"
}
bash_basename() {
local tmp
path="$1"
suffix="${2:-''}"
tmp=${path%"${path##*[!/]}"}
tmp=${tmp##*/}
tmp=${tmp%"${suffix/"$tmp"/}"}
printf '%s\n' "${tmp:-/}"
}
bash_realpath() {
local file=$1
local dir
# If the file is already absolute
[[ $file == /* ]] && {
printf '%s\n' "$file"
return
}
# Otherwise: split into directory + basename
dir="$(bash_dirname "${file}")"
file="$(bash_basename "${file}")"
# If no directory component, use current directory
if [[ $dir == "$file" ]]; then
dir="$PWD"
else
# Save current dir, move into target dir, capture $PWD, then return
local oldpwd="$PWD"
cd "$dir" || return 1
dir="$PWD"
cd "$oldpwd" || return 1
fi
printf '%s/%s\n' "$dir" "$file"
}
line_contains() {
local line="$1"
local substr="$2"
if [[ $line == *"${substr}"* ]]; then
return 0
else
return 1
fi
}
line_starts_with() {
local line="$1"
local substr="$2"
if [[ $line == "${substr}"* ]]; then
return 0
else
return 1
fi
}
is_linux() {
line_contains "${OSTYPE}" 'linux'
}
is_darwin() {
line_contains "$(print_os)" darwin
}
is_windows() {
line_contains "$(print_os)" windows
}
is_android() {
line_contains "$(print_os)" android
}
print_os() {
# cached response
if [[ -n ${FB_OS} ]]; then
echo "${FB_OS}"
return 0
fi
unset FB_OS
if [[ -f /etc/os-release ]]; then
source /etc/os-release
FB_OS="${ID}"
if [[ ${VERSION_ID} != '' ]]; then
FB_OS+="-${VERSION_ID}"
fi
if line_starts_with "${FB_OS}" 'arch'; then
FB_OS='archlinux'
fi
else
FB_OS="$(uname -o)"
fi
# lowercase
FB_OS="${FB_OS,,}"
# special treatment for windows
if line_contains "${FB_OS}" 'windows' || line_contains "${FB_OS}" 'msys'; then
FB_OS='windows'
fi
echo "${FB_OS}"
}
is_positive_integer() {
local input="$1"
if [[ ${input} != ?(-)+([[:digit:]]) || ${input} -lt 0 ]]; then
echo_fail "${input} is not a positive integer"
return 1
fi
return 0
}
print_line_indent() {
local line="$1"
if [[ ${line} =~ ^( +) ]]; then
echo -n "${BASH_REMATCH[1]}"
fi
}
replace_line() {
local file="$1"
local search="$2"
local newLine="$3"
local newFile="${TMP_DIR}/$(bash_basename "${file}")"
test -f "${newFile}" && rm "${newFile}"
while IFS= read -r line; do
if line_contains "${line}" "${search}"; then
print_line_indent "${line}" >>"${newFile}"
echo -en "${newLine}" >>"${newFile}"
continue
fi
echo "${line}" >>"${newFile}"
done <"${file}"
cp "${newFile}" "${file}"
}
remove_line() {
local file="$1"
local search="$2"
replace_line "${file}" "${search}" ''
}
bash_sort() {
local arr=("$@")
local n=${#arr[@]}
local i j val1 val2
# Bubble sort, numeric comparison
for ((i = 0; i < n; i++)); do
for ((j = 0; j < n - i - 1; j++)); do
read -r val1 _ <<<"${arr[j]}"
read -r val2 _ <<<"${arr[j + 1]}"
if (("${val1}" > "${val2}")); then
local tmp=${arr[j]}
arr[j]=${arr[j + 1]}
arr[j + 1]=$tmp
fi
done
done
printf '%s\n' "${arr[@]}"
}
_start_spinner() {
local spinChars=(
"-"
'\'
"|"
"/"
)
sleep 1
while true; do
for ((ind = 0; ind < "${#spinChars[@]}"; ind++)); do
echo -ne "${spinChars[${ind}]}" '\b\b'
sleep .25
done
done
}
spinner() {
local action="$1"
local spinPidFile="${TMP_DIR}/.spinner-pid"
case "${action}" in
start)
test -f "${spinPidFile}" && rm "${spinPidFile}"
_start_spinner &
echo $! >"${spinPidFile}"
;;
stop)
test -f "${spinPidFile}" && kill "$(<"${spinPidFile}")"
echo -ne ' \n'
;;
esac
}
get_pkgconfig_version() {
local pkg="$1"
pkg-config --modversion "${pkg}"
}
using_cmake_4() {
local cmakeVersion
IFS=$' \t' read -r _ _ cmakeVersion <<<"$(command cmake --version)"
line_starts_with "${cmakeVersion}" 4
}
recreate_dir() {
local dirs=("$@")
for dir in "${dirs[@]}"; do
test -d "${dir}" && rm -rf "${dir}"
mkdir -p "${dir}" || return 1
done
}
ensure_dir() {
local dirs=("$@")
for dir in "${dirs[@]}"; do
test -d "${dir}" || mkdir -p "${dir}" || return 1
done
}
get_remote_head() {
local url="$1"
local remoteHEAD=''
IFS=$' \t' read -r remoteHEAD _ <<< \
"$(git ls-remote "${url}" HEAD)"
echo "${remoteHEAD}"
}
fb_max() {
local a="$1"
local b="$2"
test "${a}" -gt "${b}" &&
echo "${a}" ||
echo "${b}"
}
print_padded() {
local str="$1"
local padding="$2"
echo -n "${str}"
for ((i = 0; i < padding - ${#str}; i++)); do
echo -n ' '
done
}

File diff suppressed because it is too large Load Diff

View File

@@ -14,6 +14,9 @@ DEFAULT_CLEAN=ON
FB_COMP_OPTS_DESC['LTO']='enable link time optimization' FB_COMP_OPTS_DESC['LTO']='enable link time optimization'
DEFAULT_LTO=ON DEFAULT_LTO=ON
FB_COMP_OPTS_DESC['PGO']='enable profile guided optimization'
DEFAULT_PGO=OFF
FB_COMP_OPTS_DESC['OPT']='optimization level (0-3)' FB_COMP_OPTS_DESC['OPT']='optimization level (0-3)'
DEFAULT_OPT=3 DEFAULT_OPT=3
@@ -37,25 +40,26 @@ libvmaf \
libx264 \ libx264 \
libx265 \ libx265 \
libwebp \ libwebp \
libvpx \
libass \
libvorbis \
libmp3lame\ libmp3lame\
" "
# user-overridable compile option variable names # user-overridable compile option variable names
FB_COMP_OPTS=( FB_COMP_OPTS=("${!FB_COMP_OPTS_DESC[@]}")
CLEAN LTO OPT STATIC ARCH PREFIX ENABLE
)
# sets FB_COMP_OPTS to allow for user-overriding # sets FB_COMP_OPTS to allow for user-overriding
check_compile_opts_override() { check_compile_opts_override() {
for opt in "${FB_COMP_OPTS[@]}"; do for opt in "${FB_COMP_OPTS[@]}"; do
declare -n defOptVal="DEFAULT_${opt}" declare -n defOptVal="DEFAULT_${opt}"
declare -n optVal="${opt}" declare -n optVal="${opt}"
# use given value if not overridden # use given value if not overridden
if [[ -v optVal && ${optVal} != "${defOptVal}" ]]; then if [[ -v optVal && ${optVal} != "${defOptVal}" ]]; then
echo_info "setting given value for ${opt}=${optVal}" echo_info "setting given value for ${opt}=${optVal}"
declare -g "${opt}=${optVal}" declare -g "${opt}=${optVal}"
else else
declare -g "${opt}=${defOptVal}" declare -g "${opt}=${defOptVal}"
fi fi
done done
} }

View File

@@ -1,320 +1,326 @@
#!/usr/bin/env bash #!/usr/bin/env bash
VALID_DOCKER_IMAGES=( VALID_DOCKER_IMAGES=(
'ubuntu' 'ubuntu'
'fedora' 'fedora'
'debian' 'debian'
'archlinux' 'archlinux'
) )
DOCKER_WORKDIR='/workdir' DOCKER_WORKDIR='/workdir'
set_docker_run_flags() { set_docker_run_flags() {
local cargo_git="${IGN_DIR}/cargo/git" local cargo_git="${IGN_DIR}/cargo/git"
local cargo_registry="${IGN_DIR}/cargo/registry" local cargo_registry="${IGN_DIR}/cargo/registry"
test -d "${cargo_git}" || mkdir -p "${cargo_git}" ensure_dir "${cargo_git}" "${cargo_registry}"
test -d "${cargo_registry}" || mkdir -p "${cargo_registry}" DOCKER_RUN_FLAGS=(
DOCKER_RUN_FLAGS=( --rm
--rm -v "${cargo_git}:/root/.cargo/git"
-v "${cargo_git}:/root/.cargo/git" -v "${cargo_registry}:/root/.cargo/registry"
-v "${cargo_registry}:/root/.cargo/registry" -v "${REPO_DIR}:${REPO_DIR}"
-v "${REPO_DIR}:${REPO_DIR}" -w "${REPO_DIR}"
-w "${REPO_DIR}" -e "DEBUG=${DEBUG}"
-e "DEBUG=${DEBUG}" -t
) )
for opt in "${FB_COMP_OPTS[@]}"; do for opt in "${FB_COMP_OPTS[@]}"; do
declare -n defOptVal="DEFAULT_${opt}" declare -n defOptVal="DEFAULT_${opt}"
declare -n optVal="${opt}" declare -n optVal="${opt}"
if [[ -v optVal && ${optVal} != "${defOptVal}" ]]; then if [[ -v optVal && ${optVal} != "${defOptVal}" ]]; then
DOCKER_RUN_FLAGS+=("-e" "${opt}=${optVal}") DOCKER_RUN_FLAGS+=("-e" "${opt}=${optVal}")
fi fi
done done
} }
check_docker() { check_docker() {
if missing_cmd docker; then if missing_cmd docker; then
echo_info "install docker" echo_info "install docker"
curl https://get.docker.com -sSf | bash curl https://get.docker.com -sSf | bash
fi fi
set_docker_run_flags || return 1 set_docker_run_flags || return 1
} }
# get full image digest for a given image # get full image digest for a given image
get_docker_image_tag() { get_docker_image_tag() {
local image="$1" local image="$1"
local tag='' local tag=''
case "${image}" in case "${image}" in
ubuntu) tag='ubuntu:24.04@sha256:9cbed754112939e914291337b5e554b07ad7c392491dba6daf25eef1332a22e8' ;; ubuntu) tag='ubuntu:24.04@sha256:c35e29c9450151419d9448b0fd75374fec4fff364a27f176fb458d472dfc9e54' ;;
debian) tag='debian:13@sha256:833c135acfe9521d7a0035a296076f98c182c542a2b6b5a0fd7063d355d696be' ;; debian) tag='debian:13@sha256:0d01188e8dd0ac63bf155900fad49279131a876a1ea7fac917c62e87ccb2732d' ;;
fedora) tag='fedora:42@sha256:6af051ad0a294182c3a957961df6203d91f643880aa41c2ffe3d1302e7505890' ;; fedora) tag='fedora:42@sha256:b3d16134560afa00d7cc2a9e4967eb5b954512805f3fe27d8e70bbed078e22ea' ;;
archlinux) tag='ogarcia/archlinux:latest@sha256:b93f426b23cd0ea0e1befd7d58a26eaf3e6eda3c154c0e8dd75145d11c21304c' ;; archlinux) tag='ogarcia/archlinux:latest@sha256:1d70273180e43b1f51b41514bdaa73c61f647891a53a9c301100d5c4807bf628' ;;
esac esac
echo "${tag}" echo "${tag}"
} }
# change dash to colon for docker and add namespace # change dash to colon for docker and add namespace
set_distro_image_tag() { set_distro_image_tag() {
local image_tag="${1}" local image_tag="${1}"
echo "ffmpeg_builder_${image_tag//-/:}" echo "ffmpeg_builder_${image_tag//-/:}"
} }
# change colon to dash and add extension type # change colon to dash and add extension type
docker_image_archive_name() { docker_image_archive_name() {
local image_tag="${1}" local image_tag="${1}"
echo "${image_tag//:/-}.tar.zst" echo "${image_tag//:/-}.tar.zst"
} }
echo_platform() { echo_platform() {
local platKernel platCpu local platKernel platCpu
platKernel="$(uname)" platKernel="$(uname)"
platKernel="${platKernel,,}" platKernel="${platKernel,,}"
if [[ ${HOSTTYPE} == 'x86_64' ]]; then if [[ ${HOSTTYPE} == 'x86_64' ]]; then
platCpu='amd64' platCpu='amd64'
else else
platCpu='arm64' platCpu='arm64'
fi fi
echo "${platKernel}/${platCpu}" echo "${platKernel}/${platCpu}"
} }
validate_selected_image() { validate_selected_image() {
local selectedImage="$1" local selectedImage="$1"
local valid=1 local valid=1
for image in "${VALID_DOCKER_IMAGES[@]}"; do for image in "${VALID_DOCKER_IMAGES[@]}"; do
if [[ ${selectedImage} == "${image}" ]]; then if [[ ${selectedImage} == "${image}" ]]; then
valid=0 valid=0
break break
fi fi
done done
if [[ valid -eq 1 ]]; then if [[ valid -eq 1 ]]; then
echo_fail "${selectedImage} is not valid" echo_fail "${selectedImage} is not valid"
echo_info "valid images:" "${VALID_DOCKER_IMAGES[@]}" echo_info "valid images:" "${VALID_DOCKER_IMAGES[@]}"
return 1 return 1
fi fi
} }
docker_login() { docker_login() {
echo_if_fail docker login \ echo_if_fail docker login \
-u "${DOCKER_REGISTRY_USER}" \ -u "${DOCKER_REGISTRY_USER}" \
-p "${DOCKER_REGISTRY_PASS}" \ -p "${DOCKER_REGISTRY_PASS}" \
"${DOCKER_REGISTRY}" "${DOCKER_REGISTRY}"
} }
FB_FUNC_NAMES+=('docker_build_image') FB_FUNC_NAMES+=('docker_build_image')
FB_FUNC_DESCS['docker_build_image']='build a docker image with the required dependencies pre-installed' FB_FUNC_DESCS['docker_build_image']='build a docker image with the required dependencies pre-installed'
FB_FUNC_COMPLETION['docker_build_image']="${VALID_DOCKER_IMAGES[*]}" FB_FUNC_COMPLETION['docker_build_image']="${VALID_DOCKER_IMAGES[*]}"
docker_build_image() { docker_build_image() {
local image="$1" local image="$1"
validate_selected_image "${image}" || return 1 validate_selected_image "${image}" || return 1
check_docker || return 1 check_docker || return 1
PLATFORM="${PLATFORM:-$(echo_platform)}" PLATFORM="${PLATFORM:-$(echo_platform)}"
echo_info "sourcing package manager for ${image}" echo_info "sourcing package manager for ${image}"
local dockerDistro="$(get_docker_image_tag "${image}")" local dockerDistro="$(get_docker_image_tag "${image}")"
# specific file for evaluated package manager info # specific file for evaluated package manager info
distroPkgMgr="${DOCKER_DIR}/$(bash_basename "${image}")-pkg_mgr" local distroPkgMgr="${DOCKER_DIR}/$(bash_basename "${image}")-pkg_mgr"
# get package manager info # get package manager info
docker run \ docker run \
"${DOCKER_RUN_FLAGS[@]}" \ "${DOCKER_RUN_FLAGS[@]}" \
"${dockerDistro}" \ "${dockerDistro}" \
bash -c "./scripts/print_pkg_mgr.sh" | tr -d '\r' >"${distroPkgMgr}" bash -c "./scripts/print_pkg_mgr.sh" | tr -d '\r' >"${distroPkgMgr}"
# shellcheck disable=SC1090 # shellcheck disable=SC1090
cat "${distroPkgMgr}" cat "${distroPkgMgr}"
# shellcheck disable=SC1090 # shellcheck disable=SC1090
source "${distroPkgMgr}" source "${distroPkgMgr}"
dockerfile="${DOCKER_DIR}/Dockerfile_$(bash_basename "${image}")" local dockerfile="${DOCKER_DIR}/Dockerfile_$(bash_basename "${image}")"
{ local embedPath='/Dockerfile'
echo "FROM ${dockerDistro}" {
echo 'SHELL ["/bin/bash", "-c"]' echo "FROM ${dockerDistro}"
echo 'RUN ln -sf /bin/bash /bin/sh' echo 'SHELL ["/bin/bash", "-c"]'
echo 'ENV DEBIAN_FRONTEND=noninteractive' echo 'RUN ln -sf /bin/bash /bin/sh'
# arch is rolling release, so highly likely echo 'ENV DEBIAN_FRONTEND=noninteractive'
# an updated is required between pkg changes echo "RUN ${pkg_mgr_update} && ${pkg_mgr_upgrade} && ${pkg_install} ${req_pkgs[*]}"
if line_contains "${dockerDistro}" 'arch'; then
local archRuns=''
archRuns+="${pkg_mgr_update}"
archRuns+=" && ${pkg_mgr_upgrade}"
archRuns+=" && ${pkg_install} ${req_pkgs[*]}"
echo "RUN ${archRuns}"
else
echo "RUN ${pkg_mgr_update}"
echo "RUN ${pkg_mgr_upgrade}"
printf "RUN ${pkg_install} %s\n" "${req_pkgs[@]}"
fi
# ENV for pipx/rust # ENV for pipx/rust
echo 'ENV PIPX_HOME=/root/.local' echo 'ENV PIPX_HOME=/root/.local'
echo 'ENV PIPX_BIN_DIR=/root/.local/bin' echo 'ENV PIPX_BIN_DIR=/root/.local/bin'
echo 'ENV PATH="/root/.local/bin:$PATH"' echo 'ENV PATH="/root/.local/bin:$PATH"'
echo 'ENV CARGO_HOME="/root/.cargo"' echo 'ENV CARGO_HOME="/root/.cargo"'
echo 'ENV RUSTUP_HOME="/root/.rustup"' echo 'ENV RUSTUP_HOME="/root/.rustup"'
echo 'ENV PATH="/root/.cargo/bin:$PATH"' echo 'ENV PATH="/root/.cargo/bin:$PATH"'
# add to profile # add to profile
echo 'RUN export PIPX_HOME=${PIPX_HOME} >> /etc/profile' echo 'RUN export PIPX_HOME=${PIPX_HOME} >> /etc/profile'
echo 'RUN export PIPX_BIN_DIR=${PIPX_BIN_DIR} >> /etc/profile' echo 'RUN export PIPX_BIN_DIR=${PIPX_BIN_DIR} >> /etc/profile'
echo 'RUN export CARGO_HOME=${CARGO_HOME} >> /etc/profile' echo 'RUN export CARGO_HOME=${CARGO_HOME} >> /etc/profile'
echo 'RUN export RUSTUP_HOME=${RUSTUP_HOME} >> /etc/profile' echo 'RUN export RUSTUP_HOME=${RUSTUP_HOME} >> /etc/profile'
echo 'RUN export PATH=${PATH} >> /etc/profile' echo 'RUN export PATH=${PATH} >> /etc/profile'
# make nobody:nogroup usable # make nobody:nogroup usable
echo 'RUN sed -i '/nobody/d' /etc/passwd || true' echo 'RUN sed -i '/nobody/d' /etc/passwd || true'
echo 'RUN echo "nobody:x:65534:65534:nobody:/root:/bin/bash" >> /etc/passwd' echo 'RUN echo "nobody:x:65534:65534:nobody:/root:/bin/bash" >> /etc/passwd'
echo 'RUN sed -i '/nogroup/d' /etc/group || true' echo 'RUN sed -i '/nogroup/d' /etc/group || true'
echo 'RUN echo "nogroup:x:65534:" >> /etc/group' echo 'RUN echo "nogroup:x:65534:" >> /etc/group'
# open up permissions before switching user # open up permissions before switching user
echo 'RUN chmod 777 -R /root/' echo 'RUN chmod 777 -R /root/'
# run as nobody:nogroup for rest of install # run as nobody:nogroup for rest of install
echo 'USER 65534:65534' echo 'USER 65534:65534'
# pipx # pipx
echo "RUN pipx install virtualenv" echo "RUN pipx install virtualenv"
# rust # rust
local rustupVersion='1.28.2' local rustupVersion='1.28.2'
local rustcVersion='1.88.0' local rustcVersion='1.90.0'
local rustupTarball="rustup-${rustupVersion}.tar.gz" local rustupTarball="rustup-${rustupVersion}.tar.gz"
local rustupTarballPath="${DOCKER_DIR}/${rustupTarball}" local rustupTarballPath="${DOCKER_DIR}/${rustupTarball}"
if [[ ! -f ${rustupTarballPath} ]]; then if [[ ! -f ${rustupTarballPath} ]]; then
wget https://github.com/rust-lang/rustup/archive/refs/tags/${rustupVersion}.tar.gz -O "${rustupTarballPath}" wget https://github.com/rust-lang/rustup/archive/refs/tags/${rustupVersion}.tar.gz -O "${rustupTarballPath}"
fi fi
echo "ADD ${rustupTarball} /tmp/" echo "ADD ${rustupTarball} /tmp/"
local cargoInst="\ echo "RUN cd /tmp/rustup-${rustupVersion} && bash rustup-init.sh -y --default-toolchain=${rustcVersion}"
cd /tmp/rustup-${rustupVersion} \ # install cargo-binstall
&& bash rustup-init.sh -y --default-toolchain=${rustcVersion}" echo "RUN curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash"
echo "RUN ${cargoInst}" # install cargo-c
# install cargo-binstall echo "RUN cargo-binstall -y cargo-c"
echo "RUN curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash"
# install cargo-c
echo "RUN cargo-binstall -y cargo-c"
echo "WORKDIR ${DOCKER_WORKDIR}" # final mods for PS1
echo
echo 'USER root'
echo "RUN echo \"PS1='id=\\\$(id -u)@${image}:\w\\$ '\" >> /etc/bash.bashrc"
echo 'USER 65534:65534'
echo
} >"${dockerfile}" # embed dockerfile into docker image itself
# shellcheck disable=SC2094
echo "COPY $(bash_basename "${dockerfile}") ${embedPath}"
image_tag="$(set_distro_image_tag "${image}")" echo "WORKDIR ${DOCKER_WORKDIR}"
docker buildx build \
--platform "${PLATFORM}" \
-t "${image_tag}" \
-f "${dockerfile}" \
"${DOCKER_DIR}" || return 1
# if a docker registry is defined, push to it } >"${dockerfile}"
if [[ ${DOCKER_REGISTRY} != '' ]]; then
docker_login || return 1
docker buildx build \
--push \
--platform "${PLATFORM}" \
-t "${DOCKER_REGISTRY}/${image_tag}" \
-f "${dockerfile}" \
"${DOCKER_DIR}" || return 1
fi
# FIXME uncomment # docker buildx is too aggressive with invalidating
# docker system prune -f # build layer caches. Instead of relying on docker
# to check for when to rebuild, compare the to-build
# dockerfile with the embedded dockerfile
local oldDockerfile="${dockerfile}.old"
docker_run_image "${image}" cp "${embedPath}" "${oldDockerfile}"
if diff "${dockerfile}" "${oldDockerfile}"; then
echo_pass "no dockerfile changes detected, skipping rebuild"
return 0
else
echo_warn "dockerfile changes detected, proceeding with build"
fi
image_tag="$(set_distro_image_tag "${image}")"
docker buildx build \
--platform "${PLATFORM}" \
-t "${image_tag}" \
-f "${dockerfile}" \
"${DOCKER_DIR}" || return 1
# if a docker registry is defined, push to it
if [[ ${DOCKER_REGISTRY} != '' ]]; then
docker_login || return 1
docker buildx build \
--push \
--platform "${PLATFORM}" \
-t "${DOCKER_REGISTRY}/${image_tag}" \
-f "${dockerfile}" \
"${DOCKER_DIR}" || return 1
fi
} }
FB_FUNC_NAMES+=('docker_save_image') FB_FUNC_NAMES+=('docker_save_image')
FB_FUNC_DESCS['docker_save_image']='save docker image into tar.zst' FB_FUNC_DESCS['docker_save_image']='save docker image into tar.zst'
FB_FUNC_COMPLETION['docker_save_image']="${VALID_DOCKER_IMAGES[*]}" FB_FUNC_COMPLETION['docker_save_image']="${VALID_DOCKER_IMAGES[*]}"
docker_save_image() { docker_save_image() {
local image="$1" local image="$1"
validate_selected_image "${image}" || return 1 validate_selected_image "${image}" || return 1
check_docker || return 1 check_docker || return 1
image_tag="$(set_distro_image_tag "${image}")" image_tag="$(set_distro_image_tag "${image}")"
echo_info "saving docker image for ${image_tag}" echo_info "saving docker image for ${image_tag}"
docker save "${image_tag}" | docker save "${image_tag}" |
zstd -T0 >"${DOCKER_DIR}/$(docker_image_archive_name "${image_tag}")" || zstd -T0 >"${DOCKER_DIR}/$(docker_image_archive_name "${image_tag}")" ||
return 1 return 1
} }
FB_FUNC_NAMES+=('docker_load_image') FB_FUNC_NAMES+=('docker_load_image')
FB_FUNC_DESCS['docker_load_image']='load docker image from tar.zst' FB_FUNC_DESCS['docker_load_image']='load docker image from tar.zst'
FB_FUNC_COMPLETION['docker_load_image']="${VALID_DOCKER_IMAGES[*]}" FB_FUNC_COMPLETION['docker_load_image']="${VALID_DOCKER_IMAGES[*]}"
docker_load_image() { docker_load_image() {
local image="$1" local image="$1"
validate_selected_image "${image}" || return 1 validate_selected_image "${image}" || return 1
check_docker || return 1 check_docker || return 1
image_tag="$(set_distro_image_tag "${image}")" image_tag="$(set_distro_image_tag "${image}")"
echo_info "loading docker image for ${image_tag}" echo_info "loading docker image for ${image_tag}"
local archive="${DOCKER_DIR}/$(docker_image_archive_name "${image_tag}")" local archive="${DOCKER_DIR}/$(docker_image_archive_name "${image_tag}")"
test -f "$archive" || return 1 test -f "$archive" || return 1
zstdcat -T0 "$archive" | zstdcat -T0 "$archive" | docker load || return 1
docker load || return 1 docker system prune -f
docker system prune -f
} }
FB_FUNC_NAMES+=('docker_run_image') FB_FUNC_NAMES+=('docker_run_image')
FB_FUNC_DESCS['docker_run_image']='run a docker image with the given arguments' FB_FUNC_DESCS['docker_run_image']='run a docker image with the given arguments'
FB_FUNC_COMPLETION['docker_run_image']="${VALID_DOCKER_IMAGES[*]}" FB_FUNC_COMPLETION['docker_run_image']="${VALID_DOCKER_IMAGES[*]}"
docker_run_image() { docker_run_image() {
local image="$1" local image="$1"
shift shift
validate_selected_image "${image}" || return 1 validate_selected_image "${image}" || return 1
check_docker || return 1 check_docker || return 1
local cmd=("$@") local cmd=("$@")
local runCmd=() local runCmd=()
if [[ ${cmd[*]} == '' ]]; then if [[ ${cmd[*]} == '' ]]; then
DOCKER_RUN_FLAGS+=("-it") DOCKER_RUN_FLAGS+=("-it")
else else
runCmd+=("${cmd[@]}") runCmd+=("${cmd[@]}")
fi fi
image_tag="$(set_distro_image_tag "${image}")" local image_tag="$(set_distro_image_tag "${image}")"
# if a docker registry is defined, pull from it # if a docker registry is defined, pull from it
if [[ ${DOCKER_REGISTRY} != '' ]]; then if [[ ${DOCKER_REGISTRY} != '' ]]; then
docker_login || return 1 docker_login || return 1
docker pull \ docker pull \
"${DOCKER_REGISTRY}/${image_tag}" || return 1 "${DOCKER_REGISTRY}/${image_tag}" || return 1
docker tag "${DOCKER_REGISTRY}/${image_tag}" "${image_tag}" docker tag "${DOCKER_REGISTRY}/${image_tag}" "${image_tag}"
fi fi
echo_info "running ffmpeg build with ${image_tag}" echo_info "running docker image ${image_tag}"
docker run \ docker run \
"${DOCKER_RUN_FLAGS[@]}" \ "${DOCKER_RUN_FLAGS[@]}" \
-u "$(id -u):$(id -g)" \ -u "$(id -u):$(id -g)" \
"${image_tag}" \ "${image_tag}" \
"${runCmd[@]}" || return 1 "${runCmd[@]}"
docker system prune -f local rv=$?
docker image prune -f
return 0 return ${rv}
} }
FB_FUNC_NAMES+=('build_with_docker') FB_FUNC_NAMES+=('build_with_docker')
FB_FUNC_DESCS['build_with_docker']='run docker image with given flags' FB_FUNC_DESCS['build_with_docker']='run docker image with given flags'
FB_FUNC_COMPLETION['build_with_docker']="${VALID_DOCKER_IMAGES[*]}" FB_FUNC_COMPLETION['build_with_docker']="${VALID_DOCKER_IMAGES[*]}"
build_with_docker() { build_with_docker() {
local image="$1" local image="$1"
docker_run_image "${image}" ./scripts/build.sh || return 1 docker_run_image "${image}" ./scripts/build.sh || return 1
} }
FB_FUNC_NAMES+=('docker_build_multiarch_image') FB_FUNC_NAMES+=('docker_build_multiarch_image')
FB_FUNC_DESCS['docker_build_multiarch_image']='build multiarch docker image' FB_FUNC_DESCS['docker_build_multiarch_image']='build multiarch docker image'
FB_FUNC_COMPLETION['docker_build_multiarch_image']="${VALID_DOCKER_IMAGES[*]}" FB_FUNC_COMPLETION['docker_build_multiarch_image']="${VALID_DOCKER_IMAGES[*]}"
docker_build_multiarch_image() { docker_build_multiarch_image() {
local image="$1" local image="$1"
validate_selected_image "${image}" || return 1 validate_selected_image "${image}" || return 1
check_docker || return 1 check_docker || return 1
PLATFORM='linux/amd64,linux/arm64' PLATFORM='linux/amd64,linux/arm64'
# check if we need to create multiplatform builder # check if we need to create multiplatform builder
local buildxPlats="$(docker buildx inspect | grep Platforms)" local buildxPlats="$(docker buildx inspect | grep Platforms)"
IFS=',' IFS=','
local createBuilder=0 local createBuilder=0
for plat in $PLATFORM; do for plat in $PLATFORM; do
grep -q "${plat}" <<<"${buildxPlats}" || createBuilder=1 grep -q "${plat}" <<<"${buildxPlats}" || createBuilder=1
done done
unset IFS unset IFS
if [[ ${createBuilder} == 1 ]]; then if [[ ${createBuilder} == 1 ]]; then
echo_info "creating multiplatform (${PLATFORM}) docker builder" echo_info "creating multiplatform (${PLATFORM}) docker builder"
docker buildx create \ docker buildx create \
--use \ --use \
--platform="${PLATFORM}" \ --platform="${PLATFORM}" \
--name my-multiplatform-builder \ --name my-multiplatform-builder \
--driver=docker-container --driver=docker-container
fi fi
docker_build_image "$@" docker_build_image "$@"
} }

View File

@@ -1,232 +1,246 @@
#!/usr/bin/env bash #!/usr/bin/env bash
efg_usage() { efg_usage() {
echo "efg -i input [options]" echo "efg -i input [options]"
echo -e "\t[-l NUM] low value (default: ${LOW})" echo -e "\t[-P NUM] set preset (default: ${PRESET})"
echo -e "\t[-s NUM] step value (default: ${STEP})" echo -e "\t[-l NUM] low value (default: ${LOW})"
echo -e "\t[-h NUM] high value (default: ${HIGH})" echo -e "\t[-s NUM] step value (default: ${STEP})"
echo -e "\t[-p] plot bitrates using gnuplot" echo -e "\t[-h NUM] high value (default: ${HIGH})"
echo -e "\n\t[-I] system install at ${EFG_INSTALL_PATH}" echo -e "\t[-p] plot bitrates using gnuplot"
echo -e "\t[-U] uninstall from ${EFG_INSTALL_PATH}" echo -e "\n\t[-I] system install at ${EFG_INSTALL_PATH}"
return 0 echo -e "\t[-U] uninstall from ${EFG_INSTALL_PATH}"
return 0
} }
set_efg_opts() { set_efg_opts() {
local opts='pl:s:h:i:IU' local opts='P:pl:s:h:i:IU'
local numOpts=${#opts} local numOpts=${#opts}
# default values # default values
unset INPUT unset INPUT
LOW=0 PRESET=10
STEP=1 LOW=0
HIGH=30 STEP=1
PLOT=false HIGH=30
EFG_INSTALL_PATH='/usr/local/bin/efg' PLOT=false
# only using -I or -U EFG_INSTALL_PATH='/usr/local/bin/efg'
local minOpt=1 # only using -I or -U
# using all local minOpt=1
local maxOpt=${numOpts} # using all
test $# -lt ${minOpt} && efg_usage && return 1 local maxOpt=${numOpts}
test $# -gt ${maxOpt} && efg_usage && return 1 test $# -lt ${minOpt} && efg_usage && return 1
local OPTARG OPTIND test $# -gt ${maxOpt} && efg_usage && return 1
while getopts "${opts}" flag; do local OPTARG OPTIND
case "${flag}" in while getopts "${opts}" flag; do
I) case "${flag}" in
echo_warn "attempting install" P)
sudo ln -sf "${SCRIPT_DIR}/efg.sh" \ if ! is_positive_integer "${OPTARG}"; then
"${EFG_INSTALL_PATH}" || return 1 efg_usage
echo_pass "succesfull install" return 1
exit 0 fi
;; PRESET="${OPTARG}"
U) ;;
echo_warn "attempting uninstall" I)
sudo rm "${EFG_INSTALL_PATH}" || return 1 echo_warn "attempting install"
echo_pass "succesfull uninstall" sudo ln -sf "${SCRIPT_DIR}/efg.sh" \
exit 0 "${EFG_INSTALL_PATH}" || return 1
;; echo_pass "succesfull install"
i) return ${FUNC_EXIT_SUCCESS}
if [[ $# -lt 2 ]]; then ;;
echo_fail "wrong arguments given" U)
efg_usage echo_warn "attempting uninstall"
return 1 sudo rm "${EFG_INSTALL_PATH}" || return 1
fi echo_pass "succesfull uninstall"
INPUT="${OPTARG}" return ${FUNC_EXIT_SUCCESS}
;; ;;
p) i)
missing_cmd gnuplot && return 1 if [[ $# -lt 2 ]]; then
PLOT=true echo_fail "wrong arguments given"
;; efg_usage
l) return 1
if ! is_positive_integer "${OPTARG}"; then fi
efg_usage INPUT="${OPTARG}"
return 1 ;;
fi p)
LOW="${OPTARG}" missing_cmd gnuplot && return 1
;; PLOT=true
s) ;;
if ! is_positive_integer "${OPTARG}"; then l)
efg_usage if ! is_positive_integer "${OPTARG}"; then
return 1 efg_usage
fi return 1
STEP="${OPTARG}" fi
;; LOW="${OPTARG}"
h) ;;
if ! is_positive_integer "${OPTARG}"; then s)
efg_usage if ! is_positive_integer "${OPTARG}"; then
return 1 efg_usage
fi return 1
HIGH="${OPTARG}" fi
;; STEP="${OPTARG}"
*) ;;
echo "wrong flags given" h)
efg_usage if ! is_positive_integer "${OPTARG}"; then
return 1 efg_usage
;; return 1
esac fi
done HIGH="${OPTARG}"
;;
*)
echo "wrong flags given"
efg_usage
return 1
;;
esac
done
if [[ ! -f ${INPUT} ]]; then if [[ ! -f ${INPUT} ]]; then
echo "${INPUT} does not exist" echo "${INPUT} does not exist"
efg_usage efg_usage
return 1 return 1
fi fi
# set custom EFG_DIR based off of sanitized inputfile # set custom EFG_DIR based off of sanitized inputfile
local sanitizedInput="$(bash_basename "${INPUT}")" local sanitizedInput="$(bash_basename "${INPUT}")"
local sanitizeChars=(' ' '@' ':') local sanitizeChars=(' ' '@' ':')
for char in "${sanitizeChars[@]}"; do for char in "${sanitizeChars[@]}"; do
sanitizedInput="${sanitizedInput//${char}/}" sanitizedInput="${sanitizedInput//${char}/}"
done done
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() {
# number of segments to split video # number of segments to split video
local segments=30 local segments=30
# duration of each segment # duration of each segment
local segmentTime=3 local segmentTime=3
# get times to split the input based # get times to split the input based
# off of number of segments # off of number of segments
local duration local duration
duration="$(get_duration "${INPUT}")" || return 1 duration="$(get_duration "${INPUT}")" || return 1
# trim decimal points if any # trim decimal points if any
IFS=. read -r duration _ <<<"${duration}" IFS=. read -r duration _ <<<"${duration}"
# number of seconds that equal 1 percent of the video # number of seconds that equal 1 percent of the video
local percentTime=$((duration / 100)) local percentTime=$((duration / 100))
# percent that each segment takes # percent that each segment takes
local percentSegment=$((100 / segments)) local percentSegment=$((100 / segments))
# number of seconds to increment between segments # number of seconds to increment between segments
local timeBetweenSegments=$((percentTime * percentSegment)) local timeBetweenSegments=$((percentTime * percentSegment))
if [[ ${timeBetweenSegments} -lt ${segmentTime} ]]; then if [[ ${timeBetweenSegments} -lt ${segmentTime} ]]; then
timeBetweenSegments=${segmentTime} timeBetweenSegments=${segmentTime}
fi fi
local segmentBitrates=() local segmentBitrates=()
# clean workspace # clean workspace
test -d "${EFG_DIR}" && rm -rf "${EFG_DIR}" recreate_dir "${EFG_DIR}" || return 1
mkdir -p "${EFG_DIR}"
# split up video into segments based on start times # split up video into segments based on start times
for ((time = 0; time < duration; time += timeBetweenSegments)); do for ((time = 0; time < duration; time += timeBetweenSegments)); do
local outSegment="${EFG_DIR}/segment-${#segmentBitrates[@]}.mkv" local outSegment="${EFG_DIR}/segment-${#segmentBitrates[@]}.mkv"
split_video "${INPUT}" "${time}" "${segmentTime}" "${outSegment}" || return 1 split_video "${INPUT}" "${time}" "${segmentTime}" "${outSegment}" || return 1
local segmentBitrate local segmentBitrate
segmentBitrate="$(get_avg_bitrate "${outSegment}")" || return 1 segmentBitrate="$(get_avg_bitrate "${outSegment}")" || return 1
segmentBitrates+=("${segmentBitrate}:${outSegment}") segmentBitrates+=("${segmentBitrate}:${outSegment}")
done done
local numSegments="${#segmentBitrates[@]}" local numSegments="${#segmentBitrates[@]}"
local removeSegments local removeSegments
if [[ ${numSegments} -lt ${ENCODE_SEGMENTS} ]]; then if [[ ${numSegments} -lt ${ENCODE_SEGMENTS} ]]; then
removeSegments=0 removeSegments=0
else else
removeSegments=$((numSegments - ENCODE_SEGMENTS)) removeSegments=$((numSegments - ENCODE_SEGMENTS))
fi fi
# sort the segments # sort the segments
mapfile -t sortedSegments < <(IFS=: bash_sort "${segmentBitrates[@]}") mapfile -t sortedSegments < <(IFS=: bash_sort "${segmentBitrates[@]}")
# make sure bitrate for each file is actually increasing # make sure bitrate for each file is actually increasing
local prevBitrate=0 local prevBitrate=0
# remove all but the highest bitrate segments # remove all but the highest bitrate segments
for segment in "${sortedSegments[@]}"; do for segment in "${sortedSegments[@]}"; do
test ${removeSegments} -eq 0 && break test ${removeSegments} -eq 0 && break
local file currBitrate local file currBitrate
IFS=: read -r _ file <<<"${segment}" IFS=: read -r _ file <<<"${segment}"
currBitrate="$(get_avg_bitrate "${file}")" || return 1 currBitrate="$(get_avg_bitrate "${file}")" || return 1
if [[ ${currBitrate} -lt ${prevBitrate} ]]; then if [[ ${currBitrate} -lt ${prevBitrate} ]]; then
echo_fail "${file} is not a higher bitrate than previous" echo_fail "${file} is not a higher bitrate than previous"
return 1 return 1
fi fi
prevBitrate=${currBitrate} prevBitrate=${currBitrate}
rm "${file}" || return 1 rm "${file}" || return 1
removeSegments=$((removeSegments - 1)) removeSegments=$((removeSegments - 1))
done done
} }
efg_encode() { efg_encode() {
echo -n >"${GRAIN_LOG}" local grainLogWIP="${GRAIN_LOG}.wip"
for vid in "${EFG_DIR}/"*.mkv; do echo -n >"${grainLogWIP}"
echo "file: ${vid}" >>"${GRAIN_LOG}" for vid in "${EFG_DIR}/"*.mkv; do
for ((grain = LOW; grain <= HIGH; grain += STEP)); do echo "file: ${vid}" >>"${grainLogWIP}"
local file="$(bash_basename "${vid}")" for ((grain = LOW; grain <= HIGH; grain += STEP)); do
local out="${EFG_DIR}/grain-${grain}-${file}" local file="$(bash_basename "${vid}")"
echo_info "encoding ${file} with grain ${grain}" local out="${EFG_DIR}/grain-${grain}-${file}"
echo_if_fail encode -P 10 -g ${grain} -i "${vid}" "${out}" echo_info "encoding ${file} with grain ${grain}"
echo -e "\tgrain: ${grain}, bitrate: $(get_avg_bitrate "${out}")" >>"${GRAIN_LOG}" echo_if_fail encode -P "${PRESET}" -g ${grain} -i "${vid}" "${out}" || return 1
rm "${out}" echo -e "\tgrain: ${grain}, bitrate: $(get_avg_bitrate "${out}")" >>"${grainLogWIP}"
done rm "${out}" || return 1
done done
# remove segment once complete
rm "${vid}" || return 1
done
less "${GRAIN_LOG}" # atomic move of grain log
mv "${grainLogWIP}" "${GRAIN_LOG}" || return 1
echo "$(<"${GRAIN_LOG}")"
} }
efg_plot() { efg_plot() {
declare -A normalizedBitrateSums=() declare -A normalizedBitrateSums=()
local referenceBitrate='' local referenceBitrate=''
local setNewReference='' local setNewReference=''
while read -r line; do while read -r line; do
local noWhite="${line// /}" local noWhite="${line// /}"
# new file, reset logic # new file, reset logic
if line_starts_with "${noWhite}" 'file:'; then if line_starts_with "${noWhite}" 'file:'; then
setNewReference=true setNewReference=true
continue continue
fi fi
IFS=',' read -r grainText bitrateText <<<"${noWhite}" IFS=',' read -r grainText bitrateText <<<"${noWhite}"
IFS=':' read -r _ grain <<<"${grainText}" IFS=':' read -r _ grain <<<"${grainText}"
IFS=':' read -r _ bitrate <<<"${bitrateText}" IFS=':' read -r _ bitrate <<<"${bitrateText}"
if [[ ${setNewReference} == true ]]; then if [[ ${setNewReference} == true ]]; then
referenceBitrate="${bitrate}" referenceBitrate="${bitrate}"
setNewReference=false setNewReference=false
fi fi
# bash doesn't support floats, so scale up by 10000 # bash doesn't support floats, so scale up by 10000
local normBitrate=$((bitrate * 10000 / referenceBitrate)) local normBitrate=$((bitrate * 10000 / referenceBitrate))
local currSumBitrate=${normalizedBitrateSums[${grain}]} local currSumBitrate=${normalizedBitrateSums[${grain}]}
normalizedBitrateSums[${grain}]=$((normBitrate + currSumBitrate)) normalizedBitrateSums[${grain}]=$((normBitrate + currSumBitrate))
setNewReference=false setNewReference=false
done <"${GRAIN_LOG}" done <"${GRAIN_LOG}"
# create grain:average plot file # create grain:average plot file
local plotFile="${EFG_DIR}/plot.dat" local plotFile="${EFG_DIR}/plot.dat"
echo -n >"${plotFile}" echo -n >"${plotFile}"
for ((grain = LOW; grain <= HIGH; grain += STEP)); do for ((grain = LOW; grain <= HIGH; grain += STEP)); do
local sum=${normalizedBitrateSums[${grain}]} local sum=${normalizedBitrateSums[${grain}]}
local avg=$((sum / ENCODE_SEGMENTS)) local avg=$((sum / ENCODE_SEGMENTS))
echo -e "${grain}\t${avg}" >>"${plotFile}" echo -e "${grain}\t${avg}" >>"${plotFile}"
done done
# plot data # plot data
# run subprocess for bash COLUMNS/LINES # run subprocess for bash COLUMNS/LINES
shopt -s checkwinsize shopt -s checkwinsize
(true) (true)
gnuplot -p -e "\ gnuplot -p -e "\
set terminal dumb size ${COLUMNS}, ${LINES}; \ set terminal dumb size ${COLUMNS}, ${LINES}; \
set autoscale; \ set autoscale; \
set style line 1 \ set style line 1 \
@@ -234,31 +248,37 @@ linecolor rgb '#0060ad' \
linetype 1 linewidth 2 \ linetype 1 linewidth 2 \
pointtype 7 pointsize 1.5; \ pointtype 7 pointsize 1.5; \
plot \"${plotFile}\" with linespoints linestyle 1" | less plot \"${plotFile}\" with linespoints linestyle 1" | less
echo_info "grain log: ${GRAIN_LOG}" echo_info "grain log: ${GRAIN_LOG}"
} }
FB_FUNC_NAMES+=('efg') 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() {
EFG_DIR="${TMP_DIR}/efg" EFG_DIR="${TMP_DIR}/efg"
# encode N highest-bitrate segments # encode N highest-bitrate segments
ENCODE_SEGMENTS=5 ENCODE_SEGMENTS=5
set_efg_opts "$@" || return 1 set_efg_opts "$@"
test -d "${EFG_DIR}" || mkdir "${EFG_DIR}" local ret=$?
if [[ ${ret} -eq ${FUNC_EXIT_SUCCESS} ]]; then
return 0
elif [[ ${ret} -ne 0 ]]; then
return ${ret}
fi
ensure_dir "${EFG_DIR}"
GRAIN_LOG="${EFG_DIR}/${LOW}-${STEP}-${HIGH}-grains.txt" GRAIN_LOG="${EFG_DIR}/${LOW}-${STEP}-${HIGH}-grains.txt"
if [[ ${PLOT} == true && -f ${GRAIN_LOG} ]]; then if [[ ${PLOT} == true && -f ${GRAIN_LOG} ]]; then
efg_plot efg_plot
return $? return $?
fi fi
efg_segment || return 1 efg_segment || return 1
efg_encode || return 1 efg_encode || return 1
if [[ ${PLOT} == true && -f ${GRAIN_LOG} ]]; then if [[ ${PLOT} == true && -f ${GRAIN_LOG} ]]; then
efg_plot || return 1 efg_plot || return 1
fi fi
} }

View File

@@ -2,475 +2,483 @@
# sets unmapStreams # sets unmapStreams
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=() unmapStreams=()
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}") unmapStreams+=("-map" "-0:${stream}")
fi fi
done done
} }
# sets audioParams # sets audioParams
set_audio_params() { set_audio_params() {
local file="$1" local file="$1"
local videoLang local videoLang
audioParams=() audioParams=()
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
numChannels="$(get_num_audio_channels "${file}" "${stream}")" || return 1 numChannels="$(get_num_audio_channels "${file}" "${stream}")" || return 1
if [[ ${numChannels} == '' ]]; then if [[ ${numChannels} == '' ]]; then
echo_fail "could not obtain channel count for stream ${stream}" echo_fail "could not obtain channel count for stream ${stream}"
return 1 return 1
fi fi
local channelBitrate=$((numChannels * 64)) local channelBitrate=$((numChannels * 64))
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+=( audioParams+=(
'-map' '-map'
"-0:${stream}" "-0:${stream}"
) )
elif [[ ${codec} == 'opus' ]]; then elif [[ ${codec} == 'opus' ]]; then
audioParams+=( audioParams+=(
"-c:${OUTPUT_INDEX}" "-c:${OUTPUT_INDEX}"
"copy" "copy"
) )
OUTPUT_INDEX=$((OUTPUT_INDEX + 1)) OUTPUT_INDEX=$((OUTPUT_INDEX + 1))
else else
audioParams+=( audioParams+=(
"-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}"
"libopus" "libopus"
"-b:${OUTPUT_INDEX}" "-b:${OUTPUT_INDEX}"
"${channelBitrate}k" "${channelBitrate}k"
) )
OUTPUT_INDEX=$((OUTPUT_INDEX + 1)) OUTPUT_INDEX=$((OUTPUT_INDEX + 1))
fi fi
done done
} }
# sets subtitleParams # sets subtitleParams
set_subtitle_params() { set_subtitle_params() {
local file="$1" local file="$1"
local convertCodec='eia_608' local convertCodec='eia_608'
local keepLang='eng' local keepLang='eng'
local defaultTextCodec local defaultTextCodec
if [[ ${SAME_CONTAINER} == false && ${FILE_EXT} == 'mkv' ]]; then if [[ ${SAME_CONTAINER} == false && ${FILE_EXT} == 'mkv' ]]; then
defaultTextCodec='srt' defaultTextCodec='srt'
convertCodec+='|mov_text' convertCodec+='|mov_text'
else else
defaultTextCodec='mov_text' defaultTextCodec='mov_text'
convertCodec+='|srt' convertCodec+='|srt'
fi fi
subtitleParams=() subtitleParams=()
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} != '' && ${keepLang} != "${lang}" ]]; then
subtitleParams+=( subtitleParams+=(
'-map' '-map'
"-0:${stream}" "-0:${stream}"
) )
elif [[ ${codec} =~ ${convertCodec} ]]; then elif [[ ${codec} =~ ${convertCodec} ]]; then
subtitleParams+=("-c:${OUTPUT_INDEX}" "${defaultTextCodec}") subtitleParams+=("-c:${OUTPUT_INDEX}" "${defaultTextCodec}")
OUTPUT_INDEX=$((OUTPUT_INDEX + 1)) OUTPUT_INDEX=$((OUTPUT_INDEX + 1))
fi fi
done done
} }
get_encode_versions() { get_encode_versions() {
action="${1:-}" action="${1:-}"
encodeVersion="encode=$(git -C "${REPO_DIR}" rev-parse --short HEAD)" encodeVersion="encode=$(git -C "${REPO_DIR}" rev-parse --short HEAD)"
ffmpegVersion='' ffmpegVersion=''
videoEncVersion='' videoEncVersion=''
audioEncVersion='' audioEncVersion=''
# 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_contains "${line}" 'ffmpeg='; then if line_starts_with "${line}" 'ffmpeg='; then
ffmpegVersion="${line}" ffmpegVersion="${line}"
elif line_contains "${line}" 'libsvtav1_psy=' || line_contains "${line}" 'libsvtav1='; then elif line_starts_with "${line}" 'libsvtav1'; then
videoEncVersion="${line}" videoEncVersion="${line}"
elif line_contains "${line}" 'libopus='; then elif line_starts_with "${line}" 'libopus='; then
audioEncVersion="${line}" audioEncVersion="${line}"
fi fi
done <<<"${output}" done <<<"${output}"
local version local version
if [[ ${ffmpegVersion} == '' ]]; then if [[ ${ffmpegVersion} == '' ]]; 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}" ffmpegVersion="ffmpeg=${version}"
break break
fi fi
done <<<"${output}" done <<<"${output}"
fi fi
if [[ ${videoEncVersion} == '' ]]; then if [[ ${videoEncVersion} == '' ]]; then
version="$(get_pkgconfig_version SvtAv1Enc)" version="$(get_pkgconfig_version SvtAv1Enc)"
test "${version}" == '' && return 1 test "${version}" == '' && return 1
videoEncVersion="libsvtav1=${version}" videoEncVersion="libsvtav1=${version}"
fi fi
if [[ ${audioEncVersion} == '' ]]; then if [[ ${audioEncVersion} == '' ]]; then
version="$(get_pkgconfig_version opus)" version="$(get_pkgconfig_version opus)"
test "${version}" == '' && return 1 test "${version}" == '' && return 1
audioEncVersion="libopus=${version}" audioEncVersion="libopus=${version}"
fi fi
test "${ffmpegVersion}" == '' && return 1 test "${ffmpegVersion}" == '' && return 1
test "${videoEncVersion}" == '' && return 1 test "${videoEncVersion}" == '' && return 1
test "${audioEncVersion}" == '' && return 1 test "${audioEncVersion}" == '' && return 1
if [[ ${action} == 'print' ]]; then if [[ ${action} == 'print' ]]; then
echo "${encodeVersion}" echo "${encodeVersion}"
echo "${ffmpegVersion}" echo "${ffmpegVersion}"
echo "${videoEncVersion}" echo "${videoEncVersion}"
echo "${audioEncVersion}" echo "${audioEncVersion}"
fi fi
return 0 return 0
} }
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})"
echo -e "\t[-C NUM] set CRF (default: ${CRF})" echo -e "\t[-C NUM] set CRF (default: ${CRF})"
echo -e "\t[-g NUM] set film grain for encode" 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[-p] print the command instead of executing it (default: ${PRINT_OUT})"
echo -e "\t[-c] use cropdetect (default: ${CROP})" echo -e "\t[-c] use cropdetect (default: ${CROP})"
echo -e "\t[-d] enable dolby vision (default: ${DV_TOGGLE})" echo -e "\t[-d] enable dolby vision (default: ${DV_TOGGLE})"
echo -e "\t[-v] print relevant version info" echo -e "\t[-v] print relevant version info"
echo -e "\t[-s] use same container as input, default is convert to mkv" echo -e "\t[-s] use same container as input, default is convert to mkv"
echo -e "\n\t[output] if unset, defaults to \${HOME}/av1-input-file-name.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 "\n\t[-u] update script (git pull ffmpeg-builder)"
echo -e "\t[-I] system install at ${ENCODE_INSTALL_PATH}" echo -e "\t[-I] system install at ${ENCODE_INSTALL_PATH}"
echo -e "\t[-U] uninstall from ${ENCODE_INSTALL_PATH}" echo -e "\t[-U] uninstall from ${ENCODE_INSTALL_PATH}"
return 0 return 0
} }
encode_update() { encode_update() {
git -C "${REPO_DIR}" pull git -C "${REPO_DIR}" pull
} }
set_encode_opts() { set_encode_opts() {
local opts='vi:pcsdg:P:C:uIU' local opts='vi:pcsdg:P:C:uIU'
local numOpts=${#opts} 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"
# only using -I/U # only using -I/U
local minOpt=1 local minOpt=1
# using all + output name # using all + output name
local maxOpt=$((numOpts + 1)) 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 test $# -gt ${maxOpt} && encode_usage && return 1
local optsUsed=0 local optsUsed=0
local OPTARG OPTIND local OPTARG OPTIND
while getopts "${opts}" flag; do while getopts "${opts}" flag; do
case "${flag}" in case "${flag}" in
u) u)
encode_update encode_update || return 1
exit $? return ${FUNC_EXIT_SUCCESS}
;; ;;
I) I)
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"
exit 0 return ${FUNC_EXIT_SUCCESS}
;; ;;
U) U)
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"
exit 0 return ${FUNC_EXIT_SUCCESS}
;; ;;
v) v)
get_encode_versions print get_encode_versions print || return 1
exit $? return ${FUNC_EXIT_SUCCESS}
;; ;;
i) i)
if [[ $# -lt 2 ]]; then if [[ $# -lt 2 ]]; then
echo_fail "wrong arguments given" echo_fail "wrong arguments given"
encode_usage encode_usage
return 1 return 1
fi fi
INPUT="${OPTARG}" INPUT="${OPTARG}"
optsUsed=$((optsUsed + 2)) optsUsed=$((optsUsed + 2))
;; ;;
p) p)
PRINT_OUT=true PRINT_OUT=true
optsUsed=$((optsUsed + 1)) optsUsed=$((optsUsed + 1))
;; ;;
c) c)
CROP=true CROP=true
optsUsed=$((optsUsed + 1)) optsUsed=$((optsUsed + 1))
;; ;;
d) d)
DV_TOGGLE=true DV_TOGGLE=true
optsUsed=$((optsUsed + 1)) optsUsed=$((optsUsed + 1))
;; ;;
s) s)
SAME_CONTAINER=true SAME_CONTAINER=true
optsUsed=$((optsUsed + 1)) optsUsed=$((optsUsed + 1))
;; ;;
g) g)
if ! is_positive_integer "${OPTARG}"; then if ! is_positive_integer "${OPTARG}"; then
encode_usage encode_usage
return 1 return 1
fi fi
GRAIN="film-grain=${OPTARG}:film-grain-denoise=1:" GRAIN="film-grain=${OPTARG}:film-grain-denoise=1:"
optsUsed=$((optsUsed + 2)) optsUsed=$((optsUsed + 2))
;; ;;
P) P)
if ! is_positive_integer "${OPTARG}"; then if ! is_positive_integer "${OPTARG}"; then
encode_usage encode_usage
return 1 return 1
fi fi
PRESET="${OPTARG}" PRESET="${OPTARG}"
optsUsed=$((optsUsed + 2)) optsUsed=$((optsUsed + 2))
;; ;;
C) C)
if ! is_positive_integer "${OPTARG}" || test ${OPTARG} -gt 63; then if ! is_positive_integer "${OPTARG}" || test ${OPTARG} -gt 63; then
echo_fail "${OPTARG} is not a valid CRF value (0-63)" echo_fail "${OPTARG} is not a valid CRF value (0-63)"
encode_usage encode_usage
exit 1 return 1
fi fi
CRF="${OPTARG}" CRF="${OPTARG}"
OPTS_USED=$((OPTS_USED + 2)) OPTS_USED=$((OPTS_USED + 2))
;; ;;
*) *)
echo_fail "wrong flags given" echo_fail "wrong flags given"
encode_usage encode_usage
return 1 return 1
;; ;;
esac esac
done done
# allow optional output filename # allow optional output filename
if [[ $(($# - optsUsed)) == 1 ]]; then if [[ $(($# - optsUsed)) == 1 ]]; then
OUTPUT="${*: -1}" OUTPUT="${*: -1}"
else else
local basename="$(bash_basename "${INPUT}")" local basename="$(bash_basename "${INPUT}")"
OUTPUT="${HOME}/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
fileFormat="$(get_file_format "${INPUT}")" || return 1 fileFormat="$(get_file_format "${INPUT}")" || return 1
FILE_EXT='' FILE_EXT=''
if [[ ${fileFormat} == 'MPEG-4' ]]; then if [[ ${fileFormat} == 'MPEG-4' ]]; then
FILE_EXT='mp4' FILE_EXT='mp4'
elif [[ ${fileFormat} == 'Matroska' ]]; then elif [[ ${fileFormat} == 'Matroska' ]]; then
FILE_EXT='mkv' FILE_EXT='mkv'
else else
echo "unrecognized input format" echo "unrecognized input format"
return 1 return 1
fi fi
else else
FILE_EXT="mkv" FILE_EXT="mkv"
fi fi
OUTPUT="${OUTPUT%.*}" OUTPUT="${OUTPUT%.*}"
OUTPUT+=".${FILE_EXT}" OUTPUT+=".${FILE_EXT}"
if [[ ! -f ${INPUT} ]]; then if [[ ! -f ${INPUT} ]]; then
echo "${INPUT} does not exist" echo "${INPUT} does not exist"
encode_usage encode_usage
return 1 return 1
fi fi
echo echo
echo_info "INPUT: ${INPUT}" echo_info "INPUT: ${INPUT}"
echo_info "GRAIN: ${GRAIN}" echo_info "GRAIN: ${GRAIN}"
echo_info "OUTPUT: ${OUTPUT}" echo_info "OUTPUT: ${OUTPUT}"
echo echo
} }
# shellcheck disable=SC2034 # shellcheck disable=SC2034
# 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" local genScript="${TMP_DIR}/$(bash_basename "${OUTPUT}").sh"
# global output index number to increment # global output index number to increment
OUTPUT_INDEX=0 OUTPUT_INDEX=0
# single string params # single string params
local params=( local params=(
INPUT INPUT
OUTPUT OUTPUT
PRESET PRESET
CRF CRF
crop crop
encodeVersion encodeVersion
ffmpegVersion ffmpegVersion
videoEncVersion videoEncVersion
audioEncVersion audioEncVersion
svtAv1Params svtAv1Params
) )
local crop='' local crop=''
if [[ $CROP == "true" ]]; then if [[ $CROP == "true" ]]; then
crop="$(get_crop "${INPUT}")" || return 1 crop="$(get_crop "${INPUT}")" || return 1
fi fi
svtAv1ParamsArr=( svtAv1ParamsArr=(
"tune=0" "tune=0"
"complex-hvs=1" "complex-hvs=1"
"spy-rd=1" "spy-rd=1"
"psy-rd=1" "psy-rd=1"
"sharpness=3" "sharpness=3"
"enable-overlays=1" "enable-overlays=0"
"scd=1" "hbd-mds=1"
"fast-decode=1" "scd=1"
"enable-variance-boost=1" "fast-decode=1"
"enable-qm=1" "enable-variance-boost=1"
"qm-min=4" "enable-qm=1"
"qm-max=15" "chroma-qm-min-10"
) "qm-min=4"
IFS=':' "qm-max=15"
local svtAv1Params="${GRAIN}${svtAv1ParamsArr[*]}" )
unset IFS IFS=':'
local svtAv1Params="${GRAIN}${svtAv1ParamsArr[*]}"
unset IFS
# arrays # arrays
local arrays=( local arrays=(
unmapStreams unmapStreams
audioParams audioParams
videoParams videoParams
metadata metadata
subtitleParams subtitleParams
ffmpegParams ffmpegParams
) )
local videoParams=( local videoParams=(
"-crf" '${CRF}' "-preset" '${PRESET}' "-g" "240" "-crf" '${CRF}' "-preset" '${PRESET}' "-g" "240"
) )
local ffmpegParams=( local ffmpegParams=(
'-hide_banner' '-hide_banner'
'-i' '${INPUT}' '-i' '${INPUT}'
'-y' '-y'
'-map' '0' '-map' '0'
'-c:s' 'copy' '-c:s' 'copy'
) )
# set video params # set video params
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'
) )
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}'
) )
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 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 [[ ${unmapStreams[*]} != '' ]]; then
ffmpegParams+=('${unmapStreams[@]}') ffmpegParams+=('${unmapStreams[@]}')
fi fi
if [[ ${audioParams[*]} != '' ]]; then if [[ ${audioParams[*]} != '' ]]; then
ffmpegParams+=('${audioParams[@]}') ffmpegParams+=('${audioParams[@]}')
fi fi
if [[ ${subtitleParams[*]} != '' ]]; then if [[ ${subtitleParams[*]} != '' ]]; then
ffmpegParams+=('${subtitleParams[@]}') ffmpegParams+=('${subtitleParams[@]}')
fi fi
if [[ ${crop} != '' ]]; then if [[ ${crop} != '' ]]; then
ffmpegParams+=('-vf' '${crop}') ffmpegParams+=('-vf' '${crop}')
fi fi
get_encode_versions || return 1 get_encode_versions || return 1
local metadata=( local metadata=(
'-metadata' '${encodeVersion}' '-metadata' '${encodeVersion}'
'-metadata' '${ffmpegVersion}' '-metadata' '${ffmpegVersion}'
'-metadata' '${videoEncVersion}' '-metadata' '${videoEncVersion}'
'-metadata' '${audioEncVersion}' '-metadata' '${audioEncVersion}'
'-metadata' 'svtav1_params=${svtAv1Params}' '-metadata' 'svtav1_params=${svtAv1Params}'
'-metadata' 'video_params=${videoParams[*]}' '-metadata' 'video_params=${videoParams[*]}'
) )
ffmpegParams+=('${metadata[@]}') ffmpegParams+=('${metadata[@]}')
{ {
echo '#!/usr/bin/env bash' echo '#!/usr/bin/env bash'
echo echo
# add normal params # add normal params
for param in "${params[@]}"; do for param in "${params[@]}"; do
declare -n value="${param}" declare -n value="${param}"
if [[ ${value} != '' ]]; then if [[ ${value} != '' ]]; then
echo "${param}=\"${value[*]}\"" echo "${param}=\"${value[*]}\""
fi fi
done done
for arrName in "${arrays[@]}"; do for arrName in "${arrays[@]}"; do
declare -n arr="${arrName}" declare -n arr="${arrName}"
if [[ -v arr ]]; then if [[ -v arr ]]; then
echo "${arrName}=(" echo "${arrName}=("
printf '\t"%s"\n' "${arr[@]}" printf '\t"%s"\n' "${arr[@]}"
echo ')' echo ')'
fi fi
done done
# actually do ffmpeg commmand # actually do ffmpeg commmand
echo echo
if [[ ${DV_TOGGLE} == true ]]; then if [[ ${DV_TOGGLE} == true ]]; then
echo 'ffmpeg "${ffmpegParams[@]}" -dolbyvision 1 "${OUTPUT}" || \' echo 'ffmpeg "${ffmpegParams[@]}" -dolbyvision 1 "${OUTPUT}" || \'
fi fi
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 [[ ${FILE_EXT} == 'mkv' ]] && has_cmd mkvpropedit; then
{ {
echo echo
echo 'mkvpropedit "${OUTPUT}" --add-track-statistics-tags' echo 'mkvpropedit "${OUTPUT}" --add-track-statistics-tags'
echo 'mkvpropedit "${OUTPUT}" --edit info --set "title="' echo 'mkvpropedit "${OUTPUT}" --edit info --set "title="'
} }
fi fi
echo echo
} >"${genScript}" } >"${genScript}"
if [[ ${PRINT_OUT} == true ]]; then if [[ ${PRINT_OUT} == true ]]; then
echo_info "${genScript} contents:" echo_info "${genScript} contents:"
echo "$(<"${genScript}")" echo "$(<"${genScript}")"
else else
bash -x "${genScript}" || return 1 bash -x "${genScript}" || return 1
rm "${genScript}" rm "${genScript}"
fi fi
} }
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_psy and libopus'
encode() { encode() {
set_encode_opts "$@" || return 1 set_encode_opts "$@"
gen_encode_script || return 1 local ret=$?
if [[ ${ret} -eq ${FUNC_EXIT_SUCCESS} ]]; then
return 0
elif [[ ${ret} -ne 0 ]]; then
return ${ret}
fi
gen_encode_script || return 1
} }

View File

@@ -1,128 +1,169 @@
#!/usr/bin/env bash #!/usr/bin/env bash
get_duration() { get_duration() {
local file="$1" local file="$1"
ffprobe \ ffprobe \
-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 \
"${file}" "${file}"
} }
get_avg_bitrate() { get_avg_bitrate() {
local file="$1" local file="$1"
ffprobe \ ffprobe \
-v error \ -v error \
-select_streams v:0 \ -select_streams v:0 \
-show_entries format=bit_rate \ -show_entries format=bit_rate \
-of default=noprint_wrappers=1:nokey=1 \ -of default=noprint_wrappers=1:nokey=1 \
"${file}" "${file}"
} }
split_video() { split_video() {
local file="$1" local file="$1"
local start="$2" local start="$2"
local time="$3" local time="$3"
local out="$4" local out="$4"
ffmpeg \ ffmpeg \
-ss "${start}" \ -ss "${start}" \
-i "${file}" \ -i "${file}" \
-hide_banner \ -hide_banner \
-loglevel error \ -loglevel error \
-t "${time}" \ -t "${time}" \
-map 0:v \ -map 0:v \
-reset_timestamps 1 \ -reset_timestamps 1 \
-c copy \ -c copy \
"${out}" "${out}"
} }
get_crop() { get_crop() {
local file="$1" local file="$1"
local duration local duration
duration="$(get_duration "${file}")" || return 1 duration="$(get_duration "${file}")" || return 1
# 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 / 2))
ffmpeg \ ffmpeg \
-y \ -y \
-hide_banner \ -hide_banner \
-ss 0 \ -ss 0 \
-discard 'nokey' \ -discard 'nokey' \
-i "${file}" \ -i "${file}" \
-t "${timeEnc}" \ -t "${timeEnc}" \
-map '0:v:0' \ -map '0:v:0' \
-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 |
tail -n1 | tail -n1 |
grep -o "crop=.*" grep -o "crop=.*"
} }
get_stream_codec() { get_stream_codec() {
local file="$1" local file="$1"
local stream="$2" local stream="$2"
ffprobe \ ffprobe \
-v error \ -v error \
-select_streams "${stream}" \ -select_streams "${stream}" \
-show_entries stream=codec_name \ -show_entries stream=codec_name \
-of default=noprint_wrappers=1:nokey=1 \ -of default=noprint_wrappers=1:nokey=1 \
"${file}" "${file}"
} }
get_file_format() { get_file_format() {
local file="$1" local file="$1"
local probe local probe
probe="$(ffprobe \ probe="$(ffprobe \
-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 else
echo mp4 echo mp4
fi fi
} }
get_num_streams() { get_num_streams() {
local file="$1" local file="$1"
local type="${2:-}" local type="${2:-}"
local select=() local select=()
if [[ ${type} != '' ]]; then if [[ ${type} != '' ]]; then
select=("-select_streams" "${type}") select=("-select_streams" "${type}")
fi fi
ffprobe \ ffprobe \
-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 \
"${file}" "${file}"
} }
get_num_audio_channels() { get_num_audio_channels() {
local file="$1" local file="$1"
local stream="$2" local stream="$2"
ffprobe \ ffprobe \
-v error \ -v error \
-select_streams "${stream}" \ -select_streams "${stream}" \
-show_entries stream=channels \ -show_entries stream=channels \
-of default=noprint_wrappers=1:nokey=1 \ -of default=noprint_wrappers=1:nokey=1 \
"${file}" "${file}"
} }
get_stream_lang() { get_stream_lang() {
local file="$1" local file="$1"
local stream="$2" local stream="$2"
ffprobe \ ffprobe \
-v error \ -v error \
-select_streams "${stream}" \ -select_streams "${stream}" \
-show_entries stream_tags=language \ -show_entries stream_tags=language \
-of default=noprint_wrappers=1:nokey=1 \ -of default=noprint_wrappers=1:nokey=1 \
"${file}" "${file}"
}
gen_video() {
local outFile="$1"
local addFlags=()
shift
local vf="format=yuv420p10le"
for arg in "$@"; do
case "${arg}" in
'1080p') resolution='1920x1080' ;;
'2160p') resolution='3840x2160' ;;
'grain=yes') vf+=",noise=alls=15:allf=t+u" ;;
'hdr=yes')
local colorPrimaries='bt2020'
local colorTrc='smpte2084'
local colorspace='bt2020nc'
vf+=",setparams=color_primaries=${colorPrimaries}:color_trc=${colorTrc}:colorspace=${colorspace}"
addFlags+=(
-color_primaries "${colorPrimaries}"
-color_trc "${colorTrc}"
-colorspace "${colorspace}"
-metadata:s:v:0 "mastering_display_metadata=G(13250,34500)B(7500,3000)R(34000,16000)WP(15635,16450)L(10000000,1)"
-metadata:s:v:0 "content_light_level=1000,400"
)
;;
*) echo_fail "bad arg ${arg}" && return 1 ;;
esac
done
echo_if_fail ffmpeg -y \
-hide_banner \
-f lavfi \
-i "testsrc2=size=${resolution}:rate=24:duration=5" \
-vf "${vf}" \
-c:v ffv1 \
-level 3 \
-g 1 \
-color_range tv \
"${addFlags[@]}" \
"${outFile}"
} }

View File

@@ -2,115 +2,133 @@
# shellcheck disable=SC2120 # shellcheck disable=SC2120
determine_pkg_mgr() { determine_pkg_mgr() {
# sudo used externally # sudo used externally
# shellcheck disable=SC2034 # shellcheck disable=SC2034
test "$(id -u)" -eq 0 && SUDO='' || SUDO='sudo ' if is_windows || test "$(id -u)" -eq 0; then
SUDO=''
else
SUDO='sudo '
fi
# pkg-mgr update-cmd upgrade-cmd install-cmd check-cmd # pkg-mgr update-cmd upgrade-cmd install-cmd check-cmd
# shellcheck disable=SC2016 # shellcheck disable=SC2016
local PKG_MGR_MAP=' local PKG_MGR_MAP='
pkg:pkg update:pkg upgrade:pkg install -y:dpkg -l ${pkg} pkg:pkg update:pkg upgrade:pkg install -y:dpkg -l ${pkg}
brew:brew update:brew upgrade:brew install:brew list --formula ${pkg} brew:brew update:brew upgrade:brew install:brew list --formula ${pkg}
apt-get:${SUDO}apt-get update:${SUDO}apt-get upgrade -y:${SUDO}apt-get install -y:dpkg -l ${pkg} apt-get:${SUDO}apt-get update:${SUDO}apt-get upgrade -y:${SUDO}apt-get install -y:dpkg -l ${pkg}
pacman:${SUDO}pacman -Syy:${SUDO}pacman -Syu --noconfirm:${SUDO}pacman -S --noconfirm --needed:pacman -Qi ${pkg} pacman:${SUDO}pacman -Syy:${SUDO}pacman -Syu --noconfirm:${SUDO}pacman -S --noconfirm --needed:pacman -Qi ${pkg}
dnf:${SUDO}dnf check-update || true:${SUDO}dnf upgrade --refresh -y:${SUDO}dnf install -y:dnf list -q --installed ${pkg} dnf:${SUDO}dnf check-update || true:${SUDO}dnf upgrade --refresh -y:${SUDO}dnf install -y:dnf list -q --installed ${pkg}
winget:winget update:true:${SUDO}winget install:winget list ${pkg}
' '
local supported_pkg_mgr=() local supported_pkg_mgr=()
unset pkg_mgr pkg_mgr_update pkg_mgr_upgrade pkg_install pkg_check unset pkg_mgr pkg_mgr_update pkg_mgr_upgrade pkg_install pkg_check
while read -r line; do while read -r line; do
test "${line}" == '' && continue test "${line}" == '' && continue
IFS=':' read -r pkg_mgr pkg_mgr_update pkg_mgr_upgrade pkg_install pkg_check <<<"${line}" IFS=':' read -r pkg_mgr pkg_mgr_update pkg_mgr_upgrade pkg_install pkg_check <<<"${line}"
supported_pkg_mgr+=("${pkg_mgr}") supported_pkg_mgr+=("${pkg_mgr}")
if ! has_cmd "${pkg_mgr}"; then if ! has_cmd "${pkg_mgr}"; then
pkg_mgr='' pkg_mgr=''
continue continue
fi fi
# update/install may use SUDO # update/install may use SUDO
eval "pkg_mgr_update=\"${pkg_mgr_update}\"" eval "pkg_mgr_update=\"${pkg_mgr_update}\""
eval "pkg_mgr_upgrade=\"${pkg_mgr_upgrade}\"" eval "pkg_mgr_upgrade=\"${pkg_mgr_upgrade}\""
eval "pkg_install=\"${pkg_install}\"" eval "pkg_install=\"${pkg_install}\""
break break
done <<<"${PKG_MGR_MAP}" done <<<"${PKG_MGR_MAP}"
if [[ ${pkg_mgr} == '' ]]; then if [[ ${pkg_mgr} == '' ]]; then
echo_fail "system does not use a supported package manager" "${supported_pkg_mgr[@]}" echo_fail "system does not use a supported package manager" "${supported_pkg_mgr[@]}"
return 1 return 1
fi fi
return 0 return 0
} }
print_req_pkgs() { print_req_pkgs() {
local common_pkgs=( local common_pkgs=(
autoconf automake cmake libtool autoconf automake cmake libtool
texinfo nasm yasm python3 wget texinfo nasm yasm python3 wget
meson doxygen jq ccache gawk meson doxygen jq ccache gawk
git gnuplot bison rsync ragel git gnuplot bison rsync ragel
zip unzip gperf itstool zip unzip gperf itstool
) )
# shellcheck disable=SC2034 # shellcheck disable=SC2034
local brew_pkgs=( local brew_pkgs=(
"${common_pkgs[@]}" pkgconf "${common_pkgs[@]}" pkgconf
mkvtoolnix pipx uutils-coreutils mkvtoolnix pipx uutils-coreutils
) llvm lld
local common_linux_pkgs=( )
"${common_pkgs[@]}" clang valgrind local common_linux_pkgs=(
curl bc lshw xxd pkgconf sudo "${common_pkgs[@]}" clang valgrind
) curl bc lshw xxd pkgconf sudo llvm
# shellcheck disable=SC2034 )
local apt_get_pkgs=( # shellcheck disable=SC2034
"${common_linux_pkgs[@]}" build-essential local apt_get_pkgs=(
git-core libass-dev libfreetype6-dev "${common_linux_pkgs[@]}" pipx
libsdl2-dev libva-dev libvdpau-dev build-essential libssl-dev gobjc++
libvorbis-dev libxcb1-dev pipx mawk libc6-dev mediainfo ninja-build
libxcb-shm0-dev libxcb-xfixes0-dev mkvtoolnix libgtest-dev lld
zlib1g-dev libssl-dev ninja-build libfontconfig-dev libglib2.0-dev
gobjc++ mawk libnuma-dev libc6-dev )
mediainfo mkvtoolnix libgtest-dev # shellcheck disable=SC2034
) local pacman_pkgs=(
# shellcheck disable=SC2034 "${common_linux_pkgs[@]}" base-devel
local pacman_pkgs=( python-pipx ninja lld mkvtoolnix-cli
"${common_linux_pkgs[@]}" base-devel glib2-devel
python-pipx ninja numactl )
) # shellcheck disable=SC2034
# shellcheck disable=SC2034 local dnf_pkgs=(
local dnf_pkgs=( "${common_linux_pkgs[@]}" openssl-devel
"${common_linux_pkgs[@]}" openssl-devel pipx ninja-build fontconfig-devel wget2
pipx ninja-build fontconfig-devel wget2 glibc-static glibc-devel patch
cpuinfo-devel glibc-static glibc-devel libstdc++-static libstdc++-devel
libstdc++-static libstdc++-devel patch llvm-cmake-utils llvm-devel
numactl-devel llvm-static compiler-rt lld
) mkvtoolnix glib2-static
# shellcheck disable=SC2034 )
local pkg_pkgs=( # shellcheck disable=SC2034
autoconf automake cmake libtool local pkg_pkgs=(
texinfo nasm yasm python3 wget autoconf automake cmake libtool
doxygen jq ccache gawk rust texinfo nasm yasm python3 wget
git gnuplot bison rsync ragel doxygen jq ccache gawk rust
zip unzip gperf build-essential git gnuplot bison rsync ragel
binutils ninja ndk-multilib-native-static zip unzip gperf build-essential
) binutils ninja ndk-multilib-native-static
# shellcheck disable=SC2034 libandroid-posix-semaphore
local winget_pkgs=( libandroid-posix-semaphore-static
Git.Git gerardog.gsudo libandroid-shmem
StrawberryPerl.StrawberryPerl libandroid-shmem-static
bloodrock.pkg-config-lite )
Kitware.CMake mesonbuild.meson # shellcheck disable=SC2034
Microsoft.VisualStudio.2019.BuildTools local msys_ucrt_pkgs=(
Microsoft.VisualStudio.2022.BuildTools mingw-w64-ucrt-x86_64-toolchain
GnuWin32.DiffUtils GnuWin32.Bison mingw-w64-ucrt-x86_64-autotools
GnuWin32.Gperf GnuWin32.File mingw-w64-ucrt-x86_64-clang
GnuWin32.Tar GnuWin32.UnZip mingw-w64-ucrt-x86_64-clang-libs
GnuWin32.Zip GnuWin32.Which mingw-w64-ucrt-x86_64-cmake
Rustlang.Rustup Python.Python.3.12 mingw-w64-ucrt-x86_64-compiler-rt
Ccache.Ccache LLVM.LLVM mingw-w64-ucrt-x86_64-doxygen
) mingw-w64-ucrt-x86_64-gcc-libs
mingw-w64-ucrt-x86_64-gperf
mingw-w64-ucrt-x86_64-itstool
mingw-w64-ucrt-x86_64-meson
mingw-w64-ucrt-x86_64-bc
mingw-w64-ucrt-x86_64-nasm
mingw-w64-ucrt-x86_64-yasm
mingw-w64-ucrt-x86_64-ccache
mingw-w64-ucrt-x86_64-rustup
mingw-w64-ucrt-x86_64-cargo-c
mingw-w64-ucrt-x86_64-perl
mingw-w64-ucrt-x86_64-perl-modules
)
local req_pkgs_env_name="${pkg_mgr/-/_}_pkgs" if is_windows; then
declare -n req_pkgs="${req_pkgs_env_name}" local pkg_mgr='msys_ucrt'
local sorted_req_pkgs=($(printf '%s\n' "${req_pkgs[@]}" | sort -u)) fi
echo "${sorted_req_pkgs[@]}" local req_pkgs_env_name="${pkg_mgr/-/_}_pkgs"
declare -n req_pkgs="${req_pkgs_env_name}"
local sorted_req_pkgs=($(printf '%s\n' "${req_pkgs[@]}" | sort -u))
echo "${sorted_req_pkgs[@]}"
} }
FB_FUNC_NAMES+=('print_pkg_mgr') FB_FUNC_NAMES+=('print_pkg_mgr')
@@ -118,59 +136,60 @@ FB_FUNC_NAMES+=('print_pkg_mgr')
# shellcheck disable=SC2034 # shellcheck disable=SC2034
FB_FUNC_DESCS['print_pkg_mgr']='print out evaluated package manager commands and required packages' FB_FUNC_DESCS['print_pkg_mgr']='print out evaluated package manager commands and required packages'
print_pkg_mgr() { print_pkg_mgr() {
determine_pkg_mgr || return 1 determine_pkg_mgr || return 1
echo "export pkg_mgr=\"${pkg_mgr}\"" echo "export pkg_mgr=\"${pkg_mgr}\""
echo "export pkg_mgr_update=\"${pkg_mgr_update}\"" echo "export pkg_mgr_update=\"${pkg_mgr_update}\""
echo "export pkg_mgr_upgrade=\"${pkg_mgr_upgrade}\"" echo "export pkg_mgr_upgrade=\"${pkg_mgr_upgrade}\""
echo "export pkg_install=\"${pkg_install}\"" echo "export pkg_install=\"${pkg_install}\""
echo "export pkg_check=\"${pkg_check}\"" echo "export pkg_check=\"${pkg_check}\""
echo "export req_pkgs=($(print_req_pkgs))" echo "export req_pkgs=($(print_req_pkgs))"
} }
check_for_req_pkgs() { check_for_req_pkgs() {
echo_info "checking for required packages" echo_info "checking for required packages"
local missing_pkgs=() local missing_pkgs=()
for pkg in $(print_req_pkgs); do for pkg in $(print_req_pkgs); do
# pkg_check has ${pkg} unexpanded # pkg_check has ${pkg} unexpanded
eval "pkg_check=\"${pkg_check}\"" eval "pkg_check=\"${pkg_check}\""
${pkg_check} "${pkg}" >/dev/null 2>&1 || missing_pkgs+=("${pkg}") ${pkg_check} "${pkg}" >/dev/null 2>&1 || missing_pkgs+=("${pkg}")
done done
if [[ ${#missing_pkgs[@]} -gt 0 ]]; then if [[ ${#missing_pkgs[@]} -gt 0 ]]; then
echo_warn "missing packages:" "${missing_pkgs[@]}" echo_warn "missing packages:" "${missing_pkgs[@]}"
# shellcheck disable=SC2086 # shellcheck disable=SC2086
${pkg_mgr_update} ${pkg_mgr_update}
# shellcheck disable=SC2086 # shellcheck disable=SC2086
${pkg_install} "${missing_pkgs[@]}" || return 1 ${pkg_install} "${missing_pkgs[@]}" || return 1
fi fi
echo_pass "packages from ${pkg_mgr} installed" echo_pass "packages from ${pkg_mgr} installed"
has_cmd pipx || echo_if_fail python3 -m pip install --user pipx || return 1 has_cmd pipx || echo_if_fail python3 -m pip install --user pipx || return 1
echo_if_fail pipx install virtualenv || return 1 has_cmd pipx || echo_if_fail python3 -m pipx ensurepath && source ~/.bashrc || return 1
echo_if_fail pipx ensurepath || return 1 echo_if_fail pipx install virtualenv || return 1
has_cmd meson || echo_if_fail pipx install meson || return 1 echo_if_fail pipx ensurepath || return 1
echo_pass "pipx is installed" has_cmd meson || echo_if_fail pipx install meson || return 1
echo_pass "pipx is installed"
# shellcheck disable=SC1091 # shellcheck disable=SC1091
test -f "${HOME}/.cargo/env" && source "${HOME}/.cargo/env" test -f "${HOME}/.cargo/env" && source "${HOME}/.cargo/env"
if missing_cmd cargo; then if missing_cmd cargo; then
if missing_cmd rustup; then if missing_cmd rustup; then
echo_warn "installing rustup" echo_warn "installing rustup"
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
# shellcheck disable=SC2016 # shellcheck disable=SC2016
grep -q 'source "${HOME}/.cargo/env"' "${HOME}/.bashrc" || grep -q 'source "${HOME}/.cargo/env"' "${HOME}/.bashrc" ||
echo 'source "${HOME}/.cargo/env"' >>"${HOME}/.bashrc" echo 'source "${HOME}/.cargo/env"' >>"${HOME}/.bashrc"
# shellcheck disable=SC1091 # shellcheck disable=SC1091
source "${HOME}/.bashrc" source "${HOME}/.bashrc"
fi fi
fi fi
echo_if_fail cargo install cargo-c || return 1 has_cmd cargo-cbuild || echo_if_fail cargo install cargo-c || return 1
echo_pass "cargo-c is installed" echo_pass "cargo-c is installed"
echo_pass "all required packages installed" echo_pass "all required packages installed"
return 0 return 0
} }
FB_FUNC_NAMES+=('install_deps') FB_FUNC_NAMES+=('install_deps')
@@ -178,6 +197,6 @@ FB_FUNC_NAMES+=('install_deps')
# shellcheck disable=SC2034 # shellcheck disable=SC2034
FB_FUNC_DESCS['install_deps']='install required dependencies' FB_FUNC_DESCS['install_deps']='install required dependencies'
install_deps() { install_deps() {
determine_pkg_mgr || return 1 determine_pkg_mgr || return 1
check_for_req_pkgs || return 1 check_for_req_pkgs || return 1
} }

View File

@@ -1,31 +1,29 @@
#!/usr/bin/env bash #!/usr/bin/env bash
check_for_package_cfg() { check_for_package_cfg() {
local requiredCfg='ON:ON:3' local requiredCfg='ON:ON:ON:3'
local currentCfg="${STATIC}:${LTO}:${OPT}" local currentCfg="${STATIC}:${LTO}:${PGO}:${OPT}"
if [[ ${currentCfg} == "${requiredCfg}" ]]; then if [[ ${currentCfg} == "${requiredCfg}" ]]; then
return 0 return 0
else else
return 1 return 1
fi fi
} }
FB_FUNC_NAMES+=('package') FB_FUNC_NAMES+=('package')
FB_FUNC_DESCS['package']='package ffmpeg build' FB_FUNC_DESCS['package']='package ffmpeg build'
package() { package() {
local pkgDir="${IGN_DIR}/package" local pkgDir="${IGN_DIR}/package"
test -d "${pkgDir}" && rm -rf "${pkgDir}" recreate_dir "${pkgDir}" || return 1
check_for_package_cfg || return 0 check_for_package_cfg || return 0
echo_info "packaging" echo_info "packaging"
mkdir "${pkgDir}" || return 1 set_compile_opts || return 1
cp "${PREFIX}/bin/ff"* "${pkgDir}/"
set_compile_opts || return 1 cd "${pkgDir}" || return 1
cp "${PREFIX}/bin/ff"* "${pkgDir}/" local tarball="ffmpeg-build-${HOSTTYPE}-$(print_os).tar"
tar -cf "${tarball}" ff* || return 1
cd "${pkgDir}" || return 1 xz -e -9 "${tarball}" || return 1
local tarball="ffmpeg-build-${HOSTTYPE}-$(print_os).tar" echo_pass "finished packaging ${tarball}.xz"
tar -cf "${tarball}" ff* || return 1
xz -e -9 "${tarball}" || return 1
echo_pass "finished packaging ${tarball}.xz"
} }

60
lib/pgo.sh Normal file
View File

@@ -0,0 +1,60 @@
#!/usr/bin/env bash
PGO_DIR="${IGN_DIR}/pgo"
PGO_PROFDATA="${PGO_DIR}/prof.profdata"
gen_profdata() {
recreate_dir "${PGO_DIR}" || return 1
cd "${PGO_DIR}" || return 1
setup_pgo_clips || return 1
for vid in *.mkv; do
local args=()
# add precalculated grain amount based off of filename
line_contains "${vid}" 'grain' && args+=(-g 16)
# make fhd preset 2
line_contains "${vid}" 'fhd' && args+=(-P 2)
echo_info "encoding pgo vid: ${vid}"
LLVM_PROFILE_FILE="${PGO_DIR}/default_%p.profraw" \
echo_if_fail encode -i "${vid}" "${args[@]}" "encoded-${vid}" || return 1
done
# merge profraw into profdata
local mergeCmd=()
# darwin needs special invoke
if is_darwin; then
mergeCmd+=(xcrun)
fi
mergeCmd+=(
llvm-profdata
merge
"--output=${PGO_PROFDATA}"
)
"${mergeCmd[@]}" default*.profraw || return 1
return 0
}
setup_pgo_clips() {
local clips=(
"fhd-grainy.mkv 1080p,grain=yes"
"uhd.mkv 2160p"
"uhd-hdr.mkv 2160p,hdr=yes"
)
for clip in "${clips[@]}"; do
local genVid genVidArgs pgoFile genVidArgsArr
IFS=' ' read -r genVid genVidArgs <<<"${clip}"
# pgo path is separate
pgoFile="${PGO_DIR}/${genVid}"
genVid="${TMP_DIR}/${genVid}"
# create array of args split with ,
genVidArgsArr=(${genVidArgs//,/ })
# create generated vid without any profiling if needed
test -f "${genVid}" ||
LLVM_PROFILE_FILE='/dev/null' gen_video "${genVid}" "${genVidArgsArr[@]}" || return 1
# and move to the pgo directory
test -f "${pgoFile}" ||
cp "${genVid}" "${pgoFile}" || return 1
done
}

View File

@@ -1,29 +1,29 @@
#!/usr/bin/env bash #!/usr/bin/env bash
make_bullet_points() { make_bullet_points() {
for arg in "$@"; do for arg in "$@"; do
echo "- ${arg}" echo "- ${arg}"
done done
} }
gen_function_info() { gen_function_info() {
local funcName="$1" local funcName="$1"
echo "${FB_FUNC_DESCS[${funcName}]} using \`./scripts/${funcName}.sh\`" echo "${FB_FUNC_DESCS[${funcName}]} using \`./scripts/${funcName}.sh\`"
} }
gen_compile_opts_info() { gen_compile_opts_info() {
for opt in "${FB_COMP_OPTS[@]}"; do for opt in "${FB_COMP_OPTS[@]}"; do
declare -n defOptVal="DEFAULT_${opt}" declare -n defOptVal="DEFAULT_${opt}"
echo "- \`${opt}\`: ${FB_COMP_OPTS_DESC[${opt}]} (default: ${defOptVal})" echo "- \`${opt}\`: ${FB_COMP_OPTS_DESC[${opt}]} (default: ${defOptVal})"
done done
} }
FB_FUNC_NAMES+=('gen_readme') FB_FUNC_NAMES+=('gen_readme')
FB_FUNC_DESCS['gen_readme']='generate project README.md' FB_FUNC_DESCS['gen_readme']='generate project README.md'
gen_readme() { gen_readme() {
local readme="${REPO_DIR}/README.md" local readme="${REPO_DIR}/README.md"
echo " echo "
# ffmpeg-builder # ffmpeg-builder
A collection of scripts for building \`ffmpeg\` and encoding content with the built \`ffmpeg\`. A collection of scripts for building \`ffmpeg\` and encoding content with the built \`ffmpeg\`.

View File

@@ -1,338 +0,0 @@
#!/usr/bin/env bash
# shellcheck disable=SC2034
# ANSI colors
RED='\e[0;31m'
CYAN='\e[0;36m'
GREEN='\e[0;32m'
YELLOW='\e[0;33m'
NC='\e[0m'
# echo wrappers
echo_wrapper() {
local args
if [[ $1 == '-n' ]]; then
args=("$1")
shift
fi
# COLOR is override for using ${color}
# shellcheck disable=SC2153
if [[ ${COLOR} == 'OFF' ]]; then
color=''
endColor=''
else
endColor="${NC}"
fi
echo -e "${args[@]}" "${color}${word:-''}${endColor}" "$@"
}
echo_fail() { color="${RED}" word="FAIL" echo_wrapper "$@"; }
echo_info() { color="${CYAN}" word="INFO" echo_wrapper "$@"; }
echo_pass() { color="${GREEN}" word="PASS" echo_wrapper "$@"; }
echo_warn() { color="${YELLOW}" word="WARN" echo_wrapper "$@"; }
echo_exit() {
echo_fail "$@"
exit 1
}
void() { echo "$@" >/dev/null; }
echo_if_fail() {
local cmd=("$@")
local logName="${LOGNAME:-${RANDOM}}-"
local out="${TMP_DIR}/${logName}stdout"
local err="${TMP_DIR}/${logName}stderr"
# set trace to the cmdEvalTrace and open file descriptor
local cmdEvalTrace="${TMP_DIR}/${logName}cmdEvalTrace"
exec 5>"${cmdEvalTrace}"
export BASH_XTRACEFD=5
set -x
"${cmd[@]}" >"${out}" 2>"${err}"
local retval=$?
# unset and close file descriptor
set +x
exec 5>&-
# parse out relevant part of the trace
local cmdEvalLines=()
while IFS= read -r line; do
line="${line//${PS4}/}"
test "${line}" == 'set +x' && continue
test "${line}" == '' && continue
cmdEvalLines+=("${line}")
done <"${cmdEvalTrace}"
if ! test ${retval} -eq 0; then
echo
echo_fail "command failed:"
printf "%s\n" "${cmdEvalLines[@]}"
echo_warn "command output:"
tail -n 20 "${out}"
tail -n 20 "${err}"
echo
fi
if [[ -z ${LOGNAME} ]]; then
rm "${out}" "${err}" "${cmdEvalTrace}"
fi
return ${retval}
}
is_root_owned() {
local path=$1
local uid
if stat --version >/dev/null 2>&1; then
# GNU coreutils (Linux)
uid=$(stat -c '%u' "$path")
else
# BSD/macOS
uid=$(stat -f '%u' "$path")
fi
test "$uid" -eq 0
}
dump_arr() {
arr_name="$1"
declare -n arr
arr="${arr_name}"
arr_exp=("${arr[@]}")
test "${#arr_exp}" -gt 0 || return 0
echo
echo_info "${arr_name}"
printf "\t%s\n" "${arr_exp[@]}"
}
has_cmd() {
local cmds=("$@")
local rv=0
for cmd in "${cmds[@]}"; do
command -v "${cmd}" >/dev/null 2>&1 || rv=1
done
return ${rv}
}
missing_cmd() {
local cmds=("$@")
local rv=1
for cmd in "${cmds[@]}"; do
if ! has_cmd "${cmd}"; then
echo_warn "missing ${cmd}"
rv=0
fi
done
return ${rv}
}
bash_dirname() {
local tmp=${1:-.}
[[ $tmp != *[!/]* ]] && {
printf '/\n'
return
}
tmp=${tmp%%"${tmp##*[!/]}"}
[[ $tmp != */* ]] && {
printf '.\n'
return
}
tmp=${tmp%/*}
tmp=${tmp%%"${tmp##*[!/]}"}
printf '%s\n' "${tmp:-/}"
}
bash_basename() {
local tmp
path="$1"
suffix="${2:-''}"
tmp=${path%"${path##*[!/]}"}
tmp=${tmp##*/}
tmp=${tmp%"${suffix/"$tmp"/}"}
printf '%s\n' "${tmp:-/}"
}
bash_realpath() {
local file=$1
local dir
# If the file is already absolute
[[ $file == /* ]] && {
printf '%s\n' "$file"
return
}
# Otherwise: split into directory + basename
dir="$(bash_dirname "${file}")"
file="$(bash_basename "${file}")"
# If no directory component, use current directory
if [[ $dir == "$file" ]]; then
dir="$PWD"
else
# Save current dir, move into target dir, capture $PWD, then return
local oldpwd="$PWD"
cd "$dir" || return 1
dir="$PWD"
cd "$oldpwd" || return 1
fi
printf '%s/%s\n' "$dir" "$file"
}
line_contains() {
local line="$1"
local substr="$2"
if [[ $line == *"${substr}"* ]]; then
return 0
else
return 1
fi
}
line_starts_with() {
local line="$1"
local substr="$2"
if [[ $line == "${substr}"* ]]; then
return 0
else
return 1
fi
}
is_linux() {
line_contains "${OSTYPE}" 'linux'
}
is_darwin() {
line_contains "$(print_os)" darwin
}
is_windows() {
line_contains "$(print_os)" windows
}
is_android() {
line_contains "$(print_os)" android
}
print_os() {
# cached response
if [[ -n ${FB_OS} ]]; then
echo "${FB_OS}"
return 0
fi
unset FB_OS
if [[ -f /etc/os-release ]]; then
source /etc/os-release
FB_OS="${ID}"
if [[ ${VERSION_ID} != '' ]]; then
FB_OS+="-${VERSION_ID}"
fi
if line_starts_with "${FB_OS}" 'arch'; then
FB_OS='archlinux'
fi
else
FB_OS="$(uname -o)"
fi
# lowercase
FB_OS="${FB_OS,,}"
# special treatment for windows
if line_contains "${FB_OS}" 'windows' || line_contains "${FB_OS}" 'msys'; then
FB_OS='windows'
fi
echo "${FB_OS}"
}
is_positive_integer() {
local input="$1"
if [[ ${input} != ?(-)+([[:digit:]]) || ${input} -lt 0 ]]; then
echo_fail "${input} is not a positive integer"
return 1
fi
return 0
}
replace_line() {
local file="$1"
local search="$2"
local newLine="$3"
local newFile="${TMP_DIR}/$(bash_basename "${file}")"
test -f "${newFile}" && rm "${newFile}"
while read -r line; do
if line_contains "${line}" "${search}"; then
echo -en "${newLine}" >>"${newFile}"
continue
fi
echo "${line}" >>"${newFile}"
done <"${file}"
cp "${newFile}" "${file}"
}
remove_line() {
local file="$1"
local search="$2"
replace_line "${file}" "${search}" ''
}
bash_sort() {
local arr=("$@")
local n=${#arr[@]}
local i j val1 val2
# Bubble sort, numeric comparison
for ((i = 0; i < n; i++)); do
for ((j = 0; j < n - i - 1; j++)); do
read -r val1 _ <<<"${arr[j]}"
read -r val2 _ <<<"${arr[j + 1]}"
if (("${val1}" > "${val2}")); then
local tmp=${arr[j]}
arr[j]=${arr[j + 1]}
arr[j + 1]=$tmp
fi
done
done
printf '%s\n' "${arr[@]}"
}
spinner() {
if [[ $1 == 'reset' ]]; then
echo -ne ' \n'
return 0
fi
local spinChars=(
"-"
'\'
"|"
"/"
)
while true; do
for ((ind = 0; ind < "${#spinChars[@]}"; ind++)); do
echo -ne "${spinChars[${ind}]}" '\b\b'
sleep .25
done
done
}
get_pkgconfig_version() {
local pkg="$1"
pkg-config --modversion "${pkg}"
}

67
main.sh
View File

@@ -2,8 +2,8 @@
# set top dir # set top dir
if [[ -z ${REPO_DIR} ]]; then if [[ -z ${REPO_DIR} ]]; then
thisFile="$(readlink -f "${BASH_SOURCE[0]}")" thisFile="$(readlink -f "${BASH_SOURCE[0]}")"
REPO_DIR="$(dirname "${thisFile}")" REPO_DIR="$(dirname "${thisFile}")"
fi fi
IGN_DIR="${REPO_DIR}/gitignore" IGN_DIR="${REPO_DIR}/gitignore"
@@ -15,16 +15,21 @@ DOCKER_DIR="${IGN_DIR}/docker"
PATCHES_DIR="${REPO_DIR}/patches" PATCHES_DIR="${REPO_DIR}/patches"
export REPO_DIR IGN_DIR TMP_DIR DL_DIR BUILD_DIR CCACHE_DIR DOCKER_DIR PATCHES_DIR export REPO_DIR IGN_DIR TMP_DIR DL_DIR BUILD_DIR CCACHE_DIR DOCKER_DIR PATCHES_DIR
# some functions need a way to signal early
# returns instead of failures, so if a function
# returns ${FUNC_EXIT_SUCCESS}, stop processing
test -v FUNC_EXIT_SUCCESS || readonly FUNC_EXIT_SUCCESS=9
# make paths if needed # make paths if needed
IGN_DIRS=( IGN_DIRS=(
"${TMP_DIR}" "${TMP_DIR}"
"${DL_DIR}" "${DL_DIR}"
"${BUILD_DIR}" "${BUILD_DIR}"
"${CCACHE_DIR}" "${CCACHE_DIR}"
"${DOCKER_DIR}" "${DOCKER_DIR}"
) )
for dir in "${IGN_DIRS[@]}"; do for dir in "${IGN_DIRS[@]}"; do
test -d "${dir}" || mkdir -p "${dir}" test -d "${dir}" || mkdir -p "${dir}"
done done
unset IGN_DIRS unset IGN_DIRS
@@ -42,12 +47,12 @@ FB_COMPILE_OPTS_SET=0
SCRIPT_DIR="${REPO_DIR}/scripts" SCRIPT_DIR="${REPO_DIR}/scripts"
ENTRY_SCRIPT="${SCRIPT_DIR}/entry.sh" ENTRY_SCRIPT="${SCRIPT_DIR}/entry.sh"
src_scripts() { src_scripts() {
local SCRIPT_DIR="${REPO_DIR}/scripts" local SCRIPT_DIR="${REPO_DIR}/scripts"
if [[ $FB_RUNNING_AS_SCRIPT -eq 0 ]]; then if [[ $FB_RUNNING_AS_SCRIPT -eq 0 ]]; then
rm "${SCRIPT_DIR}"/*.sh rm "${SCRIPT_DIR}"/*.sh
# shellcheck disable=SC2016 # shellcheck disable=SC2016
echo '#!/usr/bin/env bash echo '#!/usr/bin/env bash
export FB_RUNNING_AS_SCRIPT=1 export FB_RUNNING_AS_SCRIPT=1
thisFile="$(readlink -f "$0")" thisFile="$(readlink -f "$0")"
export REPO_DIR="$(cd "$(dirname "${thisFile}")/.." && echo "$PWD")" export REPO_DIR="$(cd "$(dirname "${thisFile}")/.." && echo "$PWD")"
@@ -56,32 +61,32 @@ scr_name="$(bash_basename $0)"
cmd="${scr_name//.sh/}" cmd="${scr_name//.sh/}"
if [[ $DEBUG == 1 ]]; then set -x; fi if [[ $DEBUG == 1 ]]; then set -x; fi
$cmd "$@"' >"${ENTRY_SCRIPT}" $cmd "$@"' >"${ENTRY_SCRIPT}"
chmod +x "${ENTRY_SCRIPT}" chmod +x "${ENTRY_SCRIPT}"
fi fi
for script in "${REPO_DIR}/lib/"*.sh; do for script in "${REPO_DIR}/lib/"*.sh; do
# shellcheck disable=SC1090 # shellcheck disable=SC1090
source "${script}" source "${script}"
done done
} }
FB_FUNC_NAMES+=('print_cmds') FB_FUNC_NAMES+=('print_cmds')
FB_FUNC_DESCS['print_cmds']='print usable commands' FB_FUNC_DESCS['print_cmds']='print usable commands'
print_cmds() { print_cmds() {
echo -e "~~~ Usable Commands ~~~\n" echo -e "~~~ Usable Commands ~~~\n"
for funcName in "${FB_FUNC_NAMES[@]}"; do for funcName in "${FB_FUNC_NAMES[@]}"; do
color="${CYAN}" word="${funcName}:" echo_wrapper "\n\t${FB_FUNC_DESCS[${funcName}]}" color="${CYAN}" word="${funcName}:" echo_wrapper "\n\t${FB_FUNC_DESCS[${funcName}]}"
if [[ $FB_RUNNING_AS_SCRIPT -eq 0 ]]; then if [[ $FB_RUNNING_AS_SCRIPT -eq 0 ]]; then
(cd "$SCRIPT_DIR" && ln -sf entry.sh "${funcName}.sh") (cd "$SCRIPT_DIR" && ln -sf entry.sh "${funcName}.sh")
fi fi
done done
echo -e "\n" echo -e "\n"
} }
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}"
done done
} }
src_scripts || return 1 src_scripts || return 1
@@ -93,6 +98,6 @@ check_compile_opts_override || return 1
LOCAL_PREFIX="${IGN_DIR}/$(print_os)_sysroot" LOCAL_PREFIX="${IGN_DIR}/$(print_os)_sysroot"
if [[ ${FB_RUNNING_AS_SCRIPT} -eq 0 ]]; then if [[ ${FB_RUNNING_AS_SCRIPT} -eq 0 ]]; then
print_cmds || return 1 print_cmds || return 1
fi fi
set_completions || return 1 set_completions || return 1

View File

@@ -0,0 +1,11 @@
# https://sourceforge.net/p/lame/mailman/message/36081038/
diff --git a/include/libmp3lame.sym b/include/libmp3lame.sym
index ff7d318..fd120f5 100644
--- a/include/libmp3lame.sym
+++ b/include/libmp3lame.sym
@@ -1,5 +1,4 @@
lame_init
-lame_init_old
lame_set_num_samples
lame_get_num_samples
lame_set_in_samplerate

View File

@@ -0,0 +1,24 @@
diff --git a/vorbis.pc.in b/vorbis.pc.in
index f5ca77d..50cad9e 100644
--- a/vorbis.pc.in
+++ b/vorbis.pc.in
@@ -10,6 +10,6 @@ Description: vorbis is the primary Ogg Vorbis library
Version: @VERSION@
Requires.private: ogg
Conflicts:
-Libs: -L${libdir} -lvorbis
-Libs.private: @VORBIS_LIBS@
+Libs: -L${libdir} -lvorbis
+Libs.private: @VORBIS_LIBS@ -lm
Cflags: -I${includedir}
diff --git a/vorbisenc.pc.in b/vorbisenc.pc.in
index a412b7a..4222daf 100644
--- a/vorbisenc.pc.in
+++ b/vorbisenc.pc.in
@@ -10,5 +10,5 @@ Description: vorbisenc is a library that provides a convenient API for setting u
Version: @VERSION@
Requires.private: vorbis
Conflicts:
-Libs: -L${libdir} -lvorbisenc
+Libs: -L${libdir} -lvorbisenc -lm
Cflags: -I${includedir}

View File

@@ -0,0 +1,13 @@
diff --git a/build/make/configure.sh b/build/make/configure.sh
index cc5bf6c..c229965 100644
--- a/build/make/configure.sh
+++ b/build/make/configure.sh
@@ -16,8 +16,6 @@
die_unknown(){
echo "Unknown option \"$1\"."
echo "See $0 --help for available options."
- clean_temp_files
- exit 1
}
die() {

View File

@@ -3,13 +3,13 @@
base="$(dirname "$(readlink -f "$0")")" base="$(dirname "$(readlink -f "$0")")"
inotifywait -m -r \ inotifywait -m -r \
-e close_write \ -e close_write \
-e moved_to \ -e moved_to \
--format '%w%f' \ --format '%w%f' \
"$base/lib" \ "${base}/lib" \
"$base/scripts" \ "${base}/scripts" \
"$base/main.sh" | while read -r file; do "${base}/main.sh" | while read -r file; do
if [[ -f $file && $file =~ .sh ]]; then if [[ -f $file && $file =~ .sh ]]; then
shfmt --write --simplify "$file" shfmt --indent 4 --write --simplify "${file}"
fi fi
done done