Compare commits

...

106 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
dd07038ab7 more small fixes 2025-12-05 18:08:33 -06:00
675fdec278 add configure_build and install_local_destdir 2025-12-05 11:58:45 -06:00
9f018d9036 updates for windows 2025-12-04 17:21:51 -06:00
2c675e70be remove adaptive film grain flag 2025-11-29 15:19:16 -06:00
31ae0e49ae update README 2025-11-28 13:44:49 -06:00
de86c8c27f update README.md 2025-11-28 13:36:24 -06:00
9596f68a9e update README.md 2025-11-28 13:31:30 -06:00
aa4d7e61f1 fix mismatch of output stream index vs input stream index 2025-11-25 14:06:55 -06:00
609ddea197 fix conversion to/from mov_text/srt 2025-11-25 10:05:48 -06:00
5ed906017a initial fixups for android 2025-11-19 12:43:13 -06:00
8d10c5f94b two small fixes for encode 2025-11-04 07:51:35 -06:00
96ec4b6566 add cmake/meson_build funcs 2025-11-02 13:46:28 -06:00
636c6a0ff8 make nobody:nogroup usable 2025-11-01 16:50:52 -05:00
000bdb24a0 docker exporter does not currently support exporting manifest lists 2025-11-01 16:04:44 -05:00
afc4ca05cb attempt to fix chown issue 2025-11-01 16:03:03 -05:00
4065cff08c use cargo-binstall to install cargo-c 2025-11-01 14:47:06 -05:00
2550ba56d9 fix build chains 2025-11-01 14:29:33 -05:00
81a2cde5c7 more random fixes 2025-10-28 17:31:46 -05:00
8a5ed69f86 more clean slate fixes 2025-10-28 16:31:43 -05:00
014448ac55 init fixes for windows 2025-10-28 16:14:31 -05:00
7cd3f03417 fix system PREFIX install 2025-10-27 11:41:33 -05:00
c81b0a38d6 only rebuild when metadata is different 2025-10-26 13:37:48 -05:00
1a6f442679 small fixes 2025-10-24 20:46:04 -05:00
2cc802ff78 fix how encode versioning is found and stored 2025-10-24 12:06:46 -05:00
829e431fb3 fix LTO x264 breaking full build 2025-10-24 10:52:42 -05:00
a0c0f11aae add spinner to builds 2025-10-21 11:52:17 -05:00
d5680d8816 copy av1 inputs, copy audio lang that matches video, and only copy english subs 2025-10-19 14:14:12 -05:00
4b88fe30f8 disable DV by default 2025-09-28 12:35:53 -05:00
c8660baba1 small changes 2025-09-26 12:22:33 -05:00
abe97b8df3 rip typo 2025-09-18 14:31:41 -05:00
5b93ff823f always wipe package dir 2025-09-18 08:08:50 -05:00
81e8bf2567 use full tag and save cargo in dedicated directory 2025-09-17 19:20:00 -05:00
f7a1e09ceb add runtime-cpu-detect and copy debug binaries 2025-09-15 18:59:20 -05:00
0aa77d9f84 more fixes 2025-09-13 21:41:21 -05:00
814664adee many small fixes 2025-09-13 18:03:04 -05:00
748ccd4e3c add libwebp libmp3lame 2025-09-13 13:42:30 -05:00
82ff7d4f43 docker reworking 2025-09-11 19:45:35 -05:00
0e97fb8c91 add back numa 2025-09-07 17:41:16 -05:00
97289eed42 fixes to git downloads and x265 for cmake v4 2025-09-07 11:47:17 -05:00
6be45728bd fix numa 2025-09-07 09:19:43 -05:00
a63d9e3e65 add x26{4,5} 2025-09-06 20:13:56 -05:00
0518a48045 omit --arch/--cpu for ffmpeg configure 2025-09-04 20:13:24 -05:00
cc7e60ec37 fix $ARCH 2025-09-04 17:55:24 -05:00
0fe6f2423f -march updates 2025-09-03 20:50:33 -05:00
19bb49a982 efg 2025-08-31 16:37:16 -05:00
a444fee1fb move and rename 2025-08-30 17:39:18 -05:00
407179d9ef more fixes 2025-08-30 11:44:53 -05:00
b884307cc1 fix REPO_DIR and encode 2025-08-29 17:24:11 -05:00
0d515af0f0 fix patch 2025-08-29 14:00:42 -05:00
b1c89719d0 add logName to echo_if_fail 2025-08-28 18:51:19 -05:00
315d340257 add encode script 2025-08-28 17:57:02 -05:00
96689ac1d5 revert naming 2025-08-24 13:33:30 -05:00
9c6450ecaf update libsvtav1 version, use libsvtav1_psyex, and fix lto+darwin again 2025-08-24 13:19:08 -05:00
49f9181f9d remove libc, add patches 2025-08-24 12:44:23 -05:00
e2366892ca move patch to dedicated directory 2025-08-24 12:43:14 -05:00
ab145089f4 update jenkinsfile 2025-08-23 20:34:28 -05:00
61e70f5cd2 add darwin to CI 2025-08-23 16:51:15 -05:00
48eb84654c fixup builds for static/shared darwin 2025-08-23 14:19:33 -05:00
9d7c85b250 switch to ffmpeg 8.0 2025-08-23 13:03:24 -05:00
fbfb7c224d rename variables 2025-08-23 12:54:05 -05:00
21d3167feb fix add_project_versioning_to_ffmpeg 2025-08-22 11:51:41 -05:00
bd5d5aaec9 init darwin support 2025-08-21 17:01:13 -05:00
cb78470af1 add -fPIC to all builds 2025-08-18 08:40:04 -05:00
f3ff751430 fix label and don't wipe prefix 2025-08-17 18:38:54 -05:00
559ef5583e jenkinsfile fix 2025-08-17 18:27:14 -05:00
65d8d24480 many small fixes to allow user-overridable defaults 2025-08-17 18:25:06 -05:00
4345ca5878 try debian 13 and fedora 41 2025-08-15 14:20:26 -05:00
586d871f88 fix archlinux image 2025-08-14 08:51:51 -05:00
7175dcf7ba more small fixes 2025-08-13 16:35:16 -05:00
f07486cb11 add cpuinfo to list of dependencies 2025-08-13 16:26:30 -05:00
26 changed files with 3481 additions and 1095 deletions

