Skip to main content

A custom FFmpeg and FFprobe wrapper for precise frame extraction and video metadata

Project description

Custom FFmpeg

A pure FFmpeg/FFprobe wrapper for precise frame extraction and video metadata. Uses the same system-level FFmpeg as the ball detection pipeline — installed via apt-get install ffmpeg in Docker or your local system PATH.

Features

Feature Description
VFR support Per-frame PTS timestamps via ffprobe — accurate kinematic Δt for mobile VFR video
LazyFrameLoader Extracts all frames to PNG on disk, returns file paths for random access
StreamingFrameLoader Pipes raw BGR frames from FFmpeg stdout — zero disk I/O, ideal for YOLO inference
as_frame_generator() Single-pass generator on disk PNGs yielding (idx, timestamp, ndarray)
Reliable frame count nb_frames header → packet-count fallback for VFR/MKV/MOV containers
Same FFmpeg as the pipeline Uses system FFmpeg (apt-get install ffmpeg) — identical binary to the ball detection Docker image

Prerequisites

None. FFmpeg and FFprobe are automatically downloaded and managed by imageio-ffmpeg, which ships pre-compiled static binaries for:

Platform Architecture
Linux x86_64, arm64
macOS x86_64, arm64 (Apple Silicon)
Windows x86_64

The correct binary for your OS is selected automatically at runtime. No apt-get, brew, or manual PATH configuration required.


Installation

From a local path

# Standard
pip install /path/to/scripts/custom_ffmpeg

# Editable (recommended while developing)
pip install -e /path/to/scripts/custom_ffmpeg

From a Git repository

pip install git+https://github.com/yourusername/your-repo.git#subdirectory=scripts/custom_ffmpeg

Loader Selection Guide

Use case Recommended loader
YOLO / single-pass inference StreamingFrameLoader — zero disk I/O
Overlay rendering, random frame access LazyFrameLoader — PNG on disk, index by [i]
Single-pass with disk PNGs already extracted LazyFrameLoader.as_frame_generator()

Usage

1. StreamingFrameLoader — Zero-disk streaming for YOLO inference

Pipes raw BGR24 frames directly from FFmpeg stdout as numpy arrays. No PNGs are written to disk. Timestamps are pre-fetched via ffprobe before streaming begins so loader.timestamps[i] stays in sync with YOLO result index i.

from custom_ffmpeg import StreamingFrameLoader

loader = StreamingFrameLoader("sample_video.mp4")

print(f"FPS: {loader.fps}")
print(f"Resolution: {loader.width}x{loader.height}")
print(f"Total timestamps: {len(loader.timestamps)}")

# ── Use directly with YOLO ──────────────────────────────────────────────────
# from ultralytics import YOLO
# model = YOLO("yolov8n-pose.pt")
# results = model.track(source=loader.as_numpy_generator(), stream=True)
# for i, result in enumerate(results):
#     ts = loader.timestamps[i]   # exact PTS in seconds
#     dt = loader.get_dt(i)       # true Δt for kinematics

# ── Manual iteration (index + timestamp + ndarray) ─────────────────────────
for frame_idx, timestamp, frame in loader:
    print(f"Frame {frame_idx} @ {timestamp:.4f}s — shape: {frame.shape}")

2. LazyFrameLoader — Disk-backed random access

Extracts all frames to PNG files up front. __getitem__ returns the absolute file path. Use as_frame_generator() for a single-pass iteration that also returns numpy arrays.

from custom_ffmpeg import LazyFrameLoader

loader = LazyFrameLoader("sample_video.mp4", output_dir="/tmp/my_frames")

print(f"Total frames: {len(loader)}")
print(f"FPS: {loader.fps}")

# ── Random access by index → returns file path ─────────────────────────────
frame_path = loader[0]
print(f"First frame: {frame_path}")

# Load with any library you choose
# import cv2; frame = cv2.imread(frame_path)
# from PIL import Image; frame = Image.open(frame_path)

# ── VFR-accurate time delta ─────────────────────────────────────────────────
dt = loader.get_dt(1)               # Δt between frame 0 and frame 1
ts = loader.get_timestamp(10)       # absolute PTS of frame 10 in seconds

