import { createHash } from "node:crypto"; import { mkdir, readFile, writeFile, copyFile } from "node:fs/promises"; import { homedir } from "node:os"; import { join, resolve } from "node:path"; const moduleRoot = resolve(import.meta.dir, ".."); const root = join(homedir(), ".cache", "sinter", "containers-v2"); const sources = [ ["aom", "3.15.2", "https://storage.googleapis.com/aom-releases/libaom-3.15.2.tar.gz", "67bb54b245f33ed98600e08269e6139986e48114e14042474ddd8885801dfddc", true], ["avif", "1.4.2", "https://github.com/AOMediaCodec/libavif/archive/refs/tags/v1.4.2.tar.gz", "2b645287340ba5a631d268b551dc2d72bd73ac33335962dd36dcdb6d8366921d", true], ["de265", "1.1.3", "https://github.com/strukturag/libde265/releases/download/v1.1.3/libde265-1.1.3.tar.gz", "554228bd17788c99a7e63b37ab5634722190e6e2bf60c1dcb01cef328e133905", true], ["kvazaar", "2.3.2", "https://github.com/ultravideo/kvazaar/releases/download/v2.3.2/kvazaar-2.3.2.tar.gz", "b95d2e20f2b0d8d7ed320055740be2e7a730abe28b153b5a788cfca371cc38b2", true], ["heif", "1.23.6", "https://github.com/strukturag/libheif/releases/download/v1.23.6/libheif-1.23.6.tar.gz", "4484346dc5995319dbc11e3a1c35d0a2ec46511ce370900869337fd2c7033125", true], ] as const; const versions = Object.fromEntries(sources.map(([name, version]) => [name, version])); const src = (name: string) => join(root, `${name}-${versions[name]}-src`); let variant = ""; const build = (name: string) => join(root, `${name}-${versions[name]}-${variant}-build`); async function run(args: string[]) { console.log(args.slice(0, 4).join(" ")); const p = Bun.spawn(args, { stdout: "inherit", stderr: "inherit" }); if (await p.exited) throw new Error(`Failed: ${args.join(" ")}`); } async function exists(path: string) {return Bun.file(path).exists();} await mkdir(root, { recursive: true }); if (!process.argv.includes("--copy-dist")) { for (const [name, version, url, hash, strip] of sources) { const archive=join(root, `${name}-${version}-release.tar.gz`); if (!(await exists(archive))) await run(["curl", "--fail", "-L", "--retry", "3", url, "-o", archive]); if (createHash("sha256").update(await readFile(archive)).digest("hex") !== hash) throw new Error(`Invalid source hash: ${name}`); if (!(await exists(join(src(name), ".sinter-source")))) { await mkdir(src(name), {recursive:true}); await run(["tar", "-xzf", archive, "-C", src(name), ...(strip?["--strip-components=1"]:[])]); await writeFile(join(src(name), ".sinter-source"), hash); } } const avifWriter=join(src("avif"),"src/write.c"); // An explicitly animated single frame must retain its track, timing and repetition. const writerSource=await readFile(avifWriter,"utf8"); const patchedWriter=writerSource.replace("(encoder->data->frames.count > 1);", "((encoder->data->frames.count > 1) || !encoder->data->singleImage);"); if(patchedWriter!==writerSource)await writeFile(avifWriter,patchedWriter); const common=["-DCMAKE_BUILD_TYPE=Release", "-DBUILD_SHARED_LIBS=OFF", "-DCMAKE_C_FLAGS=-O3 -sSUPPORT_LONGJMP=wasm -fwasm-exceptions -D__EMSCRIPTEN_STANDALONE_WASM__=1", "-DCMAKE_CXX_FLAGS=-O3 -sSUPPORT_LONGJMP=wasm -fwasm-exceptions -D__EMSCRIPTEN_STANDALONE_WASM__=1 -fwasm-exceptions", "-DCMAKE_POLICY_VERSION_MINIMUM=3.5"]; async function cmake(name:string, flags:string[]) { const stamp=join(build(name),".sinter-config");const signature=JSON.stringify([...common,...flags]); if(!(await exists(stamp))||await readFile(stamp,"utf8")!==signature){await run(["emcmake", "cmake", "-S", src(name), "-B", build(name), ...common, ...flags]);await writeFile(stamp,signature);} await run(["cmake", "--build", build(name), "-j", "8"]); } for (const format of ["avif", "heic"]) for (const role of ["decoder", "encoder"]) { variant = `${format}-${role}`; const decode = role === "decoder"; let libs: string[]; let includes: string[]; if (format === "avif") { await cmake("aom", ["-DAOM_TARGET_CPU=generic", "-DENABLE_APPS=OFF", "-DENABLE_DOCS=OFF", "-DENABLE_TESTS=OFF", "-DENABLE_EXAMPLES=OFF", "-DENABLE_TESTDATA=OFF", "-DENABLE_TOOLS=OFF", `-DCONFIG_AV1_DECODER=${decode ? 1 : 0}`, `-DCONFIG_AV1_ENCODER=${decode ? 0 : 1}`, "-DCONFIG_MULTITHREAD=0", "-DCONFIG_RUNTIME_CPU_DETECT=0"]); await cmake("avif", ["-DAVIF_CODEC_AOM=SYSTEM", `-DAVIF_CODEC_AOM_DECODE=${decode ? "ON" : "OFF"}`, `-DAVIF_CODEC_AOM_ENCODE=${decode ? "OFF" : "ON"}`, `-DAOM_INCLUDE_DIR=${src("aom")}`, `-DAOM_LIBRARY=${build("aom")}/libaom.a`, "-DAVIF_LIBYUV=OFF", "-DAVIF_LIBSHARPYUV=OFF", "-DAVIF_JPEG=OFF", "-DAVIF_ZLIBPNG=OFF", "-DAVIF_BUILD_APPS=OFF", "-DAVIF_BUILD_TESTS=OFF"]); libs=[`${build("avif")}/libavif.a`, `${build("aom")}/libaom.a`]; includes=[`-I${src("avif")}/include`]; } else { if(decode){ await cmake("de265", ["-DENABLE_SIMD=OFF", "-DENABLE_AVX2=OFF", "-DENABLE_AVX512=OFF", "-DENABLE_DECODER=OFF", "-DENABLE_ENCODER=OFF", "-DENABLE_SDL=OFF"]); }else{ await cmake("kvazaar", ["-DBUILD_TESTS=OFF", "-DBUILD_KVAZAAR_BINARY=OFF", "-DUSE_CRYPTO=OFF", "-DGIT_SUBMODULE=OFF", "-DCMAKE_SYSTEM_PROCESSOR=wasm32"]); } await cmake("heif", ["-DENABLE_PLUGIN_LOADING=OFF", "-DENABLE_MULTITHREADING_SUPPORT=OFF", "-DENABLE_PARALLEL_TILE_DECODING=OFF", `-DWITH_LIBDE265=${decode ? "ON" : "OFF"}`, "-DWITH_LIBDE265_PLUGIN=OFF", `-DWITH_KVAZAAR=${decode ? "OFF" : "ON"}`, "-DWITH_KVAZAAR_PLUGIN=OFF", "-DWITH_X265=OFF", "-DWITH_AOM_DECODER=OFF", "-DWITH_AOM_ENCODER=OFF", "-DWITH_DAV1D=OFF", "-DWITH_X264=OFF", "-DWITH_OpenH264_DECODER=OFF", "-DWITH_LIBSHARPYUV=OFF", "-DWITH_EXAMPLES=OFF", "-DWITH_GDK_PIXBUF=OFF", "-DBUILD_TESTING=OFF", "-DBUILD_DOCUMENTATION=OFF", `-DCMAKE_CXX_FLAGS=-O3 -sSUPPORT_LONGJMP=wasm -fwasm-exceptions -D__EMSCRIPTEN_STANDALONE_WASM__=1 -fwasm-exceptions -I${build("de265")}`, `-DLIBDE265_INCLUDE_DIR=${src("de265")}`, `-DLIBDE265_LIBRARY=${build("de265")}/libde265/libde265.a`, `-DKVAZAAR_INCLUDE_DIR=${src("kvazaar")}/src`, `-DKVAZAAR_LIBRARY=${build("kvazaar")}/libkvazaar.a`]); libs=[`${build("heif")}/libheif/libheif.a`, decode ? `${build("de265")}/libde265/libde265.a` : `${build("kvazaar")}/libkvazaar.a`]; includes=[`-I${src("heif")}/libheif/api`, `-I${build("heif")}`]; } if (process.argv.includes("--deps-only")) continue; await run(["em++", "-O3", "-sSUPPORT_LONGJMP=wasm", "-fwasm-exceptions", "-std=c++17", "--no-entry", "-sSTANDALONE_WASM=1", "-sALLOW_MEMORY_GROWTH=1", "-sINITIAL_MEMORY=33554432", "-sMAXIMUM_MEMORY=1073741824", "-sSTACK_SIZE=1048576", `-DSINTER_${format.toUpperCase()}`, `-DSINTER_${role.toUpperCase()}`, ...includes, join(moduleRoot,"native/containers/containers.cpp"), ...libs, "-o", join(root,`${variant}.wasm`)]); await mkdir(join(moduleRoot,"src/codecs"),{recursive:true}); await copyFile(join(root,`${variant}.wasm`), join(moduleRoot,`src/codecs/${variant}.wasm`)); } } else { await mkdir(join(moduleRoot,"dist"),{recursive:true}); for(const format of ["avif", "heic"]) for(const role of ["decoder", "encoder"]) { const name=`${format}-${role}.wasm`; await copyFile(join(moduleRoot,"src/codecs",name),join(moduleRoot,"dist",name)); } }