87
Jenkinsfile vendored
View File

@@ -1,47 +1,76 @@
def withDockerCreds(body) {
withCredentials([
string(credentialsId: 'DOCKER_REGISTRY', variable: 'DOCKER_REGISTRY'),
usernamePassword(
credentialsId: 'DOCKER_REGISTRY_CRED',
passwordVariable: 'DOCKER_REGISTRY_PASS',
usernameVariable: 'DOCKER_REGISTRY_USER'
)
]) {
body()
}
}
pipeline { pipeline {
agent none agent none
environment { DEBUG = "1" } environment {
DEBUG = "1"
}
options { buildDiscarder logRotator(numToKeepStr: '4') }
stages { stages {
stage('Build in Docker') { stage('build docker image') {
matrix { matrix {
axes { axes {
axis { axis { name 'DISTRO'; values 'ubuntu', 'fedora', 'debian', 'archlinux' }
name 'DISTRO'
values 'ubuntu-22.04', 'ubuntu-24.04',
'debian-12', 'fedora-42',
'ogarcia/archlinux-latest'
}
axis {
name 'ARCH'
values 'amd64', 'arm64'
}
} }
stages { stages {
stage('Build Multiarch Image') { stage('build multiarch image') {
agent { label "linux && amd64" } agent { label "linux && amd64" }
steps { steps {
withCredentials([string( withDockerCreds {
credentialsId: 'DOCKER_REGISTRY',
variable: 'DOCKER_REGISTRY'),
usernamePassword(credentialsId: 'DOCKER_REGISTRY_CRED',
passwordVariable: 'DOCKER_REGISTRY_PASS',
usernameVariable: 'DOCKER_REGISTRY_USER'
)]) {
sh "./scripts/docker_build_multiarch_image.sh ${DISTRO}" sh "./scripts/docker_build_multiarch_image.sh ${DISTRO}"
} }
} }
} }
stage('Run Multiarch Image') { }
}
}
stage('build ffmpeg on darwin') {
matrix {
axes {
axis {
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 {
stage('build on darwin ') {
agent { label "darwin" }
steps {
sh "${COMP_OPTS} ./scripts/build.sh"
archiveArtifacts allowEmptyArchive: true, artifacts: 'gitignore/package/*.tar.xz', defaultExcludes: false
}
}
}
}
}
stage('build ffmpeg on linux') {
matrix {
axes {
axis { name 'ARCH'; values 'armv8-a', 'x86-64-v3' }
axis { name 'DISTRO'; values 'ubuntu', 'fedora', 'debian', 'archlinux' }
axis {
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 {
stage('build ffmpeg on linux using docker') {
agent { label "linux && ${ARCH}" } agent { label "linux && ${ARCH}" }
steps { steps {
withCredentials([string( withDockerCreds {
credentialsId: 'DOCKER_REGISTRY', sh "${COMP_OPTS} ./scripts/build_with_docker.sh ${DISTRO}"
variable: 'DOCKER_REGISTRY'), archiveArtifacts allowEmptyArchive: true, artifacts: 'gitignore/package/*.tar.xz', defaultExcludes: false
usernamePassword(credentialsId: 'DOCKER_REGISTRY_CRED',
passwordVariable: 'DOCKER_REGISTRY_PASS',
usernameVariable: 'DOCKER_REGISTRY_USER'
)]) {
sh "./scripts/docker_run_image.sh ${DISTRO}"
} }
} }
} }

166
README.md
View File

@@ -1 +1,165 @@
# ffmpeg-builder
# ffmpeg-builder
A collection of scripts for building `ffmpeg` and encoding content with the built `ffmpeg`.
Tested on:
- linux x86_64/aarch64 on:
- ubuntu
- fedora
- debian
- archlinux
- darwin aarch64
With these scripts you can:
1. install required dependencies using `./scripts/install_deps.sh`
2. build ffmpeg with the desired configuration using `./scripts/build.sh`
3. encode a file using libsvtav1_psy and libopus using `./scripts/encode.sh`
4. estimate the film grain of a given file using `./scripts/efg.sh`
# Building
This project supports multiple ways to build.
## Configuration
Configuration is done through environment variables.
By default, this project will build a static `ffmpeg` binary in `./gitignore/sysroot/bin/ffmpeg`.
The default enabled libraries included in the `ffmpeg` build are:
- libsvtav1_psy
- libopus
- libdav1d
- libaom
- librav1e
- libvmaf
- libx264
- libx265
- libwebp
- libvpx
- libass
- libvorbis
- libmp3lame
The user-overridable compile options are:
- `ENABLE`: configure what ffmpeg enables (default: libsvtav1_psy libopus libdav1d libaom librav1e libvmaf libx264 libx265 libwebp libvpx libass libvorbis libmp3lame)
- `PREFIX`: prefix to install to, default is local install in ./gitignore/sysroot (default: local)
- `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:
- only build libsvtav1_psy and libopus: `ENABLE='libsvtav1_psy libopus' ./scripts/build.sh`
- build shared libraries in custom path: `PREFIX=/usr/local STATIC=OFF ./scripts/build.sh`
- build without LTO at O1: `LTO=OFF OPT=1 ./scripts/build.sh`
### Native build
Make sure to install required dependencies using `./scripts/install_deps.sh`. Then build ffmpeg with the desired configuration using `./scripts/build.sh`.
### Docker build
1. choose a given distro from: `ubuntu fedora debian archlinux`.
2. build a docker image with the required dependencies pre-installed using `./scripts/docker_build_image.sh` `<distro>`.
3. run a docker image with the given arguments using `./scripts/docker_run_image.sh` `<distro>` `./scripts/build.sh`
Docker builds support the same configuration options as native builds. For example to build `ffmpeg` on ubuntu with only `libdav1d` enabled:
```bash
./scripts/docker_build_image.sh ubuntu
ENABLE='libdav1d' ./scripts/docker_run_image.sh ubuntu ./scripts/build.sh
```
# Encoding scripts
The encoding scripts are designed to be installed to system paths for re-use via symbolic links back to this repo using the `-I` flag.
## Encoding with svtav1-psy and opus
```bash
encode -i input [options] output
[-P NUM] set preset (default: 3)
[-C NUM] set CRF (default: 25)
[-g NUM] set film grain for encode
[-p] print the command instead of executing it (default: false)
[-c] use cropdetect (default: false)
[-d] enable dolby vision (default: false)
[-v] print relevant version info
[-s] use same container as input, default is convert to mkv
[output] if unset, defaults to ${PWD}/av1-input-file-name.mkv
[-u] update script (git pull ffmpeg-builder)
[-I] system install at /usr/local/bin/encode
[-U] uninstall from /usr/local/bin/encode
```
- Uses svtav1-psy for the video encoder.
- Uses libopus for the audio encoder.
- Skips re-encoding av1/opus streams.
- Only maps audio streams that match the video stream language if the video stream has a defined language.
- Only maps english subtitle streams.
- Adds track statistics to the output mkv file and embeds the encoder versions to the output metadata. For example:
```
ENCODE : aa4d7e6
FFMPEG : 8.0
LIBOPUS : 1.5.2
LIBSVTAV1_PSY : 3.0.2-B
SVTAV1_PARAMS : film-grain=8:film-grain-denoise=1:tune=0:...
VIDEO_PARAMS : -crf 25 -preset 3 -g 240
```
Example usage:
- standard usage: `encode -i input.mkv output.mkv`
- print out what will be executed without starting encoding: `encode -i input.mkv -p`
- encode with film-grain synthesis value of 20 with denoising: `encode -g 20 -i input.mkv`
## Estimate film-grain
```bash
efg -i input [options]
[-P NUM] set preset (default: 10)
[-l NUM] low value (default: 0)
[-s NUM] step value (default: 1)
[-h NUM] high value (default: 30)
[-p] plot bitrates using gnuplot
[-I] system install at /usr/local/bin/efg
[-U] uninstall from /usr/local/bin/efg
```
- Provides a way to estimate the ideal film-grain amount by encoding from low-high given values and the step amount.
- Observing the point of diminishing returns, one can make an informed decision for a given film-grain value to choose.
Example usage:
- `efg -i input.mkv -p`
```
10000 +------------------------------------------------------------------------------------------------------------------------------------------+
| **G* + + + + + |
| ** /Volumes/External/ffmpeg-builder/gitignore/tmp/efg-matrix-reloaded.mkv/plot.dat ***G*** |
| *G** |
| **G |
9000 |-+ ** +-|
| *G** |
| **G |
| ** |
| *G* |
8000 |-+ ** +-|
| *G** |
| **G |
| ** |
| *G** |
7000 |-+ **G* +-|
| ** |
| *G* |
| **G** |
6000 |-+ **G* +-|
| ** |
| *G* |
| **G** |
| **G* |
5000 |-+ **G** +-|
| **G****G* |
| **G** |
| **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

@@ -1,160 +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_fail() { echo -e "${RED}FAIL${NC}:" "$@"; }
echo_info() { echo -e "${CYAN}INFO${NC}:" "$@"; }
echo_pass() { echo -e "${GREEN}PASS${NC}:" "$@"; }
echo_warn() { echo -e "${YELLOW}WARN${NC}:" "$@"; }
echo_exit() {
echo_fail "$@"
exit 1
}
void() { echo "$@" >/dev/null; }
echo_if_fail() {
local cmd=("$@")
local out="${TMP_DIR}/.stdout-${RANDOM}"
local err="${TMP_DIR}/.stderr-${RANDOM}"
# set trace to the cmdEvalTrace and open file descriptor
local cmdEvalTrace="${TMP_DIR}/.cmdEvalTrace-${RANDOM}"
test -d "${TMP_DIR}" || mkdir -p "${TMP_DIR}"
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=()
cmd=()
while IFS= read -r line; do
cmdEvalLines+=("${line}")
done <"${cmdEvalTrace}"
local cmdEvalLineNum=${#cmdEvalLines[@]}
for ((i = 1; i < cmdEvalLineNum - 2; i++)); do
local trimmedCmd="${cmdEvalLines[${i}]}"
trimmedCmd="${trimmedCmd/+ /}"
cmd+=("${trimmedCmd}")
done
if ! test ${retval} -eq 0; then
echo
echo_fail "command failed:"
printf "%s\n" "${cmd[@]}"
echo_warn "command output:"
tail -n 10 "${out}"
tail -n 10 "${err}"
echo
fi
rm "${out}" "${err}" "${cmdEvalTrace}"
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 cmd="$1"
command -v "${cmd}" >/dev/null 2>&1
}
missing_cmd() {
local cmd="$1"
rv=1
if ! has_cmd "${cmd}"; then
echo_warn "missing ${cmd}"
rv=0
fi
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:-/}"
}
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
}

View File

@@ -3,26 +3,63 @@
# variables used externally # variables used externally
# shellcheck disable=SC2034 # shellcheck disable=SC2034
# clean build directories before building # compile option descriptions
CLEAN=true unset FB_COMP_OPTS_DESC
# enable link time optimization declare -Ag FB_COMP_OPTS_DESC
LTO=false
# optimization level (0-3) # default compile options
OPT_LVL=0 FB_COMP_OPTS_DESC['CLEAN']='clean build directories before building'
# static or shared build DEFAULT_CLEAN=ON
STATIC=true
# CPU type (amd64/v{1,2,3}...) FB_COMP_OPTS_DESC['LTO']='enable link time optimization'
CPU=native DEFAULT_LTO=ON
# architecture type
ARCH=native FB_COMP_OPTS_DESC['PGO']='enable profile guided optimization'
# prefix to install, leave empty for non-system install (local) DEFAULT_PGO=OFF
PREFIX=''
# configure what ffmpeg enables FB_COMP_OPTS_DESC['OPT']='optimization level (0-3)'
FFMPEG_ENABLES=( DEFAULT_OPT=3
libopus
libdav1d FB_COMP_OPTS_DESC['STATIC']='static or shared build'
libsvtav1_psy DEFAULT_STATIC=ON
libaom
librav1e FB_COMP_OPTS_DESC['ARCH']='architecture type (x86-64-v{1,2,3,4}, armv8-a, etc)'
libvmaf DEFAULT_ARCH=native
)
FB_COMP_OPTS_DESC['PREFIX']='prefix to install to, default is local install in ./gitignore/sysroot'
DEFAULT_PREFIX='local'
FB_COMP_OPTS_DESC['ENABLE']='configure what ffmpeg enables'
DEFAULT_ENABLE="\
libsvtav1_psy \
libopus \
libdav1d \
libaom \
librav1e \
libvmaf \
libx264 \
libx265 \
libwebp \
libvpx \
libass \
libvorbis \
libmp3lame\
"
# user-overridable compile option variable names
FB_COMP_OPTS=("${!FB_COMP_OPTS_DESC[@]}")
# sets FB_COMP_OPTS to allow for user-overriding
check_compile_opts_override() {
for opt in "${FB_COMP_OPTS[@]}"; do
declare -n defOptVal="DEFAULT_${opt}"
declare -n optVal="${opt}"
# use given value if not overridden
if [[ -v optVal && ${optVal} != "${defOptVal}" ]]; then
echo_info "setting given value for ${opt}=${optVal}"
declare -g "${opt}=${optVal}"
else
declare -g "${opt}=${defOptVal}"
fi
done
}

View File

@@ -1,279 +1,326 @@
#!/usr/bin/env bash #!/usr/bin/env bash
VALID_DOCKER_IMAGES=( VALID_DOCKER_IMAGES=(
'ubuntu-22.04' 'ubuntu-24.04' 'ubuntu'
'fedora-41' 'fedora-42' 'fedora'
'debian-12' 'debian'
'ogarcia/archlinux-latest' 'archlinux'
) )
DOCKER_WORKDIR='/workdir' DOCKER_WORKDIR='/workdir'
DOCKER_RUN_FLAGS=(
--rm set_docker_run_flags() {
-v "${REPO_DIR}:${DOCKER_WORKDIR}" local cargo_git="${IGN_DIR}/cargo/git"
-w "${DOCKER_WORKDIR}" local cargo_registry="${IGN_DIR}/cargo/registry"
-e "DEBUG=${DEBUG}" ensure_dir "${cargo_git}" "${cargo_registry}"
) DOCKER_RUN_FLAGS=(
--rm
-v "${cargo_git}:/root/.cargo/git"
-v "${cargo_registry}:/root/.cargo/registry"
-v "${REPO_DIR}:${REPO_DIR}"
-w "${REPO_DIR}"
-e "DEBUG=${DEBUG}"
-t
)
for opt in "${FB_COMP_OPTS[@]}"; do
declare -n defOptVal="DEFAULT_${opt}"
declare -n optVal="${opt}"
if [[ -v optVal && ${optVal} != "${defOptVal}" ]]; then
DOCKER_RUN_FLAGS+=("-e" "${opt}=${optVal}")
fi
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
}
# get full image digest for a given image
get_docker_image_tag() {
local image="$1"
local tag=''
case "${image}" in
ubuntu) tag='ubuntu:24.04@sha256:c35e29c9450151419d9448b0fd75374fec4fff364a27f176fb458d472dfc9e54' ;;
debian) tag='debian:13@sha256:0d01188e8dd0ac63bf155900fad49279131a876a1ea7fac917c62e87ccb2732d' ;;
fedora) tag='fedora:42@sha256:b3d16134560afa00d7cc2a9e4967eb5b954512805f3fe27d8e70bbed078e22ea' ;;
archlinux) tag='ogarcia/archlinux:latest@sha256:1d70273180e43b1f51b41514bdaa73c61f647891a53a9c301100d5c4807bf628' ;;
esac
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}"
} }
# sets DISTROS
validate_selected_image() { validate_selected_image() {
local selectedImage="${1:-}" local selectedImage="$1"
for distro in "${VALID_DOCKER_IMAGES[@]}"; do local valid=1
if [[ ${selectedImage} == "${distro}" ]]; then for image in "${VALID_DOCKER_IMAGES[@]}"; do
DISTROS+=("${distro}") if [[ ${selectedImage} == "${image}" ]]; then
fi valid=0
done break
if [[ ${DISTROS[*]} == '' ]]; then fi
echo_fail "${selectedImage} is not valid" done
echo_info "valid images:" "${VALID_DOCKER_IMAGES[@]}" if [[ valid -eq 1 ]]; then
return 1 echo_fail "${selectedImage} is not valid"
fi echo_info "valid images:" "${VALID_DOCKER_IMAGES[@]}"
return 1
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 docker image with 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() {
validate_selected_image "$@" || return 1 local image="$1"
check_docker || return 1 validate_selected_image "${image}" || return 1
test -d "${DOCKER_DIR}" || mkdir -p "${DOCKER_DIR}" check_docker || return 1
PLATFORM="${PLATFORM:-$(echo_platform)}" PLATFORM="${PLATFORM:-$(echo_platform)}"
for distro in "${DISTROS[@]}"; do echo_info "sourcing package manager for ${image}"
image_tag="$(set_distro_image_tag "${distro}")" local dockerDistro="$(get_docker_image_tag "${image}")"
echo_info "sourcing package manager for ${image_tag}" # specific file for evaluated package manager info
local distroPkgMgr="${DOCKER_DIR}/$(bash_basename "${image}")-pkg_mgr"
# get package manager info
docker run \
"${DOCKER_RUN_FLAGS[@]}" \
"${dockerDistro}" \
bash -c "./scripts/print_pkg_mgr.sh" | tr -d '\r' >"${distroPkgMgr}"
# shellcheck disable=SC1090
cat "${distroPkgMgr}"
# shellcheck disable=SC1090
source "${distroPkgMgr}"
# docker expects colon instead of dash local dockerfile="${DOCKER_DIR}/Dockerfile_$(bash_basename "${image}")"
dockerDistro="${distro//-/:}" local embedPath='/Dockerfile'
# specific file for evaluated package manager info {
distroPkgMgr="${DOCKER_DIR}/$(bash_basename "${distro}")-pkg_mgr)" echo "FROM ${dockerDistro}"
# get package manager info echo 'SHELL ["/bin/bash", "-c"]'
echo 'RUN ln -sf /bin/bash /bin/sh'
echo 'ENV DEBIAN_FRONTEND=noninteractive'
echo "RUN ${pkg_mgr_update} && ${pkg_mgr_upgrade} && ${pkg_install} ${req_pkgs[*]}"
# TODO REMOVE # ENV for pipx/rust
if is_root_owned "${IGN_DIR}"; then echo 'ENV PIPX_HOME=/root/.local'
docker run \ echo 'ENV PIPX_BIN_DIR=/root/.local/bin'
"${DOCKER_RUN_FLAGS[@]}" \ echo 'ENV PATH="/root/.local/bin:$PATH"'
"${dockerDistro}" \ echo 'ENV CARGO_HOME="/root/.cargo"'
chown -R "$(id -u):$(id -g)" "${DOCKER_WORKDIR}"/gitignore echo 'ENV RUSTUP_HOME="/root/.rustup"'
fi echo 'ENV PATH="/root/.cargo/bin:$PATH"'
if ! echo_if_fail ls; then # add to profile
docker run \ echo 'RUN export PIPX_HOME=${PIPX_HOME} >> /etc/profile'
"${DOCKER_RUN_FLAGS[@]}" \ echo 'RUN export PIPX_BIN_DIR=${PIPX_BIN_DIR} >> /etc/profile'
"${dockerDistro}" \ echo 'RUN export CARGO_HOME=${CARGO_HOME} >> /etc/profile'
chown -R "$(id -u):$(id -g)" "${DOCKER_WORKDIR}"/gitignore echo 'RUN export RUSTUP_HOME=${RUSTUP_HOME} >> /etc/profile'
fi echo 'RUN export PATH=${PATH} >> /etc/profile'
docker run \ # make nobody:nogroup usable
"${DOCKER_RUN_FLAGS[@]}" \ echo 'RUN sed -i '/nobody/d' /etc/passwd || true'
"${dockerDistro}" \ echo 'RUN echo "nobody:x:65534:65534:nobody:/root:/bin/bash" >> /etc/passwd'
bash -c "./scripts/print_pkg_mgr.sh" | tr -d '\r' >"${distroPkgMgr}" echo 'RUN sed -i '/nogroup/d' /etc/group || true'
# shellcheck disable=SC1090 echo 'RUN echo "nogroup:x:65534:" >> /etc/group'
cat "${distroPkgMgr}" # open up permissions before switching user
source "${distroPkgMgr}" echo 'RUN chmod 777 -R /root/'
# run as nobody:nogroup for rest of install
echo 'USER 65534:65534'
# pipx
echo "RUN pipx install virtualenv"
# rust
local rustupVersion='1.28.2'
local rustcVersion='1.90.0'
local rustupTarball="rustup-${rustupVersion}.tar.gz"
local rustupTarballPath="${DOCKER_DIR}/${rustupTarball}"
if [[ ! -f ${rustupTarballPath} ]]; then
wget https://github.com/rust-lang/rustup/archive/refs/tags/${rustupVersion}.tar.gz -O "${rustupTarballPath}"
fi
dockerfile="${DOCKER_DIR}/Dockerfile_$(bash_basename "${distro}")" echo "ADD ${rustupTarball} /tmp/"
{ echo "RUN cd /tmp/rustup-${rustupVersion} && bash rustup-init.sh -y --default-toolchain=${rustcVersion}"
echo "FROM ${dockerDistro}" # install cargo-binstall
echo 'SHELL ["/bin/bash", "-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"
echo 'RUN ln -sf /bin/bash /bin/sh' # install cargo-c
echo 'ENV DEBIAN_FRONTEND=noninteractive' echo "RUN cargo-binstall -y cargo-c"
# arch is rolling release, so highly likely
# an updated is required between pkg changes
if line_contains "${dockerDistro}" 'archlinux'; 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
echo 'RUN pipx install virtualenv'
echo 'RUN pipx ensurepath'
echo 'ENV CARGO_HOME="/root/.cargo"'
echo 'ENV RUSTUP_HOME="/root/.rustup"'
echo 'ENV PATH="/root/.cargo/bin:$PATH"'
local cargoInst=''
cargoInst+='curl https://sh.rustup.rs -sSf | bash -s -- -y'
cargoInst+=' && rustup update stable'
cargoInst+=' && cargo install cargo-c'
cargoInst+=' && rm -rf "${CARGO_HOME}"/registry "${CARGO_HOME}"/git'
echo "RUN ${cargoInst}"
# since any user may run this image,
# open up root tools to everyone
echo 'ENV PATH="/root/.local/bin:$PATH"'
echo 'RUN chmod 777 -R /root/'
echo "WORKDIR ${DOCKER_WORKDIR}"
} >"${dockerfile}" # 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
docker buildx build \ # embed dockerfile into docker image itself
--platform "${PLATFORM}" \ # shellcheck disable=SC2094
-t "${image_tag}" \ echo "COPY $(bash_basename "${dockerfile}") ${embedPath}"
-f "${dockerfile}" \
. || return 1
# if a docker registry is defined, push to it echo "WORKDIR ${DOCKER_WORKDIR}"
if [[ ${DOCKER_REGISTRY} != '' ]]; then
docker_login || return 1
docker buildx build \
--push \
--platform "${PLATFORM}" \
-t "${DOCKER_REGISTRY}/${image_tag}" \
-f "${dockerfile}" \
. || return 1
fi
docker system prune -f } >"${dockerfile}"
done
# docker buildx is too aggressive with invalidating
# 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() {
validate_selected_image "$@" || return 1 local image="$1"
check_docker || return 1 validate_selected_image "${image}" || return 1
for distro in "${DISTROS[@]}"; do check_docker || return 1
image_tag="$(set_distro_image_tag "${distro}")" 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
done
} }
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() {
validate_selected_image "$@" || return 1 local image="$1"
check_docker || return 1 validate_selected_image "${image}" || return 1
for distro in "${DISTROS[@]}"; do check_docker || return 1
image_tag="$(set_distro_image_tag "${distro}")" 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
done
} }
FB_FUNC_NAMES+=('docker_run_image') FB_FUNC_NAMES+=('docker_run_image')
FB_FUNC_DESCS['docker_run_image']='run docker image to build ffmpeg' 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() {
validate_selected_image "$@" || return 1 local image="$1"
check_docker || return 1 shift
validate_selected_image "${image}" || return 1
check_docker || return 1
for distro in "${DISTROS[@]}"; do local cmd=("$@")
image_tag="$(set_distro_image_tag "${distro}")" local runCmd=()
if [[ ${cmd[*]} == '' ]]; then
DOCKER_RUN_FLAGS+=("-it")
else
runCmd+=("${cmd[@]}")
fi
# TODO REMOVE local image_tag="$(set_distro_image_tag "${image}")"
if is_root_owned "${IGN_DIR}"; then
docker run \
"${DOCKER_RUN_FLAGS[@]}" \
"${image_tag}" \
chown -R "$(id -u):$(id -g)" "${DOCKER_WORKDIR}"/gitignore
fi
if ! echo_if_fail ls; then
docker run \
"${DOCKER_RUN_FLAGS[@]}" \
"${image_tag}" \
chown -R "$(id -u):$(id -g)" "${DOCKER_WORKDIR}"/gitignore
fi
testfile="${PREFIX}/ffmpeg-build-testfile"
if ! touch "${testfile}" 2>/dev/null; then
docker run \
"${DOCKER_RUN_FLAGS[@]}" \
"${image_tag}" \
chown -R "$(id -u):$(id -g)" "${DOCKER_WORKDIR}"/gitignore
fi
# 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}" "${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 for ${image_tag}" echo_info "running docker image ${image_tag}"
docker run \
"${DOCKER_RUN_FLAGS[@]}" \
-u "$(id -u):$(id -g)" \
"${image_tag}" \
"${runCmd[@]}"
docker run \ local rv=$?
"${DOCKER_RUN_FLAGS[@]}" \ docker image prune -f
-u "$(id -u):$(id -g)" \ return ${rv}
"${image_tag}" \ }
./scripts/build.sh || return 1
done FB_FUNC_NAMES+=('build_with_docker')
FB_FUNC_DESCS['build_with_docker']='run docker image with given flags'
FB_FUNC_COMPLETION['build_with_docker']="${VALID_DOCKER_IMAGES[*]}"
build_with_docker() {
local image="$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() {
validate_selected_image "$@" || return 1 local image="$1"
check_docker || return 1 validate_selected_image "${image}" || return 1
PLATFORM='linux/amd64,linux/arm64' check_docker || return 1
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 "$@"
} }

284
lib/efg.sh Normal file
View File

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

484
lib/encode.sh Normal file
View File

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

169
lib/ffmpeg.sh Normal file
View File

@@ -0,0 +1,169 @@
#!/usr/bin/env bash
get_duration() {
local file="$1"
ffprobe \
-v error \
-show_entries format=duration \
-of default=noprint_wrappers=1:nokey=1 \
"${file}"
}
get_avg_bitrate() {
local file="$1"
ffprobe \
-v error \
-select_streams v:0 \
-show_entries format=bit_rate \
-of default=noprint_wrappers=1:nokey=1 \
"${file}"
}
split_video() {
local file="$1"
local start="$2"
local time="$3"
local out="$4"
ffmpeg \
-ss "${start}" \
-i "${file}" \
-hide_banner \
-loglevel error \
-t "${time}" \
-map 0:v \
-reset_timestamps 1 \
-c copy \
"${out}"
}
get_crop() {
local file="$1"
local duration
duration="$(get_duration "${file}")" || return 1
# don't care about decimal points
IFS='.' read -r duration _ <<<"${duration}"
# get crop value for first half of input
local timeEnc=$((duration / 2))
ffmpeg \
-y \
-hide_banner \
-ss 0 \
-discard 'nokey' \
-i "${file}" \
-t "${timeEnc}" \
-map '0:v:0' \
-filter:v:0 'cropdetect=limit=100:round=16:skip=2:reset_count=0' \
-codec:v 'wrapped_avframe' \
-f 'null' '/dev/null' 2>&1 |
grep -o crop=.* |
sort -bh |
uniq -c |
sort -bh |
tail -n1 |
grep -o "crop=.*"
}
get_stream_codec() {
local file="$1"
local stream="$2"
ffprobe \
-v error \
-select_streams "${stream}" \
-show_entries stream=codec_name \
-of default=noprint_wrappers=1:nokey=1 \
"${file}"
}
get_file_format() {
local file="$1"
local probe
probe="$(ffprobe \
-v error \
-show_entries format=format_name \
-of default=noprint_wrappers=1:nokey=1 \
"${file}")" || return 1
if line_contains "${probe}" 'matroska'; then
echo mkv
else
echo mp4
fi
}
get_num_streams() {
local file="$1"
local type="${2:-}"
local select=()
if [[ ${type} != '' ]]; then
select=("-select_streams" "${type}")
fi
ffprobe \
-v error "${select[@]}" \
-show_entries stream=index \
-of default=noprint_wrappers=1:nokey=1 \
"${file}"
}
get_num_audio_channels() {
local file="$1"
local stream="$2"
ffprobe \
-v error \
-select_streams "${stream}" \
-show_entries stream=channels \
-of default=noprint_wrappers=1:nokey=1 \
"${file}"
}
get_stream_lang() {
local file="$1"
local stream="$2"
ffprobe \
-v error \
-select_streams "${stream}" \
-show_entries stream_tags=language \
-of default=noprint_wrappers=1:nokey=1 \
"${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,88 +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}
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}
' '
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 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 )
) # 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 llvm-cmake-utils llvm-devel
) llvm-static compiler-rt lld
mkvtoolnix glib2-static
)
# shellcheck disable=SC2034
local pkg_pkgs=(
autoconf automake cmake libtool
texinfo nasm yasm python3 wget
doxygen jq ccache gawk rust
git gnuplot bison rsync ragel
zip unzip gperf build-essential
binutils ninja ndk-multilib-native-static
libandroid-posix-semaphore
libandroid-posix-semaphore-static
libandroid-shmem
libandroid-shmem-static
)
# shellcheck disable=SC2034
local msys_ucrt_pkgs=(
mingw-w64-ucrt-x86_64-toolchain
mingw-w64-ucrt-x86_64-autotools
mingw-w64-ucrt-x86_64-clang
mingw-w64-ucrt-x86_64-clang-libs
mingw-w64-ucrt-x86_64-cmake
mingw-w64-ucrt-x86_64-compiler-rt
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')
@@ -91,71 +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))"
}
print_os() {
if [[ -f /etc/os-release ]]; then
source /etc/os-release
local OS="${ID}"
if [[ ${VERSION_ID} != '' ]]; then
OS+="-${VERSION_ID}"
fi
echo "${OS}"
fi
} }
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"
echo_if_fail pipx install virtualenv || return 1 has_cmd pipx || echo_if_fail python3 -m pip install --user pipx || return 1
echo_if_fail pipx ensurepath || return 1 has_cmd pipx || echo_if_fail python3 -m pipx ensurepath && source ~/.bashrc || return 1
echo_pass "pipx is installed" echo_if_fail pipx install virtualenv || return 1
echo_if_fail pipx ensurepath || return 1
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 https://sh.rustup.rs -sSf | 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 rustup default stable || return 1 has_cmd cargo-cbuild || echo_if_fail cargo install cargo-c || return 1
echo_if_fail rustup update stable || return 1 echo_pass "cargo-c is installed"
echo_pass "rustup is installed" echo_pass "all required packages installed"
echo_if_fail cargo install cargo-c || return 1
echo_pass "cargo-c is installed"
echo_pass "all required packages installed"
return 0 return 0
} }
FB_FUNC_NAMES+=('install_deps') FB_FUNC_NAMES+=('install_deps')
@@ -163,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
} }