# ── Single-pass YOLO integration ────────────────────────────────────────────
# results = model.track(
#     source=(frame for _, _, frame in loader.as_frame_generator()),
#     stream=True,
# )
# loader.timestamps[i] aligns exactly with YOLO result index i

# ── Cleanup temp directory when done ───────────────────────────────────────
loader.release()

3. VideoUtils — Metadata without loading frames

Uses pure FFprobe. get_video_stats() automatically falls back to a packet-count pass when nb_frames is missing or zero (common on VFR/MOV/MKV containers).

from custom_ffmpeg import VideoUtils

stats = VideoUtils.get_video_stats("sample_video.mp4")
# Returns: { rotation, width, height, fps, frame_count }

if stats:
    print(f"Resolution : {stats['width']}x{stats['height']}")
    print(f"FPS        : {stats['fps']}")
    print(f"Frame count: {stats['frame_count']}")   # always accurate, even on VFR
    print(f"Rotation   : {stats['rotation']}°")

# Rotation only
rotation = VideoUtils.get_video_rotation("sample_video.mp4")
# Priority: Display Matrix → stream_tags.rotate → w>h portrait fallback

API Reference

StreamingFrameLoader(video_path, rotation=0)

Member Type Description
fps / avg_fps float Nominal container FPS (display only)
width, height int Frame dimensions after rotation
timestamps list[float] Per-frame PTS in seconds (ffprobe)
__iter__() yields (int, float, ndarray) (frame_idx, timestamp_sec, BGR array)
as_numpy_generator() yields ndarray Frame arrays only — pass directly to YOLO
get_timestamp(idx) float PTS in seconds for frame idx
get_dt(idx) float True Δt between frame idx and idx-1

LazyFrameLoader(video_path, rotation=0, output_dir=None)

Member Type Description
fps / avg_fps float Nominal container FPS (display only)
width, height int Frame dimensions
timestamps list[float] Per-frame PTS in seconds (ffprobe)
__getitem__(idx) str Absolute path to frame PNG
__len__() int Total frames extracted
as_frame_generator() yields (int, float, ndarray) On-demand disk reads (requires cv2)
get_timestamp(idx) float PTS in seconds for frame idx
get_dt(idx) float True Δt between frame idx and idx-1
release() Deletes the temporary frames directory

VideoUtils

Method Returns Description
get_video_stats(path) dict | None rotation, width, height, fps, frame_count
get_video_rotation(path) int Rotation angle in degrees (0/90/180/270)

