diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5d5bced..f1b79b5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -9,8 +9,8 @@ env: FORCE_COLOR: 1 jobs: build: - name: Build on macOS M1 - runs-on: macos-13-xlarge + name: Build on macOS ARM64 + runs-on: macos-15 steps: - uses: actions/checkout@main - uses: actions/setup-node@v3 @@ -19,7 +19,7 @@ jobs: - name: Install deps run: HOMEBREW_INSTALL_FROM_API=1 brew install yasm nasm pkg-config cmake make meson ninja libtool autoconf automake - run: curl https://sh.rustup.rs -sSf | sh -s -- -y - - run: rm /opt/homebrew/opt/libx11/lib/libX11.6.dylib + - run: rm -f /opt/homebrew/opt/libx11/lib/libX11.6.dylib - run: node clean.mjs - run: node compile-ffmpeg.mjs - run: node generate-bindings.mjs @@ -32,7 +32,7 @@ jobs: path: ffmpeg.tar.gz build-macos-x64: name: Build on macOS x64 - runs-on: macos-13 + runs-on: macos-15-intel steps: - uses: actions/checkout@main - uses: actions/setup-node@v3 diff --git a/.gitignore b/.gitignore index 0fa339e..ec9cfda 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,8 @@ out-test.png out-test.jpeg opus-1.3.1 av1 +aom +aom-build zimg fdk-aac-2.0.2 fdkaac.tar.gz diff --git a/Dockerfile-aws b/Dockerfile-aws index c7edec3..f363bf2 100644 --- a/Dockerfile-aws +++ b/Dockerfile-aws @@ -23,7 +23,7 @@ RUN amazon-linux-extras install epel python3.8 -y RUN python3.8 -m pip install meson RUN python3.8 -m pip install ninja RUN yum install yasm nasm unzip curl gcc gcc-c++ tar git make ca-certificates pkgconfig bash make cmake3 build-base llvm-static llvm-dev clang-static clang-dev perl zlib zlib-devel clang-libs clang ninja autoconf automake libtool patchelf -y -RUN cd app && CFLAGS="$CFLAGS -static-libgcc" CXXFLAGS="$CXXFLAGS -static-libgcc -static-libstdc++" LDFLAGS="$LDFLAGS -static-libgcc -static-libstdc++" node compile-ffmpeg.mjs old-cmake || tail -n 30 ffmpeg/ffbuild/config.log +RUN cd app && CFLAGS="$CFLAGS -static-libgcc" CXXFLAGS="$CXXFLAGS -static-libgcc -static-libstdc++" LDFLAGS="$LDFLAGS -static-libgcc -static-libstdc++" node compile-ffmpeg.mjs old-cmake RUN source "$HOME/.cargo/env" && cd app && node generate-bindings.mjs RUN source "$HOME/.cargo/env" && cd app && node zip.mjs RUN source "$HOME/.cargo/env" && cd app && node test-ffmpeg.mjs diff --git a/clean.mjs b/clean.mjs index dcfbed6..6d9633b 100644 --- a/clean.mjs +++ b/clean.mjs @@ -15,6 +15,8 @@ const paths = [ path.join(process.cwd(), "x264"), path.join(process.cwd(), "x265"), path.join(process.cwd(), "av1"), + path.join(process.cwd(), "aom"), + path.join(process.cwd(), "aom-build"), path.join(process.cwd(), "zimg"), path.join(process.cwd(), "opus"), path.join(process.cwd(), "libmp3lame"), diff --git a/compile-av1.mjs b/compile-av1.mjs index 300eabe..b128194 100644 --- a/compile-av1.mjs +++ b/compile-av1.mjs @@ -1,8 +1,34 @@ import { execSync } from "child_process"; import { join, dirname } from "node:path"; -import { existsSync, mkdirSync, rmSync, cpSync, writeFileSync } from "node:fs"; +import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs"; +import { PREFIX } from "./const.mjs"; -export const enableAv1 = (isWindows) => { +const getCmakeCommand = () => { + try { + execSync("cmake --version", { stdio: "ignore" }); + return "cmake"; + } catch { + try { + execSync("cmake3 --version", { stdio: "ignore" }); + return "cmake3"; + } catch { + throw new Error("Neither cmake nor cmake3 is available in PATH."); + } + } +}; + +const getToolPath = (tool) => { + try { + return execSync(`command -v ${tool}`, { + encoding: "utf8", + stdio: ["ignore", "pipe", "ignore"], + }).trim(); + } catch { + throw new Error(`Required tool '${tool}' is not available in PATH.`); + } +}; + +const enableDav1d = (isWindows) => { const pkgConfig = ` prefix=${process.cwd()}/av1/build includedir=$\{prefix\}/include @@ -61,3 +87,99 @@ Cflags: -I$\{prefix\}/src -I$\{srcdir\}/src -I$\{prefix\} -I$\{srcdir\} -I$\{pre writeFileSync(outPath, pkgConfig); }; + +const enableLibaom = (isWindows) => { + const AOM_TAG = "v3.9.1"; + const AOM_BUILD_DIR = "aom-build"; + const shouldEnableNasm = !( + process.platform === "darwin" && + process.arch === "x64" + ); + const windowsToolchain = isWindows + ? { + cc: getToolPath("x86_64-w64-mingw32-gcc"), + cxx: getToolPath("x86_64-w64-mingw32-g++"), + rc: getToolPath("x86_64-w64-mingw32-windres"), + ar: getToolPath("x86_64-w64-mingw32-ar"), + ranlib: getToolPath("x86_64-w64-mingw32-ranlib"), + } + : null; + if (!existsSync("aom")) { + execSync("git clone https://aomedia.googlesource.com/aom aom", { + stdio: "inherit", + }); + } + + execSync("git fetch --tags", { + cwd: "aom", + stdio: "inherit", + }); + + execSync(`git checkout ${AOM_TAG}`, { + cwd: "aom", + stdio: "inherit", + }); + + rmSync(AOM_BUILD_DIR, { + force: true, + recursive: true, + }); + mkdirSync(AOM_BUILD_DIR, { + recursive: true, + }); + + const cmakeCmd = getCmakeCommand(); + execSync( + [ + cmakeCmd, + join(process.cwd(), "aom"), + `-DCMAKE_INSTALL_PREFIX=${join(process.cwd(), "aom", PREFIX)}`, + "-DCMAKE_INSTALL_LIBDIR=lib", + "-DCMAKE_BUILD_TYPE=MinSizeRel", + "-DBUILD_SHARED_LIBS=OFF", + "-DENABLE_TESTS=0", + "-DENABLE_TESTDATA=0", + "-DENABLE_DOCS=0", + "-DENABLE_EXAMPLES=0", + "-DENABLE_TOOLS=0", + `-DENABLE_NASM=${shouldEnableNasm ? "1" : "0"}`, + "-DCONFIG_PIC=1", + "-DCMAKE_POSITION_INDEPENDENT_CODE=ON", + "-DCONFIG_AV1_DECODER=0", + "-DCONFIG_AV1_ENCODER=1", + "-DCONFIG_AV1_HIGHBITDEPTH=0", + isWindows ? "-DCMAKE_SYSTEM_NAME=Windows" : null, + isWindows ? `-DCMAKE_C_COMPILER=${windowsToolchain.cc}` : null, + isWindows ? `-DCMAKE_CXX_COMPILER=${windowsToolchain.cxx}` : null, + isWindows ? `-DCMAKE_RC_COMPILER=${windowsToolchain.rc}` : null, + isWindows ? `-DCMAKE_AR=${windowsToolchain.ar}` : null, + isWindows ? `-DCMAKE_RANLIB=${windowsToolchain.ranlib}` : null, + ] + .filter(Boolean) + .join(" "), + { + cwd: AOM_BUILD_DIR, + stdio: "inherit", + } + ); + + execSync("make", { + cwd: AOM_BUILD_DIR, + stdio: "inherit", + }); + + execSync("make install", { + cwd: AOM_BUILD_DIR, + stdio: "inherit", + }); + + execSync("cp -r " + PREFIX + " ../", { + cwd: "aom", + stdio: "inherit", + }); +}; + +export const enableAv1 = (isWindows) => { + enableDav1d(isWindows); + enableLibaom(isWindows); +}; diff --git a/compile-ffmpeg.mjs b/compile-ffmpeg.mjs index bd7de15..7b0e6e2 100644 --- a/compile-ffmpeg.mjs +++ b/compile-ffmpeg.mjs @@ -29,7 +29,6 @@ if (existsSync("/opt/homebrew/opt/sdl2/lib/libSDL2-2.0.0.dylib")) { const decoders = [ "aac", "ac3", - "av1", "flac", "h264", "hevc", @@ -197,6 +196,11 @@ const extraLdFlags = [ execSync("cp -r remotion ffmpeg", { stdio: "inherit" }); +const pkgConfigDirs = [ + path.join(process.cwd(), PREFIX, "lib", "pkgconfig"), + path.join(process.cwd(), PREFIX, "lib64", "pkgconfig"), +]; + execSync( [ path.posix.join(process.cwd().replace(/\\/g, "/"), "ffmpeg", "configure"), @@ -213,6 +217,7 @@ execSync( "--enable-small", "--enable-shared", "--enable-libdav1d", + "--enable-libaom", "--enable-libzimg", "--enable-libfdk-aac", "--disable-static", @@ -250,6 +255,7 @@ execSync( "--enable-filter=tonemap", "--enable-filter=copy", "--disable-doc", + "--disable-debug", "--enable-gpl", "--enable-nonfree", "--disable-encoders", @@ -262,6 +268,7 @@ execSync( "--enable-encoder=pcm_s24le", "--enable-encoder=libx264", "--enable-encoder=libx265", + "--enable-encoder=libaom-av1", "--enable-libvpx", "--enable-encoder=libvpx_vp8", "--enable-encoder=libvpx_vp9", @@ -295,7 +302,7 @@ execSync( { env: { ...process.env, - PKG_CONFIG_PATH: path.join(process.cwd(), PREFIX) + "/lib/pkgconfig", + PKG_CONFIG_PATH: pkgConfigDirs.join(path.delimiter), }, cwd: "ffmpeg", stdio: "inherit", diff --git a/zip.mjs b/zip.mjs index 2356a71..b1c5c23 100644 --- a/zip.mjs +++ b/zip.mjs @@ -1,6 +1,7 @@ import { execSync } from "child_process"; import { copyFileSync, + rmSync, readdirSync, renameSync, unlinkSync, @@ -11,9 +12,103 @@ import { PREFIX } from "./const.mjs"; const isWindows = process.argv[2] === "windows"; const remotionLibDir = path.join(process.cwd(), "remotion", "lib"); +const remotionLib64Dir = path.join(process.cwd(), "remotion", "lib64"); +const remotionBinDir = path.join(process.cwd(), "remotion", "bin"); + +const getStripTool = () => { + const candidates = isWindows + ? ["x86_64-w64-mingw32-strip"] + : process.platform === "darwin" + ? ["strip", "llvm-strip"] + : ["strip", "llvm-strip"]; + + for (const tool of candidates) { + try { + execSync(`command -v ${tool}`, { + stdio: "ignore", + }); + return tool; + } catch { + // Continue trying alternatives. + } + } + + return null; +}; + +const stripFilesInDir = (dir, shouldStrip, stripTool, stripArgs) => { + if (!existsSync(dir)) { + return; + } + + const files = readdirSync(dir, { + withFileTypes: true, + }); + + for (const file of files) { + if (!file.isFile()) { + continue; + } + + if (!shouldStrip(file.name)) { + continue; + } + + const fullPath = path.join(dir, file.name); + try { + execSync( + [stripTool, ...stripArgs, `"${fullPath.replaceAll('"', '\\"')}"`].join( + " " + ), + { + stdio: "inherit", + } + ); + } catch (err) { + console.warn(`Skipping strip for ${fullPath}:`, err.message); + } + } +}; + +const removeDevArtifacts = () => { + const devPaths = [ + path.join(remotionLibDir, "pkgconfig"), + path.join(remotionLib64Dir, "pkgconfig"), + path.join(remotionLibDir, "cmake"), + path.join(remotionLib64Dir, "cmake"), + ]; + + for (const devPath of devPaths) { + if (existsSync(devPath)) { + rmSync(devPath, { + force: true, + recursive: true, + }); + } + } + + for (const libDir of [remotionLibDir, remotionLib64Dir]) { + if (!existsSync(libDir)) { + continue; + } + + const files = readdirSync(libDir, { + withFileTypes: true, + }); + for (const file of files) { + if (!file.isFile()) { + continue; + } + if (file.name.endsWith(".a") || file.name.endsWith(".la")) { + rmSync(path.join(libDir, file.name), { + force: true, + }); + } + } + } +}; if (isWindows) { - const remotionBinDir = path.join(process.cwd(), "remotion", "bin"); copyFileSync( "libwinpthread-1.dll", path.join(remotionLibDir, "libwinpthread-1.dll") @@ -49,6 +144,35 @@ if (isWindows) { } } +const stripTool = getStripTool(); +if (stripTool) { + const stripArgs = + process.platform === "darwin" ? ["-x"] : ["--strip-unneeded"]; + stripFilesInDir( + remotionBinDir, + (fileName) => (isWindows ? fileName.endsWith(".exe") : true), + stripTool, + stripArgs + ); + stripFilesInDir( + remotionLibDir, + (fileName) => + isWindows + ? fileName.endsWith(".dll") + : fileName.endsWith(".dylib") || fileName.includes(".so"), + stripTool, + stripArgs + ); + stripFilesInDir( + remotionLib64Dir, + (fileName) => fileName.endsWith(".dylib") || fileName.includes(".so"), + stripTool, + stripArgs + ); +} + +removeDevArtifacts(); + execSync(`tar cvzf ffmpeg.tar.gz ${PREFIX} bindings.rs`, { stdio: "inherit", });