#!/usr/bin/env bash # build-libmpv-mingw64.sh # # Builds a yauiclient-optimised libmpv SDK for Windows x64 (shared DLL). # Generic enough to use with any project that depends on libmpv. # # libmpv-2.dll - all non-FFmpeg deps statically linked in # libmpv.dll.a - MinGW import lib # libmpv.lib - MSVC import lib (via gendef + llvm-lib) # include/mpv/ - public headers # runtime/ - libmpv-2.dll + FFmpeg DLLs (avcodec-62 etc.) # # Feature profile: # OpenGL render API, D3D11VA + DXVA2 hwdec, LuaJIT, Blu-ray, # network + local playback, Ivy Bridge CPU baseline. # No Vulkan, no encoders, no capture devices. # # Prerequisites - install once in MSYS2 MinGW64 shell: # pacman -S --needed \ # mingw-w64-x86_64-libass \ # mingw-w64-x86_64-libplacebo \ # mingw-w64-x86_64-freetype \ # mingw-w64-x86_64-harfbuzz \ # mingw-w64-x86_64-lcms2 \ # mingw-w64-x86_64-luajit \ # mingw-w64-x86_64-libjpeg-turbo \ # mingw-w64-x86_64-libbluray \ # mingw-w64-x86_64-dav1d \ # mingw-w64-x86_64-libvpx \ # mingw-w64-x86_64-opus \ # mingw-w64-x86_64-libvorbis \ # mingw-w64-x86_64-spirv-cross \ # mingw-w64-x86_64-llvm \ # mingw-w64-x86_64-tools-git # Note: libzvbi has no MSYS2 package - built from source automatically # libzvbi requires autoconf/automake: pacman -S --needed --noconfirm autoconf automake # # tools-git provides gendef; llvm provides llvm-lib # # For DEBUG_SYMBOLS=1 builds (see below), also install: # pacman -S --needed mingw-w64-x86_64-cv2pdb # (cv2pdb converts GCC/MinGW DWARF debug info into a real MSVC-format # .pdb so Visual Studio's Native Memory profiler / WinDbg can resolve # symbols. Plain -g alone is NOT enough for VS to read it -- VS reads # CodeView/PDB, not DWARF.) # # IMPORTANT: Always launch MSYS2 from a Windows Explorer shortcut, NOT from # JP Software TCC/LE -- TCC intercepts windres child processes otherwise. # # Usage: # ./build-libmpv-yauiclient.sh v0.41.0 [v0.40.0 ...] # # DEBUG_SYMBOLS=1 ./build-libmpv-yauiclient.sh v0.41.0 # Builds with debug info retained (no stripping, meson debugoptimized # buildtype, FFmpeg --enable-debug=3 --disable-stripping) and runs # cv2pdb over every produced DLL (libmpv-2.dll + all copied FFmpeg # DLLs) to emit matching .pdb files alongside them. Optimization # level is left unchanged so the leak still reproduces at normal # speed -- this is NOT a full debug/-O0 build, just symbols retained. # # Environment overrides (all optional): # FFMPEG6_VERSION FFmpeg 6.x version (default: 6.1.1) - used for mpv < v0.40.0 # FFMPEG7_VERSION FFmpeg 7.x version (default: 7.1.1) - used for mpv >= v0.40.0 # LIBPLACEBO_VERSION libplacebo version (default: 7.351.0) # CUSTOM_PATCH_DIR directory with ffmpeg/ and/or mpv/ patch subdirs # WORK_DIR scratch space (default: $PWD/work) # OUT_DIR output ZIPs (default: $PWD/out) # JOBS parallel jobs (default: nproc) # DEBUG_SYMBOLS 1 = retain debug info + generate .pdb via cv2pdb (default: 0) set -euo pipefail # Diagnostic: under set -e, a script can exit silently with no indication of # which command or line caused it. This prints exactly that, to stderr, if # it ever happens - remove once the current multi-tag-build investigation is # resolved, but there's no real cost to leaving it in permanently either. trap 'echo "==> SCRIPT EXITED (code $?) at line $LINENO: $BASH_COMMAND" >&2' ERR # --------------------------------------------------------------------------- # Argument validation # --------------------------------------------------------------------------- if [[ $# -eq 0 ]]; then echo "Usage: $0 v0.41.0 [v0.40.0 ...]" >&2 exit 2 fi MPV_TAGS=("$@") # --------------------------------------------------------------------------- # Configuration # --------------------------------------------------------------------------- FFMPEG6_VERSION="${FFMPEG6_VERSION:-6.1.1}" LIBPLACEBO_VERSION="${LIBPLACEBO_VERSION:-7.351.0}" ZVBI_VERSION="${ZVBI_VERSION:-0.2.44}" FFMPEG7_VERSION="${FFMPEG7_VERSION:-7.1.1}" CUSTOM_PATCH_DIR="${CUSTOM_PATCH_DIR:-}" WORK_DIR="${WORK_DIR:-$PWD/work}" SRC_DIR="$WORK_DIR/src" BUILD_DIR="$WORK_DIR/build" OUT_DIR="${OUT_DIR:-$PWD/out}" JOBS="${JOBS:-$(nproc)}" DEBUG_SYMBOLS="${DEBUG_SYMBOLS:-0}" # --------------------------------------------------------------------------- # ffmpeg_version_for_tag -> stdout: e.g. "7.1.1" # mpv v0.40.0+ requires FFmpeg 7; earlier tags use FFmpeg 6. # --------------------------------------------------------------------------- ffmpeg_version_for_tag() { local tag="$1" local minor minor="$(echo "$tag" | sed 's/^v[0-9]*\.\([0-9]*\)\..*/\1/')" if [[ "$minor" -ge 40 ]]; then echo "$FFMPEG7_VERSION" else echo "$FFMPEG6_VERSION" fi } ffmpeg_deps_dir() { local ver="$1" local major="${ver%%.*}" echo "$WORK_DIR/deps-ffmpeg${major}" } # --------------------------------------------------------------------------- # Helpers # --------------------------------------------------------------------------- log() { printf "\n==> %s\n" "$*"; } die() { echo "ERROR: $*" >&2; exit 1; } require_tool() { for t in "$@"; do command -v "$t" &>/dev/null || die "'$t' not found - install it first" done } # --------------------------------------------------------------------------- # cv2pdb_convert # # Runs cv2pdb over a MinGW-built DLL that still has its embedded DWARF # debug info (i.e. NOT stripped), producing a matching .pdb next to it. # No-op unless DEBUG_SYMBOLS=1. Best-effort: warns and continues on # failure rather than aborting the whole build. # --------------------------------------------------------------------------- cv2pdb_convert() { local dll="$1" [[ "$DEBUG_SYMBOLS" == "1" ]] || return 0 [[ -f "$dll" ]] || { log "WARNING: cv2pdb skipped - $dll not found"; return 0; } if ! command -v cv2pdb &>/dev/null; then log "WARNING: cv2pdb not found - install it (see header comment). Skipping PDB for $(basename "$dll")" return 0 fi log "Generating PDB for $(basename "$dll") via cv2pdb" # IMPORTANT: cv2pdb does two things, not one: # 1) writes a .pdb file next to the DLL # 2) REWRITES the DLL's PE debug directory (CodeView entry: GUID+age) # to point at that .pdb # VS/WinDbg match a PDB to a DLL via that GUID+age, not by filename. # Running cv2pdb against a throwaway copy and discarding the patched # copy (an earlier version of this function did exactly that) produces # a real .pdb that the shipped DLL has no reference to -- symbols never # resolve, everything still shows Unknown. So we patch the real DLL # in place; a backup with the original (DWARF-only, gdb-usable) debug # info is kept alongside it as .dll.dwarf-orig for reference. local dll_dir dll_base backup dll_dir="$(dirname "$dll")" dll_base="$(basename "$dll" .dll)" backup="$dll_dir/${dll_base}.dll.dwarf-orig" cp -f "$dll" "$backup" if cv2pdb "$(cygpath -w "$dll")" 2>&1; then local produced_pdb="$dll_dir/${dll_base}.pdb" if [[ -f "$produced_pdb" ]]; then log " -> $produced_pdb (DLL patched in place to reference it)" rm -f "$backup" else log "WARNING: cv2pdb reported success but no .pdb found for $(basename "$dll") - restoring original DLL" mv -f "$backup" "$dll" fi else log "WARNING: cv2pdb failed for $(basename "$dll") - restoring original (unpatched) DLL" mv -f "$backup" "$dll" fi } # --------------------------------------------------------------------------- # Pre-flight checks # --------------------------------------------------------------------------- require_tool git meson ninja pkg-config zip md5sum ar if ! command -v llvm-lib &>/dev/null; then log "WARNING: llvm-lib not found - MSVC .lib will not be generated" fi if ! command -v gendef &>/dev/null; then log "WARNING: gendef not found - .def / .lib generation skipped" fi if [[ "$DEBUG_SYMBOLS" == "1" ]] && ! command -v cv2pdb &>/dev/null; then log "WARNING: DEBUG_SYMBOLS=1 but cv2pdb not found - install mingw-w64-x86_64-cv2pdb" log " Build will continue but no .pdb files will be produced." fi # --------------------------------------------------------------------------- # Directory & env setup # --------------------------------------------------------------------------- mkdir -p "$SRC_DIR" "$BUILD_DIR" "$OUT_DIR" export PATH="/mingw64/bin:$PATH" export PKG_CONFIG_PATH="/mingw64/lib/pkgconfig${PKG_CONFIG_PATH:+:$PKG_CONFIG_PATH}" # Ivy Bridge baseline (AVX1 + SSE4.2, no AVX2) # DEBUG_SYMBOLS=1 adds -g (keeps -O2 -- this is "debug info retained", # not a full unoptimized debug build, so timing/reproduction behaviour # for the leak investigation is unaffected). # # NO_INLINE=1 additionally appends -fno-inline (and friends) - keeps -O2's # other optimizations (register allocation, vectorization, etc: timing # stays close to a normal build) but stops the compiler merging/inlining # function bodies into each other's compiled address ranges. Diagnostic- # only: cv2pdb's DWARF->PDB conversion has repeatedly shown it can't # always cleanly separate an inlined callee from its caller (multiple # "[anon_*]" unresolved frames this investigation turned out to be real, # named functions once caught via a live WinDbg breakpoint instead of a # static heap-profiler label). If a leak is being misattributed to one # function's profiler label because a neighbouring function's code got # folded into the same address range, NO_INLINE=1 should separate them # so the profiler's own symbol names become trustworthy again. Expect a # real performance cost (meaningfully slower than normal -O2) - fine for # a short diagnostic session, not something to ship. _debug_cflag="" if [[ "$DEBUG_SYMBOLS" == "1" ]]; then _debug_cflag=" -g" log "DEBUG_SYMBOLS=1: building with debug info retained (-g, no stripping, cv2pdb PDB generation)" fi if [[ "${NO_INLINE:-0}" == "1" ]]; then _debug_cflag="${_debug_cflag} -fno-inline -fno-inline-functions -fno-inline-small-functions -fno-inline-functions-called-once" log "NO_INLINE=1: disabling inlining (-fno-inline*) - diagnostic build, expect slower playback than normal" fi export CFLAGS="${CFLAGS:--O2 -march=x86-64 -mtune=generic}${_debug_cflag}" export CXXFLAGS="${CXXFLAGS:--O2 -march=x86-64 -mtune=generic}${_debug_cflag}" # --------------------------------------------------------------------------- # version_ge - true (0) if version a >= version b. # Strips a leading 'v' from either argument. Uses sort -V (version sort) # rather than string/numeric comparison, since naive comparison gets # multi-digit version parts wrong (e.g. "0.9.0" vs "0.10.0" - lexically # '9' > '1', which is the wrong answer for a version comparison). # --------------------------------------------------------------------------- version_ge() { local a="${1#v}" b="${2#v}" [[ "$a" == "$b" ]] && return 0 local highest highest="$(printf '%s\n%s\n' "$a" "$b" | sort -V | tail -n1)" [[ "$highest" == "$a" ]] } # --------------------------------------------------------------------------- # apply_patches [current_version] # --------------------------------------------------------------------------- apply_patches() { local repo="$1" subdir="$2" current_version="${3:-}" [[ -z "$CUSTOM_PATCH_DIR" ]] && return 0 local patch_dir="$CUSTOM_PATCH_DIR/$subdir" if [[ ! -d "$patch_dir" ]]; then # CUSTOM_PATCH_DIR is set but this specific subdir (ffmpeg/ or mpv/) # doesn't exist under it - almost always means the patch file was # dropped directly in CUSTOM_PATCH_DIR instead of CUSTOM_PATCH_DIR/$subdir. # This used to fail silently (no log line at all, patches just never # applied); flag it loudly instead since a silently-unpatched build # is much worse than a noisy warning. if [[ -n "$(find "$CUSTOM_PATCH_DIR" -maxdepth 1 -name '*.patch' 2>/dev/null)" ]]; then log "WARNING: found *.patch file(s) directly in $CUSTOM_PATCH_DIR but" log " expected them under $patch_dir - these will NOT be applied." log " Move them into: $patch_dir" fi return 0 fi log "Applying $subdir patches from $patch_dir" shopt -s nullglob local any_applied=0 local any_failed=0 for p in "$patch_dir"/*.patch; do # Optional gating: a patch file may declare a minimum version it # applies to via a header comment on one of its first few lines: # # min-version: 0.40.0 # Patches written against a specific FFmpeg/mpv source structure can # fail to apply - or in the worst case apply "successfully" against # coincidentally-similar-looking but structurally different older # code - if the same CUSTOM_PATCH_DIR is reused across builds # targeting different versions (e.g. testing against v0.41.0 but # also building an older v0.37.0 for production, which may not even # have the source files/functions a newer-version-specific patch # expects). This lets a patch opt in to being skipped outright, # loudly, rather than either failing to apply (aborting the whole # build, per the die below) or - worse - silently applying somewhere # it was never verified against. if [[ -n "$current_version" ]]; then local min_ver min_ver="$(grep -m1 -oE '^# *min-version: *[0-9][0-9.]*' "$p" 2>/dev/null | grep -oE '[0-9][0-9.]*$')" if [[ -n "$min_ver" ]] && ! version_ge "$current_version" "$min_ver"; then log " skipping $(basename "$p") - requires >= $min_ver, building $current_version" continue fi fi echo " patch: $(basename "$p")" if ( cd "$repo" && patch -p1 < "$p" ); then any_applied=1 else any_failed=1 log "ERROR: patch failed to apply: $(basename "$p")" fi done shopt -u nullglob if [[ $any_failed -eq 1 ]]; then die "One or more $subdir patches failed to apply (see .rej files in $repo). Aborting build rather than silently compiling unpatched source." fi if [[ $any_applied -eq 0 ]]; then log "WARNING: $patch_dir exists but contained no *.patch files" fi } # --------------------------------------------------------------------------- # build_ffmpeg_if_needed # --------------------------------------------------------------------------- build_ffmpeg_if_needed() { local ver="$1" local deps_dir deps_dir="$(ffmpeg_deps_dir "$ver")" local stamp="$deps_dir/.ffmpeg-built-$ver" # DEBUG_SYMBOLS changes configure flags, so a prior non-debug build's # stamp must not be treated as satisfying a debug request or vice versa. local stamp_tag="$ver" [[ "$DEBUG_SYMBOLS" == "1" ]] && stamp_tag="${ver}-dbg" stamp="$deps_dir/.ffmpeg-built-$stamp_tag" if [[ -f "$stamp" ]]; then # Verify ALL expected DLLs are present, not just avcodec - a build that # was interrupted partway through (or an older run from before a DLL # was added to this list) can leave avcodec-*.dll sitting there while # avfilter/avformat/swscale/etc are missing. Trusting the stamp in that # case silently produces an incomplete runtime/ later, with no error - # just some DLLs quietly never getting copied. Check them all. local all_present=1 for pat in avcodec avformat avutil avfilter swresample swscale; do find "$deps_dir/bin" -maxdepth 1 -name "${pat}-*.dll" 2>/dev/null | grep -q . \ || { all_present=0; break; } done if [[ -s "$stamp" && $all_present -eq 1 ]]; then log "FFmpeg $ver already built - skipping" return 0 fi log "FFmpeg $ver stamp exists but one or more expected DLLs are missing from $deps_dir/bin - rebuilding" rm -f "$stamp" fi mkdir -p "$deps_dir" local ff_src="$SRC_DIR/ffmpeg" log "Fetching FFmpeg $ver" if [[ ! -d "$ff_src/.git" ]]; then git clone https://github.com/FFmpeg/FFmpeg.git "$ff_src" fi ( cd "$ff_src" git fetch --tags --prune # Reset any patches applied by a previous run before checking out - # without this, `git checkout n${ver}` is a silent no-op when already # on that tag (nothing to diff), so a prior run's applied patches stay # sitting in the working tree as uncommitted changes. apply_patches() # then tries to reapply the same diff on top of itself, and `patch` # (correctly) prompts "Reversed (or previously applied) patch detected!" # since the target already matches the patch's "after" state. git reset --hard HEAD git clean -fd git checkout "n${ver}" local actual_tag actual_tag="$(git describe --tags --exact-match 2>/dev/null || git describe --tags)" log "FFmpeg source at: $actual_tag" ) apply_patches "$ff_src" "ffmpeg" "$ver" log "Cleaning FFmpeg tree" ( cd "$ff_src" make distclean >/dev/null 2>&1 || true rm -f ffbuild/config.sh ffbuild/config.mak ffbuild/config.log ) # Hide .dll.a files before FFmpeg configure so external libs link statically. # Also hide libpng16 DLL and pkgconfig to prevent FFmpeg linking it dynamically hide_dll_a_files local png_dll="/mingw64/bin/libpng16-16.dll" local png_pc="/mingw64/lib/pkgconfig/libpng16.pc" local png_pc2="/mingw64/lib/pkgconfig/libpng.pc" local png_dll_hidden=0 if [[ -f "$png_dll" ]]; then mv "$png_dll" "${png_dll}.bak"; png_dll_hidden=1; fi [[ -f "$png_pc" ]] && mv "$png_pc" "${png_pc}.bak" [[ -f "$png_pc2" ]] && mv "$png_pc2" "${png_pc2}.bak" # Debug flags: --enable-debug=3 keeps rich DWARF info; --disable-stripping # stops FFmpeg's own build/install rules from stripping the resulting # shared libs. Without DEBUG_SYMBOLS=1 this stays exactly as before # (--disable-debug, symbols stripped) so normal releases are unaffected. local _debug_ff_flags=() if [[ "$DEBUG_SYMBOLS" == "1" ]]; then _debug_ff_flags=(--enable-debug=3 --disable-stripping) else _debug_ff_flags=(--disable-debug) fi # --disable-decoder=magicyuv below: CVE-2026-8461 "PixelSmash" (CVSS 8.8) # is a heap out-of-bounds write in FFmpeg's MagicYUV decoder, RCE-capable # via a single crafted media file (disclosed 2026-06-22, JFrog). Fixed # upstream only in FFmpeg 8.1.2+; this script pins 7.1.1, which does not # have the fix. MagicYUV is a niche lossless codec with no relevance to # broadcast TV/PVR content - disabling it entirely is the # vendor-recommended workaround for anyone not on 8.1.2+. This flag was # already present in build-libmpv-linux.sh / build-libmpv-linux-next.sh; # restoring it here, since it was missing only from this script. log "Configuring FFmpeg $ver" ( cd "$ff_src" ./configure \ --prefix="$deps_dir" \ --enable-shared \ --disable-static \ --disable-programs \ --disable-doc \ "${_debug_ff_flags[@]}" \ --cpu=generic \ --enable-runtime-cpudetect \ --disable-encoders \ --disable-avdevice \ --disable-decoder=png \ --disable-decoder=apng \ --disable-decoder=magicyuv \ --disable-encoder=png \ --disable-encoder=apng \ --disable-parser=png \ --disable-demuxer=apng \ --disable-demuxer=image_png_pipe \ --disable-muxer=apng \ --disable-muxer=image2 \ --disable-vaapi \ --enable-dxva2 \ --enable-d3d11va \ --enable-swresample \ --enable-swscale \ --disable-lzma \ --disable-bzlib \ --enable-libzvbi \ --extra-cflags="-I$WORK_DIR/deps-zvbi/include" \ --extra-ldflags="-L$WORK_DIR/deps-zvbi/lib -Wl,-Bstatic -Wl,-Bdynamic" \ --windres=false \ || { echo "ERROR: FFmpeg $ver configure failed" >&2; restore_dll_a_files; [[ $png_dll_hidden -eq 1 ]] && mv "${png_dll}.bak" "$png_dll"; [[ -f "${png_pc}.bak" ]] && mv "${png_pc}.bak" "$png_pc"; [[ -f "${png_pc2}.bak" ]] && mv "${png_pc2}.bak" "$png_pc2"; exit 1; } ) log "Building FFmpeg $ver" ( cd "$ff_src" && make -j"$JOBS" && make install ) \ || { restore_dll_a_files; [[ $png_dll_hidden -eq 1 ]] && mv "${png_dll}.bak" "$png_dll"; [[ -f "${png_pc}.bak" ]] && mv "${png_pc}.bak" "$png_pc"; [[ -f "${png_pc2}.bak" ]] && mv "${png_pc2}.bak" "$png_pc2"; die "FFmpeg $ver build failed"; } restore_dll_a_files [[ ${png_dll_hidden:-0} -eq 1 ]] && [[ -f "${png_dll}.bak" ]] && mv "${png_dll}.bak" "$png_dll" [[ -f "${png_pc}.bak" ]] && mv "${png_pc}.bak" "$png_pc" [[ -f "${png_pc2}.bak" ]] && mv "${png_pc2}.bak" "$png_pc2" # Verify avcodec DLL was actually produced before stamping find "$deps_dir/bin" -name "avcodec-*.dll" | grep -q . \ || die "FFmpeg $ver build produced no avcodec DLL - check logs" # cv2pdb pass over every FFmpeg shared lib produced (avcodec/avformat/ # avutil/avfilter/swresample/swscale) -- a large share of the actual # decode/demux/cache code lives here, not just in libmpv-2.dll itself. if [[ "$DEBUG_SYMBOLS" == "1" ]]; then log "Generating PDBs for FFmpeg DLLs" for f in "$deps_dir/bin/"av*.dll "$deps_dir/bin/"sw*.dll; do [[ -f "$f" ]] && cv2pdb_convert "$f" done fi log "FFmpeg $ver built successfully: $(find "$deps_dir/bin" -name "av*.dll" -o -name "sw*.dll" | sort | tr '\n' ' ')" # Write version into stamp so it's non-empty (empty stamp = incomplete build) echo "$ver" > "$stamp" } # --------------------------------------------------------------------------- # build_libplacebo_if_needed # # Builds libplacebo from source with Vulkan disabled so the resulting DLL # only depends on Windows system DLLs + shaderc/spirv-cross (already static # in libmpv-2.dll). The MSYS2 system libplacebo is built with Vulkan enabled # and pulls in libgcc, libstdc++, libdovi, libshaderc_shared, vulkan-1.dll etc. # This build statically links all deps so the DLL only needs Windows system DLLs. # lcms (ICC colour management) is disabled to avoid the liblzma dependency chain. # # Output: $WORK_DIR/deps-placebo/bin/libplacebo-NNN.dll # --------------------------------------------------------------------------- build_libplacebo_if_needed() { local deps_dir="$WORK_DIR/deps-placebo" local stamp_tag="$LIBPLACEBO_VERSION" [[ "$DEBUG_SYMBOLS" == "1" ]] && stamp_tag="${LIBPLACEBO_VERSION}-dbg" local stamp="$deps_dir/.placebo-built-$stamp_tag" if [[ -f "$stamp" ]] && find "$deps_dir/bin" -name "libplacebo-*.dll" 2>/dev/null | grep -q .; then log "libplacebo $LIBPLACEBO_VERSION already built - skipping" return 0 fi mkdir -p "$deps_dir" local src="$SRC_DIR/libplacebo" log "Fetching libplacebo $LIBPLACEBO_VERSION" if [[ ! -d "$src/.git" ]]; then git clone https://code.videolan.org/videolan/libplacebo.git "$src" fi ( cd "$src" git fetch --tags --prune || true git reset --hard HEAD git clean -fd git checkout "v${LIBPLACEBO_VERSION}" git submodule update --init --recursive ) log "Configuring libplacebo $LIBPLACEBO_VERSION (static deps, no Vulkan, no MinGW runtime)" local build_dir="$BUILD_DIR/placebo-$LIBPLACEBO_VERSION" rm -rf "$build_dir" mkdir -p "$build_dir" # link-opts.ini: statically link C++/GCC runtimes and all non-system deps # so the resulting DLL only needs Windows system DLLs local pl_link_opts="$build_dir/link-opts.ini" cat > "$pl_link_opts" <<'PLINI' [built-in options] c_link_args = ['-static-libgcc', '-static-libstdc++', '-Wl,-Bstatic', '-lshaderc_combined', '-lglslang', '-lglslang-default-resource-limits', '-lSPIRV-Tools', '-lSPIRV-Tools-opt', '-lSPIRV-Tools-link', '-lSPIRV-Tools-reduce', '-lSPIRV-Tools-diff', '-lspirv-cross-c', '-lspirv-cross-glsl', '-lspirv-cross-hlsl', '-lspirv-cross-msl', '-lspirv-cross-cpp', '-lspirv-cross-reflect', '-lspirv-cross-core', '-lspirv-cross-util', '-ldovi', '-lm', '-Wl,-Bdynamic', '-lws2_32', '-lbcrypt', '-lntdll', '-luserenv'] cpp_link_args = ['-static-libgcc', '-static-libstdc++', '-Wl,-Bstatic', '-lshaderc_combined', '-lglslang', '-lglslang-default-resource-limits', '-lSPIRV-Tools', '-lSPIRV-Tools-opt', '-lSPIRV-Tools-link', '-lSPIRV-Tools-reduce', '-lSPIRV-Tools-diff', '-lspirv-cross-c', '-lspirv-cross-glsl', '-lspirv-cross-hlsl', '-lspirv-cross-msl', '-lspirv-cross-cpp', '-lspirv-cross-reflect', '-lspirv-cross-core', '-lspirv-cross-util', '-ldovi', '-lm', '-Wl,-Bdynamic', '-lws2_32', '-lbcrypt', '-lntdll', '-luserenv'] PLINI # Hide .dll.a files so meson picks up static .a for these deps local hidden_pl=() for lib in shaderc_shared spirv-cross-c spirv-cross-c-shared spirv-cross-glsl spirv-cross-hlsl spirv-cross-msl spirv-cross-cpp spirv-cross-reflect spirv-cross-core spirv-cross-util dovi; do local f="/mingw64/lib/lib${lib}.dll.a" if [[ -f "$f" ]]; then mv "$f" "${f}.plbak" hidden_pl+=("$f") fi done trap 'for _f in "${hidden_pl[@]}"; do [[ -f "${_f}.plbak" ]] && mv "${_f}.plbak" "$_f"; done' EXIT # release keeps -O-level optimization; DEBUG_SYMBOLS switches to # debugoptimized (reliably emits -g and never strips, same reasoning as # the FFmpeg and mpv build steps - 'release' buildtype does not # necessarily emit -g at all regardless of CFLAGS, independent of the # -Dstrip=false setting below, which only controls whether existing # debug info gets stripped, not whether it gets generated in the first # place). This mirrors the DEBUG_SYMBOLS handling already present for # FFmpeg (build_ffmpeg_if_needed) and mpv (build_libmpv_tag) - this # function was missed when DEBUG_SYMBOLS support was first added, which # is why libplacebo-*.dll never got a working PDB despite cv2pdb_convert # being called on it further down: there was nothing for cv2pdb to # convert. local _pl_buildtype="release" [[ "$DEBUG_SYMBOLS" == "1" ]] && _pl_buildtype="debugoptimized" ( cd "$src" meson setup "$build_dir" --prefix="$deps_dir" --buildtype="$_pl_buildtype" --default-library=shared --wrap-mode=nodownload --native-file="$pl_link_opts" -Dvulkan=disabled -Dd3d11=enabled -Dopengl=enabled -Dglslang=disabled -Dshaderc=enabled -Dlcms=disabled -Ddovi=enabled -Dunwind=disabled -Dtests=false -Dbench=false -Dfuzz=false -Dstrip=false ) log "Building libplacebo $LIBPLACEBO_VERSION" ninja -C "$build_dir" -j"$JOBS" ninja -C "$build_dir" install # Restore hidden .dll.a files for _f in "${hidden_pl[@]}"; do [[ -f "${_f}.plbak" ]] && mv "${_f}.plbak" "$_f" done trap - EXIT local dll dll="$(find "$deps_dir/bin" -name "libplacebo-*.dll" | head -1)" [[ -n "$dll" ]] || die "libplacebo DLL not found after build" cv2pdb_convert "$dll" log "libplacebo built: $(basename "$dll")" log "DLL dependencies (should be Windows system DLLs only):" objdump -p "$dll" | grep "DLL Name" || true local unexpected unexpected="$(objdump -p "$dll" | grep "DLL Name" \ | grep -Eiv "kernel32|user32|ntdll|msvcrt|advapi32|gdi32|shlwapi|version|bcrypt|rpcrt4|userenv|ws2_32|bcryptprimitives|api-ms" \ || true)" if [[ -n "$unexpected" ]]; then log "WARNING: unexpected libplacebo DLL dependencies:" echo "$unexpected" else log "libplacebo deps clean - Windows system DLLs only" fi echo "$LIBPLACEBO_VERSION" > "$stamp" } # --------------------------------------------------------------------------- # build_libzvbi_if_needed # # Builds libzvbi from source as a static library so FFmpeg can link it into # avcodec statically — no extra DLL to ship. libzvbi provides the dvb_teletext # decoder used for teletext subtitles common in Australian/European DVB streams. # # No MSYS2 mingw-w64-x86_64-zvbi package exists so no hiding needed. # Output: $WORK_DIR/deps-zvbi/lib/libzvbi.a # --------------------------------------------------------------------------- build_libzvbi_if_needed() { local deps_dir="$WORK_DIR/deps-zvbi" local stamp="$deps_dir/.zvbi-built-$ZVBI_VERSION" if [[ -f "$stamp" ]] && [[ -f "$deps_dir/lib/libzvbi.a" ]]; then log "libzvbi $ZVBI_VERSION already built - skipping" return 0 fi mkdir -p "$deps_dir" local src="$SRC_DIR/zvbi" log "Fetching libzvbi $ZVBI_VERSION" if [[ ! -d "$src/.git" ]]; then git clone https://github.com/zapping-vbi/zvbi.git "$src" fi ( cd "$src" git fetch --tags --prune || true git reset --hard HEAD git clean -fd git checkout "v${ZVBI_VERSION}" ) log "Bootstrapping libzvbi $ZVBI_VERSION" # Ensure autoconf/automake are available if ! command -v autoconf &>/dev/null || ! command -v automake &>/dev/null || ! command -v autopoint &>/dev/null || ! command -v libtool &>/dev/null; then log "Installing autoconf/automake..." pacman -S --needed --noconfirm autoconf automake libtool gettext gettext-devel gettext fi ( cd "$src" && ./autogen.sh ) log "Configuring libzvbi $ZVBI_VERSION (static only, no VBI capture devices)" # Hide libpng pkg-config so zvbi configure can't find it local zvbi_png_pc="/mingw64/lib/pkgconfig/libpng16.pc" local zvbi_png_pc2="/mingw64/lib/pkgconfig/libpng.pc" [[ -f "$zvbi_png_pc" ]] && mv "$zvbi_png_pc" "${zvbi_png_pc}.zbak" [[ -f "$zvbi_png_pc2" ]] && mv "$zvbi_png_pc2" "${zvbi_png_pc2}.zbak" ( cd "$src" ./configure \ --prefix="$deps_dir" \ --host=x86_64-w64-mingw32 \ --enable-static \ --disable-shared \ --disable-dvb \ --disable-v4l \ --disable-bktr \ --disable-proxy \ --disable-nls \ --without-doxygen \ --without-libiconv-prefix \ --without-png \ CFLAGS="-O2 -march=x86-64 -mtune=generic${_debug_cflag}" \ LIBPNG_CFLAGS="" LIBPNG_LIBS="" \ ac_cv_lib_png_png_destroy_write_struct=no ) # Restore png pkg-config [[ -f "${zvbi_png_pc}.zbak" ]] && mv "${zvbi_png_pc}.zbak" "$zvbi_png_pc" [[ -f "${zvbi_png_pc2}.zbak" ]] && mv "${zvbi_png_pc2}.zbak" "$zvbi_png_pc2" log "Building libzvbi $ZVBI_VERSION" make -C "$src" -j"$JOBS" make -C "$src" install [[ -f "$deps_dir/lib/libzvbi.a" ]] || die "libzvbi.a not found after build" # Remove any shared lib artefacts so FFmpeg picks up the static .a only rm -f "$deps_dir/lib/libzvbi.dll.a" rm -f "$deps_dir/bin/libzvbi-"*.dll log "libzvbi built: $deps_dir/lib/libzvbi.a" echo "$ZVBI_VERSION" > "$stamp" } # --------------------------------------------------------------------------- # prepare_spirv_cross_fat_archive # # libspirv-cross-c-shared is what meson/pkg-config records in build.ninja # (hardcoded path, not a -l flag). That archive only has the C wrapper; # the C++ backends must be merged in. Build a fat archive in-place. # --------------------------------------------------------------------------- prepare_spirv_cross_fat_archive() { local fat="/mingw64/lib/libspirv-cross-c-shared.a" # Already has core symbols? if ar t "$fat" 2>/dev/null | grep -q "spirv_cross_c.cpp\|ParsedIR"; then log "spirv-cross fat archive already prepared - skipping" return 0 fi log "Building fat libspirv-cross-c-shared.a (merging all backends)" local tmp tmp="$(mktemp -d /tmp/sc-merge.XXXXXX)" ( cd "$tmp" for lib in spirv-cross-c spirv-cross-glsl spirv-cross-hlsl \ spirv-cross-msl spirv-cross-cpp spirv-cross-reflect \ spirv-cross-core spirv-cross-util; do ar x "/mingw64/lib/lib${lib}.a" done ar rcs "$fat" ./*.o ./*.obj 2>/dev/null || ar rcs "$fat" ./* ) rm -rf "$tmp" log "Fat archive built: $fat" } # --------------------------------------------------------------------------- # hide_dll_a_files / restore_dll_a_files # # Meson resolves deps at configure time and records full .dll.a paths in # build.ninja. Hiding them before configure forces static .a selection. # --------------------------------------------------------------------------- HIDDEN_DLL_A=() HIDDEN_FFMPEG_PC=() hide_dll_a_files() { local _use_dynamic_placebo="${1:-0}" HIDDEN_DLL_A=() local libs=( pthread winpthread xml2 fontconfig freetype fribidi harfbuzz unibreak dovi bluray lcms2 jpeg z luajit-5.1 va lzma png16 spirv-cross-c spirv-cross-c-shared shaderc_shared brotlidec brotlicommon bz2 expat graphite2 png16 iconv intl spirv-cross-glsl spirv-cross-hlsl spirv-cross-msl spirv-cross-cpp spirv-cross-reflect spirv-cross-core spirv-cross-util va-win32 ) # libplacebo on MSYS2 is DLL-only (no static .a) - keep .dll.a visible # so meson links against libplacebo.dll at configure time if [[ "$_use_dynamic_placebo" != "1" ]]; then libs+=(placebo) fi for lib in "${libs[@]}"; do local f="/mingw64/lib/lib${lib}.dll.a" if [[ -f "$f" ]]; then mv "$f" "${f}.bak" HIDDEN_DLL_A+=("$f") fi done log "Hid ${#HIDDEN_DLL_A[@]} .dll.a files" } restore_dll_a_files() { for f in "${HIDDEN_DLL_A[@]}"; do [[ -f "${f}.bak" ]] && mv "${f}.bak" "$f" done log "Restored ${#HIDDEN_DLL_A[@]} .dll.a files" } # Hide MSYS2 system FFmpeg .pc files so meson picks up our source-built # FFmpeg from deps_dir/lib/pkgconfig instead. Without this, meson finds # the MSYS2 avcodec-62 (full codec set, huge deps) even though our # PKG_CONFIG_PATH puts deps_dir first -- the MSYS2 .pc files are also # present in /mingw64/lib/pkgconfig and may win via pkg-config precedence. hide_system_ffmpeg_pc() { HIDDEN_FFMPEG_PC=() for pc in /mingw64/lib/pkgconfig/libav*.pc \ /mingw64/lib/pkgconfig/libsw*.pc \ /mingw64/lib/pkgconfig/libpostproc.pc \ /mingw64/lib/pkgconfig/libplacebo.pc; do if [[ -f "$pc" ]]; then mv "$pc" "${pc}.bak" HIDDEN_FFMPEG_PC+=("$pc") fi done log "Hid ${#HIDDEN_FFMPEG_PC[@]} MSYS2 FFmpeg .pc files" } restore_system_ffmpeg_pc() { for pc in "${HIDDEN_FFMPEG_PC[@]}"; do [[ -f "${pc}.bak" ]] && mv "${pc}.bak" "$pc" done log "Restored ${#HIDDEN_FFMPEG_PC[@]} MSYS2 FFmpeg .pc files" } # Patch codec .pc files to force static linking during FFmpeg configure. # require_pkg_config runs a test link using the Libs: line from the .pc file. # We prepend -Wl,-Bstatic and append -Wl,-Bdynamic so the test link and the # final DLL link both pull in the static .a rather than the .dll.a. PATCHED_PC_FILES=() patch_codec_pc_files_for_static() { PATCHED_PC_FILES=() local pc_dir="/mingw64/lib/pkgconfig" local libs=(dav1d opus vorbis vorbisenc vpx) for lib in "${libs[@]}"; do local pc="$pc_dir/${lib}.pc" [[ -f "$pc" ]] || continue # Only patch if not already patched if grep -q "Bstatic" "$pc"; then log " $pc already patched - skipping" continue fi cp "$pc" "${pc}.orig" # Rewrite Libs: line to wrap with -Wl,-Bstatic ... -Wl,-Bdynamic sed -i 's|^Libs: \(.*\)$|Libs: -Wl,-Bstatic -Wl,-Bdynamic|' "$pc" # Also patch Libs.private if present sed -i 's|^Libs.private: \(.*\)$|Libs.private: -Wl,-Bstatic -Wl,-Bdynamic|' "$pc" PATCHED_PC_FILES+=("$pc") log " Patched $pc for static linking" done log "Patched ${#PATCHED_PC_FILES[@]} codec .pc files for static linking" } restore_codec_pc_files() { for pc in "${PATCHED_PC_FILES[@]}"; do [[ -f "${pc}.orig" ]] && mv "${pc}.orig" "$pc" done log "Restored ${#PATCHED_PC_FILES[@]} codec .pc files" } # --------------------------------------------------------------------------- # build_libmpv_tag # --------------------------------------------------------------------------- build_libmpv_tag() { local tag="$1" local ff_ver ff_ver="$(ffmpeg_version_for_tag "$tag")" local deps_dir deps_dir="$(ffmpeg_deps_dir "$ff_ver")" local mpv_src="$SRC_DIR/mpv" local build_dir="$BUILD_DIR/mpv-$tag" local stage_dir="$BUILD_DIR/stage-$tag" local sdk_dir="$OUT_DIR/sdk-$tag" local runtime_dir="$OUT_DIR/runtime-$tag" # Extract minor version for feature gating (e.g. 41 from v0.41.0) local tag_minor tag_minor="$(echo "$tag" | sed 's/^v[0-9]*\.\([0-9]*\)\..*/\1/')" log "Building libmpv $tag (FFmpeg $ff_ver, mpv minor=$tag_minor)" # ── clone / checkout ────────────────────────────────────────────────────── if [[ ! -d "$mpv_src/.git" ]]; then git clone https://github.com/mpv-player/mpv.git "$mpv_src" fi ( cd "$mpv_src" git fetch --tags --prune || true # Reset any patches applied by a previous tag build before switching git reset --hard HEAD git clean -fd git checkout "$tag" git submodule update --init --recursive || true ) apply_patches "$mpv_src" "mpv" "$tag" # ── source patches ──────────────────────────────────────────────────────── # 1) DXGI_DEBUG_D3D11: MSYS2 d3d11sdklayers.h already defines it; # mpv's d3d11_helpers.h redefines when HAVE_DXGI_DEBUG_D3D11=0. local dxgi_header="$mpv_src/video/out/gpu/d3d11_helpers.h" if grep -q "^#if !HAVE_DXGI_DEBUG_D3D11$" "$dxgi_header" 2>/dev/null; then log "Patching DXGI_DEBUG_D3D11 redefinition guard" sed -i 's/^#if !HAVE_DXGI_DEBUG_D3D11$/#if !HAVE_DXGI_DEBUG_D3D11 \&\& !defined(DXGI_DEBUG_D3D11)/' \ "$dxgi_header" fi # 3) ao_wasapi_utils.c: ksmedia.h uses WAVE_FORMAT_DOLBY_AC3_SPDIF / WAVE_FORMAT_DTS # before they are defined. Fixed in newer MinGW headers but affects older mpv # tags built against MSYS2 GCC 15. Inject #include before the first # system include in ao_wasapi_utils.c so the constants are defined in time. local wasapi_src="$mpv_src/audio/out/ao_wasapi_utils.c" if [[ -f "$wasapi_src" ]] && ! grep -q "mmreg.h" "$wasapi_src"; then log "Patching ao_wasapi_utils.c: inject mmreg.h before ksmedia.h" python3 -c " import sys txt = open(sys.argv[1]).read() marker = '#include ' idx = txt.find(marker) if idx != -1: txt = txt[:idx+len(marker)] + '\n#include ' + txt[idx+len(marker):] open(sys.argv[1], 'w').write(txt) " "$wasapi_src" fi # 2) windows.compile_resources: unreliable with TCC/LE installed system-wide. # osdep/mpv.rc only adds version info; libmpv DLL is unaffected. if grep -q "windows.compile_resources" "$mpv_src/meson.build" 2>/dev/null; then log "Patching out windows.compile_resources" grep -n "windows.compile_resources\|depends: version_h" "$mpv_src/meson.build" \ | awk -F: '{print $1}' \ | while read -r lineno; do sed -i "${lineno}s/^/# PATCHED: /" "$mpv_src/meson.build" done fi # ── system prep ─────────────────────────────────────────────────────────── # libdl stub: some MinGW .pc files reference -ldl on Windows. if [[ ! -f /mingw64/lib/libdl.a ]]; then log "Creating empty libdl.a stub" echo '' | ar rc /mingw64/lib/libdl.a fi # shaderc: meson looks for libshaderc_shared.a; symlink to the fat combined archive. if [[ ! -f /mingw64/lib/libshaderc_shared.a ]]; then log "Creating libshaderc_shared.a -> libshaderc_combined.a" ln -sf /mingw64/lib/libshaderc_combined.a /mingw64/lib/libshaderc_shared.a fi # spirv-cross: fat archive with all backends merged. prepare_spirv_cross_fat_archive # ── configure ───────────────────────────────────────────────────────────── rm -rf "$build_dir" "$stage_dir" mkdir -p "$build_dir" "$stage_dir" export PKG_CONFIG_PATH="$deps_dir/lib/pkgconfig:$WORK_DIR/deps-placebo/lib/pkgconfig:/mingw64/lib/pkgconfig" export PATH="$deps_dir/bin:$WORK_DIR/deps-placebo/bin:/mingw64/bin:$PATH" export LIBRARY_PATH="$WORK_DIR/deps-placebo/lib:${LIBRARY_PATH:-}" # native.ini: windres with explicit gcc preprocessor (avoids TCC/LE). local native_file="$build_dir/native.ini" local windres_path gcc_path windres_path="$(cygpath -m "$(which windres)")" gcc_path="$(which gcc)" cat > "$native_file" <=v0.41) local _static_libs="'-lstdc++', '-lwinpthread', '-lshaderc_combined', '-lglslang', '-lglslang-default-resource-limits', '-lSPIRV-Tools', '-lSPIRV-Tools-opt', '-lSPIRV-Tools-link', '-lSPIRV-Tools-reduce', '-lSPIRV-Tools-diff', '-lspirv-cross-c', '-lspirv-cross-glsl', '-lspirv-cross-hlsl', '-lspirv-cross-msl', '-lspirv-cross-cpp', '-lspirv-cross-reflect', '-lspirv-cross-core', '-lspirv-cross-util', '-lass', '-lfontconfig', '-lfreetype', '-lbrotlidec', '-lbrotlicommon', '-lpng16', '-lbz2', '-lgraphite2', '-lharfbuzz', '-lfribidi', '-lunibreak', '-lexpat', '-lxml2', '-ldovi', '-liconv', '-lintl', '-lbluray', '-lzvbi', '-llcms2', '-ljpeg', '-lz', '-lluajit-5.1'" local _link_args="'-static-libgcc', '-static-libstdc++', '-Wl,-Bstatic', ${_static_libs}, '-Wl,-Bdynamic', '-ldwrite', '-lbcrypt', '-lrpcrt4'" cat > "$link_opts_file" </dev/null; then version_suffix_flag=("-Dversion-suffix=-yauiclient") else log "NOTE: this tag does not support -Dversion-suffix, skipping" fi # DEBUG_SYMBOLS=1: debugoptimized keeps -O2-ish codegen but reliably # emits -g and never strips, unlike 'release' which can strip depending # on meson version/config. -Dstrip=false is explicit belt-and-braces. local _buildtype="release" local _strip_flag=() if [[ "$DEBUG_SYMBOLS" == "1" ]]; then _buildtype="debugoptimized" _strip_flag=(-Dstrip=false) fi ( cd "$mpv_src" meson setup "$build_dir" \ --prefix="$stage_dir" \ --buildtype="$_buildtype" \ --default-library=shared \ --wrap-mode=nodownload \ --native-file="$native_file" \ --native-file="$link_opts_file" \ -Dlibmpv=true \ -Dcplayer=false \ -Dgl=enabled \ -Dgl-win32=enabled \ -Dgl-dxinterop=enabled \ -Dd3d-hwaccel=enabled \ -Dd3d9-hwaccel=enabled \ -Dd3d11=enabled \ -Dspirv-cross=enabled \ -Dwasapi=enabled \ -Dlua=luajit \ -Dlcms2=enabled \ -Dvaapi=disabled \ -Dvulkan=disabled \ -Djavascript=disabled \ -Dcaca=disabled \ -Drubberband=disabled \ -Dzimg=disabled \ -Dsdl2-audio=disabled \ -Dsdl2-video=disabled \ -Dvapoursynth=disabled \ "${_strip_flag[@]}" \ "${version_suffix_flag[@]}" ) log "Compiling libmpv (shared DLL)" ninja -C "$build_dir" -j"$JOBS" restore_dll_a_files restore_system_ffmpeg_pc trap - EXIT # ── verify DLL dependencies ─────────────────────────────────────────────── local dll dll="$(find "$build_dir" -maxdepth 2 -name "libmpv-*.dll" | head -1)" [[ -n "$dll" ]] || die "libmpv DLL not found after build of $tag" log "DLL dependencies (should be FFmpeg + Windows system only):" objdump -p "$dll" | grep "DLL Name" || true local unexpected unexpected="$(objdump -p "$dll" | grep "DLL Name" \ | grep -Eiv "avcodec|avdevice|avfilter|avformat|avutil|swresample|swscale|libva_win32|libplacebo|kernel32|user32|ntdll|msvcrt|advapi32|ws2_32|ole32|gdi32|shell32|shcore|shlwapi|opengl32|dwmapi|avrt|imm32|dwrite|uxtheme|version|bcrypt|rpcrt4|userenv|winmm|secur32|ws2_32|api-ms" \ || true)" if [[ -n "$unexpected" ]]; then log "WARNING: unexpected DLL dependencies:" echo "$unexpected" fi # ── install + SDK assembly ──────────────────────────────────────────────── ninja -C "$build_dir" install rm -rf "$sdk_dir" mkdir -p "$sdk_dir/lib/pkgconfig" "$sdk_dir/include" local mpv_dll_a mpv_dll_a="$(find "$build_dir" "$stage_dir" -name "libmpv.dll.a" | head -1)" [[ -n "$mpv_dll_a" ]] && cp -f "$mpv_dll_a" "$sdk_dir/lib/libmpv.dll.a" cp -f "$dll" "$sdk_dir/lib/" # Generate the PDB from the copy that will actually ship (after the # cp above), so the .pdb's embedded path/signature matches the DLL # that ends up in sdk_dir/runtime_dir, not the intermediate build dir copy. cv2pdb_convert "$sdk_dir/lib/$(basename "$dll")" cat > "$sdk_dir/lib/pkgconfig/libmpv.pc" </dev/null || { log "Skipping MSVC .lib (gendef not found)"; return 0; } command -v llvm-lib &>/dev/null || { log "Skipping MSVC .lib (llvm-lib not found)"; return 0; } log "Generating MSVC libmpv.lib via gendef + llvm-lib" # gendef always writes .def in the current directory - never stdout local tmpdir tmpdir="$(mktemp -d /tmp/gendef.XXXXXX)" ( cd "$tmpdir" && gendef "$dll" ) 2>/dev/null || true local written written="$(find "$tmpdir" -maxdepth 1 -name "*.def" | head -1)" if [[ -z "$written" || ! -s "$written" ]]; then log "WARNING: gendef produced no .def - .lib not produced" rm -rf "$tmpdir" return 0 fi cp -f "$written" "$sdk_dir/lib/libmpv.def" rm -rf "$tmpdir" llvm-lib /def:"$(cygpath -w "$sdk_dir/lib/libmpv.def")" /out:"$(cygpath -w "$sdk_dir/lib/libmpv.lib")" /machine:x64 2>/dev/null || log "WARNING: llvm-lib failed - .lib not produced" [[ -f "$sdk_dir/lib/libmpv.lib" ]] && log "MSVC libmpv.lib generated OK" || log "WARNING: libmpv.lib not produced" } generate_msvc_lib || true cp -R "$stage_dir/include/mpv" "$sdk_dir/include/" # ── runtime ─────────────────────────────────────────────────────────────── rm -rf "$runtime_dir" mkdir -p "$runtime_dir" cp -f "$dll" "$runtime_dir/" cp -f "$sdk_dir/lib/$(basename "$dll" .dll).pdb" "$runtime_dir/" 2>/dev/null || true local copied=0 # avdevice is intentionally disabled (--disable-avdevice in configure) # and will never exist - not included here or in the completeness check above. for pattern in avcodec-'*'.dll avformat-'*'.dll \ avutil-'*'.dll avfilter-'*'.dll \ swresample-'*'.dll swscale-'*'.dll; do local pattern_copied=0 for f in "$deps_dir/bin/"$pattern; do [[ -f "$f" ]] && { cp -f "$f" "$runtime_dir/"; copied=$(( copied + 1 )); pattern_copied=$(( pattern_copied + 1 )); } done if [[ $pattern_copied -eq 0 ]]; then log "WARNING: no files matched '$pattern' in $deps_dir/bin - not copied to runtime dir" fi # Carry over PDBs generated during build_ffmpeg_if_needed, if present for f in "$deps_dir/bin/"${pattern%.dll}.pdb; do [[ -f "$f" ]] && cp -f "$f" "$runtime_dir/" done done log "Copied $copied FFmpeg DLLs + libmpv DLL to runtime dir" (( copied > 0 )) || log "WARNING: no FFmpeg DLLs found in $deps_dir/bin" # libplacebo is always a shared DLL - use our source-built version (no Vulkan deps) if [[ "$dynamic_placebo" == "1" ]]; then local placebo_dll placebo_dll="$(find "$WORK_DIR/deps-placebo/bin" -name "libplacebo-*.dll" | head -1)" if [[ -n "$placebo_dll" ]]; then cp -f "$placebo_dll" "$runtime_dir/" cp -f "${placebo_dll%.dll}.pdb" "$runtime_dir/" 2>/dev/null || true log "Copied $(basename "$placebo_dll") to runtime dir (source-built, no Vulkan)" else log "WARNING: source-built libplacebo DLL not found - falling back to MSYS2" placebo_dll="$(find /mingw64/bin -maxdepth 1 -name "libplacebo-*.dll" | head -1)" [[ -n "$placebo_dll" ]] && cp -f "$placebo_dll" "$runtime_dir/" || log "WARNING: libplacebo DLL not found anywhere" fi # libplacebo is built with static deps - no extra DLLs needed log "libplacebo is self-contained (static deps) - no extra DLLs to copy" fi # ── SDK: tar.gz ─────────────────────────────────────────────────────────── ( cd "$sdk_dir" && tar czf "$OUT_DIR/libmpv-sdk-$tag.tar.gz" . ) # ── libmpv-2.dll standalone: 7z + sha256 ────────────────────────────────── # Separate small archive just for swapping libmpv-2.dll via installer. # If DEBUG_SYMBOLS=1, the matching .pdb ships in the same archive so a # symbol-swap test is a single download. local dll_only_dir="$BUILD_DIR/dll-only-$tag" rm -rf "$dll_only_dir" mkdir -p "$dll_only_dir" cp -f "$dll" "$dll_only_dir/libmpv-2.dll" if [[ "$DEBUG_SYMBOLS" == "1" && -f "$sdk_dir/lib/$(basename "$dll" .dll).pdb" ]]; then cp -f "$sdk_dir/lib/$(basename "$dll" .dll).pdb" "$dll_only_dir/libmpv-2.pdb" fi 7z a -mx=9 "$OUT_DIR/libmpv-dll-$tag.7z" "$dll_only_dir"/* > /dev/null sha256sum "$OUT_DIR/libmpv-dll-$tag.7z" | awk '{print $1}' > "$OUT_DIR/libmpv-dll-$tag.sha256" rm -rf "$dll_only_dir" # ── Runtime: tar.gz (not used by installer, for manual deployment) ───────── ( cd "$runtime_dir" && tar czf "$OUT_DIR/libmpv-runtime-$tag.tar.gz" . ) log "Done: $tag" log " SDK: $OUT_DIR/libmpv-sdk-$tag.tar.gz" log " DLL: $OUT_DIR/libmpv-dll-$tag.7z" log " Runtime: $OUT_DIR/libmpv-runtime-$tag.tar.gz" if [[ "$DEBUG_SYMBOLS" == "1" ]]; then log " PDBs included for libmpv-2.dll + FFmpeg DLLs (where cv2pdb succeeded)" fi } # --------------------------------------------------------------------------- # Main # --------------------------------------------------------------------------- log "FFmpeg 6 version : $FFMPEG6_VERSION (mpv < v0.40.0)" log "FFmpeg 7 version : $FFMPEG7_VERSION (mpv >= v0.40.0)" log "MPV tags : ${MPV_TAGS[*]}" log "DEBUG_SYMBOLS : $DEBUG_SYMBOLS" declare -A ff_versions_needed for tag in "${MPV_TAGS[@]}"; do ver="$(ffmpeg_version_for_tag "$tag")" ff_versions_needed["$ver"]=1 done build_libzvbi_if_needed build_libplacebo_if_needed for ver in "${!ff_versions_needed[@]}"; do build_ffmpeg_if_needed "$ver" done for tag in "${MPV_TAGS[@]}"; do log "===== Starting tag: $tag =====" build_libmpv_tag "$tag" log "===== Finished tag: $tag =====" done log "All builds complete. Output in: $OUT_DIR"