Publishing to PyPI

  1. Install build tools:

    pip install build twine
    
  2. Build the package:

    python -m build
    

    Creates dist/custom_ffmpeg-x.y.z-py3-none-any.whl and .tar.gz.

  3. Test locally before uploading:

    # Validate metadata and README render
    twine check dist/*
    
    # Install in a clean environment
    python -m venv test_env && source test_env/bin/activate
    pip install dist/custom_ffmpeg-*.whl
    python -c "import custom_ffmpeg; print('OK')"
    deactivate && rm -rf test_env
    
  4. Upload to PyPI:

    python -m twine upload dist/*
    # Username: __token__   Password: pypi-... (your API token)
    

    (Use --repository testpypi to do a dry run on TestPyPI first.)

  • Lazy Frame Loader: Exact 1-to-1 mapping of encoded video frames to extracted images.
  • Video Utilities: Extract rotation, dimensions, and FPS relying purely on FFmpeg/FFprobe.
  • Zero third-party dependencies: Does not require opencv-python or other heavy image processing libraries.

Prerequisites

This library requires FFmpeg and FFprobe to be installed on your system and available in your system's PATH. You can verify your installation by running:

ffmpeg -version
ffprobe -version

Installation

You can install this module directly into any project using pip.

Install Locally

If you have cloned or downloaded this repository, navigate to your new project's environment and install it using the path to the custom_ffmpeg directory:

# Standard installation
pip install /path/to/custom_ffmpeg

# Editable installation (useful if you are actively developing the custom_ffmpeg module)
pip install -e /path/to/custom_ffmpeg

Install via Git

If this module is hosted in a git repository, you can install it directly via Git:

pip install git+https://github.com/yourusername/your-repo.git#subdirectory=scripts/custom_ffmpeg

Usage

1. Extracting Frames (LazyFrameLoader)

The LazyFrameLoader will use FFmpeg to extract frames into a temporary folder. Since there are no OpenCV dependencies, accessing an index will return the absolute file path to the extracted PNG frame.

from custom_ffmpeg import LazyFrameLoader

# Initialize the loader (this will automatically run ffmpeg to extract frames)
video_path = "sample_video.mp4"
loader = LazyFrameLoader(video_path)

print(f"Total frames extracted: {len(loader)}")
print(f"Video FPS: {loader.fps}")

# Access the first frame
frame_path = loader[0]
print(f"Path to first frame: {frame_path}")

# Calculate true time delta (dt) between frames for accurate kinematics
# This is especially important for VFR (Variable Frame Rate) videos.
dt = loader.get_dt(1)
print(f"Time delta between frame 0 and 1: {dt} seconds")

# Cleanup the temporary extracted frames directory when done
loader.release()

2. Getting Video Metadata (VideoUtils)

VideoUtils uses FFprobe to extract accurate metadata from the video without loading any frames.

from custom_ffmpeg import VideoUtils

video_path = "sample_video.mp4"
stats = VideoUtils.get_video_stats(video_path)

if stats:
    print(f"Resolution: {stats['width']}x{stats['height']}")
    print(f"FPS: {stats['fps']}")
    print(f"Total Frames: {stats['frame_count']}")
    print(f"Rotation: {stats['rotation']} degrees")

Publishing to PyPI

If you want to publish this package to the public Python Package Index (PyPI) or a private repository, follow these steps:

  1. Install Build Tools: Ensure you have build and twine installed in your environment.

    pip install build twine
    
  2. Build the Package: Run the following command from the same directory as pyproject.toml. It will create a dist/ folder containing a .tar.gz source archive and a .whl built distribution.

    python -m build
    
  3. Test the Build Locally (Optional but Recommended): Before uploading, it is a good idea to verify the built package locally.

    First, check if the package description will render correctly on PyPI:

    twine check dist/*
    

    Next, you can test installing the compiled .whl file in a fresh virtual environment to ensure it works exactly as the end-user will experience it:

    # Create a test virtual environment
    python -m venv test_env
    source test_env/bin/activate
    
    # Install the built wheel file
    pip install dist/custom_ffmpeg-0.1.0-py3-none-any.whl
    
    # Verify the import works
    python -c "import custom_ffmpeg; print('Success!')"
    
    # Clean up and exit
    deactivate
    rm -rf test_env
    
  4. Upload to PyPI: Use twine to upload the generated archives. You will be prompted for your PyPI API token (username is __token__).

    python -m twine upload dist/*
    

    (To test the upload safely without publishing to the real PyPI, use TestPyPI: python -m twine upload --repository testpypi dist/*)

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

custom_ffmpeg_v-0.1.1.tar.gz (16.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

custom_ffmpeg_v-0.1.1-py3-none-any.whl (12.7 kB view details)

Uploaded Python 3

File details

Details for the file custom_ffmpeg_v-0.1.1.tar.gz.

File metadata

  • Download URL: custom_ffmpeg_v-0.1.1.tar.gz
  • Upload date:
  • Size: 16.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.9

File hashes

Hashes for custom_ffmpeg_v-0.1.1.tar.gz
Algorithm Hash digest
SHA256 d3bf18959437769fdd7558082be1ce95c8ab7bcad8d7870c3f8f1355147280f6
MD5 a39d290cf2cd33339745141114fd97fc
BLAKE2b-256 810d88b52a983e3c90d2f93445a2c4d35741eadaa2837e61e4253979345e4a3d

See more details on using hashes here.

File details

Details for the file custom_ffmpeg_v-0.1.1-py3-none-any.whl.

File metadata

File hashes

Hashes for custom_ffmpeg_v-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 74c3305ce216618f107df6021029ed4f7215e7e45c52f9c00d0fa8e062869062
MD5 20dd933e9573993dd55ac0467e316b40
BLAKE2b-256 0969ca7ed4a8b2cd1ca595cc3532b777c0f2da52659d6472e78d83441d61fec7

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page