Compare commits

..

125 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
c9abd937e8 fix rm -rf 2025-08-11 17:48:01 -05:00
f68eaba7d4 more rando fixes 2025-08-11 17:45:42 -05:00
722d4757ed more packages and testing libc 2025-08-10 13:30:04 -05:00
96ae2fd553 add project versioning 2025-08-09 16:04:36 -05:00
390d50e8cb more basename trickery 2025-08-08 21:48:21 -05:00
9365a60f87 use basename for distros that have different owner 2025-08-08 21:45:16 -05:00
a94bf0de65 forgot comma 2025-08-08 21:39:58 -05:00
3dfb07fce5 add arch and update build versions 2025-08-08 21:38:06 -05:00
a1aa2ee651 welp 2025-08-08 17:15:33 -05:00
474f42f2a3 continuing 2025-08-08 16:55:51 -05:00
42fbd42fc2 more random removals 2025-08-08 16:47:34 -05:00
0d71fb86c9 inject PATH for cargo builds 2025-08-08 16:43:12 -05:00
02373d3f37 again 2025-08-08 16:17:38 -05:00
d336b17cf4 allow anyone to use 2025-08-08 16:13:12 -05:00
a6239fd0da hacks for static builds 2025-08-08 15:57:42 -05:00
a376bfeea3 yea 2025-08-08 13:07:40 -05:00
7bf733a356 oops 2025-08-08 12:25:23 -05:00
0584974538 is_root_owned 2025-08-08 12:22:47 -05:00
da533a8a09 hardcode libdir 2025-08-08 12:10:00 -05:00
27 changed files with 3504 additions and 833 deletions

View File

@@ -1 +0,0 @@
gitignore*

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

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,126 +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}"
touch "${cmdEvalTrace}" || ${SUDO} rm -rf "${TMP_DIR}"
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}
}
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:-/}"
}

View File

