From f9bc968de76a4ad673129862f795a7377253352c Mon Sep 17 00:00:00 2001 From: youge325 Date: Fri, 10 Apr 2026 15:35:17 +0800 Subject: [PATCH 01/14] Publish ARM wheels to GitHub --- .github/workflows/paddle_arm_wheel.yaml | 219 ++++++++++++++++++++++++ 1 file changed, 219 insertions(+) create mode 100644 .github/workflows/paddle_arm_wheel.yaml diff --git a/.github/workflows/paddle_arm_wheel.yaml b/.github/workflows/paddle_arm_wheel.yaml new file mode 100644 index 000000000..a9f3f0805 --- /dev/null +++ b/.github/workflows/paddle_arm_wheel.yaml @@ -0,0 +1,219 @@ +name: Build ARM Wheels for Paddle + +on: + push: + branches: [paddle] + tags: ["v*"] + pull_request: + merge_group: + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref_name }}-${{ github.ref_type == 'branch' && github.sha }}-${{ github.event_name == 'workflow_dispatch' }} + cancel-in-progress: true + +permissions: + id-token: write + contents: write + +defaults: + run: + shell: bash -l -eo pipefail {0} + +jobs: + build-paddlecodec-wheel-arm: + # If your repo does not have GitHub-hosted ARM runners enabled, + # replace this with self-hosted labels, e.g. [self-hosted, Linux, ARM64]. + runs-on: ubuntu-24.04-arm + container: + image: quay.io/pypa/manylinux_2_28_aarch64 + strategy: + fail-fast: false + matrix: + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] + permissions: + id-token: write + contents: read + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Setup conda environment + uses: conda-incubator/setup-miniconda@v3 + with: + auto-update-conda: true + miniforge-version: latest + activate-environment: build + python-version: ${{ matrix.python-version }} + + - name: Install build dependencies + run: | + python -m pip install --upgrade pip + pip install build wheel setuptools + + - name: Install PaddlePaddle nightly + run: | + pip install --pre paddlepaddle -i https://www.paddlepaddle.org.cn/packages/nightly/cpu/ + + - name: Run pre-build script + run: | + bash packaging/pre_build_script.sh + + - name: Build wheel + run: | + # Use pre-built FFmpeg from PyTorch S3 + export BUILD_AGAINST_ALL_FFMPEG_FROM_S3=1 + export TORCHCODEC_CMAKE_BUILD_DIR=$(pwd)/build_cmake + python -m build --wheel -vvv --no-isolation + + - name: Repair wheel + run: | + pip install auditwheel + + # 1. Extract internal libraries from the wheel to a temporary directory + # This allows auditwheel to find them when checking dependencies + mkdir -p temp_libs + unzip -j dist/*.whl "torchcodec/*.so" -d temp_libs || true + + # 2. Prepare LD_LIBRARY_PATH + # FFmpeg libraries + FFMPEG_LIB_PATHS=$(find $(pwd)/build_cmake/_deps -type d -name "lib" | tr '\n' ':') + # PaddlePaddle libraries + PADDLE_PATH=$(python -c "import paddle; print(paddle.__path__[0])") + PADDLE_LIB_PATHS="$PADDLE_PATH/base:$PADDLE_PATH/libs" + # Wheel internal libraries + INTERNAL_LIB_PATH=$(pwd)/temp_libs + + export LD_LIBRARY_PATH=${FFMPEG_LIB_PATHS}${PADDLE_LIB_PATHS}:${INTERNAL_LIB_PATH}:${LD_LIBRARY_PATH} + + # 3. Repair wheel with auditwheel + # We exclude all external libraries because we want to rely on system libraries (like FFmpeg) + # or libraries provided by other packages (like PaddlePaddle). + # auditwheel 6.1.0+ supports wildcards in --exclude. + auditwheel repair dist/*.whl --plat manylinux_2_28_aarch64 -w wheelhouse/ --exclude "*" + + # Cleanup + rm -rf temp_libs + rm dist/*.whl + mv wheelhouse/*.whl dist/ + rmdir wheelhouse + + - name: Upload wheel artifact + uses: actions/upload-artifact@v5 + with: + name: paddlecodec-wheel-linux-arm64-py${{ matrix.python-version }} + path: dist/*.whl + + - name: Run post-build script + run: | + bash packaging/post_build_script.sh + + - name: List wheel contents + run: | + wheel_path=$(find dist -type f -name "*.whl") + echo "Wheel path: $wheel_path" + unzip -l $wheel_path + + test-paddlecodec-wheel-arm: + needs: build-paddlecodec-wheel-arm + runs-on: ubuntu-24.04-arm + strategy: + fail-fast: false + matrix: + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] + # FFmpeg 8.0 depends on libopenvino.so.2520, PaddlePaddle CPU depends on libopenvino.so.2500 + # There has some conflict causing test failures, but it works with PaddlePaddle GPU. + # We skip FFmpeg 8.0 tests for PaddlePaddle CPU builds for now. + ffmpeg-version: ["4.4.2", "5.1.2", "6.1.1", "7.0.1"] + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Download wheel artifact + uses: actions/download-artifact@v4 + with: + name: paddlecodec-wheel-linux-arm64-py${{ matrix.python-version }} + path: dist/ + + - name: Install FFmpeg via conda + uses: conda-incubator/setup-miniconda@v3 + with: + auto-update-conda: true + miniforge-version: latest + activate-environment: test + python-version: ${{ matrix.python-version }} + + - name: Install FFmpeg from conda-forge + run: | + conda install "ffmpeg=${{ matrix.ffmpeg-version }}" -c conda-forge -y + ffmpeg -version + + - name: Install PaddlePaddle nightly in conda env + run: | + pip install --pre paddlepaddle -i https://www.paddlepaddle.org.cn/packages/nightly/cpu/ + + - name: Install paddlecodec from wheel + run: | + wheel_path=$(find dist -type f -name "*.whl") + echo "Installing $wheel_path" + pip install $wheel_path -vvv + + - name: Install test dependencies + run: | + pip install numpy pytest pillow + + - name: Delete src folder + run: | + # Delete src/ to ensure we're testing the installed wheel, not source code + rm -rf src/ + ls -la + + - name: Run tests + run: | + pytest --override-ini="addopts=-v" -s test_paddle + + publish-pypi-arm: + runs-on: ubuntu-latest + name: Publish ARM Wheels to PyPI + if: "startsWith(github.ref, 'refs/tags/')" + needs: + - test-paddlecodec-wheel-arm + permissions: + id-token: write + + steps: + - name: Retrieve ARM release distributions + uses: actions/download-artifact@v6 + with: + pattern: paddlecodec-wheel-linux-arm64-* + path: dist/ + merge-multiple: true + + - name: Publish ARM release distributions to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + + publish-release-arm: + runs-on: ubuntu-latest + name: Publish ARM Wheels to GitHub + if: "startsWith(github.ref, 'refs/tags/')" + needs: + - test-paddlecodec-wheel-arm + permissions: + contents: write + steps: + - uses: actions/download-artifact@v6 + with: + pattern: paddlecodec-wheel-linux-arm64-* + path: dist/ + merge-multiple: true + - name: Publish ARM wheels to GitHub + uses: softprops/action-gh-release@v2 + with: + draft: true + files: dist/* + tag_name: ${{ github.ref_name }} \ No newline at end of file From 17cdfffa354cbd2743f0d8cbf1fec671e6013e5c Mon Sep 17 00:00:00 2001 From: youge325 Date: Fri, 10 Apr 2026 16:18:21 +0800 Subject: [PATCH 02/14] fix --- .github/workflows/paddle_arm_wheel.yaml | 29 ++++++++++++++++--------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/.github/workflows/paddle_arm_wheel.yaml b/.github/workflows/paddle_arm_wheel.yaml index a9f3f0805..3779c6511 100644 --- a/.github/workflows/paddle_arm_wheel.yaml +++ b/.github/workflows/paddle_arm_wheel.yaml @@ -20,6 +20,9 @@ defaults: run: shell: bash -l -eo pipefail {0} +env: + ARM_BUILD_FFMPEG_VERSION: "7.0.1" + jobs: build-paddlecodec-wheel-arm: # If your repo does not have GitHub-hosted ARM runners enabled, @@ -59,10 +62,17 @@ jobs: run: | bash packaging/pre_build_script.sh + - name: Install build-time FFmpeg + run: | + conda install "ffmpeg=${{ env.ARM_BUILD_FFMPEG_VERSION }}" pkg-config -c conda-forge -y + ffmpeg -version + pkg-config --modversion libavcodec + - name: Build wheel run: | - # Use pre-built FFmpeg from PyTorch S3 - export BUILD_AGAINST_ALL_FFMPEG_FROM_S3=1 + # Linux ARM does not have pre-built multi-version FFmpeg tarballs in the + # upstream S3 bucket, so build against the native conda FFmpeg install. + export I_CONFIRM_THIS_IS_NOT_A_LICENSE_VIOLATION=1 export TORCHCODEC_CMAKE_BUILD_DIR=$(pwd)/build_cmake python -m build --wheel -vvv --no-isolation @@ -76,15 +86,15 @@ jobs: unzip -j dist/*.whl "torchcodec/*.so" -d temp_libs || true # 2. Prepare LD_LIBRARY_PATH - # FFmpeg libraries - FFMPEG_LIB_PATHS=$(find $(pwd)/build_cmake/_deps -type d -name "lib" | tr '\n' ':') + # FFmpeg libraries from the active conda environment + FFMPEG_LIB_PATHS="${CONDA_PREFIX}/lib" # PaddlePaddle libraries PADDLE_PATH=$(python -c "import paddle; print(paddle.__path__[0])") PADDLE_LIB_PATHS="$PADDLE_PATH/base:$PADDLE_PATH/libs" # Wheel internal libraries INTERNAL_LIB_PATH=$(pwd)/temp_libs - export LD_LIBRARY_PATH=${FFMPEG_LIB_PATHS}${PADDLE_LIB_PATHS}:${INTERNAL_LIB_PATH}:${LD_LIBRARY_PATH} + export LD_LIBRARY_PATH=${FFMPEG_LIB_PATHS}:${PADDLE_LIB_PATHS}:${INTERNAL_LIB_PATH}:${LD_LIBRARY_PATH} # 3. Repair wheel with auditwheel # We exclude all external libraries because we want to rely on system libraries (like FFmpeg) @@ -121,10 +131,9 @@ jobs: fail-fast: false matrix: python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] - # FFmpeg 8.0 depends on libopenvino.so.2520, PaddlePaddle CPU depends on libopenvino.so.2500 - # There has some conflict causing test failures, but it works with PaddlePaddle GPU. - # We skip FFmpeg 8.0 tests for PaddlePaddle CPU builds for now. - ffmpeg-version: ["4.4.2", "5.1.2", "6.1.1", "7.0.1"] + # ARM wheels currently build against a single native FFmpeg major because + # the pre-built multi-version Linux FFmpeg bundle is only published for x86_64. + ffmpeg-version: ["7.0.1"] steps: - name: Checkout repository uses: actions/checkout@v6 @@ -216,4 +225,4 @@ jobs: with: draft: true files: dist/* - tag_name: ${{ github.ref_name }} \ No newline at end of file + tag_name: ${{ github.ref_name }} From 27b73c4337bf02eeaa696c7f371900f2f68c551a Mon Sep 17 00:00:00 2001 From: youge325 Date: Fri, 10 Apr 2026 17:30:57 +0800 Subject: [PATCH 03/14] try to use pytorch image --- .github/workflows/paddle_arm_wheel.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/paddle_arm_wheel.yaml b/.github/workflows/paddle_arm_wheel.yaml index 3779c6511..b22dd181d 100644 --- a/.github/workflows/paddle_arm_wheel.yaml +++ b/.github/workflows/paddle_arm_wheel.yaml @@ -29,7 +29,7 @@ jobs: # replace this with self-hosted labels, e.g. [self-hosted, Linux, ARM64]. runs-on: ubuntu-24.04-arm container: - image: quay.io/pypa/manylinux_2_28_aarch64 + image: pytorch/manylinux2_28_aarch64-builder:cpu-aarch64 strategy: fail-fast: false matrix: From c9cbfb350842c12f06e482716e7f63cb13fffeac Mon Sep 17 00:00:00 2001 From: youge325 Date: Fri, 10 Apr 2026 20:11:15 +0800 Subject: [PATCH 04/14] try to fix network problem --- .github/workflows/paddle_arm_wheel.yaml | 3 ++ test_paddle/test_video_decode.py | 56 ++++++++++++++++++++----- 2 files changed, 49 insertions(+), 10 deletions(-) diff --git a/.github/workflows/paddle_arm_wheel.yaml b/.github/workflows/paddle_arm_wheel.yaml index b22dd181d..dcd29157b 100644 --- a/.github/workflows/paddle_arm_wheel.yaml +++ b/.github/workflows/paddle_arm_wheel.yaml @@ -127,6 +127,9 @@ jobs: test-paddlecodec-wheel-arm: needs: build-paddlecodec-wheel-arm runs-on: ubuntu-24.04-arm + env: + PADDLECODEC_TEST_VIDEO: test/resources/testsrc2.mp4 + PADDLECODEC_TEST_NUM_FRAMES: "8" strategy: fail-fast: false matrix: diff --git a/test_paddle/test_video_decode.py b/test_paddle/test_video_decode.py index 4e41cb19e..62e8ff593 100644 --- a/test_paddle/test_video_decode.py +++ b/test_paddle/test_video_decode.py @@ -10,6 +10,8 @@ import httpx import numpy as np +DEFAULT_TEST_VIDEO = "https://paddlenlp.bj.bcebos.com/datasets/paddlemix/demo_video/example_video.mp4" + @dataclass class VideoMetadata(Mapping): @@ -168,14 +170,48 @@ def sample_indices_fn_func(metadata, **fn_kwargs): return video, metadata +def _get_optional_int_env(name: str) -> Optional[int]: + value = os.getenv(name) + return int(value) if value else None + + +def _assert_video_decode_smoke( + video, + metadata: VideoMetadata, + expected_num_frames: Optional[int] = None, +): + assert video.ndim == 4 + assert len(metadata.frames_indices) == video.shape[0] + if expected_num_frames is not None: + assert video.shape[0] == expected_num_frames + assert metadata.total_num_frames >= video.shape[0] >= 1 + assert metadata.fps is not None and metadata.fps > 0 + assert metadata.width is not None and metadata.width > 0 + assert metadata.height is not None and metadata.height > 0 + assert metadata.duration is not None and metadata.duration > 0 + assert tuple(video.shape[-2:]) == (metadata.height, metadata.width) + assert video.shape[-3] == 3 + assert video.to(paddle.int64).sum().item() > 0 + + def test_video_decode(): - url = "https://paddlenlp.bj.bcebos.com/datasets/paddlemix/demo_video/example_video.mp4" - video, metadata = load_video(url, backend="torchcodec") - assert video.to(paddle.int64).sum().item() == 247759890390 - assert metadata.total_num_frames == 263 - assert metadata.fps == pytest.approx(29.99418249715141) - assert metadata.width == 1920 - assert metadata.height == 1080 - assert metadata.duration == pytest.approx(8.768367) - for i, idx in enumerate(metadata.frames_indices): - assert idx == i + video_path = os.getenv("PADDLECODEC_TEST_VIDEO", DEFAULT_TEST_VIDEO) + num_frames = _get_optional_int_env("PADDLECODEC_TEST_NUM_FRAMES") + + video, metadata = load_video( + video_path, + backend="torchcodec", + num_frames=num_frames, + ) + + if video_path == DEFAULT_TEST_VIDEO and num_frames is None: + assert video.to(paddle.int64).sum().item() == 247759890390 + assert metadata.total_num_frames == 263 + assert metadata.fps == pytest.approx(29.99418249715141) + assert metadata.width == 1920 + assert metadata.height == 1080 + assert metadata.duration == pytest.approx(8.768367) + for i, idx in enumerate(metadata.frames_indices): + assert idx == i + else: + _assert_video_decode_smoke(video, metadata, expected_num_frames=num_frames) From 516c690aaacc6a1e8ea8c0d10fe85fa730450b33 Mon Sep 17 00:00:00 2001 From: youge325 Date: Sat, 11 Apr 2026 11:27:46 +0800 Subject: [PATCH 05/14] update ffmpeg and wheel build workflow with upstream main branch --- .github/workflows/build_ffmpeg.yaml | 27 +++++++ .github/workflows/paddle_arm_wheel.yaml | 62 ++++++++------- ...fetch_and_expose_non_gpl_ffmpeg_libs.cmake | 76 +++++++++++++------ 3 files changed, 113 insertions(+), 52 deletions(-) diff --git a/.github/workflows/build_ffmpeg.yaml b/.github/workflows/build_ffmpeg.yaml index 9b9317e3b..5494bf0b1 100644 --- a/.github/workflows/build_ffmpeg.yaml +++ b/.github/workflows/build_ffmpeg.yaml @@ -48,6 +48,33 @@ jobs: mkdir -p "${artifact_dir}" mv ffmpeg.tar.gz "${artifact_dir}/${FFMPEG_VERSION}.tar.gz" + LGPL-Linux-aarch64: + strategy: + fail-fast: false + matrix: + ffmpeg-version: ["4.4.4", "5.1.4", "6.1.1", "7.0.1", "8.0"] + uses: pytorch/test-infra/.github/workflows/linux_job_v2.yml@main + permissions: + id-token: write + contents: read + with: + job-name: Build + upload-artifact: ffmpeg-lgpl-linux_aarch64-${{ matrix.ffmpeg-version }} + repository: meta-pytorch/torchcodec + runner: linux.arm64.2xlarge + docker-image: pytorch/manylinux2_28_aarch64-builder:cpu-aarch64 + script: | + export FFMPEG_VERSION="${{ matrix.ffmpeg-version }}" + export FFMPEG_ROOT="${PWD}/ffmpeg" + + packaging/build_ffmpeg.sh + + tar -cf ffmpeg.tar.gz ffmpeg/include ffmpeg/lib + + artifact_dir="${RUNNER_ARTIFACT_DIR}/$(date +%Y-%m-%d)/linux_aarch64" + mkdir -p "${artifact_dir}" + mv ffmpeg.tar.gz "${artifact_dir}/${FFMPEG_VERSION}.tar.gz" + LGPL-macOS: strategy: fail-fast: false diff --git a/.github/workflows/paddle_arm_wheel.yaml b/.github/workflows/paddle_arm_wheel.yaml index dcd29157b..319823357 100644 --- a/.github/workflows/paddle_arm_wheel.yaml +++ b/.github/workflows/paddle_arm_wheel.yaml @@ -21,19 +21,36 @@ defaults: shell: bash -l -eo pipefail {0} env: - ARM_BUILD_FFMPEG_VERSION: "7.0.1" + ARM_TEST_PYTHON_VERSION: "3.10" jobs: - build-paddlecodec-wheel-arm: + generate-matrix: + runs-on: ubuntu-latest + outputs: + build-matrix: ${{ steps.set-matrix.outputs.build-matrix }} + test-matrix: ${{ steps.set-matrix.outputs.test-matrix }} + steps: + - name: Generate ARM build and test matrix + id: set-matrix + run: | + printf '%s\n' \ + 'build-matrix={"python-version":["3.9","3.10","3.11","3.12","3.13"]}' \ + >> "$GITHUB_OUTPUT" + printf '%s\n' \ + 'test-matrix={"include":[{"python-version":"'"${ARM_TEST_PYTHON_VERSION}"'","ffmpeg-version":"4.4.2"},{"python-version":"'"${ARM_TEST_PYTHON_VERSION}"'","ffmpeg-version":"5.1.2"},{"python-version":"'"${ARM_TEST_PYTHON_VERSION}"'","ffmpeg-version":"6.1.1"},{"python-version":"'"${ARM_TEST_PYTHON_VERSION}"'","ffmpeg-version":"7.0.1"}]}' \ + >> "$GITHUB_OUTPUT" + + build: + needs: generate-matrix # If your repo does not have GitHub-hosted ARM runners enabled, # replace this with self-hosted labels, e.g. [self-hosted, Linux, ARM64]. + name: Build and Upload ARM wheel runs-on: ubuntu-24.04-arm container: image: pytorch/manylinux2_28_aarch64-builder:cpu-aarch64 strategy: fail-fast: false - matrix: - python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] + matrix: ${{ fromJson(needs.generate-matrix.outputs.build-matrix) }} permissions: id-token: write contents: read @@ -62,16 +79,11 @@ jobs: run: | bash packaging/pre_build_script.sh - - name: Install build-time FFmpeg - run: | - conda install "ffmpeg=${{ env.ARM_BUILD_FFMPEG_VERSION }}" pkg-config -c conda-forge -y - ffmpeg -version - pkg-config --modversion libavcodec - - name: Build wheel run: | - # Linux ARM does not have pre-built multi-version FFmpeg tarballs in the - # upstream S3 bucket, so build against the native conda FFmpeg install. + # Match upstream's Linux workflow and build the ARM wheel against the + # pre-built non-GPL FFmpeg bundles published on S3. + export BUILD_AGAINST_ALL_FFMPEG_FROM_S3=1 export I_CONFIRM_THIS_IS_NOT_A_LICENSE_VIOLATION=1 export TORCHCODEC_CMAKE_BUILD_DIR=$(pwd)/build_cmake python -m build --wheel -vvv --no-isolation @@ -86,8 +98,8 @@ jobs: unzip -j dist/*.whl "torchcodec/*.so" -d temp_libs || true # 2. Prepare LD_LIBRARY_PATH - # FFmpeg libraries from the active conda environment - FFMPEG_LIB_PATHS="${CONDA_PREFIX}/lib" + # FFmpeg libraries fetched from the build-time S3 bundles + FFMPEG_LIB_PATHS=$(find "$(pwd)/build_cmake/_deps" -type d -name "lib" -print | paste -sd: -) # PaddlePaddle libraries PADDLE_PATH=$(python -c "import paddle; print(paddle.__path__[0])") PADDLE_LIB_PATHS="$PADDLE_PATH/base:$PADDLE_PATH/libs" @@ -124,28 +136,22 @@ jobs: echo "Wheel path: $wheel_path" unzip -l $wheel_path - test-paddlecodec-wheel-arm: - needs: build-paddlecodec-wheel-arm + install-and-test: + needs: [generate-matrix, build] + name: Install and test ARM wheel runs-on: ubuntu-24.04-arm env: PADDLECODEC_TEST_VIDEO: test/resources/testsrc2.mp4 PADDLECODEC_TEST_NUM_FRAMES: "8" strategy: fail-fast: false - matrix: - python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] - # ARM wheels currently build against a single native FFmpeg major because - # the pre-built multi-version Linux FFmpeg bundle is only published for x86_64. - ffmpeg-version: ["7.0.1"] + # Match upstream's staged Linux wheel flow: build all Python variants, then + # smoke-test one representative ARM wheel across multiple runtime FFmpeg majors. + matrix: ${{ fromJson(needs.generate-matrix.outputs.test-matrix) }} steps: - name: Checkout repository uses: actions/checkout@v6 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - - name: Download wheel artifact uses: actions/download-artifact@v4 with: @@ -194,7 +200,7 @@ jobs: name: Publish ARM Wheels to PyPI if: "startsWith(github.ref, 'refs/tags/')" needs: - - test-paddlecodec-wheel-arm + - install-and-test permissions: id-token: write @@ -214,7 +220,7 @@ jobs: name: Publish ARM Wheels to GitHub if: "startsWith(github.ref, 'refs/tags/')" needs: - - test-paddlecodec-wheel-arm + - install-and-test permissions: contents: write steps: diff --git a/src/torchcodec/_core/fetch_and_expose_non_gpl_ffmpeg_libs.cmake b/src/torchcodec/_core/fetch_and_expose_non_gpl_ffmpeg_libs.cmake index 07abd2e87..781fce5a0 100644 --- a/src/torchcodec/_core/fetch_and_expose_non_gpl_ffmpeg_libs.cmake +++ b/src/torchcodec/_core/fetch_and_expose_non_gpl_ffmpeg_libs.cmake @@ -23,31 +23,59 @@ set( if (LINUX) set(lib_dir "lib") - set( - platform_url - ${base_url}/linux_x86_64 - ) + if (CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64|arm64|ARM64") + set( + platform_url + ${base_url}/linux_aarch64 + ) - set( - f4_sha256 - 1a083f1922443bedb5243d04896383b8c606778a7ddb9d886c8303e55339fe0c - ) - set( - f5_sha256 - 65d6ad54082d94dcb3f801d73df2265e0e1bb303c7afbce7723e3b77ccd0e207 - ) - set( - f6_sha256 - 8bd5939c2f4a4b072e837e7870c13fe7d13824e5ff087ab534e4db4e90b7be9c - ) - set( - f7_sha256 - 1cb946d8b7c6393c2c3ebe1f900b8de7a2885fe614c45d4ec32c9833084f2f26 - ) - set( - f8_sha256 - c55b3c1a4b5e4d5fdd7c632bea3ab6f45b4e37cc8e0999dda3f84a8ed8defad8 - ) + set( + f4_sha256 + a310a2ed9ffe555fd3278dae15065541098dd35e124564671dcda6a6620ac842 + ) + set( + f5_sha256 + 89ca7996bccbc2db49adaa401d20fdbabffe0e1b4e07a0f81d6b143e858b7c8d + ) + set( + f6_sha256 + ae44c67b4587d061b8e9cc8990ca891ee013fe52ad79e5016ba29871562621da + ) + set( + f7_sha256 + 948e2cac66ca6f68ff526d5e84138e94bce0f1a7c83f502d15d85d0bd3ddc112 + ) + set( + f8_sha256 + b9cfd99ae75a14e58300854967d4dc49de0b3daa551df51ea1f52a3f08d2c8af + ) + else() + set( + platform_url + ${base_url}/linux_x86_64 + ) + + set( + f4_sha256 + 1a083f1922443bedb5243d04896383b8c606778a7ddb9d886c8303e55339fe0c + ) + set( + f5_sha256 + 65d6ad54082d94dcb3f801d73df2265e0e1bb303c7afbce7723e3b77ccd0e207 + ) + set( + f6_sha256 + 8bd5939c2f4a4b072e837e7870c13fe7d13824e5ff087ab534e4db4e90b7be9c + ) + set( + f7_sha256 + 1cb946d8b7c6393c2c3ebe1f900b8de7a2885fe614c45d4ec32c9833084f2f26 + ) + set( + f8_sha256 + c55b3c1a4b5e4d5fdd7c632bea3ab6f45b4e37cc8e0999dda3f84a8ed8defad8 + ) + endif() set( f4_library_file_names libavutil.so.56 From b9052e703aead49d59b9e51e95c237fa852631ef Mon Sep 17 00:00:00 2001 From: youge325 Date: Sat, 11 Apr 2026 12:04:01 +0800 Subject: [PATCH 06/14] run full tests --- .github/workflows/paddle_arm_wheel.yaml | 3 -- test_paddle/test_video_decode.py | 58 +++++-------------------- 2 files changed, 10 insertions(+), 51 deletions(-) diff --git a/.github/workflows/paddle_arm_wheel.yaml b/.github/workflows/paddle_arm_wheel.yaml index 319823357..3a3c532f3 100644 --- a/.github/workflows/paddle_arm_wheel.yaml +++ b/.github/workflows/paddle_arm_wheel.yaml @@ -140,9 +140,6 @@ jobs: needs: [generate-matrix, build] name: Install and test ARM wheel runs-on: ubuntu-24.04-arm - env: - PADDLECODEC_TEST_VIDEO: test/resources/testsrc2.mp4 - PADDLECODEC_TEST_NUM_FRAMES: "8" strategy: fail-fast: false # Match upstream's staged Linux wheel flow: build all Python variants, then diff --git a/test_paddle/test_video_decode.py b/test_paddle/test_video_decode.py index 62e8ff593..f708b464e 100644 --- a/test_paddle/test_video_decode.py +++ b/test_paddle/test_video_decode.py @@ -10,8 +10,6 @@ import httpx import numpy as np -DEFAULT_TEST_VIDEO = "https://paddlenlp.bj.bcebos.com/datasets/paddlemix/demo_video/example_video.mp4" - @dataclass class VideoMetadata(Mapping): @@ -168,50 +166,14 @@ def sample_indices_fn_func(metadata, **fn_kwargs): video_decoder = VIDEO_DECODERS[backend] video, metadata = video_decoder(file_obj, sample_indices_fn, **kwargs) return video, metadata - - -def _get_optional_int_env(name: str) -> Optional[int]: - value = os.getenv(name) - return int(value) if value else None - - -def _assert_video_decode_smoke( - video, - metadata: VideoMetadata, - expected_num_frames: Optional[int] = None, -): - assert video.ndim == 4 - assert len(metadata.frames_indices) == video.shape[0] - if expected_num_frames is not None: - assert video.shape[0] == expected_num_frames - assert metadata.total_num_frames >= video.shape[0] >= 1 - assert metadata.fps is not None and metadata.fps > 0 - assert metadata.width is not None and metadata.width > 0 - assert metadata.height is not None and metadata.height > 0 - assert metadata.duration is not None and metadata.duration > 0 - assert tuple(video.shape[-2:]) == (metadata.height, metadata.width) - assert video.shape[-3] == 3 - assert video.to(paddle.int64).sum().item() > 0 - - def test_video_decode(): - video_path = os.getenv("PADDLECODEC_TEST_VIDEO", DEFAULT_TEST_VIDEO) - num_frames = _get_optional_int_env("PADDLECODEC_TEST_NUM_FRAMES") - - video, metadata = load_video( - video_path, - backend="torchcodec", - num_frames=num_frames, - ) - - if video_path == DEFAULT_TEST_VIDEO and num_frames is None: - assert video.to(paddle.int64).sum().item() == 247759890390 - assert metadata.total_num_frames == 263 - assert metadata.fps == pytest.approx(29.99418249715141) - assert metadata.width == 1920 - assert metadata.height == 1080 - assert metadata.duration == pytest.approx(8.768367) - for i, idx in enumerate(metadata.frames_indices): - assert idx == i - else: - _assert_video_decode_smoke(video, metadata, expected_num_frames=num_frames) + url = "https://paddlenlp.bj.bcebos.com/datasets/paddlemix/demo_video/example_video.mp4" + video, metadata = load_video(url, backend="torchcodec") + assert video.to(paddle.int64).sum().item() == 247759890390 + assert metadata.total_num_frames == 263 + assert metadata.fps == pytest.approx(29.99418249715141) + assert metadata.width == 1920 + assert metadata.height == 1080 + assert metadata.duration == pytest.approx(8.768367) + for i, idx in enumerate(metadata.frames_indices): + assert idx == i From cb68f641c72edb053683e4ac700b0c5d126f0173 Mon Sep 17 00:00:00 2001 From: youge325 Date: Sat, 11 Apr 2026 13:21:42 +0800 Subject: [PATCH 07/14] Revert "run full tests" This reverts commit b9052e703aead49d59b9e51e95c237fa852631ef. --- .github/workflows/paddle_arm_wheel.yaml | 3 ++ test_paddle/test_video_decode.py | 58 ++++++++++++++++++++----- 2 files changed, 51 insertions(+), 10 deletions(-) diff --git a/.github/workflows/paddle_arm_wheel.yaml b/.github/workflows/paddle_arm_wheel.yaml index 3a3c532f3..319823357 100644 --- a/.github/workflows/paddle_arm_wheel.yaml +++ b/.github/workflows/paddle_arm_wheel.yaml @@ -140,6 +140,9 @@ jobs: needs: [generate-matrix, build] name: Install and test ARM wheel runs-on: ubuntu-24.04-arm + env: + PADDLECODEC_TEST_VIDEO: test/resources/testsrc2.mp4 + PADDLECODEC_TEST_NUM_FRAMES: "8" strategy: fail-fast: false # Match upstream's staged Linux wheel flow: build all Python variants, then diff --git a/test_paddle/test_video_decode.py b/test_paddle/test_video_decode.py index f708b464e..62e8ff593 100644 --- a/test_paddle/test_video_decode.py +++ b/test_paddle/test_video_decode.py @@ -10,6 +10,8 @@ import httpx import numpy as np +DEFAULT_TEST_VIDEO = "https://paddlenlp.bj.bcebos.com/datasets/paddlemix/demo_video/example_video.mp4" + @dataclass class VideoMetadata(Mapping): @@ -166,14 +168,50 @@ def sample_indices_fn_func(metadata, **fn_kwargs): video_decoder = VIDEO_DECODERS[backend] video, metadata = video_decoder(file_obj, sample_indices_fn, **kwargs) return video, metadata + + +def _get_optional_int_env(name: str) -> Optional[int]: + value = os.getenv(name) + return int(value) if value else None + + +def _assert_video_decode_smoke( + video, + metadata: VideoMetadata, + expected_num_frames: Optional[int] = None, +): + assert video.ndim == 4 + assert len(metadata.frames_indices) == video.shape[0] + if expected_num_frames is not None: + assert video.shape[0] == expected_num_frames + assert metadata.total_num_frames >= video.shape[0] >= 1 + assert metadata.fps is not None and metadata.fps > 0 + assert metadata.width is not None and metadata.width > 0 + assert metadata.height is not None and metadata.height > 0 + assert metadata.duration is not None and metadata.duration > 0 + assert tuple(video.shape[-2:]) == (metadata.height, metadata.width) + assert video.shape[-3] == 3 + assert video.to(paddle.int64).sum().item() > 0 + + def test_video_decode(): - url = "https://paddlenlp.bj.bcebos.com/datasets/paddlemix/demo_video/example_video.mp4" - video, metadata = load_video(url, backend="torchcodec") - assert video.to(paddle.int64).sum().item() == 247759890390 - assert metadata.total_num_frames == 263 - assert metadata.fps == pytest.approx(29.99418249715141) - assert metadata.width == 1920 - assert metadata.height == 1080 - assert metadata.duration == pytest.approx(8.768367) - for i, idx in enumerate(metadata.frames_indices): - assert idx == i + video_path = os.getenv("PADDLECODEC_TEST_VIDEO", DEFAULT_TEST_VIDEO) + num_frames = _get_optional_int_env("PADDLECODEC_TEST_NUM_FRAMES") + + video, metadata = load_video( + video_path, + backend="torchcodec", + num_frames=num_frames, + ) + + if video_path == DEFAULT_TEST_VIDEO and num_frames is None: + assert video.to(paddle.int64).sum().item() == 247759890390 + assert metadata.total_num_frames == 263 + assert metadata.fps == pytest.approx(29.99418249715141) + assert metadata.width == 1920 + assert metadata.height == 1080 + assert metadata.duration == pytest.approx(8.768367) + for i, idx in enumerate(metadata.frames_indices): + assert idx == i + else: + _assert_video_decode_smoke(video, metadata, expected_num_frames=num_frames) From 1d3ea2f40070cb04b3545496bfe2acc64f13dee6 Mon Sep 17 00:00:00 2001 From: youge325 Date: Sat, 11 Apr 2026 17:16:19 +0800 Subject: [PATCH 08/14] add multi-python multi-ffmpeg build workflow --- .github/workflows/paddle_arm_wheel.yaml | 40 ++++++++++++++++++------- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/.github/workflows/paddle_arm_wheel.yaml b/.github/workflows/paddle_arm_wheel.yaml index 319823357..849eed992 100644 --- a/.github/workflows/paddle_arm_wheel.yaml +++ b/.github/workflows/paddle_arm_wheel.yaml @@ -20,9 +20,6 @@ defaults: run: shell: bash -l -eo pipefail {0} -env: - ARM_TEST_PYTHON_VERSION: "3.10" - jobs: generate-matrix: runs-on: ubuntu-latest @@ -33,12 +30,33 @@ jobs: - name: Generate ARM build and test matrix id: set-matrix run: | - printf '%s\n' \ - 'build-matrix={"python-version":["3.9","3.10","3.11","3.12","3.13"]}' \ - >> "$GITHUB_OUTPUT" - printf '%s\n' \ - 'test-matrix={"include":[{"python-version":"'"${ARM_TEST_PYTHON_VERSION}"'","ffmpeg-version":"4.4.2"},{"python-version":"'"${ARM_TEST_PYTHON_VERSION}"'","ffmpeg-version":"5.1.2"},{"python-version":"'"${ARM_TEST_PYTHON_VERSION}"'","ffmpeg-version":"6.1.1"},{"python-version":"'"${ARM_TEST_PYTHON_VERSION}"'","ffmpeg-version":"7.0.1"}]}' \ - >> "$GITHUB_OUTPUT" + python - <<'PY' + import itertools + import json + import os + + python_versions = ["3.9", "3.10", "3.11", "3.12", "3.13"] + # FFmpeg 8.0 is intentionally excluded for Paddle CPU builds to match + # the existing x86 workflow and avoid the current OpenVINO conflict. + ffmpeg_versions = ["4.4.2", "5.1.2", "6.1.1", "7.0.1"] + + build_matrix = {"python-version": python_versions} + test_matrix = { + "include": [ + { + "python-version": python_version, + "ffmpeg-version": ffmpeg_version, + } + for python_version, ffmpeg_version in itertools.product( + python_versions, ffmpeg_versions + ) + ] + } + + with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as f: + f.write(f"build-matrix={json.dumps(build_matrix)}\n") + f.write(f"test-matrix={json.dumps(test_matrix)}\n") + PY build: needs: generate-matrix @@ -145,8 +163,8 @@ jobs: PADDLECODEC_TEST_NUM_FRAMES: "8" strategy: fail-fast: false - # Match upstream's staged Linux wheel flow: build all Python variants, then - # smoke-test one representative ARM wheel across multiple runtime FFmpeg majors. + # Match the existing Paddle x86 flow while keeping the upstream-style staged + # build/test split: exercise every Python and FFmpeg combination post-build. matrix: ${{ fromJson(needs.generate-matrix.outputs.test-matrix) }} steps: - name: Checkout repository From 3cb2a0fc4f4dc76d7a9bd822b14b607bd523ef58 Mon Sep 17 00:00:00 2001 From: youge325 Date: Sat, 11 Apr 2026 19:57:49 +0800 Subject: [PATCH 09/14] try to use actions/cache@v4 --- .github/workflows/paddle_arm_wheel.yaml | 42 ++++++++++++++++-- .github/workflows/paddle_wheel.yaml | 41 +++++++++++++++++- test_paddle/test_video_decode.py | 57 ++++++------------------- 3 files changed, 91 insertions(+), 49 deletions(-) diff --git a/.github/workflows/paddle_arm_wheel.yaml b/.github/workflows/paddle_arm_wheel.yaml index 849eed992..dd3356a97 100644 --- a/.github/workflows/paddle_arm_wheel.yaml +++ b/.github/workflows/paddle_arm_wheel.yaml @@ -20,6 +20,11 @@ defaults: run: shell: bash -l -eo pipefail {0} +env: + PADDLECODEC_TEST_VIDEO_URL: https://paddlenlp.bj.bcebos.com/datasets/paddlemix/demo_video/example_video.mp4 + PADDLECODEC_TEST_VIDEO_CACHE_KEY: paddlecodec-test-video-v1-example-video + PADDLECODEC_TEST_VIDEO_PATH: .github/test-assets/example_video.mp4 + jobs: generate-matrix: runs-on: ubuntu-latest @@ -58,6 +63,32 @@ jobs: f.write(f"test-matrix={json.dumps(test_matrix)}\n") PY + prepare-test-video: + runs-on: ubuntu-latest + name: Prepare cached Paddle test video + steps: + - name: Restore cached test video + id: cache-test-video + uses: actions/cache@v4 + with: + path: ${{ env.PADDLECODEC_TEST_VIDEO_PATH }} + key: ${{ env.PADDLECODEC_TEST_VIDEO_CACHE_KEY }} + + - name: Download test video + if: steps.cache-test-video.outputs.cache-hit != 'true' + run: | + mkdir -p "$(dirname "${PADDLECODEC_TEST_VIDEO_PATH}")" + curl --fail --location --retry 5 --retry-all-errors \ + --output "${PADDLECODEC_TEST_VIDEO_PATH}" \ + "${PADDLECODEC_TEST_VIDEO_URL}" + + - name: Upload cached test video artifact + uses: actions/upload-artifact@v5 + with: + name: paddlecodec-test-video + path: ${{ env.PADDLECODEC_TEST_VIDEO_PATH }} + if-no-files-found: error + build: needs: generate-matrix # If your repo does not have GitHub-hosted ARM runners enabled, @@ -155,12 +186,11 @@ jobs: unzip -l $wheel_path install-and-test: - needs: [generate-matrix, build] + needs: [generate-matrix, prepare-test-video, build] name: Install and test ARM wheel runs-on: ubuntu-24.04-arm env: - PADDLECODEC_TEST_VIDEO: test/resources/testsrc2.mp4 - PADDLECODEC_TEST_NUM_FRAMES: "8" + PADDLECODEC_TEST_VIDEO: .github/test-assets/example_video.mp4 strategy: fail-fast: false # Match the existing Paddle x86 flow while keeping the upstream-style staged @@ -176,6 +206,12 @@ jobs: name: paddlecodec-wheel-linux-arm64-py${{ matrix.python-version }} path: dist/ + - name: Download cached test video artifact + uses: actions/download-artifact@v4 + with: + name: paddlecodec-test-video + path: .github/test-assets/ + - name: Install FFmpeg via conda uses: conda-incubator/setup-miniconda@v3 with: diff --git a/.github/workflows/paddle_wheel.yaml b/.github/workflows/paddle_wheel.yaml index 4976e20c9..86dd9c0e0 100644 --- a/.github/workflows/paddle_wheel.yaml +++ b/.github/workflows/paddle_wheel.yaml @@ -20,7 +20,38 @@ defaults: run: shell: bash -l -eo pipefail {0} +env: + PADDLECODEC_TEST_VIDEO_URL: https://paddlenlp.bj.bcebos.com/datasets/paddlemix/demo_video/example_video.mp4 + PADDLECODEC_TEST_VIDEO_CACHE_KEY: paddlecodec-test-video-v1-example-video + PADDLECODEC_TEST_VIDEO_PATH: .github/test-assets/example_video.mp4 + jobs: + prepare-test-video: + runs-on: ubuntu-latest + name: Prepare cached Paddle test video + steps: + - name: Restore cached test video + id: cache-test-video + uses: actions/cache@v4 + with: + path: ${{ env.PADDLECODEC_TEST_VIDEO_PATH }} + key: ${{ env.PADDLECODEC_TEST_VIDEO_CACHE_KEY }} + + - name: Download test video + if: steps.cache-test-video.outputs.cache-hit != 'true' + run: | + mkdir -p "$(dirname "${PADDLECODEC_TEST_VIDEO_PATH}")" + curl --fail --location --retry 5 --retry-all-errors \ + --output "${PADDLECODEC_TEST_VIDEO_PATH}" \ + "${PADDLECODEC_TEST_VIDEO_URL}" + + - name: Upload cached test video artifact + uses: actions/upload-artifact@v5 + with: + name: paddlecodec-test-video + path: ${{ env.PADDLECODEC_TEST_VIDEO_PATH }} + if-no-files-found: error + build-paddlecodec-wheel: runs-on: ubuntu-latest container: @@ -113,8 +144,10 @@ jobs: unzip -l $wheel_path test-paddlecodec-wheel: - needs: build-paddlecodec-wheel + needs: [prepare-test-video, build-paddlecodec-wheel] runs-on: ubuntu-latest + env: + PADDLECODEC_TEST_VIDEO: .github/test-assets/example_video.mp4 strategy: fail-fast: false matrix: @@ -138,6 +171,12 @@ jobs: name: paddlecodec-wheel-linux-py${{ matrix.python-version }} path: dist/ + - name: Download cached test video artifact + uses: actions/download-artifact@v4 + with: + name: paddlecodec-test-video + path: .github/test-assets/ + - name: Install FFmpeg via conda uses: conda-incubator/setup-miniconda@v3 with: diff --git a/test_paddle/test_video_decode.py b/test_paddle/test_video_decode.py index 62e8ff593..1be61bd3e 100644 --- a/test_paddle/test_video_decode.py +++ b/test_paddle/test_video_decode.py @@ -10,8 +10,6 @@ import httpx import numpy as np -DEFAULT_TEST_VIDEO = "https://paddlenlp.bj.bcebos.com/datasets/paddlemix/demo_video/example_video.mp4" - @dataclass class VideoMetadata(Mapping): @@ -170,48 +168,17 @@ def sample_indices_fn_func(metadata, **fn_kwargs): return video, metadata -def _get_optional_int_env(name: str) -> Optional[int]: - value = os.getenv(name) - return int(value) if value else None - - -def _assert_video_decode_smoke( - video, - metadata: VideoMetadata, - expected_num_frames: Optional[int] = None, -): - assert video.ndim == 4 - assert len(metadata.frames_indices) == video.shape[0] - if expected_num_frames is not None: - assert video.shape[0] == expected_num_frames - assert metadata.total_num_frames >= video.shape[0] >= 1 - assert metadata.fps is not None and metadata.fps > 0 - assert metadata.width is not None and metadata.width > 0 - assert metadata.height is not None and metadata.height > 0 - assert metadata.duration is not None and metadata.duration > 0 - assert tuple(video.shape[-2:]) == (metadata.height, metadata.width) - assert video.shape[-3] == 3 - assert video.to(paddle.int64).sum().item() > 0 - - def test_video_decode(): - video_path = os.getenv("PADDLECODEC_TEST_VIDEO", DEFAULT_TEST_VIDEO) - num_frames = _get_optional_int_env("PADDLECODEC_TEST_NUM_FRAMES") - - video, metadata = load_video( - video_path, - backend="torchcodec", - num_frames=num_frames, + video_path = os.getenv( + "PADDLECODEC_TEST_VIDEO", + "https://paddlenlp.bj.bcebos.com/datasets/paddlemix/demo_video/example_video.mp4", ) - - if video_path == DEFAULT_TEST_VIDEO and num_frames is None: - assert video.to(paddle.int64).sum().item() == 247759890390 - assert metadata.total_num_frames == 263 - assert metadata.fps == pytest.approx(29.99418249715141) - assert metadata.width == 1920 - assert metadata.height == 1080 - assert metadata.duration == pytest.approx(8.768367) - for i, idx in enumerate(metadata.frames_indices): - assert idx == i - else: - _assert_video_decode_smoke(video, metadata, expected_num_frames=num_frames) + video, metadata = load_video(video_path, backend="torchcodec") + assert video.to(paddle.int64).sum().item() == 247759890390 + assert metadata.total_num_frames == 263 + assert metadata.fps == pytest.approx(29.99418249715141) + assert metadata.width == 1920 + assert metadata.height == 1080 + assert metadata.duration == pytest.approx(8.768367) + for i, idx in enumerate(metadata.frames_indices): + assert idx == i From 35f7341aa9a76d6a916c2330bffe0865dc2d1cd9 Mon Sep 17 00:00:00 2001 From: youge325 Date: Sat, 11 Apr 2026 20:01:57 +0800 Subject: [PATCH 10/14] no need to build wheel for arm ourselves --- .github/workflows/build_ffmpeg.yaml | 27 --------------------------- 1 file changed, 27 deletions(-) diff --git a/.github/workflows/build_ffmpeg.yaml b/.github/workflows/build_ffmpeg.yaml index 5494bf0b1..9b9317e3b 100644 --- a/.github/workflows/build_ffmpeg.yaml +++ b/.github/workflows/build_ffmpeg.yaml @@ -48,33 +48,6 @@ jobs: mkdir -p "${artifact_dir}" mv ffmpeg.tar.gz "${artifact_dir}/${FFMPEG_VERSION}.tar.gz" - LGPL-Linux-aarch64: - strategy: - fail-fast: false - matrix: - ffmpeg-version: ["4.4.4", "5.1.4", "6.1.1", "7.0.1", "8.0"] - uses: pytorch/test-infra/.github/workflows/linux_job_v2.yml@main - permissions: - id-token: write - contents: read - with: - job-name: Build - upload-artifact: ffmpeg-lgpl-linux_aarch64-${{ matrix.ffmpeg-version }} - repository: meta-pytorch/torchcodec - runner: linux.arm64.2xlarge - docker-image: pytorch/manylinux2_28_aarch64-builder:cpu-aarch64 - script: | - export FFMPEG_VERSION="${{ matrix.ffmpeg-version }}" - export FFMPEG_ROOT="${PWD}/ffmpeg" - - packaging/build_ffmpeg.sh - - tar -cf ffmpeg.tar.gz ffmpeg/include ffmpeg/lib - - artifact_dir="${RUNNER_ARTIFACT_DIR}/$(date +%Y-%m-%d)/linux_aarch64" - mkdir -p "${artifact_dir}" - mv ffmpeg.tar.gz "${artifact_dir}/${FFMPEG_VERSION}.tar.gz" - LGPL-macOS: strategy: fail-fast: false From b6de0fe4d30cefaa5071c60930b0f51ed03dae07 Mon Sep 17 00:00:00 2001 From: youge325 Date: Sat, 11 Apr 2026 21:06:25 +0800 Subject: [PATCH 11/14] use faster runner --- .github/workflows/paddle_arm_wheel.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/paddle_arm_wheel.yaml b/.github/workflows/paddle_arm_wheel.yaml index dd3356a97..b381196b3 100644 --- a/.github/workflows/paddle_arm_wheel.yaml +++ b/.github/workflows/paddle_arm_wheel.yaml @@ -91,10 +91,8 @@ jobs: build: needs: generate-matrix - # If your repo does not have GitHub-hosted ARM runners enabled, - # replace this with self-hosted labels, e.g. [self-hosted, Linux, ARM64]. name: Build and Upload ARM wheel - runs-on: ubuntu-24.04-arm + runs-on: linux.arm64.2xlarge container: image: pytorch/manylinux2_28_aarch64-builder:cpu-aarch64 strategy: @@ -188,7 +186,9 @@ jobs: install-and-test: needs: [generate-matrix, prepare-test-video, build] name: Install and test ARM wheel - runs-on: ubuntu-24.04-arm + runs-on: linux.arm64.2xlarge + container: + image: pytorch/manylinux2_28_aarch64-builder:cpu-aarch64 env: PADDLECODEC_TEST_VIDEO: .github/test-assets/example_video.mp4 strategy: From 89eae288a64874b67e263d62f755ee3d3ac69f5e Mon Sep 17 00:00:00 2001 From: youge325 Date: Sat, 11 Apr 2026 22:08:45 +0800 Subject: [PATCH 12/14] use public runner --- .github/workflows/paddle_arm_wheel.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/paddle_arm_wheel.yaml b/.github/workflows/paddle_arm_wheel.yaml index b381196b3..07ff8ca2f 100644 --- a/.github/workflows/paddle_arm_wheel.yaml +++ b/.github/workflows/paddle_arm_wheel.yaml @@ -92,7 +92,7 @@ jobs: build: needs: generate-matrix name: Build and Upload ARM wheel - runs-on: linux.arm64.2xlarge + runs-on: ubuntu-24.04-arm container: image: pytorch/manylinux2_28_aarch64-builder:cpu-aarch64 strategy: @@ -186,7 +186,7 @@ jobs: install-and-test: needs: [generate-matrix, prepare-test-video, build] name: Install and test ARM wheel - runs-on: linux.arm64.2xlarge + runs-on: ubuntu-24.04-arm container: image: pytorch/manylinux2_28_aarch64-builder:cpu-aarch64 env: From 56feb21de7b404a9e049afce830078b73e19e7b3 Mon Sep 17 00:00:00 2001 From: youge325 Date: Sat, 11 Apr 2026 22:31:40 +0800 Subject: [PATCH 13/14] merge two workflow files into one --- .github/workflows/paddle_arm_wheel.yaml | 291 ------------------------ .github/workflows/paddle_wheel.yaml | 74 +++--- 2 files changed, 43 insertions(+), 322 deletions(-) delete mode 100644 .github/workflows/paddle_arm_wheel.yaml diff --git a/.github/workflows/paddle_arm_wheel.yaml b/.github/workflows/paddle_arm_wheel.yaml deleted file mode 100644 index 07ff8ca2f..000000000 --- a/.github/workflows/paddle_arm_wheel.yaml +++ /dev/null @@ -1,291 +0,0 @@ -name: Build ARM Wheels for Paddle - -on: - push: - branches: [paddle] - tags: ["v*"] - pull_request: - merge_group: - workflow_dispatch: - -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref_name }}-${{ github.ref_type == 'branch' && github.sha }}-${{ github.event_name == 'workflow_dispatch' }} - cancel-in-progress: true - -permissions: - id-token: write - contents: write - -defaults: - run: - shell: bash -l -eo pipefail {0} - -env: - PADDLECODEC_TEST_VIDEO_URL: https://paddlenlp.bj.bcebos.com/datasets/paddlemix/demo_video/example_video.mp4 - PADDLECODEC_TEST_VIDEO_CACHE_KEY: paddlecodec-test-video-v1-example-video - PADDLECODEC_TEST_VIDEO_PATH: .github/test-assets/example_video.mp4 - -jobs: - generate-matrix: - runs-on: ubuntu-latest - outputs: - build-matrix: ${{ steps.set-matrix.outputs.build-matrix }} - test-matrix: ${{ steps.set-matrix.outputs.test-matrix }} - steps: - - name: Generate ARM build and test matrix - id: set-matrix - run: | - python - <<'PY' - import itertools - import json - import os - - python_versions = ["3.9", "3.10", "3.11", "3.12", "3.13"] - # FFmpeg 8.0 is intentionally excluded for Paddle CPU builds to match - # the existing x86 workflow and avoid the current OpenVINO conflict. - ffmpeg_versions = ["4.4.2", "5.1.2", "6.1.1", "7.0.1"] - - build_matrix = {"python-version": python_versions} - test_matrix = { - "include": [ - { - "python-version": python_version, - "ffmpeg-version": ffmpeg_version, - } - for python_version, ffmpeg_version in itertools.product( - python_versions, ffmpeg_versions - ) - ] - } - - with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as f: - f.write(f"build-matrix={json.dumps(build_matrix)}\n") - f.write(f"test-matrix={json.dumps(test_matrix)}\n") - PY - - prepare-test-video: - runs-on: ubuntu-latest - name: Prepare cached Paddle test video - steps: - - name: Restore cached test video - id: cache-test-video - uses: actions/cache@v4 - with: - path: ${{ env.PADDLECODEC_TEST_VIDEO_PATH }} - key: ${{ env.PADDLECODEC_TEST_VIDEO_CACHE_KEY }} - - - name: Download test video - if: steps.cache-test-video.outputs.cache-hit != 'true' - run: | - mkdir -p "$(dirname "${PADDLECODEC_TEST_VIDEO_PATH}")" - curl --fail --location --retry 5 --retry-all-errors \ - --output "${PADDLECODEC_TEST_VIDEO_PATH}" \ - "${PADDLECODEC_TEST_VIDEO_URL}" - - - name: Upload cached test video artifact - uses: actions/upload-artifact@v5 - with: - name: paddlecodec-test-video - path: ${{ env.PADDLECODEC_TEST_VIDEO_PATH }} - if-no-files-found: error - - build: - needs: generate-matrix - name: Build and Upload ARM wheel - runs-on: ubuntu-24.04-arm - container: - image: pytorch/manylinux2_28_aarch64-builder:cpu-aarch64 - strategy: - fail-fast: false - matrix: ${{ fromJson(needs.generate-matrix.outputs.build-matrix) }} - permissions: - id-token: write - contents: read - steps: - - name: Checkout repository - uses: actions/checkout@v6 - - - name: Setup conda environment - uses: conda-incubator/setup-miniconda@v3 - with: - auto-update-conda: true - miniforge-version: latest - activate-environment: build - python-version: ${{ matrix.python-version }} - - - name: Install build dependencies - run: | - python -m pip install --upgrade pip - pip install build wheel setuptools - - - name: Install PaddlePaddle nightly - run: | - pip install --pre paddlepaddle -i https://www.paddlepaddle.org.cn/packages/nightly/cpu/ - - - name: Run pre-build script - run: | - bash packaging/pre_build_script.sh - - - name: Build wheel - run: | - # Match upstream's Linux workflow and build the ARM wheel against the - # pre-built non-GPL FFmpeg bundles published on S3. - export BUILD_AGAINST_ALL_FFMPEG_FROM_S3=1 - export I_CONFIRM_THIS_IS_NOT_A_LICENSE_VIOLATION=1 - export TORCHCODEC_CMAKE_BUILD_DIR=$(pwd)/build_cmake - python -m build --wheel -vvv --no-isolation - - - name: Repair wheel - run: | - pip install auditwheel - - # 1. Extract internal libraries from the wheel to a temporary directory - # This allows auditwheel to find them when checking dependencies - mkdir -p temp_libs - unzip -j dist/*.whl "torchcodec/*.so" -d temp_libs || true - - # 2. Prepare LD_LIBRARY_PATH - # FFmpeg libraries fetched from the build-time S3 bundles - FFMPEG_LIB_PATHS=$(find "$(pwd)/build_cmake/_deps" -type d -name "lib" -print | paste -sd: -) - # PaddlePaddle libraries - PADDLE_PATH=$(python -c "import paddle; print(paddle.__path__[0])") - PADDLE_LIB_PATHS="$PADDLE_PATH/base:$PADDLE_PATH/libs" - # Wheel internal libraries - INTERNAL_LIB_PATH=$(pwd)/temp_libs - - export LD_LIBRARY_PATH=${FFMPEG_LIB_PATHS}:${PADDLE_LIB_PATHS}:${INTERNAL_LIB_PATH}:${LD_LIBRARY_PATH} - - # 3. Repair wheel with auditwheel - # We exclude all external libraries because we want to rely on system libraries (like FFmpeg) - # or libraries provided by other packages (like PaddlePaddle). - # auditwheel 6.1.0+ supports wildcards in --exclude. - auditwheel repair dist/*.whl --plat manylinux_2_28_aarch64 -w wheelhouse/ --exclude "*" - - # Cleanup - rm -rf temp_libs - rm dist/*.whl - mv wheelhouse/*.whl dist/ - rmdir wheelhouse - - - name: Upload wheel artifact - uses: actions/upload-artifact@v5 - with: - name: paddlecodec-wheel-linux-arm64-py${{ matrix.python-version }} - path: dist/*.whl - - - name: Run post-build script - run: | - bash packaging/post_build_script.sh - - - name: List wheel contents - run: | - wheel_path=$(find dist -type f -name "*.whl") - echo "Wheel path: $wheel_path" - unzip -l $wheel_path - - install-and-test: - needs: [generate-matrix, prepare-test-video, build] - name: Install and test ARM wheel - runs-on: ubuntu-24.04-arm - container: - image: pytorch/manylinux2_28_aarch64-builder:cpu-aarch64 - env: - PADDLECODEC_TEST_VIDEO: .github/test-assets/example_video.mp4 - strategy: - fail-fast: false - # Match the existing Paddle x86 flow while keeping the upstream-style staged - # build/test split: exercise every Python and FFmpeg combination post-build. - matrix: ${{ fromJson(needs.generate-matrix.outputs.test-matrix) }} - steps: - - name: Checkout repository - uses: actions/checkout@v6 - - - name: Download wheel artifact - uses: actions/download-artifact@v4 - with: - name: paddlecodec-wheel-linux-arm64-py${{ matrix.python-version }} - path: dist/ - - - name: Download cached test video artifact - uses: actions/download-artifact@v4 - with: - name: paddlecodec-test-video - path: .github/test-assets/ - - - name: Install FFmpeg via conda - uses: conda-incubator/setup-miniconda@v3 - with: - auto-update-conda: true - miniforge-version: latest - activate-environment: test - python-version: ${{ matrix.python-version }} - - - name: Install FFmpeg from conda-forge - run: | - conda install "ffmpeg=${{ matrix.ffmpeg-version }}" -c conda-forge -y - ffmpeg -version - - - name: Install PaddlePaddle nightly in conda env - run: | - pip install --pre paddlepaddle -i https://www.paddlepaddle.org.cn/packages/nightly/cpu/ - - - name: Install paddlecodec from wheel - run: | - wheel_path=$(find dist -type f -name "*.whl") - echo "Installing $wheel_path" - pip install $wheel_path -vvv - - - name: Install test dependencies - run: | - pip install numpy pytest pillow - - - name: Delete src folder - run: | - # Delete src/ to ensure we're testing the installed wheel, not source code - rm -rf src/ - ls -la - - - name: Run tests - run: | - pytest --override-ini="addopts=-v" -s test_paddle - - publish-pypi-arm: - runs-on: ubuntu-latest - name: Publish ARM Wheels to PyPI - if: "startsWith(github.ref, 'refs/tags/')" - needs: - - install-and-test - permissions: - id-token: write - - steps: - - name: Retrieve ARM release distributions - uses: actions/download-artifact@v6 - with: - pattern: paddlecodec-wheel-linux-arm64-* - path: dist/ - merge-multiple: true - - - name: Publish ARM release distributions to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 - - publish-release-arm: - runs-on: ubuntu-latest - name: Publish ARM Wheels to GitHub - if: "startsWith(github.ref, 'refs/tags/')" - needs: - - install-and-test - permissions: - contents: write - steps: - - uses: actions/download-artifact@v6 - with: - pattern: paddlecodec-wheel-linux-arm64-* - path: dist/ - merge-multiple: true - - name: Publish ARM wheels to GitHub - uses: softprops/action-gh-release@v2 - with: - draft: true - files: dist/* - tag_name: ${{ github.ref_name }} diff --git a/.github/workflows/paddle_wheel.yaml b/.github/workflows/paddle_wheel.yaml index 86dd9c0e0..3ce2964c6 100644 --- a/.github/workflows/paddle_wheel.yaml +++ b/.github/workflows/paddle_wheel.yaml @@ -53,13 +53,28 @@ jobs: if-no-files-found: error build-paddlecodec-wheel: - runs-on: ubuntu-latest + name: Build and upload Paddle wheel (${{ matrix.arch-name }}, py${{ matrix.python-version }}) + runs-on: ${{ matrix.runner }} container: - image: pytorch/manylinux2_28-builder:cpu + image: ${{ matrix.container-image }} strategy: fail-fast: false matrix: python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] + arch: ["x86_64", "arm64"] + include: + - arch: x86_64 + arch-name: x86_64 + runner: ubuntu-latest + container-image: pytorch/manylinux2_28-builder:cpu + artifact-prefix: paddlecodec-wheel-linux + wheel-platform: manylinux_2_28_x86_64 + - arch: arm64 + arch-name: arm64 + runner: ubuntu-24.04-arm + container-image: pytorch/manylinux2_28_aarch64-builder:cpu-aarch64 + artifact-prefix: paddlecodec-wheel-linux-arm64 + wheel-platform: manylinux_2_28_aarch64 permissions: id-token: write contents: read @@ -90,8 +105,8 @@ jobs: - name: Build wheel run: | - # Use pre-built FFmpeg from PyTorch S3 export BUILD_AGAINST_ALL_FFMPEG_FROM_S3=1 + export I_CONFIRM_THIS_IS_NOT_A_LICENSE_VIOLATION=1 export TORCHCODEC_CMAKE_BUILD_DIR=$(pwd)/build_cmake python -m build --wheel -vvv --no-isolation @@ -99,29 +114,18 @@ jobs: run: | pip install auditwheel - # 1. Extract internal libraries from the wheel to a temporary directory - # This allows auditwheel to find them when checking dependencies mkdir -p temp_libs unzip -j dist/*.whl "torchcodec/*.so" -d temp_libs || true - # 2. Prepare LD_LIBRARY_PATH - # FFmpeg libraries - FFMPEG_LIB_PATHS=$(find $(pwd)/build_cmake/_deps -type d -name "lib" | tr '\n' ':') - # PaddlePaddle libraries + FFMPEG_LIB_PATHS=$(find "$(pwd)/build_cmake/_deps" -type d -name "lib" -print | paste -sd: -) PADDLE_PATH=$(python -c "import paddle; print(paddle.__path__[0])") PADDLE_LIB_PATHS="$PADDLE_PATH/base:$PADDLE_PATH/libs" - # Wheel internal libraries INTERNAL_LIB_PATH=$(pwd)/temp_libs - export LD_LIBRARY_PATH=${FFMPEG_LIB_PATHS}${PADDLE_LIB_PATHS}:${INTERNAL_LIB_PATH}:${LD_LIBRARY_PATH} + export LD_LIBRARY_PATH=${FFMPEG_LIB_PATHS}:${PADDLE_LIB_PATHS}:${INTERNAL_LIB_PATH}:${LD_LIBRARY_PATH} - # 3. Repair wheel with auditwheel - # We exclude all external libraries because we want to rely on system libraries (like FFmpeg) - # or libraries provided by other packages (like PaddlePaddle). - # auditwheel 6.1.0+ supports wildcards in --exclude. - auditwheel repair dist/*.whl --plat manylinux_2_28_x86_64 -w wheelhouse/ --exclude "*" + auditwheel repair dist/*.whl --plat ${{ matrix.wheel-platform }} -w wheelhouse/ --exclude "*" - # Cleanup rm -rf temp_libs rm dist/*.whl mv wheelhouse/*.whl dist/ @@ -130,7 +134,7 @@ jobs: - name: Upload wheel artifact uses: actions/upload-artifact@v5 with: - name: paddlecodec-wheel-linux-py${{ matrix.python-version }} + name: ${{ matrix.artifact-prefix }}-py${{ matrix.python-version }} path: dist/*.whl - name: Run post-build script @@ -144,31 +148,41 @@ jobs: unzip -l $wheel_path test-paddlecodec-wheel: + name: Install and test Paddle wheel (${{ matrix.arch-name }}, py${{ matrix.python-version }}, ffmpeg ${{ matrix.ffmpeg-version }}) needs: [prepare-test-video, build-paddlecodec-wheel] - runs-on: ubuntu-latest + runs-on: ${{ matrix.runner }} + container: + image: ${{ matrix.container-image }} env: PADDLECODEC_TEST_VIDEO: .github/test-assets/example_video.mp4 strategy: fail-fast: false matrix: python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] + arch: ["x86_64", "arm64"] # FFmpeg 8.0 depends on libopenvino.so.2520, PaddlePaddle CPU depends on libopenvino.so.2500 # There has some conflict causing test failures, but it works with PaddlePaddle GPU. # We skip FFmpeg 8.0 tests for PaddlePaddle CPU builds for now. ffmpeg-version: ["4.4.2", "5.1.2", "6.1.1", "7.0.1"] + include: + - arch: x86_64 + arch-name: x86_64 + runner: ubuntu-latest + container-image: pytorch/manylinux2_28-builder:cpu + artifact-prefix: paddlecodec-wheel-linux + - arch: arm64 + arch-name: arm64 + runner: ubuntu-24.04-arm + container-image: pytorch/manylinux2_28_aarch64-builder:cpu-aarch64 + artifact-prefix: paddlecodec-wheel-linux-arm64 steps: - name: Checkout repository uses: actions/checkout@v6 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - - name: Download wheel artifact uses: actions/download-artifact@v4 with: - name: paddlecodec-wheel-linux-py${{ matrix.python-version }} + name: ${{ matrix.artifact-prefix }}-py${{ matrix.python-version }} path: dist/ - name: Download cached test video artifact @@ -206,7 +220,6 @@ jobs: - name: Delete src folder run: | - # Delete src/ to ensure we're testing the installed wheel, not source code rm -rf src/ ls -la @@ -216,18 +229,17 @@ jobs: publish-pypi: runs-on: ubuntu-latest - name: Publish to PyPI + name: Publish Paddle wheels to PyPI if: "startsWith(github.ref, 'refs/tags/')" needs: - test-paddlecodec-wheel permissions: id-token: write - steps: - name: Retrieve release distributions uses: actions/download-artifact@v6 with: - pattern: paddlecodec-wheel-linux-* + pattern: paddlecodec-wheel-linux* path: dist/ merge-multiple: true @@ -236,7 +248,7 @@ jobs: publish-release: runs-on: ubuntu-latest - name: Publish to GitHub + name: Publish Paddle wheels to GitHub if: "startsWith(github.ref, 'refs/tags/')" needs: - test-paddlecodec-wheel @@ -245,7 +257,7 @@ jobs: steps: - uses: actions/download-artifact@v6 with: - pattern: paddlecodec-wheel-linux-* + pattern: paddlecodec-wheel-linux* path: dist/ merge-multiple: true - name: Get tag name From b62ae46935aeea7706d8c8cd078261cbd02b977a Mon Sep 17 00:00:00 2001 From: SigureMo Date: Sun, 12 Apr 2026 01:44:58 +0800 Subject: [PATCH 14/14] Make pixel value guard cross platform Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- test_paddle/test_video_decode.py | 37 +++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/test_paddle/test_video_decode.py b/test_paddle/test_video_decode.py index 1be61bd3e..240944cc2 100644 --- a/test_paddle/test_video_decode.py +++ b/test_paddle/test_video_decode.py @@ -2,6 +2,7 @@ paddle.enable_compat(scope={"torchcodec"}) import pytest +import subprocess from dataclasses import dataclass, fields from io import BytesIO from typing import Callable, Mapping, Optional, Union @@ -11,6 +12,40 @@ import numpy as np +def ffmpeg_rgb_sum(video_path_or_url: str) -> int: + # Use the local FFmpeg build as the oracle because YUV->RGB conversion is + # architecture- and FFmpeg-build-dependent. + proc = subprocess.Popen( + [ + "ffmpeg", + "-v", + "error", + "-vsync", + "0", + "-i", + video_path_or_url, + "-f", + "rawvideo", + "-pix_fmt", + "rgb24", + "-", + ], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + assert proc.stdout is not None + assert proc.stderr is not None + + total = 0 + while chunk := proc.stdout.read(16 * 1024 * 1024): + total += sum(chunk) + + stderr = proc.stderr.read().decode("utf-8", "ignore") + if proc.wait() != 0: + raise RuntimeError(f"ffmpeg failed to decode video: {stderr}") + return total + + @dataclass class VideoMetadata(Mapping): total_num_frames: int @@ -174,7 +209,7 @@ def test_video_decode(): "https://paddlenlp.bj.bcebos.com/datasets/paddlemix/demo_video/example_video.mp4", ) video, metadata = load_video(video_path, backend="torchcodec") - assert video.to(paddle.int64).sum().item() == 247759890390 + assert video.to(paddle.int64).sum().item() == ffmpeg_rgb_sum(video_path) assert metadata.total_num_frames == 263 assert metadata.fps == pytest.approx(29.99418249715141) assert metadata.width == 1920