diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index ee2c85f9..7d432b6c 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -63,7 +63,7 @@ jobs: - name: Collect dependencies (macOS) if: runner.os == 'macOS' run: | - brew install python3 ffmpeg libass zimg imagemagick autoconf automake libtool pkg-config gcc ffmpeg llvm@12 meson xxhash vapoursynth fftw dylibbundler + brew install ffmpeg libass zimg imagemagick autoconf automake libtool ffmpeg llvm@19 meson xxhash vapoursynth fftw dylibbundler cd ci bash build-dependencies-macos.sh @@ -82,12 +82,14 @@ jobs: wget -q https://raw.githubusercontent.com/styler00dollar/VapourSynth-RIFE-ncnn-Vulkan/master/models/rife-v4.26_ensembleFalse/flownet.param cd ../../ - - name: Install LLVM and Clang - if: runner.os != 'Windows' && runner.os != 'Linux' # Skip on Windows and Linux - uses: KyleMayes/install-llvm-action@v2 - with: - env: true - version: 17 + - name: Collect dependencies (Linux) + if: runner.os == 'Linux' + run: | + sudo apt-get remove llvm* + sudo apt-get remove --purge -y 'python3-*' || true + sudo apt-get install -y libzimg-dev meson libxxhash-dev nasm libfftw3-dev llvm-16 + cd ci + bash build-dependencies-linux.sh - name: Setup vcpkg uses: lukka/run-vcpkg@v11 @@ -122,6 +124,36 @@ jobs: env: VCPKG_ROOT: ${{ github.workspace }}/vcpkg + - name: Copy installer requirements (Windows) + if: runner.os == 'Windows' + shell: bash + run: | + cp -r ci/out/* installer/dependencies/lib + + curl -L https://aka.ms/vs/17/release/vc_redist.x64.exe -o installer/redist/vc_redist.x64.exe + + cp bin/${{ matrix.build-type }}/*.exe installer/resources + + - name: Build installer (Windows) + if: runner.os == 'Windows' + uses: Minionguyjpro/Inno-Setup-Action@v1.2.2 + with: + path: installer/setup.iss + options: /O+ + + - name: Rename Installer (Windows) + if: runner.os == 'Windows' + run: | + mv installer/output/blur-installer.exe installer/output/blur-Windows-Installer-x64.exe + + - name: Create AppImage (Linux) + if: runner.os == 'Linux' + shell: bash + run: | + cd ci + bash build-appimage.sh "../bin/${{ matrix.build-type }}/blur" "../bin/${{ matrix.build-type }}/blur-cli" + cp Blur-*.AppImage Blur.AppImage + - name: Prepare Binaries id: prep shell: bash @@ -206,33 +238,12 @@ jobs: # Output for subsequent steps echo "build_dir=$build_dir" >> $GITHUB_OUTPUT + echo "cli_binary=$cli_binary" >> $GITHUB_OUTPUT + echo "gui_binary=$gui_binary" >> $GITHUB_OUTPUT echo "cli_path=$cli_path" >> $GITHUB_OUTPUT echo "gui_path=$gui_path" >> $GITHUB_OUTPUT echo "configuration=$configuration" >> $GITHUB_OUTPUT - - name: Copy installer requirements (Windows) - if: runner.os == 'Windows' - shell: bash - run: | - cp -r ci/out/* installer/dependencies/lib - - curl -L https://aka.ms/vs/17/release/vc_redist.x64.exe -o installer/redist/vc_redist.x64.exe - - cp ${{ steps.prep.outputs.build_dir }}/${{ steps.prep.outputs.gui_path }} installer/resources/blur.exe - cp ${{ steps.prep.outputs.build_dir }}/${{ steps.prep.outputs.cli_path }} installer/resources/blur-cli.exe - - - name: Build installer (Windows) - if: runner.os == 'Windows' - uses: Minionguyjpro/Inno-Setup-Action@v1.2.2 - with: - path: installer/setup.iss - options: /O+ - - - name: Rename Installer (Windows) - if: runner.os == 'Windows' - run: | - mv installer/output/blur-installer.exe installer/output/blur-Windows-Installer-x64.exe - - name: Upload Artifacts uses: actions/upload-artifact@v4 with: @@ -241,6 +252,7 @@ jobs: ${{ steps.prep.outputs.build_dir }}/${{ steps.prep.outputs.cli_path }} ${{ steps.prep.outputs.build_dir }}/${{ steps.prep.outputs.gui_path }} ${{ runner.os == 'Windows' && 'installer/output/blur-Windows-Installer-x64.exe' || '' }} + ${{ runner.os == 'Linux' && 'ci/Blur.AppImage' || '' }} - name: Release uses: softprops/action-gh-release@v2 @@ -250,3 +262,4 @@ jobs: ${{ steps.prep.outputs.build_dir }}/${{ steps.prep.outputs.cli_path }} ${{ steps.prep.outputs.build_dir }}/${{ steps.prep.outputs.gui_path }} ${{ runner.os == 'Windows' && 'installer/output/blur-Windows-Installer-x64.exe' || '' }} + ${{ runner.os == 'Linux' && 'ci/Blur.AppImage' || '' }} diff --git a/ci/.gitignore b/ci/.gitignore index 93d84e89..a1d280c0 100644 --- a/ci/.gitignore +++ b/ci/.gitignore @@ -1,3 +1,5 @@ build download out +appimage +*.AppImage diff --git a/ci/build-appimage.sh b/ci/build-appimage.sh new file mode 100755 index 00000000..f9ce4b4f --- /dev/null +++ b/ci/build-appimage.sh @@ -0,0 +1,67 @@ +#!/bin/bash +set -e + +rm -rf appimage/ + +# sudo cp -r out/python/bin/* /usr/local/bin +# sudo cp -r out/python/include/* /usr/local/include +# sudo cp -r out/python/lib/* /usr/local/lib + +# create appdir structure +mkdir -p appimage/usr/bin/ +mkdir -p appimage/usr/bin/ffmpeg/ +mkdir -p appimage/usr/bin/vapoursynth/ +mkdir -p appimage/usr/lib/ +mkdir -p appimage/usr/share/applications/ +mkdir -p appimage/usr/share/icons/hicolor/256x256/apps/ + +# download linuxdeploy tool +rm -f linuxdeploy-x86_64.AppImage +wget -q https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage +chmod +x linuxdeploy-x86_64.AppImage + +# copy binaries from arguments +for arg in "$@"; do + cp "$arg" appimage/usr/bin/ +done + +# copy desktop file and icon +cp ../resources/blur.desktop appimage/usr/share/applications/blur.desktop +cp ../resources/blur.png appimage/usr/share/icons/hicolor/256x256/apps/blur.png + +# copy python bin & lib +cp out/python/bin/* appimage/usr/bin/ +cp -r out/python/lib/* appimage/usr/lib/ + +# copy shared libraries +cp /usr/local/lib/libvapoursynth* appimage/usr/lib/ +cp -r out/ffmpeg-shared/lib/* appimage/usr/lib/ + +# # this is also required for a vapoursynth plugin that i forget +# cp /usr/lib64/libfftw3* appimage/usr/lib + +# copy vapoursynth plugins +mkdir appimage/usr/bin/vapoursynth-plugins/ +cp out/vapoursynth-plugins/*.so appimage/usr/bin/vapoursynth-plugins/ + +# copy additional binaries +cp -r out/ffmpeg/* appimage/usr/bin/ffmpeg/ +cp -r out/vapoursynth/* appimage/usr/bin/vapoursynth/ + +# copy vapoursynth scripts +mkdir -p appimage/usr/bin/lib/ +old_dir=$PWD +cd ../src/vapoursynth/ +find . -name "*.py" -exec cp --parents {} ../../ci/appimage/usr/bin/lib/ \; +cd $old_dir + +# set executable permissions for all binaries +chmod +x appimage/usr/bin/* +chmod +x appimage/usr/bin/ffmpeg* +chmod +x appimage/usr/bin/vapoursynth* + +# build the appimage +# export NO_STRIP=true # (fedora) + +export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH +./linuxdeploy-x86_64.AppImage --appdir=appimage --output=appimage diff --git a/ci/build-dependencies-linux.sh b/ci/build-dependencies-linux.sh new file mode 100755 index 00000000..594472d1 --- /dev/null +++ b/ci/build-dependencies-linux.sh @@ -0,0 +1,176 @@ +#!/bin/bash +set -e + +out_dir=out + +echo "Building dependencies for Linux" + +# clean outputs every run +rm -rf $out_dir +mkdir -p $out_dir + +download_archive() { + local url="$1" + local dir_name="$2" + local out_path="$3" + local subfolder="$4" + + original_dir=$(pwd) + + mkdir -p download + cd download + + if [ -d "$dir_name" ]; then + echo "$dir_name already exists. Skipping download." + cd "$dir_name" + else + mkdir -p "$dir_name" && cd "$dir_name" + + echo "Downloading $dir_name" + + if [[ "$url" == *.zip ]]; then + wget -q "$url" -O "$dir_name.zip" + unzip "$dir_name.zip" + rm "$dir_name.zip" + elif [[ "$url" == *.tar.xz ]]; then + wget -q "$url" -O "$dir_name.tar.xz" + tar -xf "$dir_name.tar.xz" + rm "$dir_name.tar.xz" + else + echo "Unsupported archive format: $url" + cd "$original_dir" + return 1 + fi + fi + + dest_path="$original_dir/$out_dir/$out_path" + + echo "Copying files from $subfolder to $dest_path" + + cp -r "${subfolder:=.}" "$dest_path" + + cd "$original_dir" +} + +build() { + local repo="$1" + local pull_args="$2" + local name="$3" + local build_cmd="$4" + local lib_path="$5" + local out_path="$6" + + echo "--- Building $name ---" + + mkdir -p build + cd build + + if [ ! -d "$name" ]; then + echo "Cloning $name..." + # shellcheck disable=SC2086 + git clone $pull_args "$repo" "$name" + cd "$name" + else + echo "Updating $name..." + cd "$name" + git pull + fi + + eval "$build_cmd" + + # copy built stuff + dest_path="../../$out_dir/$out_path" + mkdir -p "$dest_path" + + if [[ -n "$lib_path" ]]; then + echo "Copying $name libraries to $dest_path" + find "$lib_path" -name "*.so" -exec cp {} "$dest_path" \; + else + echo "Skipping copy: lib_path is empty" + fi + + cd ../.. +} + +# downloads +## ffmpeg (shared) (for building bestsource) (libavutil in apt is outdated) +download_archive \ + "https://github.com/BtbN/FFmpeg-Builds/releases/download/latest/ffmpeg-master-latest-linux64-gpl-shared.tar.xz" \ + "ffmpeg-shared" \ + "ffmpeg-shared" \ + "ffmpeg-master-latest-linux64-gpl-shared" + +sudo cp -r "$out_dir/ffmpeg-shared/bin/"* /usr/local/bin/ +sudo cp -r "$out_dir/ffmpeg-shared/lib/"* /usr/local/lib/ +sudo cp -r "$out_dir/ffmpeg-shared/include/"* /usr/local/include/ + +mkdir -p $out_dir/ffmpeg +cp $out_dir/ffmpeg-shared/bin/ffmpeg $out_dir/ffmpeg + +## svpflow +download_archive \ + "https://web.archive.org/web/20190322064557/http://www.svp-team.com/files/gpl/svpflow-4.2.0.142.zip" \ + "svpflow" \ + "vapoursynth-plugins" \ + "svpflow-4.2.0.142/lib-linux" + +## python for vapoursynth +mkdir -p download/python +cd download/python + +if [ ! -d "python" ]; then + wget -q https://github.com/astral-sh/python-build-standalone/releases/download/20250317/cpython-3.12.9+20250317-x86_64-unknown-linux-gnu-install_only.tar.gz -O python.tar.gz + mkdir -p python + tar -xzf python.tar.gz -C python --strip-components 1 + rm python.tar.gz +fi + +# copy python to output directory +python_dest_path="../../$out_dir/python" +mkdir -p "$python_dest_path" +cp -R python/* "$python_dest_path" + +cd ../.. + +$out_dir/python/bin/pip install --upgrade pip +$out_dir/python/bin/pip install cython + +# builds +## vapoursynth + +PATH="$PWD/$out_dir/python/bin:$PATH" +PYTHON_PREFIX="$PWD/$out_dir/python" + +build "https://github.com/vapoursynth/vapoursynth.git" "" "vapoursynth" " +./autogen.sh +PYTHON3_LIBS=\"-L$PYTHON_PREFIX/lib/python3.12 -L$PYTHON_PREFIX/lib -lpython3.12\" \ + PYTHON3_CFLAGS=\"-I$PYTHON_PREFIX/include/python3.12\" \ + ./configure --with-python_prefix=\"$PYTHON_PREFIX\" --with-cython=\"$PYTHON_PREFIX/bin/cython\" +make +sudo make install +" "" "vapoursynth" + +### copy vspipe +cp build/vapoursynth/.libs/vspipe $out_dir/vapoursynth + +## bestsource +build "https://github.com/vapoursynth/bestsource.git" "--depth 1 --recurse-submodules --shallow-submodules --remote-submodules" "bestsource" " +meson setup build +ninja -C build +" "build" "vapoursynth-plugins" + +## mvtools +build "https://github.com/dubhater/vapoursynth-mvtools.git" "" "mvtools" " +meson setup build +ninja -C build +" "build" "vapoursynth-plugins" + +## akarin +rm -rf build/akarin +build "https://github.com/Jaded-Encoding-Thaumaturgy/akarin-vapoursynth-plugin.git" "" "akarin" " +git checkout 689cba74e7c71caf808b6feaaba0a32981c1956f +meson build +ninja -C build +" "build" "vapoursynth-plugins" + +echo "done" diff --git a/ci/build-dependencies-macos.sh b/ci/build-dependencies-macos.sh index 0bfc3ca0..b801901f 100755 --- a/ci/build-dependencies-macos.sh +++ b/ci/build-dependencies-macos.sh @@ -228,11 +228,14 @@ meson build ninja -C build " "build" "vapoursynth-plugins" -PATH="/opt/homebrew/opt/llvm@12/bin:$PATH" +PATH="/opt/homebrew/opt/llvm@19/bin:$PATH" + +ZSTD_PREFIX=$(brew --prefix zstd) ## akarin -build "https://github.com/AkarinVS/vapoursynth-plugin.git" "--depth 1 --single-branch" "akarin" " +build "https://github.com/Jaded-Encoding-Thaumaturgy/akarin-vapoursynth-plugin.git" "" "akarin" " meson build +sed -i.bak \"s|-lzstd|-L${ZSTD_PREFIX}/lib -lzstd|g\" build/build.ninja # fuck you ninja -C build " "build" "vapoursynth-plugins" diff --git a/resources/blur.desktop b/resources/blur.desktop new file mode 100644 index 00000000..3df17990 --- /dev/null +++ b/resources/blur.desktop @@ -0,0 +1,8 @@ +[Desktop Entry] +Name=Blur +Comment=Blur Application +Exec=blur +Icon=blur +Type=Application +Categories=Utility; +Terminal=false diff --git a/resources/blur.png b/resources/blur.png new file mode 100644 index 00000000..3f1376b8 Binary files /dev/null and b/resources/blur.png differ diff --git a/src/common/blur.cpp b/src/common/blur.cpp index f40d2dc3..555f10f8 100644 --- a/src/common/blur.cpp +++ b/src/common/blur.cpp @@ -27,8 +27,8 @@ Blur::InitialisationResponse Blur::initialise(bool _verbose, bool _using_preview used_installer = std::filesystem::exists(resources_path / "lib\\vapoursynth\\vspipe.exe") && std::filesystem::exists(resources_path / "lib\\ffmpeg\\ffmpeg.exe"); #elif defined(__linux__) - // todo - used_installer = false; + used_installer = std::filesystem::exists(resources_path / "vapoursynth/vspipe") && + std::filesystem::exists(resources_path / "ffmpeg/ffmpeg"); #elif defined(__APPLE__) used_installer = std::filesystem::exists(resources_path / "vapoursynth/vspipe") && std::filesystem::exists(resources_path / "ffmpeg/ffmpeg"); @@ -40,7 +40,8 @@ Blur::InitialisationResponse Blur::initialise(bool _verbose, bool _using_preview ffmpeg_path = (blur.resources_path / "lib\\ffmpeg\\ffmpeg.exe").wstring(); ffprobe_path = (blur.resources_path / "lib\\ffmpeg\\ffprobe.exe").wstring(); #elif defined(__linux__) - // todo + vspipe_path = (blur.resources_path / "vapoursynth/vspipe").wstring(); + ffmpeg_path = (blur.resources_path / "ffmpeg/ffmpeg").wstring(); #elif defined(__APPLE__) vspipe_path = (blur.resources_path / "vapoursynth/vspipe").wstring(); ffmpeg_path = (blur.resources_path / "ffmpeg/ffmpeg").wstring(); diff --git a/src/common/rendering.cpp b/src/common/rendering.cpp index bcf49224..ef39f1a2 100644 --- a/src/common/rendering.cpp +++ b/src/common/rendering.cpp @@ -173,6 +173,9 @@ RenderCommandsResult Render::build_render_commands() { #if defined(__APPLE__) L"-a", std::format(L"macos_bundled={}", blur.used_installer ? L"true" : L"false"), +#elif defined(__linux__) + L"-a", + std::format(L"linux_bundled={}", true ? L"true" : L"false"), #endif #if defined(_WIN32) L"-a", @@ -338,12 +341,16 @@ RenderResult Render::do_render(RenderCommands render_commands) { bp::environment env = boost::this_process::environment(); -#if defined(__APPLE__) if (blur.used_installer) { +#if defined(__APPLE__) env["PYTHONHOME"] = (blur.resources_path / "python").string(); env["PYTHONPATH"] = (blur.resources_path / "python/lib/python3.12/site-packages").string(); - } +#elif defined(__linux__) + env["LD_LIBRARY_PATH"] = (blur.resources_path / "../lib").string(); + env["PYTHONHOME"] = (blur.resources_path / "..").string(); + env["PYTHONPATH"] = (blur.resources_path / "../lib/python3.12/site-packages").string(); #endif + } // Launch vspipe process bp::child vspipe_process( @@ -366,6 +373,7 @@ RenderResult Render::do_render(RenderCommands render_commands) { bp::std_in < vspipe_stdout, bp::std_out.null(), // bp::std_err.null(), + env, io_context #ifdef _WIN32 , diff --git a/src/common/rendering_frame.cpp b/src/common/rendering_frame.cpp index 602087b2..f2c4f604 100644 --- a/src/common/rendering_frame.cpp +++ b/src/common/rendering_frame.cpp @@ -29,6 +29,13 @@ RenderCommandsResult FrameRender::build_render_commands( #if defined(__APPLE__) L"-a", std::format(L"macos_bundled={}", blur.used_installer ? L"true" : L"false"), +#elif defined(__linux__) + L"-a", + std::format(L"linux_bundled={}", true ? L"true" : L"false"), +#endif +#ifdef WIN32 + L"-a", + L"lsmash=true", #endif #if defined(_WIN32) L"-a", @@ -85,12 +92,16 @@ FrameRender::DoRenderResult FrameRender::do_render(RenderCommands render_command bp::environment env = boost::this_process::environment(); -#if defined(__APPLE__) if (blur.used_installer) { +#if defined(__APPLE__) env["PYTHONHOME"] = (blur.resources_path / "python").string(); env["PYTHONPATH"] = (blur.resources_path / "python/lib/python3.12/site-packages").string(); - } +#elif defined(__linux__) + env["LD_LIBRARY_PATH"] = (blur.resources_path / "../lib").string(); + env["PYTHONHOME"] = (blur.resources_path / "..").string(); + env["PYTHONPATH"] = (blur.resources_path / "../lib/python3.12/site-packages").string(); #endif + } // Declare as local variables first, then move or assign auto vspipe_process = bp::child( @@ -112,6 +123,7 @@ FrameRender::DoRenderResult FrameRender::do_render(RenderCommands render_command bp::std_in < vspipe_stdout, bp::std_out.null(), bp::std_err.null(), + env, io_context #ifdef _WIN32 , diff --git a/src/common/utils.cpp b/src/common/utils.cpp index 14aa15ed..98976998 100644 --- a/src/common/utils.cpp +++ b/src/common/utils.cpp @@ -265,6 +265,14 @@ std::filesystem::path u::get_settings_path() { u::VideoInfo u::get_video_info(const std::filesystem::path& path) { namespace bp = boost::process; + bp::environment env = boost::this_process::environment(); + + if (blur.used_installer) { +#ifdef __linux__ + env["LD_LIBRARY_PATH"] = (blur.resources_path / "../lib").string(); +#endif + } + bp::ipstream pipe_stream; bp::child c( blur.ffprobe_path.wstring(), @@ -356,6 +364,14 @@ std::vector u::get_hardware_encoding_devices() { else init_hw = true; + bp::environment env = boost::this_process::environment(); + + if (blur.used_installer) { +#ifdef __linux__ + env["LD_LIBRARY_PATH"] = (blur.resources_path / "../lib").string(); +#endif + } + // First check available hardware acceleration methods bp::ipstream pipe_stream; bp::child c( @@ -365,7 +381,8 @@ std::vector u::get_hardware_encoding_devices() { "-hide_banner", "-hwaccels", bp::std_out > pipe_stream, - bp::std_err > bp::null + bp::std_err > bp::null, + env #ifdef _WIN32 , bp::windows::create_no_window diff --git a/src/vapoursynth/blur.py b/src/vapoursynth/blur.py index b4dca04d..2c045670 100644 --- a/src/vapoursynth/blur.py +++ b/src/vapoursynth/blur.py @@ -12,10 +12,22 @@ "libbestsource.dylib", } - for dylib in plugin_dir.glob("*.dylib"): - if dylib.name not in ignored: - print("loading", dylib.name) - core.std.LoadPlugin(path=str(dylib)) + for plugin in plugin_dir.glob("*.dylib"): + if plugin.name not in ignored: + print("loading", plugin.name) + core.std.LoadPlugin(path=str(plugin)) + +if vars().get("linux_bundled") == "true": + # load plugins + plugin_dir = Path("../vapoursynth-plugins") + ignored = { + "libbestsource.so", + } + + for plugin in plugin_dir.glob("*.so"): + if plugin.name not in ignored: + print("loading", plugin.name) + core.std.LoadPlugin(path=str(plugin)) # add blur.py folder to path so it can reference scripts sys.path.insert(1, str(Path(__file__).parent))