@@ -3,24 +3,63 @@
# variables used externally
# shellcheck disable=SC2034
# complete clean before building
CLEAN=true
# enable link time optimization
LTO=false
# optimization level (0-3)
OPT_LVL=0
# static or shared build
STATIC=true
# CPU type (x86_v{1,2,3}...)
CPU=native
# architecture type
ARCH=native
# prefix to install, leave empty for non-system install (local)
PREFIX=''
# configure what ffmpeg enables
FFMPEG_ENABLES=(
libopus
libdav1d
libsvtav1_psy
# libaom
)
# compile option descriptions
unset FB_COMP_OPTS_DESC
declare -Ag FB_COMP_OPTS_DESC
# default compile options
FB_COMP_OPTS_DESC['CLEAN']='clean build directories before building'
DEFAULT_CLEAN=ON
FB_COMP_OPTS_DESC['LTO']='enable link time optimization'
DEFAULT_LTO=ON
FB_COMP_OPTS_DESC['PGO']='enable profile guided optimization'
DEFAULT_PGO=OFF
FB_COMP_OPTS_DESC['OPT']='optimization level (0-3)'
DEFAULT_OPT=3
FB_COMP_OPTS_DESC['STATIC']='static or shared build'
DEFAULT_STATIC=ON
FB_COMP_OPTS_DESC['ARCH']='architecture type (x86-64-v{1,2,3,4}, armv8-a, etc)'
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,203 +1,326 @@
#!/usr/bin/env bash
VALID_DOCKER_IMAGES=(
'ubuntu-22.04' 'ubuntu-24.04'
'fedora-41' 'fedora-42'
'debian-12'
'archlinux-latest'
'ubuntu'
'fedora'
'debian'
'archlinux'
)
DOCKER_WORKDIR='/workdir'
set_docker_run_flags() {
local cargo_git="${IGN_DIR}/cargo/git"
local cargo_registry="${IGN_DIR}/cargo/registry"
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() {
if missing_cmd docker; then
echo_info "install docker"
curl https://get.docker.com -sSf | bash
fi
if missing_cmd docker; then
echo_info "install docker"
curl https://get.docker.com -sSf | bash
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
set_distro_image_tag() {
local image_tag="${1}"
echo "ffmpeg_builder_${image_tag//-/:}"
local image_tag="${1}"
echo "ffmpeg_builder_${image_tag//-/:}"
}
# change colon to dash and add extension type
docker_image_archive_name() {
local image_tag="${1}"
echo "${image_tag//:/-}.tar.zst"
local image_tag="${1}"
echo "${image_tag//:/-}.tar.zst"
}
echo_platform() {
local platKernel platCpu
platKernel="$(uname)"
platKernel="${platKernel,,}"
if [[ ${HOSTTYPE} == 'x86_64' ]]; then
platCpu='amd64'
else
platCpu='arm64'
fi
local platKernel platCpu
platKernel="$(uname)"
platKernel="${platKernel,,}"
if [[ ${HOSTTYPE} == 'x86_64' ]]; then
platCpu='amd64'
else
platCpu='arm64'
fi
echo "${platKernel}/${platCpu}"
echo "${platKernel}/${platCpu}"
}
validate_selected_image() {
local selectedImage="${1:-}"
for distro in "${VALID_DOCKER_IMAGES[@]}"; do
if [[ ${selectedImage} == "${distro}" ]]; then
DISTROS+=("${distro}")
fi
done
if [[ ${DISTROS[*]} == '' ]]; then
echo_fail "${selectedImage} is not valid"
echo_info "valid images:" "${VALID_DOCKER_IMAGES[@]}"
return 1
fi
local selectedImage="$1"
local valid=1
for image in "${VALID_DOCKER_IMAGES[@]}"; do
if [[ ${selectedImage} == "${image}" ]]; then
valid=0
break
fi
done
if [[ valid -eq 1 ]]; then
echo_fail "${selectedImage} is not valid"
echo_info "valid images:" "${VALID_DOCKER_IMAGES[@]}"
return 1
fi
}
docker_login() {
echo_if_fail docker login \
-u "${DOCKER_REGISTRY_USER}" \
-p "${DOCKER_REGISTRY_PASS}" \
"${DOCKER_REGISTRY}"
echo_if_fail docker login \
-u "${DOCKER_REGISTRY_USER}" \
-p "${DOCKER_REGISTRY_PASS}" \
"${DOCKER_REGISTRY}"
}
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[*]}"
docker_build_image() {
validate_selected_image "$@" || return 1
check_docker || return 1
test -d "${DOCKER_DIR}" || mkdir -p "${DOCKER_DIR}"
PLATFORM="${PLATFORM:-$(echo_platform)}"
local image="$1"
validate_selected_image "${image}" || return 1
check_docker || return 1
PLATFORM="${PLATFORM:-$(echo_platform)}"
for distro in "${DISTROS[@]}"; do
image_tag="$(set_distro_image_tag "${distro}")"
echo_info "sourcing package manager for ${image_tag}"
echo_info "sourcing package manager for ${image}"
local dockerDistro="$(get_docker_image_tag "${image}")"
# 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
dockerDistro="${distro//-/:}"
# specific file for evaluated package manager info
distroPkgMgr="${DOCKER_DIR}/${distro}-pkg_mgr"
# get package manager info
docker run --rm \
--platform "$(echo_platform)" \
-v "${REPO_DIR}":/workdir \
-w /workdir \
"${dockerDistro}" \
bash -c "./scripts/print_pkg_mgr.sh" | tr -d '\r' >"${distroPkgMgr}"
# shellcheck disable=SC1090
cat "${distroPkgMgr}"
source "${distroPkgMgr}"
local dockerfile="${DOCKER_DIR}/Dockerfile_$(bash_basename "${image}")"
local embedPath='/Dockerfile'
{
echo "FROM ${dockerDistro}"
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[*]}"
dockerfile="${DOCKER_DIR}/Dockerfile_${distro}"
{
echo "FROM ${dockerDistro}"
echo 'SHELL ["/bin/bash", "-c"]'
echo 'RUN ln -sf /bin/bash /bin/sh'
echo 'ENV DEBIAN_FRONTEND=noninteractive'
echo "RUN ${pkg_mgr_update}"
echo "RUN ${pkg_mgr_upgrade}"
printf "RUN ${pkg_install} %s\n" "${req_pkgs[@]}"
echo 'RUN pipx install virtualenv'
echo 'RUN pipx ensurepath'
echo 'RUN curl https://sh.rustup.rs -sSf | bash -s -- -y'
echo 'ENV PATH="~/.cargo/bin:$PATH"'
echo 'RUN rustup default stable && rustup update stable'
echo 'RUN cargo install cargo-c'
# ENV for pipx/rust
echo 'ENV PIPX_HOME=/root/.local'
echo 'ENV PIPX_BIN_DIR=/root/.local/bin'
echo 'ENV PATH="/root/.local/bin:$PATH"'
echo 'ENV CARGO_HOME="/root/.cargo"'
echo 'ENV RUSTUP_HOME="/root/.rustup"'
echo 'ENV PATH="/root/.cargo/bin:$PATH"'
# add to profile
echo 'RUN export PIPX_HOME=${PIPX_HOME} >> /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 RUSTUP_HOME=${RUSTUP_HOME} >> /etc/profile'
echo 'RUN export PATH=${PATH} >> /etc/profile'
} >"${dockerfile}"
# make nobody:nogroup usable
echo 'RUN sed -i '/nobody/d' /etc/passwd || true'
echo 'RUN echo "nobody:x:65534:65534:nobody:/root:/bin/bash" >> /etc/passwd'
echo 'RUN sed -i '/nogroup/d' /etc/group || true'
echo 'RUN echo "nogroup:x:65534:" >> /etc/group'
# open up permissions before switching user
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
docker buildx build \
--platform "${PLATFORM}" \
-t "${image_tag}" \
-f "${dockerfile}" \
. || return 1
echo "ADD ${rustupTarball} /tmp/"
echo "RUN cd /tmp/rustup-${rustupVersion} && bash rustup-init.sh -y --default-toolchain=${rustcVersion}"
# install cargo-binstall
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"
# 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}" \
. || return 1
fi
# 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 system prune -f
done
# embed dockerfile into docker image itself
# shellcheck disable=SC2094
echo "COPY $(bash_basename "${dockerfile}") ${embedPath}"
echo "WORKDIR ${DOCKER_WORKDIR}"
} >"${dockerfile}"
# 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_DESCS['docker_save_image']='save docker image into tar.zst'
FB_FUNC_COMPLETION['docker_save_image']="${VALID_DOCKER_IMAGES[*]}"
docker_save_image() {
validate_selected_image "$@" || return 1
check_docker || return 1
for distro in "${DISTROS[@]}"; do
image_tag="$(set_distro_image_tag "${distro}")"
echo_info "saving docker image for ${image_tag}"
docker save "${image_tag}" |
zstd -T0 >"${DOCKER_DIR}/$(docker_image_archive_name "${image_tag}")" ||
return 1
done
local image="$1"
validate_selected_image "${image}" || return 1
check_docker || return 1
image_tag="$(set_distro_image_tag "${image}")"
echo_info "saving docker image for ${image_tag}"
docker save "${image_tag}" |
zstd -T0 >"${DOCKER_DIR}/$(docker_image_archive_name "${image_tag}")" ||
return 1
}
FB_FUNC_NAMES+=('docker_load_image')
FB_FUNC_DESCS['docker_load_image']='load docker image from tar.zst'
FB_FUNC_COMPLETION['docker_load_image']="${VALID_DOCKER_IMAGES[*]}"
docker_load_image() {
validate_selected_image "$@" || return 1
check_docker || return 1
for distro in "${DISTROS[@]}"; do
image_tag="$(set_distro_image_tag "${distro}")"
echo_info "loading docker image for ${image_tag}"
local archive="${DOCKER_DIR}/$(docker_image_archive_name "${image_tag}")"
test -f "$archive" || return 1
zstdcat -T0 "$archive" |
docker load || return 1
docker system prune -f
done
local image="$1"
validate_selected_image "${image}" || return 1
check_docker || return 1
image_tag="$(set_distro_image_tag "${image}")"
echo_info "loading docker image for ${image_tag}"
local archive="${DOCKER_DIR}/$(docker_image_archive_name "${image_tag}")"
test -f "$archive" || return 1
zstdcat -T0 "$archive" | docker load || return 1
docker system prune -f
}
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[*]}"
docker_run_image() {
validate_selected_image "$@" || return 1
check_docker || return 1
local image="$1"
shift
validate_selected_image "${image}" || return 1
check_docker || return 1
for distro in "${DISTROS[@]}"; do
image_tag="$(set_distro_image_tag "${distro}")"
local cmd=("$@")
local runCmd=()
if [[ ${cmd[*]} == '' ]]; then
DOCKER_RUN_FLAGS+=("-it")
else
runCmd+=("${cmd[@]}")
fi
# if a docker registry is defined, pull from it
if [[ ${DOCKER_REGISTRY} != '' ]]; then
docker_login || return 1
docker pull \
"${DOCKER_REGISTRY}/${image_tag}"
docker tag "${DOCKER_REGISTRY}/${image_tag}" "${image_tag}"
fi
local image_tag="$(set_distro_image_tag "${image}")"
echo_info "running ffmpeg build for ${image_tag}"
docker run --rm \
-v "${REPO_DIR}":/workdir \
-w /workdir \
-e DEBUG="${DEBUG}" \
"${image_tag}" \
./scripts/build.sh || return 1
done
# if a docker registry is defined, pull from it
if [[ ${DOCKER_REGISTRY} != '' ]]; then
docker_login || return 1
docker pull \
"${DOCKER_REGISTRY}/${image_tag}" || return 1
docker tag "${DOCKER_REGISTRY}/${image_tag}" "${image_tag}"
fi
echo_info "running docker image ${image_tag}"
docker run \
"${DOCKER_RUN_FLAGS[@]}" \
-u "$(id -u):$(id -g)" \
"${image_tag}" \
"${runCmd[@]}"
local rv=$?
docker image prune -f
return ${rv}
}
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_DESCS['docker_build_multiarch_image']='build multiarch docker image'
FB_FUNC_COMPLETION['docker_build_multiarch_image']="${VALID_DOCKER_IMAGES[*]}"
docker_build_multiarch_image() {
validate_selected_image "$@" || return 1
check_docker || return 1
PLATFORM='linux/amd64,linux/arm64'
docker buildx create \
--use \
--platform="${PLATFORM}" \
--name my-multiplatform-builder \
--driver=docker-container
docker_build_image "$@"
local image="$1"
validate_selected_image "${image}" || return 1
check_docker || return 1
PLATFORM='linux/amd64,linux/arm64'
# check if we need to create multiplatform builder
local buildxPlats="$(docker buildx inspect | grep Platforms)"
IFS=','
local createBuilder=0
for plat in $PLATFORM; do
grep -q "${plat}" <<<"${buildxPlats}" || createBuilder=1
done
unset IFS
if [[ ${createBuilder} == 1 ]]; then
echo_info "creating multiplatform (${PLATFORM}) docker builder"
docker buildx create \
--use \
--platform="${PLATFORM}" \
--name my-multiplatform-builder \
--driver=docker-container
fi
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,85 +2,133 @@
# shellcheck disable=SC2120
determine_pkg_mgr() {
# sudo used externally
# shellcheck disable=SC2034
test "$(id -u)" -eq 0 && SUDO='' || SUDO='sudo '
# sudo used externally
# shellcheck disable=SC2034
if is_windows || test "$(id -u)" -eq 0; then
SUDO=''
else
SUDO='sudo '
fi
# pkg-mgr update-cmd upgrade-cmd install-cmd check-cmd
# shellcheck disable=SC2016
local PKG_MGR_MAP='
# pkg-mgr update-cmd upgrade-cmd install-cmd check-cmd
# shellcheck disable=SC2016
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}
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}
dnf:${SUDO}dnf check-update || true:${SUDO}dnf upgrade --refresh -y:${SUDO}dnf install -y:dnf list -q --installed ${pkg}
'
local supported_pkg_mgr=()
unset pkg_mgr pkg_mgr_update pkg_mgr_upgrade pkg_install pkg_check
while read -r line; do
test "${line}" == '' && continue
IFS=':' read -r pkg_mgr pkg_mgr_update pkg_mgr_upgrade pkg_install pkg_check <<<"${line}"
supported_pkg_mgr+=("${pkg_mgr}")
if ! has_cmd "${pkg_mgr}"; then
pkg_mgr=''
continue
fi
# update/install may use SUDO
eval "pkg_mgr_update=\"${pkg_mgr_update}\""
eval "pkg_mgr_upgrade=\"${pkg_mgr_upgrade}\""
eval "pkg_install=\"${pkg_install}\""
break
done <<<"${PKG_MGR_MAP}"
local supported_pkg_mgr=()
unset pkg_mgr pkg_mgr_update pkg_mgr_upgrade pkg_install pkg_check
while read -r line; do
test "${line}" == '' && continue
IFS=':' read -r pkg_mgr pkg_mgr_update pkg_mgr_upgrade pkg_install pkg_check <<<"${line}"
supported_pkg_mgr+=("${pkg_mgr}")
if ! has_cmd "${pkg_mgr}"; then
pkg_mgr=''
continue
fi
# update/install may use SUDO
eval "pkg_mgr_update=\"${pkg_mgr_update}\""
eval "pkg_mgr_upgrade=\"${pkg_mgr_upgrade}\""
eval "pkg_install=\"${pkg_install}\""
break
done <<<"${PKG_MGR_MAP}"
if [[ ${pkg_mgr} == '' ]]; then
echo_fail "system does not use a supported package manager" "${supported_pkg_mgr[@]}"
return 1
fi
if [[ ${pkg_mgr} == '' ]]; then
echo_fail "system does not use a supported package manager" "${supported_pkg_mgr[@]}"
return 1
fi
return 0
return 0
}
print_req_pkgs() {
local common_pkgs=(
autoconf automake cmake libtool
texinfo nasm yasm python3 wget
meson doxygen jq ccache gawk
)
# shellcheck disable=SC2034
local brew_pkgs=(
"${common_pkgs[@]}" pkgconf
mkvtoolnix pipx
)
local common_linux_pkgs=(
"${common_pkgs[@]}" clang valgrind
curl bc lshw xxd pkgconf
)
# shellcheck disable=SC2034
local apt_get_pkgs=(
"${common_linux_pkgs[@]}" build-essential
git-core libass-dev libfreetype6-dev
libsdl2-dev libva-dev libvdpau-dev
libvorbis-dev libxcb1-dev pipx
libxcb-shm0-dev libxcb-xfixes0-dev
zlib1g-dev libssl-dev ninja-build
gobjc++ mawk libnuma-dev
mediainfo mkvtoolnix libgtest-dev
)
# shellcheck disable=SC2034
local pacman_pkgs=(
"${common_linux_pkgs[@]}" base-devel
python-pipx ninja
)
# shellcheck disable=SC2034
local dnf_pkgs=(
"${common_linux_pkgs[@]}" openssl-devel
pipx ninja-build fontconfig-devel wget2
cpuinfo-devel glibc-static libstdc++-static
)
local common_pkgs=(
autoconf automake cmake libtool
texinfo nasm yasm python3 wget
meson doxygen jq ccache gawk
git gnuplot bison rsync ragel
zip unzip gperf itstool
)
# shellcheck disable=SC2034
local brew_pkgs=(
"${common_pkgs[@]}" pkgconf
mkvtoolnix pipx uutils-coreutils
llvm lld
)
local common_linux_pkgs=(
"${common_pkgs[@]}" clang valgrind
curl bc lshw xxd pkgconf sudo llvm
)
# shellcheck disable=SC2034
local apt_get_pkgs=(
"${common_linux_pkgs[@]}" pipx
build-essential libssl-dev gobjc++
mawk libc6-dev mediainfo ninja-build
mkvtoolnix libgtest-dev lld
libfontconfig-dev libglib2.0-dev
)
# shellcheck disable=SC2034
local pacman_pkgs=(
"${common_linux_pkgs[@]}" base-devel
python-pipx ninja lld mkvtoolnix-cli
glib2-devel
)
# shellcheck disable=SC2034
local dnf_pkgs=(
"${common_linux_pkgs[@]}" openssl-devel
pipx ninja-build fontconfig-devel wget2
glibc-static glibc-devel patch
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"
declare -n req_pkgs="${req_pkgs_env_name}"
local sorted_req_pkgs=($(printf '%s\n' "${req_pkgs[@]}" | sort))
echo "${sorted_req_pkgs[@]}"
if is_windows; then
local pkg_mgr='msys_ucrt'
fi
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')
@@ -88,67 +136,60 @@ FB_FUNC_NAMES+=('print_pkg_mgr')
# shellcheck disable=SC2034
FB_FUNC_DESCS['print_pkg_mgr']='print out evaluated package manager commands and required packages'
print_pkg_mgr() {
determine_pkg_mgr || return 1
echo "export pkg_mgr=\"${pkg_mgr}\""
echo "export pkg_mgr_update=\"${pkg_mgr_update}\""
echo "export pkg_mgr_upgrade=\"${pkg_mgr_upgrade}\""
echo "export pkg_install=\"${pkg_install}\""
echo "export pkg_check=\"${pkg_check}\""
echo "export req_pkgs=($(print_req_pkgs))"
}
print_os() {
if [[ -f /etc/os-release ]]; then
source /etc/os-release
echo "${ID}-${VERSION_ID}"
fi
determine_pkg_mgr || return 1
echo "export pkg_mgr=\"${pkg_mgr}\""
echo "export pkg_mgr_update=\"${pkg_mgr_update}\""
echo "export pkg_mgr_upgrade=\"${pkg_mgr_upgrade}\""
echo "export pkg_install=\"${pkg_install}\""
echo "export pkg_check=\"${pkg_check}\""
echo "export req_pkgs=($(print_req_pkgs))"
}
check_for_req_pkgs() {
echo_info "checking for required packages"
local missing_pkgs=()
for pkg in $(print_req_pkgs); do
# pkg_check has ${pkg} unexpanded
eval "pkg_check=\"${pkg_check}\""
${pkg_check} "${pkg}" >/dev/null 2>&1 || missing_pkgs+=("${pkg}")
done
echo_info "checking for required packages"
local missing_pkgs=()
for pkg in $(print_req_pkgs); do
# pkg_check has ${pkg} unexpanded
eval "pkg_check=\"${pkg_check}\""
${pkg_check} "${pkg}" >/dev/null 2>&1 || missing_pkgs+=("${pkg}")
done
if [[ ${#missing_pkgs[@]} -gt 0 ]]; then
echo_warn "missing packages:" "${missing_pkgs[@]}"
# shellcheck disable=SC2086
${pkg_mgr_update}
# shellcheck disable=SC2086
${pkg_install} "${missing_pkgs[@]}" || return 1
fi
if [[ ${#missing_pkgs[@]} -gt 0 ]]; then
echo_warn "missing packages:" "${missing_pkgs[@]}"
# shellcheck disable=SC2086
${pkg_mgr_update}
# shellcheck disable=SC2086
${pkg_install} "${missing_pkgs[@]}" || return 1
fi
echo_pass "packages from ${pkg_mgr} installed"
echo_if_fail pipx install virtualenv || return 1
echo_if_fail pipx ensurepath || return 1
echo_pass "pipx is 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 pipx ensurepath && source ~/.bashrc || return 1
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
test -f "${HOME}/.cargo/env" && source "${HOME}/.cargo/env"
# shellcheck disable=SC1091
test -f "${HOME}/.cargo/env" && source "${HOME}/.cargo/env"
if missing_cmd cargo; then
if missing_cmd rustup; then
echo_warn "installing rustup"
curl https://sh.rustup.rs -sSf | sh -s -- -y
# shellcheck disable=SC2016
grep -q 'source "${HOME}/.cargo/env"' "${HOME}/.bashrc" ||
echo 'source "${HOME}/.cargo/env"' >>"${HOME}/.bashrc"
# shellcheck disable=SC1091
source "${HOME}/.bashrc"
fi
fi
if missing_cmd cargo; then
if missing_cmd rustup; then
echo_warn "installing rustup"
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
# shellcheck disable=SC2016
grep -q 'source "${HOME}/.cargo/env"' "${HOME}/.bashrc" ||
echo 'source "${HOME}/.cargo/env"' >>"${HOME}/.bashrc"
# shellcheck disable=SC1091
source "${HOME}/.bashrc"
fi
fi
echo_if_fail rustup default stable || return 1
echo_if_fail rustup update stable || return 1
echo_pass "rustup is installed"
echo_if_fail cargo install cargo-c || return 1
echo_pass "cargo-c is installed"
echo_pass "all required packages installed"
has_cmd cargo-cbuild || 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')
@@ -156,6 +197,6 @@ FB_FUNC_NAMES+=('install_deps')
# shellcheck disable=SC2034
FB_FUNC_DESCS['install_deps']='install required dependencies'
install_deps() {
determine_pkg_mgr || return 1
check_for_req_pkgs || return 1
determine_pkg_mgr || 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
# set top dir
relativeRepoRoot="${BASH_SOURCE[0]//'main.sh'/}"
if [[ -d ${relativeRepoRoot} ]]; then
preloadCmd="cd ${relativeRepoRoot} &&"
if [[ -z ${REPO_DIR} ]]; then
thisFile="$(readlink -f "${BASH_SOURCE[0]}")"
REPO_DIR="$(dirname "${thisFile}")"
fi
REPO_DIR="$(${preloadCmd} echo "$PWD")"
unset relativeRepoRoot preloadCmd
IGN_DIR="${REPO_DIR}/gitignore"
TMP_DIR="${IGN_DIR}/tmp"
@@ -14,7 +12,26 @@ DL_DIR="${IGN_DIR}/downloads"
BUILD_DIR="${IGN_DIR}/builds"
CCACHE_DIR="${IGN_DIR}/ccache"
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
unset FB_FUNC_NAMES FB_FUNC_DESCS FB_FUNC_COMPLETION
@@ -30,56 +47,57 @@ FB_COMPILE_OPTS_SET=0
SCRIPT_DIR="${REPO_DIR}/scripts"
ENTRY_SCRIPT="${SCRIPT_DIR}/entry.sh"
src_scripts() {
local SCRIPT_DIR="${REPO_DIR}/scripts"
local SCRIPT_DIR="${REPO_DIR}/scripts"
if [[ $FB_RUNNING_AS_SCRIPT -eq 0 ]]; then
rm "${SCRIPT_DIR}"/*.sh
# shellcheck disable=SC2016
echo '#!/usr/bin/env bash
cd "$(dirname "$(readlink -f $0)")/.."
if [[ $FB_RUNNING_AS_SCRIPT -eq 0 ]]; then
rm "${SCRIPT_DIR}"/*.sh
# shellcheck disable=SC2016
echo '#!/usr/bin/env bash
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)"
cmd="${scr_name//.sh/}"
if [[ $DEBUG == 1 ]]; then set -x; fi
$cmd $@' >"${ENTRY_SCRIPT}"
chmod +x "${ENTRY_SCRIPT}"
fi
$cmd "$@"' >"${ENTRY_SCRIPT}"
chmod +x "${ENTRY_SCRIPT}"
fi
for script in "${REPO_DIR}/lib/"*.sh; do
# shellcheck disable=SC1090
source "${script}"
done
for script in "${REPO_DIR}/lib/"*.sh; do
# shellcheck disable=SC1090
source "${script}"
done
}
FB_FUNC_NAMES+=('print_cmds')
FB_FUNC_DESCS['print_cmds']='print usable commands'
print_cmds() {
echo -e "\n~~~ Usable Commands ~~~"
for funcname in "${FB_FUNC_NAMES[@]}"; do
echo -e "${CYAN}${funcname}${NC}:\n\t" "${FB_FUNC_DESCS[${funcname}]}"
if [[ $FB_RUNNING_AS_SCRIPT -eq 0 ]]; then
(cd "$SCRIPT_DIR" && ln -sf entry.sh "${funcname}.sh")
fi
done
echo -e "~~~~~~~~~~~~~~~~~~~~~~~\n"
echo -e "~~~ Usable Commands ~~~\n"
for funcName in "${FB_FUNC_NAMES[@]}"; do
color="${CYAN}" word="${funcName}:" echo_wrapper "\n\t${FB_FUNC_DESCS[${funcName}]}"
if [[ $FB_RUNNING_AS_SCRIPT -eq 0 ]]; then
(cd "$SCRIPT_DIR" && ln -sf entry.sh "${funcName}.sh")
fi
done
echo -e "\n"
}
set_completions() {
for funcname in "${FB_FUNC_NAMES[@]}"; do
complete -W "${FB_FUNC_COMPLETION[${funcname}]}" "${funcname}"
done
for funcName in "${FB_FUNC_NAMES[@]}"; do
complete -W "${FB_FUNC_COMPLETION[${funcName}]}" "${funcName}"
done
}
# shellcheck disable=SC1091
test -f "${HOME}/.bashrc" && source "${HOME}/.bashrc"
src_scripts || return 1
determine_pkg_mgr || return 1
check_compile_opts_override || return 1
# shellcheck disable=SC2154
test "${PREFIX}" == '' && PREFIX="${IGN_DIR}/$(print_os)_sysroot"
# set local prefix since some functions need it
# as opposed to user-defined PREFIX
LOCAL_PREFIX="${IGN_DIR}/$(print_os)_sysroot"
if [[ $FB_RUNNING_AS_SCRIPT -eq 0 ]]; then
print_cmds || return 1
if [[ ${FB_RUNNING_AS_SCRIPT} -eq 0 ]]; then
print_cmds || return 1
fi
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
Author: Salome Thirot <salome.thirot@arm.com>
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
cd "$(dirname "$(readlink -f $0)")/.."
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)"
cmd="${scr_name//.sh/}"
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")")"
inotifywait -m -r \
-e close_write \
-e moved_to \
--format '%w%f' "$base/lib" "$base/scripts" | while read -r file; do
if [[ -f $file && $file =~ .sh ]]; then
shfmt --write --simplify "$file"
fi
-e close_write \
-e moved_to \
--format '%w%f' \
"${base}/lib" \
"${base}/scripts" \
"${base}/main.sh" | while read -r file; do
if [[ -f $file && $file =~ .sh ]]; then
shfmt --indent 4 --write --simplify "${file}"
fi
done