diff --git a/.github/workflows/ci-meson.yml b/.github/workflows/ci-meson.yml new file mode 100644 index 0000000000..881aa3e722 --- /dev/null +++ b/.github/workflows/ci-meson.yml @@ -0,0 +1,73 @@ +name: Meson Build + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + build-meson: + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-latest + cc: gcc + cxx: g++ + - os: ubuntu-latest + cc: clang + cxx: clang++ + - os: macos-latest + cc: clang + cxx: clang++ + - os: windows-latest + cc: gcc + cxx: g++ + + name: "Test (${{ matrix.os }}, ${{ matrix.cc }})" + runs-on: ${{ matrix.os }} + env: + CC: ${{ matrix.cc }} + CXX: ${{ matrix.cxx }} + defaults: + run: + shell: ${{ matrix.os == 'windows-latest' && 'msys2 {0}' || 'bash' }} + + steps: + - name: Checkout code + uses: actions/checkout@v6 + + - name: Install deps (ubuntu) + if: matrix.os == 'ubuntu-latest' + run: | + sudo add-apt-repository ppa:ubuntuhandbook1/ffmpeg7 -y + sudo apt-get update + sudo apt-get install autoconf automake wget ffmpeg libavformat-dev libavcodec-dev libswscale-dev libavutil-dev libswresample-dev meson + + - name: Install deps (macOS) + if: matrix.os == 'macos-latest' + run: | + brew update + brew install autoconf automake libtool wget ffmpeg meson + + - name: Setup MSYS2 (MINGW64) + if: matrix.os == 'windows-latest' + uses: msys2/setup-msys2@v2 + with: + msystem: MINGW64 + install: >- + mingw-w64-x86_64-ca-certificates + mingw-w64-x86_64-ffmpeg + mingw-w64-x86_64-meson + mingw-w64-x86_64-toolchain + + - name: Configure with Meson + run: > + meson setup builddir -Dtest=enabled + + - name: Build + run: meson compile -C builddir --verbose + + - name: Run tests + run: meson test -C builddir --verbose diff --git a/.github/workflows/ci-on-release.yml b/.github/workflows/ci-on-release.yml new file mode 100644 index 0000000000..0a7ff6b96d --- /dev/null +++ b/.github/workflows/ci-on-release.yml @@ -0,0 +1,51 @@ +name: On Release + +on: + release: + types: [published] + +jobs: + call-build-win: + uses: ./.github/workflows/ci-windows.yml + + release-win: + name: Release Windows artifacts + runs-on: windows-latest + needs: call-build-win + permissions: + contents: write + + steps: + - name: Checkout code + uses: actions/checkout@v6 + + - name: Download wheel artifact + uses: actions/download-artifact@v7 + + - name: Set RELEASE_TAG environment variable + run: | + $release_tag = "ffms2-${{ github.event.release.tag_name }}-msvc" + echo "RELEASE_TAG=$release_tag" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append + + - name: Create package + run: | + mkdir $env:RELEASE_TAG + cd $env:RELEASE_TAG + Copy-Item -Path "..\ffms2_windows_x64\share\doc\ffms2" -Destination "doc" -Recurse + Copy-Item -Path "..\ffms2_windows_x64\include" -Destination "include" -Recurse + Copy-Item -Path "..\etc\*" -Destination "." + mkdir x86 + Copy-Item -Path "..\ffms2_windows_x86\bin\ffms2.dll" -Destination "x86" + Copy-Item -Path "..\ffms2_windows_x86\bin\ffmsindex.exe" -Destination "x86" + Copy-Item -Path "..\ffms2_windows_x86\lib\ffms2.lib" -Destination "x86" + mkdir x64 + Copy-Item -Path "..\ffms2_windows_x64\bin\ffms2.dll" -Destination "x64" + Copy-Item -Path "..\ffms2_windows_x64\bin\ffmsindex.exe" -Destination "x64" + Copy-Item -Path "..\ffms2_windows_x64\lib\ffms2.lib" -Destination "x64" + cd .. + 7z a "$env:RELEASE_TAG.7z" $env:RELEASE_TAG + + - name: Upload artifact to GitHub Release + env: + GITHUB_TOKEN: ${{ github.token }} + run: gh release upload '${{ github.ref_name }}' "$env:RELEASE_TAG.7z" diff --git a/.github/workflows/ci-windows.yml b/.github/workflows/ci-windows.yml new file mode 100644 index 0000000000..110afa516f --- /dev/null +++ b/.github/workflows/ci-windows.yml @@ -0,0 +1,73 @@ +name: Build on Windows + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + workflow_call: + +jobs: + build-win: + strategy: + fail-fast: false + matrix: + arch: [x86, x64] + + name: "Test Windows MSVC (${{ matrix.arch }})" + runs-on: windows-latest + + steps: + - name: Checkout code + uses: actions/checkout@v6 + + - name: Install Meson + run: pip install meson + + - name: Install ffmpeg + run: vcpkg install ffmpeg[avcodec,avdevice,avfilter,avformat,swresample,swscale,zlib,bzip2,core,dav1d,gpl,version3,lzma,openssl,xml2]:${{ matrix.arch }}-windows-static + + - name: Set VCPKG_PATH environment variable + run: | + $vcpkg_path = Split-Path (Get-Command vcpkg).Source -Parent + echo "VCPKG_PATH=$vcpkg_path" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append + + - name: Install pkgconf + uses: msys2/setup-msys2@v2 + id: msys2 + with: + msystem: MINGW64 + install: mingw-w64-x86_64-pkgconf + + - name: Setup MSVC Developer Command Prompt + uses: ilammy/msvc-dev-cmd@v1 + with: + arch: ${{ matrix.arch }} + + - name: Configure with Meson + env: + PKG_CONFIG: "${{ steps.msys2.outputs.msys2-location }}\\mingw64\\bin\\pkg-config.EXE" + run: > + meson setup builddir + --buildtype=release + --vsenv + -Dtest=enabled + -Davisynth=enabled + --pkg-config-path="$env:VCPKG_PATH\installed\${{ matrix.arch }}-windows-static\lib\pkgconfig" + -Db_vscrt=mt + # Use "-Db_vscrt=mt" to avoid this warning: LINK : warning LNK4098: defaultlib 'LIBCMT' conflicts with use of other libs; use /NODEFAULTLIB:library + + - name: Build + run: meson compile -C builddir --verbose + + - name: Run tests + run: meson test -C builddir --verbose + + - name: Install + run: meson install -C builddir --destdir install + + - name: Upload Build + uses: actions/upload-artifact@v6 + with: + name: ffms2_windows_${{ matrix.arch }} + path: builddir/install diff --git a/.gitignore b/.gitignore index f344dc0309..9e3d080b4e 100644 --- a/.gitignore +++ b/.gitignore @@ -92,3 +92,7 @@ m4/lt~obsolete.m4 # Generated Makefile Makefile + +# Meson +/subprojects/* +!/subprojects/*.wrap diff --git a/doc/ffms2-api.md b/doc/ffms2-api.md index ace92aa5c6..476659ee3d 100644 --- a/doc/ffms2-api.md +++ b/doc/ffms2-api.md @@ -21,17 +21,17 @@ FFMS2 has the following dependencies: - Further recommended configuration options: `--disable-debug --disable-muxers --disable-encoders --disable-filters --disable-hwaccels --disable-network --disable-devices - **[zlib][zlib]** -Compiling the library on non-Windows is trivial; the usual `./configure && make && make install` will suffice if FFmpeg and zlib are installed to the default locations. +Compiling the library is trivial; the usual `meson setup builddir && meson install -C builddir` will suffice if FFmpeg and zlib are installed to the default locations. ### Windows-specific compilation notes You have several options on how to build FFMS2 on Windows. -You can build both FFmpeg and FFMS2 with MinGW-w64, FFmpeg with clang-cl and FFMS2 with VC++ (shared only), or both with VC++. -The standard Avisynth 2.5 plugin requires building FFMS2 with VC++, while the Avisynth C plugin (which supports Avisynth 2.6) requires building with MinGW (and using the `c_plugin`branch). VapouSynth works as-is with MinGW-w64. +You can build both FFmpeg and FFMS2 with MinGW-w64, FFmpeg with clang-cl and FFMS2 with VC++ or both with VC++. These days building everything with MinGW works without doing anything unusual. -You'll have to manually add the location which you installed the headers and libraries to to VC++'s search paths (and if you're building both 32-bit and 64-bit, be sure to add the correct ones). +If you wanna use clang-cl or VC++, you will need to install pkgconf, so meson can found where you installed ffmpeg. +The easiest way is to download the latest pkgconf `.msi` from their [CI](https://github.com/pkgconf/pkgconf/actions?query=branch:master). [ffmpeg]: http://www.ffmpeg.org [zlib]: http://www.zlib.net diff --git a/doc/ffms2-changelog.md b/doc/ffms2-changelog.md index e340995377..00c5e88aac 100644 --- a/doc/ffms2-changelog.md +++ b/doc/ffms2-changelog.md @@ -3,6 +3,7 @@ - FFmpeg 7.1 is now the minimum requirement. - Added layered decoding support, for e.g. spatial MV-HEVC. - Added LastEndPTS to FFMS_VideoProperties. This field is the equivalent of LastEndTime, but it is expressed in the video timebase. + - Added meson build system. It will eventually replace autotools and MSBuild. - 5.0 - Fixed all issues with FFmpeg 6.1 which is now the minimum requirement diff --git a/meson.build b/meson.build new file mode 100644 index 0000000000..952a93e8b9 --- /dev/null +++ b/meson.build @@ -0,0 +1,170 @@ +project( + 'ffms2', + 'cpp', + default_options : [ + 'buildtype=release', + 'warning_level=2', + 'b_ndebug=if-release', + 'cpp_std=c++11' + ], + license : 'MIT', + license_files : 'COPYING', + meson_version : '>=1.3.0', + version : '5.1.1' +) + +cpp_compiler = meson.get_compiler('cpp') + +global_cpp_private_args = [ + '-DFFMS_EXPORTS', + '-D__STDC_CONSTANT_MACROS', +] + +if get_option('buildtype') == 'debug' and host_machine.system() == 'windows' + global_cpp_private_args += ['-DFFMS_WIN_DEBUG'] +endif + +ffms2_cpp_static_args = [ + '-DFFMS_STATIC', +] + +ffms2_link_args = [] +if cpp_compiler.has_link_argument('-Wl,-Bsymbolic') + # The -Wl,-Bsymbolic flag is required only when building ffms2 as a shared library against a static build of FFmpeg. + # Unfortunately, there is no reliable way to detect whether FFmpeg is static at configure time (especially when using *-uninstalled.pc files). + # To avoid unexpected link errors, always use this linker flag. + # For details, see FFmpeg's advanced linking notes:https://ffmpeg.org/platform.html#Advanced-linking-configuration + ffms2_link_args += ['-Wl,-Bsymbolic'] +endif + +ffmsindex_link_args = [] +if host_machine.system() == 'windows' and cpp_compiler.has_argument('-municode') + ffmsindex_link_args += ['-municode'] +endif + +libavutil_dep = dependency('libavutil', version : '>=59.39.0') + +ffms2_deps = [ + dependency('libavformat', version : '>=61.7.0'), + dependency('libavcodec', version : '>=61.19.0'), + dependency('libswscale', version : '>=8.3.0'), + libavutil_dep, + dependency('libswresample', version : '>=5.3.0'), + dependency('zlib') +] + +ffmsindex_deps = [ + libavutil_dep +] + +ffms2_src = [ + 'src/core/audiosource.cpp', + 'src/core/audiosource.h', + 'src/core/ffms.cpp', + 'src/core/filehandle.cpp', + 'src/core/filehandle.h', + 'src/core/indexing.cpp', + 'src/core/indexing.h', + 'src/core/track.cpp', + 'src/core/track.h', + 'src/core/utils.cpp', + 'src/core/utils.h', + 'src/core/videosource.cpp', + 'src/core/videosource.h', + 'src/core/videoutils.cpp', + 'src/core/videoutils.h', + 'src/core/zipfile.cpp', + 'src/core/zipfile.h', + 'src/vapoursynth/VapourSynth4.h', + 'src/vapoursynth/VSHelper4.h', + 'src/vapoursynth/vapoursource4.cpp', + 'src/vapoursynth/vapoursource4.h', + 'src/vapoursynth/vapoursynth4.cpp', +] + +if get_option('avisynth').enabled() + avisynth_dep = dependency('avisynth', required : false, version : '>=3.7.3') + + if avisynth_dep.found() + avisynth_dep = avisynth_dep.partial_dependency(compile_args: true, includes: true) + else + cmake = import('cmake') + opt_var = cmake.subproject_options() + opt_var.add_cmake_defines({'HEADERS_ONLY' : true}) + avisynth_subproject = cmake.subproject('AviSynth+', options : opt_var) + avisynth_dep = avisynth_subproject.dependency('AviSynth-Headers') + endif + + ffms2_deps += avisynth_dep + + ffms2_src += [ + 'src/avisynth/avssources.cpp', + 'src/avisynth/avssources.h', + 'src/avisynth/avisynth.cpp', + ] +endif + +ffmsindex_src = [ + 'src/index/ffmsindex.cpp', + 'src/index/vsutf16.h' +] + +includes = include_directories('include') + +ffms2_lib = library( + 'ffms2', + ffms2_src, + cpp_args : global_cpp_private_args, + cpp_static_args : ffms2_cpp_static_args, + dependencies : ffms2_deps, + gnu_symbol_visibility : 'hidden', + include_directories : includes, + install : true, + link_args : ffms2_link_args, +) + +executable( + 'ffmsindex', + ffmsindex_src, + cpp_args : global_cpp_private_args, + dependencies : ffmsindex_deps, + gnu_symbol_visibility : 'hidden', + include_directories : includes, + install : true, + link_args : ffmsindex_link_args, + link_with : ffms2_lib, +) + +static_cflags = [] +if get_option('default_library') == 'static' + static_cflags += '-DFFMS_STATIC' +endif + +# TODO - When meson implement a way to specify element in the field "Cflags.private", we will need to add the flag "-DFFMS_STATIC". +# See: https://github.com/mesonbuild/meson/issues/14749 +pkg_config = import('pkgconfig') +pkg_config.generate( + ffms2_lib, + description : 'The Fabulous FM Library 2', + extra_cflags : static_cflags, +) + +# TODO - When meson implement a way to specify a static compile args, we will need to add the flag "-DFFMS_STATIC". +# See: https://github.com/mesonbuild/meson/issues/5723 +ffms2_dep = declare_dependency( + link_with : ffms2_lib, + include_directories : includes, + compile_args : static_cflags, +) +meson.override_dependency('ffms2', ffms2_dep) + +install_data( + ['doc/ffms2-api.md', 'doc/ffms2-avisynth.md', 'doc/ffms2-changelog.md', 'doc/ffms2-vapoursynth.md'], + install_dir : join_paths(get_option('datadir'), 'doc', 'ffms2') +) + +install_headers(['include/ffms.h', 'include/ffmscompat.h']) + +if get_option('test').enabled() + subdir('test') +endif diff --git a/meson.options b/meson.options new file mode 100644 index 0000000000..349ef24343 --- /dev/null +++ b/meson.options @@ -0,0 +1,2 @@ +option('avisynth', type: 'feature', value : 'disabled', description: 'Enable avisynth') +option('test', type: 'feature', value : 'disabled', description: 'Enable test') diff --git a/subprojects/AviSynth+.wrap b/subprojects/AviSynth+.wrap new file mode 100644 index 0000000000..cb1026e2ec --- /dev/null +++ b/subprojects/AviSynth+.wrap @@ -0,0 +1,4 @@ +[wrap-git] +url = https://github.com/AviSynth/AviSynthPlus +revision = v3.7.5 +depth = 1 diff --git a/subprojects/gtest.wrap b/subprojects/gtest.wrap new file mode 100644 index 0000000000..6d463a7def --- /dev/null +++ b/subprojects/gtest.wrap @@ -0,0 +1,15 @@ +[wrap-file] +directory = googletest-release-1.12.1 +source_url = https://github.com/google/googletest/archive/release-1.12.1.tar.gz +source_filename = gtest-1.12.1.tar.gz +source_hash = 81964fe578e9bd7c94dfdb09c8e4d6e6759e19967e397dbea48d1c10e45d0df2 +patch_filename = gtest_1.12.1-1_patch.zip +patch_url = https://wrapdb.mesonbuild.com/v2/gtest_1.12.1-1/get_patch +patch_hash = 75143f11e174952bc768699fde3176511fe8e33b25dc6f6347d89e41648e99cf +wrapdb_version = 1.12.1-1 + +[provide] +gtest = gtest_dep +gtest_main = gtest_main_dep +gmock = gmock_dep +gmock_main = gmock_main_dep diff --git a/test/download_samples.py b/test/download_samples.py new file mode 100644 index 0000000000..717860c2d4 --- /dev/null +++ b/test/download_samples.py @@ -0,0 +1,63 @@ +from argparse import ArgumentParser +from pathlib import Path +from urllib.parse import urljoin +from urllib.request import urlretrieve + + +def download(url: str, file: Path) -> None: + if file.is_file(): + print(f"[skip] {file.name}") + return + + print(f"[download] {url}") + urlretrieve(url, file) + + +def main() -> None: + parser = ArgumentParser( + description="Sample downloader." + ) + parser.add_argument( + "sample_path", + type=Path, + help=""" + Path where to save the samples. + """, + ) + + args = parser.parse_args() + + sample_path: Path = args.sample_path + + SAMPLES_URL = "https://storage.googleapis.com/ffms2tests/" + SAMPLES_FILE = [ + "test.mp4", + "hdr10tags-both.mkv", + "hdr10tags-container.mkv", + "hdr10tags-stream.mp4", + "qrvideo_hflip_90.mov", + "qrvideo_hflip_270.mov", + "qrvideo_vflip.mov", + "vp9_audfirst.webm", + "qrvideo_24fps_1elist_1ctts.mov", + "qrvideo_24fps_1elist_ends_last_bframe.mov", + "qrvideo_24fps_1elist_noctts.mov", + "qrvideo_24fps_2elist_elist1_dur_zero.mov", + "qrvideo_24fps_2elist_elist1_ends_bframe.mov", + "qrvideo_24fps_2s_3elist.mov", + "qrvideo_24fps_3elist_1ctts.mov", + "qrvideo_24fps_elist_starts_ctts_2ndsample.mov", + "qrvideo_stream_shorter_than_movie.mov", + ] + + if not sample_path.is_dir(): + sample_path.mkdir(parents=True) + + for sample in SAMPLES_FILE: + url = urljoin(SAMPLES_URL, sample) + file = sample_path.joinpath(sample) + download(url, file) + + +if __name__ == "__main__": + main() diff --git a/test/meson.build b/test/meson.build new file mode 100644 index 0000000000..a4d3b1397a --- /dev/null +++ b/test/meson.build @@ -0,0 +1,46 @@ +python = import('python').find_installation() +sample_dir = join_paths(meson.current_source_dir(), 'samples') +run_command( + python, join_paths(meson.current_source_dir(), 'download_samples.py'), sample_dir, + check : true +) + +gtest_dep = dependency('gtest') + +hdr = executable( + 'hdr', + files('hdr.cpp'), + cpp_args : ['-DSAMPLES_DIR=' + sample_dir + ''], + dependencies : [ + gtest_dep, + ffms2_dep, + libavutil_dep, + ], +) +test('hdr', hdr) + + +indexer = executable( + 'indexer', + files('indexer.cpp', 'tests.cpp'), + cpp_args : ['-DSAMPLES_DIR=' + sample_dir + ''], + dependencies : [ + gtest_dep, + ffms2_dep, + libavutil_dep, + ], +) +test('indexer', indexer, timeout : 120) + + +display_matrix = executable( + 'display_matrix', + files('display_matrix.cpp'), + cpp_args : ['-DSAMPLES_DIR=' + sample_dir + ''], + dependencies : [ + gtest_dep, + ffms2_dep, + libavutil_dep, + ], +) +test('display_matrix', display_matrix)