29
lib/package.sh Normal file
View File

@@ -0,0 +1,29 @@
#!/usr/bin/env bash
check_for_package_cfg() {
local requiredCfg='ON:ON:ON:3'
local currentCfg="${STATIC}:${LTO}:${PGO}:${OPT}"
if [[ ${currentCfg} == "${requiredCfg}" ]]; then
return 0
else
return 1
fi
}
FB_FUNC_NAMES+=('package')
FB_FUNC_DESCS['package']='package ffmpeg build'
package() {
local pkgDir="${IGN_DIR}/package"
recreate_dir "${pkgDir}" || return 1
check_for_package_cfg || return 0
echo_info "packaging"
set_compile_opts || return 1
cp "${PREFIX}/bin/ff"* "${pkgDir}/"
cd "${pkgDir}" || return 1
local tarball="ffmpeg-build-${HOSTTYPE}-$(print_os).tar"
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
}

148
lib/readme.sh Normal file
View File

@@ -0,0 +1,148 @@
#!/usr/bin/env bash
make_bullet_points() {
for arg in "$@"; do
echo "- ${arg}"
done
}
gen_function_info() {
local funcName="$1"
echo "${FB_FUNC_DESCS[${funcName}]} using \`./scripts/${funcName}.sh\`"
}
gen_compile_opts_info() {
for opt in "${FB_COMP_OPTS[@]}"; do
declare -n defOptVal="DEFAULT_${opt}"
echo "- \`${opt}\`: ${FB_COMP_OPTS_DESC[${opt}]} (default: ${defOptVal})"
done
}
FB_FUNC_NAMES+=('gen_readme')
FB_FUNC_DESCS['gen_readme']='generate project README.md'
gen_readme() {
local readme="${REPO_DIR}/README.md"
echo "
# ffmpeg-builder
A collection of scripts for building \`ffmpeg\` and encoding content with the built \`ffmpeg\`.
Tested on:
- linux x86_64/aarch64 on:
$(printf ' - %s\n' "${VALID_DOCKER_IMAGES[@]}")
- darwin aarch64
With these scripts you can:
1. $(gen_function_info install_deps)
2. $(gen_function_info build)
3. $(gen_function_info encode)
4. $(gen_function_info efg)
# Building
This project supports multiple ways to build.
## Configuration
Configuration is done through environment variables.
By default, this project will build a static \`ffmpeg\` binary in \`./gitignore/sysroot/bin/ffmpeg\`.
The default enabled libraries included in the \`ffmpeg\` build are:
$(make_bullet_points ${DEFAULT_ENABLE})
The user-overridable compile options are:
$(gen_compile_opts_info)
Examples:
- only build libsvtav1_psy and libopus: \`ENABLE='libsvtav1_psy libopus' ./scripts/build.sh\`
- build shared libraries in custom path: \`PREFIX=/usr/local STATIC=OFF ./scripts/build.sh\`
- build without LTO at O1: \`LTO=OFF OPT=1 ./scripts/build.sh\`
### Native build
Make sure to $(gen_function_info install_deps). Then $(gen_function_info build).
### Docker build
1. choose a given distro from: \`${VALID_DOCKER_IMAGES[*]}\`.
2. $(gen_function_info docker_build_image) \`<distro>\`.
3. $(gen_function_info docker_run_image) \`<distro>\` \`./scripts/build.sh\`
Docker builds support the same configuration options as native builds. For example to build \`ffmpeg\` on ubuntu with only \`libdav1d\` enabled:
\`\`\`bash
./scripts/docker_build_image.sh ubuntu
ENABLE='libdav1d' ./scripts/docker_run_image.sh ubuntu ./scripts/build.sh
\`\`\`
# Encoding scripts
The encoding scripts are designed to be installed to system paths for re-use via symbolic links back to this repo using the \`-I\` flag.
## Encoding with svtav1-psy and opus
\`\`\`bash
$(encode)
\`\`\`
- Uses svtav1-psy for the video encoder.
- Uses libopus for the audio encoder.
- Skips re-encoding av1/opus streams.
- Only maps audio streams that match the video stream language if the video stream has a defined language.
- Only maps english subtitle streams.
- Adds track statistics to the output mkv file and embeds the encoder versions to the output metadata. For example:
\`\`\`
ENCODE : aa4d7e6
FFMPEG : 8.0
LIBOPUS : 1.5.2
LIBSVTAV1_PSY : 3.0.2-B
SVTAV1_PARAMS : film-grain=8:film-grain-denoise=1:tune=0:...
VIDEO_PARAMS : -crf 25 -preset 3 -g 240
\`\`\`
Example usage:
- standard usage: \`encode -i input.mkv output.mkv\`
- print out what will be executed without starting encoding: \`encode -i input.mkv -p\`
- encode with film-grain synthesis value of 20 with denoising: \`encode -g 20 -i input.mkv\`
## Estimate film-grain
\`\`\`bash
$(efg)
\`\`\`
- Provides a way to estimate the ideal film-grain amount by encoding from low-high given values and the step amount.
- Observing the point of diminishing returns, one can make an informed decision for a given film-grain value to choose.
Example usage:
- \`efg -i input.mkv -p\`
\`\`\`
10000 +------------------------------------------------------------------------------------------------------------------------------------------+
| **G* + + + + + |
| ** "/Volumes/External/ffmpeg-builder/gitignore/tmp/efg-matrix-reloaded.mkv/plot.dat" ***G*** |
| *G** |
| **G |
9000 |-+ ** +-|
| *G** |
| **G |
| ** |
| *G* |
8000 |-+ ** +-|
| *G** |
| **G |
| ** |
| *G** |
7000 |-+ **G* +-|
| ** |
| *G* |
| **G** |
6000 |-+ **G* +-|
| ** |
| *G* |
| **G** |
| **G* |
5000 |-+ **G** +-|
| **G****G* |
| **G** |
| **G****G* |
| **G****G* |
4000 |-+ **G****G****G* +-|
| **G****|
| |
| |
| + + + + + |
3000 +------------------------------------------------------------------------------------------------------------------------------------------+
0 5 10 15 20 25 30
\`\`\`
" >"${readme}"
}

92
main.sh
View File

@@ -1,12 +1,10 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# set top dir # set top dir
relativeRepoRoot="${BASH_SOURCE[0]//'main.sh'/}" if [[ -z ${REPO_DIR} ]]; then
if [[ -d ${relativeRepoRoot} ]]; then thisFile="$(readlink -f "${BASH_SOURCE[0]}")"
preloadCmd="cd ${relativeRepoRoot} &&" REPO_DIR="$(dirname "${thisFile}")"
fi fi
REPO_DIR="$(${preloadCmd} echo "$PWD")"
unset relativeRepoRoot preloadCmd
IGN_DIR="${REPO_DIR}/gitignore" IGN_DIR="${REPO_DIR}/gitignore"
TMP_DIR="${IGN_DIR}/tmp" TMP_DIR="${IGN_DIR}/tmp"
@@ -14,7 +12,26 @@ DL_DIR="${IGN_DIR}/downloads"
BUILD_DIR="${IGN_DIR}/builds" BUILD_DIR="${IGN_DIR}/builds"
CCACHE_DIR="${IGN_DIR}/ccache" CCACHE_DIR="${IGN_DIR}/ccache"
DOCKER_DIR="${IGN_DIR}/docker" DOCKER_DIR="${IGN_DIR}/docker"
export REPO_DIR IGN_DIR TMP_DIR DL_DIR BUILD_DIR CCACHE_DIR DOCKER_DIR PATCHES_DIR="${REPO_DIR}/patches"
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
IGN_DIRS=(
"${TMP_DIR}"
"${DL_DIR}"
"${BUILD_DIR}"
"${CCACHE_DIR}"
"${DOCKER_DIR}"
)
for dir in "${IGN_DIRS[@]}"; do
test -d "${dir}" || mkdir -p "${dir}"
done
unset IGN_DIRS
# function names, descriptions, completions # function names, descriptions, completions
unset FB_FUNC_NAMES FB_FUNC_DESCS FB_FUNC_COMPLETION unset FB_FUNC_NAMES FB_FUNC_DESCS FB_FUNC_COMPLETION
@@ -30,56 +47,57 @@ 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
cd "$(dirname "$(readlink -f $0)")/.."
export FB_RUNNING_AS_SCRIPT=1 export FB_RUNNING_AS_SCRIPT=1
. main.sh thisFile="$(readlink -f "$0")"
export REPO_DIR="$(cd "$(dirname "${thisFile}")/.." && echo "$PWD")"
source "${REPO_DIR}/main.sh" || exit 1
scr_name="$(bash_basename $0)" 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 "\n~~~ Usable Commands ~~~" echo -e "~~~ Usable Commands ~~~\n"
for funcname in "${FB_FUNC_NAMES[@]}"; do for funcName in "${FB_FUNC_NAMES[@]}"; do
echo -e "${CYAN}${funcname}${NC}:\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
} }
# shellcheck disable=SC1091
test -f "${HOME}/.bashrc" && source "${HOME}/.bashrc"
src_scripts || return 1 src_scripts || return 1
determine_pkg_mgr || return 1 determine_pkg_mgr || return 1
check_compile_opts_override || return 1
# shellcheck disable=SC2154 # set local prefix since some functions need it
test "${PREFIX}" == '' && PREFIX="${IGN_DIR}/$(print_os)_sysroot" # as opposed to user-defined PREFIX
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

@@ -1,3 +1,4 @@
# https://gitlab.com/AOMediaCodec/SVT-AV1/-/issues/2265
commit 5def505f7f193d890be61e869831378f212a07bd commit 5def505f7f193d890be61e869831378f212a07bd
Author: Salome Thirot <salome.thirot@arm.com> Author: Salome Thirot <salome.thirot@arm.com>
Date: Fri May 2 11:20:54 2025 +0100 Date: Fri May 2 11:20:54 2025 +0100

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

@@ -0,0 +1 @@
entry.sh

1
scripts/efg.sh Symbolic link
View File

@@ -0,0 +1 @@
entry.sh

1
scripts/encode.sh Symbolic link
View File

@@ -0,0 +1 @@
entry.sh

View File

@@ -1,8 +1,9 @@
#!/usr/bin/env bash #!/usr/bin/env bash
cd "$(dirname "$(readlink -f $0)")/.."
export FB_RUNNING_AS_SCRIPT=1 export FB_RUNNING_AS_SCRIPT=1
. main.sh thisFile="$(readlink -f "$0")"
export REPO_DIR="$(cd "$(dirname "${thisFile}")/.." && echo "$PWD")"
source "${REPO_DIR}/main.sh" || exit 1
scr_name="$(bash_basename $0)" 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 $@ $cmd "$@"

1
scripts/gen_readme.sh Symbolic link
View File

@@ -0,0 +1 @@
entry.sh

1
scripts/package.sh Symbolic link
View File

@@ -0,0 +1 @@
entry.sh

View File

@@ -3,10 +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' "$base/lib" "$base/scripts" | while read -r file; do --format '%w%f' \
if [[ -f $file && $file =~ .sh ]]; then "${base}/lib" \
shfmt --write --simplify "$file" "${base}/scripts" \
fi "${base}/main.sh" | while read -r file; do
if [[ -f $file && $file =~ .sh ]]; then
shfmt --indent 4 --write --simplify "${file}"
fi
done done