Skip to content
Closed
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
15 changes: 10 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,13 @@ jobs:
with:
pixi-version: v0.81.0
run-install: false
global-environments: ffmpeg=7.1.1=gpl_* --with libclang=18 --with pkg-config
global-environments: ffmpeg=7.1.1=gpl_* --with libclang=18 --with pkg-config --with libheif --with libwebp --with nasm
global-cache: true
- name: Install build tools
run: |
python -m venv .venv
.venv/bin/python -m pip install --upgrade pip
.venv/bin/pip install numpy pytest ruff 'maturin>=1.8,<2'
.venv/bin/pip install numpy pytest ruff 'maturin>=1.8,<2' 'Pillow>=11.3' cmake ninja meson
echo "$GITHUB_WORKSPACE/.venv/bin" >> "$GITHUB_PATH"
echo "VIRTUAL_ENV=$GITHUB_WORKSPACE/.venv" >> "$GITHUB_ENV"
- name: Configure prebuilt FFmpeg
Expand All @@ -49,17 +49,22 @@ jobs:
echo "PKG_CONFIG_PATH=$prefix/lib/pkgconfig" >> "$GITHUB_ENV"
echo "BINDGEN_EXTRA_CLANG_ARGS=-I$(gcc -print-file-name=include)" >> "$GITHUB_ENV"
"$prefix/bin/ffmpeg" -version
- name: Build dedicated image libraries
run: |
bash scripts/build_image_deps.sh "$PWD/.native-deps/images"
echo "PKG_CONFIG_PATH=$PWD/.native-deps/images/lib/pkgconfig:$PKG_CONFIG_PATH" >> "$GITHUB_ENV"
echo "LD_LIBRARY_PATH=$PWD/.native-deps/images/lib:$LD_LIBRARY_PATH" >> "$GITHUB_ENV"
- name: Static checks
run: |
ruff check src/tensorcodec tests scripts
ruff format --check src/tensorcodec tests scripts
ruff check src/tensorcodec tests scripts benchmarks/image_decode.py
ruff format --check src/tensorcodec tests scripts benchmarks/image_decode.py
cargo fmt --manifest-path native/Cargo.toml --check
cargo clippy --manifest-path native/Cargo.toml --locked -- -D warnings
- name: Build extension against prebuilt FFmpeg
run: maturin develop --locked
- name: Install pinned reference
run: python -m pip install torch==2.14.1 torchcodec==0.17.0 --index-url https://download.pytorch.org/whl/cpu
- name: Validate independent contract against reference
run: pytest tests/test_video_contract.py tests/test_audio_contract.py --backend torchcodec
run: pytest tests/test_video_contract.py tests/test_audio_contract.py tests/test_images.py --backend torchcodec
- name: Run contract and differential tests
run: pytest --compare
6 changes: 3 additions & 3 deletions .github/workflows/macos-wheels.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,17 +31,17 @@ jobs:
global-cache: true
- name: Install build tools
run: |
brew install nasm pkg-config coreutils meson ninja
brew install nasm pkg-config coreutils meson ninja cmake
uv venv
uv pip install numpy pytest 'maturin>=1.8,<2' delocate twine
uv pip install numpy pytest 'maturin>=1.8,<2' delocate twine 'Pillow>=11.3'
echo "$PWD/.venv/bin" >> "$GITHUB_PATH"
echo "LIBCLANG_PATH=$(xcode-select -p)/Toolchains/XcodeDefault.xctoolchain/usr/lib" >> "$GITHUB_ENV"
echo "$HOME/.pixi/envs/ffmpeg/bin" >> "$GITHUB_PATH"
- uses: actions/cache/restore@v6
id: native-cache
with:
path: .native-deps/macos
key: macos14-${{ matrix.arch }}-${{ hashFiles('scripts/build_openssl.sh', 'scripts/build_dav1d.sh', 'scripts/build_ffmpeg.sh', 'scripts/build_macos_wheel.sh') }}
key: macos14-${{ matrix.arch }}-${{ hashFiles('scripts/build_openssl.sh', 'scripts/build_dav1d.sh', 'scripts/build_ffmpeg.sh', 'scripts/build_image_deps.sh', 'scripts/build_macos_wheel.sh') }}
- name: Build and bundle wheel
run: bash scripts/build_macos_wheel.sh
- uses: actions/cache/save@v6
Expand Down
9 changes: 6 additions & 3 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ jobs:
id: native-cache
with:
path: .native-deps
key: manylinux2014-${{ matrix.target }}-${{ hashFiles('scripts/build_nasm.sh', 'scripts/build_openssl.sh', 'scripts/build_dav1d.sh', 'scripts/build_ffmpeg.sh') }}
key: manylinux2014-${{ matrix.target }}-${{ hashFiles('scripts/build_nasm.sh', 'scripts/build_openssl.sh', 'scripts/build_dav1d.sh', 'scripts/build_ffmpeg.sh', 'scripts/build_image_deps.sh') }}
- name: Build portable Linux wheel
uses: PyO3/maturin-action@v1
with:
Expand All @@ -50,16 +50,19 @@ jobs:
export LD_LIBRARY_PATH="$prefix/openssl/lib:$prefix/ffmpeg/lib"
if [ ! -f "$prefix/ready" ]; then
yum install -y perl-core
/opt/python/cp310-cp310/bin/python -m pip install meson==1.12.1 ninja==1.13.2
/opt/python/cp310-cp310/bin/python -m pip install meson==1.12.1 ninja==1.13.2 cmake==4.4.3
export PATH="/opt/python/cp310-cp310/bin:$PATH"
if [ "$(uname -m)" = x86_64 ]; then
bash scripts/build_nasm.sh "$prefix/nasm"
export PATH="$prefix/nasm/bin:$PATH"
fi
bash scripts/build_openssl.sh "$prefix/openssl"
bash scripts/build_ffmpeg.sh "$prefix/ffmpeg"
bash scripts/build_image_deps.sh "$prefix/images"
touch "$prefix/ready"
fi
export PKG_CONFIG_PATH="$prefix/images/lib/pkgconfig:$PKG_CONFIG_PATH"
export LD_LIBRARY_PATH="$prefix/images/lib:$LD_LIBRARY_PATH"
export FFMPEG_DIR="$prefix/ffmpeg"
- name: Check native cache completeness
if: always()
Expand Down Expand Up @@ -87,7 +90,7 @@ jobs:
sudo apt-get update
sudo apt-get install -y ffmpeg
uv venv
uv pip install pytest numpy twine dist/*.whl
uv pip install pytest numpy twine 'Pillow>=11.3' dist/*.whl
- name: Generate baseline runtime fixtures
run: uv run --no-sync python scripts/check_wheel_runtime.py generate .runtime-fixtures
- name: Test on glibc 2.17 with Python 3.10 and 3.13
Expand Down
31 changes: 25 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

# TensorCodec

CPU video/audio decoding with TorchCodec-style APIs and NumPy output.
CPU video/audio/image decoding with TorchCodec-style APIs and NumPy output.

<p align="center">
<a href="https://github.com/MilkClouds/tensorcodec/actions/workflows/ci.yml"><img src="https://github.com/MilkClouds/tensorcodec/actions/workflows/ci.yml/badge.svg?branch=main" alt="CI"></a>
Expand All @@ -18,7 +18,7 @@ CPU video/audio decoding with TorchCodec-style APIs and NumPy output.

</div>

- **TorchCodec API without PyTorch.** CPU video/audio decoder interfaces follow
- **TorchCodec API without PyTorch.** CPU video/audio/image decoder interfaces follow
TorchCodec and return NumPy arrays.
- **Validated playback semantics.** Frame selection, ordering, timestamps and audio
ranges are checked against TorchCodec 0.17.0 and independently generated media.
Expand Down Expand Up @@ -50,9 +50,25 @@ with AudioDecoder("audio.wav", sample_rate=16000, num_channels=1) as audio:
waveform = samples.data # float32: (channels, samples)
```

Arrays keep their storage after the decoder closes. Paths, URLs, encoded bytes,
Arrays keep their storage after the decoder closes. Video/audio paths, URLs, encoded bytes,
1-D uint8 arrays and seekable file objects are supported.

Image decoding uses TorchCodec's function API and returns CHW arrays (NCHW for animations):

```python
from tensorcodec.decoders import decode_image, decode_jpeg, decode_png

rgb = decode_image("image.webp")
batch = decode_jpeg(["left.jpg", "right.jpg"])
depth = decode_png("depth.png", mode="UNCHANGED", output_dtype="auto")
```

Images use dedicated backends: Rust `png`, libjpeg-turbo, libwebp/libwebpdemux,
giflib and libavif. These are bundled in wheels; FFmpeg handles audio/video.
HEIC/HEIF optionally loads system `libheif`, as it does in TorchCodec.
No Pillow or PyTorch runtime dependency is added. See [image compatibility](docs/compatibility.md#images)
for color modes, high-depth conversion, input types and limitations.

For time-based windows without an initial full packet scan (since v0.1.4):

```python
Expand All @@ -75,14 +91,14 @@ indices, `len(decoder)`, or `get_all_frames()`. See [the contract](docs/compatib

## Features

TensorCodec 0.1.5 relative to TorchCodec 0.17.0.
Current source relative to TorchCodec 0.17.0; image functions are not in the 0.1.5 release.
✓ supported · △ partial support · — not implemented.

| Component | TensorCodec | TorchCodec 0.17.0 |
| --- | --- | --- |
| Video decoder | △ CPU, SDR/HDR RGB | ✓ CPU / CUDA |
| Audio decoder | ✓ CPU | ✓ CPU |
| Image decoders | — | ✓ |
| Image decoders | △ CPU; HEIC needs optional system libheif | ✓ CPU; JPEG also CUDA |
| Video / audio / image encoders | — | ✓ |
| Clip samplers | — | ✓ |
| Decoder transforms | ✓ Resize, CenterCrop, RandomCrop | ✓ |
Expand Down Expand Up @@ -174,7 +190,10 @@ See [container behavior](docs/container_robustness.md) for seek limitations and
<summary>Build from source and run tests</summary>

Source builds require Rust 1.88+, Clang/libclang, pkg-config and FFmpeg 7 development
headers/libraries. Python handles API and playback selection; Rust + PyO3 handles
headers/libraries, plus libjpeg-turbo, libwebp/libwebpdemux and libavif.
`scripts/build_image_deps.sh PREFIX` builds the image libraries; put
`PREFIX/lib/pkgconfig` in `PKG_CONFIG_PATH` and `PREFIX/lib` on the library search
path. It needs CMake, Ninja, Meson and NASM on x86. Python handles API and playback selection; Rust + PyO3 handles
FFmpeg. Native decoding releases the GIL, allowing separate decoder instances to
run concurrently across Python threads. Calls on the same instance are serialized.
The default is one FFmpeg thread per decoder; use independent workers for concurrent
Expand Down
Loading
Loading