Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 138 additions & 0 deletions .github/workflows/build-decord.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
# SPDX-FileCopyrightText: 2026 The RISE Project
# SPDX-License-Identifier: MIT
---
# Based on upstream's own manylinux wheel job (builds FFmpeg from source, then decord against
# it, then retags the result py3-none since libdecord.so is ctypes-loaded):
# https://github.com/dmlc/decord/blob/v0.6.0/.github/workflows/pypi.yml
# https://github.com/dmlc/decord/blob/v0.6.0/tools/build_manylinux2010.sh
# Unlike upstream's script, FFmpeg is built here without --enable-gpl/--enable-nonfree/
# libx264/libvpx: decord only ever decodes (no avcodec_find_encoder/AVOutputFormat use
# anywhere in src/), so a plain LGPL FFmpeg build already covers everything it links against.
name: Build decord wheels (riscv64)

on:
workflow_dispatch:
inputs:
version:
description: 'decord version to build (git tag without leading v, e.g. 0.6.0)'
required: true
default: '0.6.0'
pull_request:
paths:
- '.github/workflows/build-decord.yml'
- 'patches/decord/**'

concurrency:
group: ${{ github.workflow }}-${{ inputs.version || '0.6.0' }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true

permissions:
contents: read # to fetch code (actions/checkout)

env:
DECORD_VERSION: ${{ inputs.version || '0.6.0' }}
# decord's own configure.py never gained riscv64 support (FFmpeg didn't add ARCH_RISCV
# until 5.0); 6.1.6 is the last release before the 7.0 ch_layout API removal that
# src/audio/audio_reader.cc's AVCodecParameters::channels/av_get_default_channel_layout
# calls would otherwise need patching for.
FFMPEG_VERSION: 6.1.6
MANYLINUX_RISCV64_IMAGE: quay.io/pypa/manylinux_2_39_riscv64

jobs:
setup:
uses: $/.github/workflows/_setup.yml

build_wheels:
needs: [setup]
name: Build decord ${{ inputs.version || '0.6.0' }} py3-none-manylinux_riscv64
runs-on: ubuntu-24.04-riscv
timeout-minutes: 360

steps:
- name: Checkout decord v${{ env.DECORD_VERSION }}
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: dmlc/decord
ref: v${{ env.DECORD_VERSION }}
submodules: true
persist-credentials: false

- name: Checkout python-wheels
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
path: python-wheels
persist-credentials: false

- name: Patch decord source
run: git apply python-wheels/patches/decord/${{ env.DECORD_VERSION }}/00*.patch

# setuptools' default license-files glob (LICEN[CSZ]E*) picks this up from the
# project root alongside decord's own LICENSE, since the wheel bundles the LGPL
# FFmpeg .so's built below.
- name: Stage the FFmpeg licence text for packaging
run: curl -sL -o LICENSE.FFmpeg https://raw.githubusercontent.com/FFmpeg/FFmpeg/n${{ env.FFMPEG_VERSION }}/COPYING.LGPLv2.1

- name: Build wheel
uses: pypa/cibuildwheel@1828c10ab37f080699c7b81cea34097c684a7074 # v4.2.0
with:
package-dir: python
output-dir: wheelhouse/
# wheel_include_libs in python/setup.py only ever bundles a ctypes-loaded
# libdecord.so, no interpreter-specific extension -- one build serves everyone
# (retagged py3-none below, same as upstream's own pypi.yml rename step).
only: cp312-manylinux_riscv64
env:
CIBW_MANYLINUX_RISCV64_IMAGE: ${{ env.MANYLINUX_RISCV64_IMAGE }}
CIBW_ENVIRONMENT_LINUX: LD_LIBRARY_PATH=/tmp/ffmpeg-build/lib:$LD_LIBRARY_PATH
CIBW_BEFORE_BUILD: |
set -eux && \
curl -L -O https://ffmpeg.org/releases/ffmpeg-${{ env.FFMPEG_VERSION }}.tar.bz2 && \
tar xjf ffmpeg-${{ env.FFMPEG_VERSION }}.tar.bz2 && cd ffmpeg-${{ env.FFMPEG_VERSION }} && \
./configure --prefix=/tmp/ffmpeg-build --disable-static --enable-shared --enable-pic --disable-programs --disable-doc && \
make -j$(nproc) && make install && \
cd .. && rm -rf ffmpeg-${{ env.FFMPEG_VERSION }}* && \
cmake -S {project} -B {project}/build -DUSE_CUDA=OFF -DFFMPEG_DIR=/tmp/ffmpeg-build -DCMAKE_BUILD_TYPE=Release && \
cmake --build {project}/build --parallel $(nproc)
# test_video_corrupted_get_batch imports nose.tools.assert_raises directly,
# same legacy framework upstream's own pypi.yml runs these tests under.
CIBW_TEST_REQUIRES: pytest numpy nose
CIBW_TEST_SOURCES: tests examples
# Mirrors upstream's own sanity check (all 3 of pypi.yml's platform jobs run
# this same file, historically via `nose`).
CIBW_TEST_COMMAND: pytest -v tests/python/unittests/test_video_reader.py

# setup.py's get_tag() has no py3-none override (unlike plotext's), so cibuildwheel
# produces a cp312-cp312 tag; retag it the same way upstream's own pypi.yml does
# (mv ... cp36-cp36m -> py3-none), just with the wheel/RECORD metadata kept correct.
- name: Retag the wheel py3-none (libdecord.so is ctypes-loaded, not interpreter-specific)
run: |
python3 -m pip install -q "wheel>=0.42"
python3 -m wheel tags --python-tag py3 --abi-tag none --remove wheelhouse/decord-*.whl

- name: Check the wheel carries libdecord and a vendored LGPL FFmpeg
run: |
python3 - wheelhouse/*.whl <<'EOF'
import sys, zipfile

names = zipfile.ZipFile(sys.argv[1]).namelist()
for want in ("decord/libdecord.so", "libavcodec", "licenses/LICENSE.FFmpeg"):
if not any(want in n for n in names):
raise SystemExit(f"error: {want} missing from {sys.argv[1]}")
print("\n".join(n for n in names if ".so" in n))
EOF

- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: decord-${{ env.DECORD_VERSION }}-py3-none-manylinux_riscv64
path: wheelhouse/*.whl
if-no-files-found: error

publish:
name: Publish decord ${{ inputs.version || '0.6.0' }}
needs: [setup, build_wheels]
permissions:
contents: write
pull-requests: write
uses: $/.github/workflows/_publish-wheel.yml
with:
artifact-pattern: decord-${{ inputs.version || '0.6.0' }}-*-manylinux_riscv64
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
From 1c3480c359dfd2ade99e9de7c82453992e6a7f53 Mon Sep 17 00:00:00 2001
From: Ludovic Henry <git@ludovic.dev>
Date: Fri, 11 Sep 2026 03:00:28 +0200
Subject: [PATCH] ffmpeg_common.h: explicitly include libavcodec/bsf.h

Upstream-Status: To upstream [decord is unmaintained: no commits addressing FFmpeg API compatibility since v0.6.0 (2021-06-14)]

AVBSFContext and av_bsf_free() (used a few lines below to define
AVBSFContextPtr) were declared directly in libavcodec/avcodec.h through
FFmpeg 4.1.x, the version decord's own CI (tools/build_manylinux2010.sh)
built against. FFmpeg moved the whole bitstream-filter API out to its own
libavcodec/bsf.h and dropped avcodec.h's copy somewhere before 5.1 (still
missing in current FFmpeg master), so ffmpeg_common.h's include list needs
updating to build against any FFmpeg newer than upstream's own pin:

ffmpeg_common.h:187:5: error: 'AVBSFContext' was not declared in this scope
ffmpeg_common.h:187:48: error: 'av_bsf_free' was not declared in this scope

Not riscv64-specific: any build against FFmpeg 5.1+ hits this on any
architecture.
---
src/video/ffmpeg/ffmpeg_common.h | 1 +
1 file changed, 1 insertion(+)

diff --git a/src/video/ffmpeg/ffmpeg_common.h b/src/video/ffmpeg/ffmpeg_common.h
index b0b973f..f0f7316 100644
--- a/src/video/ffmpeg/ffmpeg_common.h
+++ b/src/video/ffmpeg/ffmpeg_common.h
@@ -21,6 +21,7 @@
extern "C" {
#endif
#include <libavcodec/avcodec.h>
+#include <libavcodec/bsf.h>
#include <libavformat/avformat.h>
#include <libavformat/avio.h>
#include <libavfilter/avfilter.h>
--
2.50.1 (Apple Git-155)

Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
From 941cde5866ec9e756b58dee2e08561661e375098 Mon Sep 17 00:00:00 2001
From: Ludovic Henry <git@ludovic.dev>
Date: Fri, 11 Sep 2026 04:28:44 +0200
Subject: [PATCH] video_reader.cc: const-qualify the AVCodec*
av_find_best_stream fills

Upstream-Status: To upstream [decord is unmaintained: no commits addressing FFmpeg API compatibility since v0.6.0 (2021-06-14)]

FFmpeg's avcodec_find_decoder()/av_find_best_stream() family started
returning const AVCodec* around FFmpeg 5.x (part of the same
const-correctness pass that added AVCodecParameters' constness earlier);
SetVideoStream()'s local AVCodec *dec was never updated to match, and
&dec no longer converts to the const AVCodec** av_find_best_stream()
now expects:

video_reader.cc:149:88: error: invalid conversion from 'AVCodec**' to
'const AVCodec**' [-fpermissive]

codecs_ (std::vector<const AVCodec*>) and every other AVCodec* use in
this file were already const; grepped src/ and include/ for any other
non-const "AVCodec *" declaration and found none.
---
src/video/video_reader.cc | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/video/video_reader.cc b/src/video/video_reader.cc
index af4858d..99c9635 100644
--- a/src/video/video_reader.cc
+++ b/src/video/video_reader.cc
@@ -145,7 +145,7 @@ VideoReader::~VideoReader(){

void VideoReader::SetVideoStream(int stream_nb) {
if (!fmt_ctx_) return;
- AVCodec *dec;
+ const AVCodec *dec;
int st_nb = av_find_best_stream(fmt_ctx_.get(), AVMEDIA_TYPE_VIDEO, stream_nb, -1, &dec, 0);
// LOG(INFO) << "find best stream: " << st_nb;
CHECK_GE(st_nb, 0) << "ERROR cannot find video stream with wanted index: " << stream_nb;
--
2.50.1 (Apple Git-155)

Loading