Metadata-Version: 2.4
Name: video-processor-library
Version: 0.1.0
Summary: A video processor library built on custom FFmpeg wrappers for precise frame extraction and video metadata
Author-email: kishan0822 <kishan@sportzengage.ai>
Project-URL: Homepage, https://github.com/sportzengage/detection_trajectory_complete_pipeline
Classifier: Programming Language :: Python :: 3
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Requires-Python: >=3.7
Description-Content-Type: text/markdown
Requires-Dist: imageio-ffmpeg>=0.5.1

# Frame Extraction Library

A small Python toolkit for extracting frames from video files with FFmpeg, while keeping frames precisely aligned to their true (possibly variable) timestamps via FFprobe.

It's built around three pieces:

- **`FFMMPEG_CommandBuilder`** — locates the `ffmpeg`/`ffprobe` binaries (via `imageio_ffmpeg`, falling back to system `PATH`) and exposes version helpers.
- **`VideoProbe`** — a lazy, cached wrapper around `ffprobe` that exposes geometry, rotation, codec, timing, color, audio, and packet-level statistics (VFR/VBR detection) for a video file.
- **`VideoProcessor`** — extracts frames to disk with `ffmpeg` and pairs each frame index with its exact presentation timestamp (PTS) from `VideoProbe`, so downstream kinematic/analytics code never has to assume a constant frame rate.

## Why this exists

Naively extracting frames and assuming a fixed FPS breaks down on variable-frame-rate (VFR) video — common in phone-recorded footage. This library:

1. Extracts frames with `ffmpeg`, respecting device rotation metadata.
2. Independently probes the *true* per-frame timestamps with `ffprobe`.
3. Validates that the frame count and timestamp count agree, truncating defensively if `ffmpeg` and `ffprobe` disagree (e.g. a dropped frame), to prevent silent index misalignment.
4. Gives you exact `Δt` between any two frames — essential for correct velocity/kinematic calculations on VFR footage.

## Installation

Requires Python 3.10+ and `imageio_ffmpeg`:

```bash
pip install imageio-ffmpeg
```


## Quick start: extracting frames

```python
from your_package import VideoProcessor
from your_package.extraction_enum import ExtractionRotation, ExtractionFPSmode

processor = VideoProcessor(
    video_path="input.mp4",
    rotation=ExtractionRotation.ROTATION_90_CW,   # or ROTATION_180_CW / ROTATION_270_CW / a "no rotation" value
    output_dir="frames/",
    fps_mode=ExtractionFPSmode.PASSTHROUGH,       # whichever ExtractionFPSmode your enum defines
    filename_prefix="clip01",           # optional, produces clip01_frame_0001.png, ...
)

# 1. Extract timestamps first (or after — order doesn't matter, each call
#    cross-validates against the other if both have already run)
timestamps = processor.extract_timestamps()

# 2. Extract frames to disk
frame_files = processor.extract_frames_to_disk()

print(f"Extracted {len(processor)} frames")
print(f"Average FPS: {processor.fps}")

# Exact timestamp of frame 10
t = processor.get_timestamp(10)

# True Δt between frame 10 and frame 9 (handles VFR correctly)
dt = processor.get_dt(10)
```

### Notes on `VideoProcessor`

- The constructor does **no** I/O — it just validates that `output_dir` exists. Nothing is probed or extracted until you explicitly call `extract_timestamps()` and/or `extract_frames_to_disk()`.
- Frames are written as `{prefix_}frame_%04d.png` into `output_dir`.
- `rotation` controls an `ffmpeg -vf transpose=...` filter applied during extraction (not just a metadata flag) — the pixels are physically rotated, and `-noautorotate` plus `-map_metadata -1` are used so the source's own rotation metadata doesn't get applied twice.
- `fps_mode` controls FFmpeg's `-fps_mode` flag (e.g. passthrough vs. constant-frame-rate resampling).
- If both `extract_timestamps()` and `extract_frames_to_disk()` have been called, each one re-validates that the timestamp count matches the frame count, truncating both lists to the shorter length if they disagree (logged as a `CRITICAL` error) — this guards against `ffmpeg`/`ffprobe` disagreeing on frame count.
- `get_timestamp(idx)` falls back to `idx / avg_fps` if `extract_timestamps()` was never called (and raises if `avg_fps` is also unset).
- `get_dt(0)` always returns `0.0`.

## Probing a video directly

You can also use `VideoProbe` on its own, without extracting any frames — useful for inspecting a video's properties up front:

```python
from your_package.video_probe import VideoProbe

probe = VideoProbe("input.mp4")

probe.display_dimensions      # DisplayDimensions(width=..., height=...) — post-rotation
probe.coded_dimensions        # CodedDimensions — as stored in the file
probe.rotation                # RotationResult(degrees=..., source=...)
probe.fps                     # average FPS
probe.duration                # seconds, or None
probe.codec                   # e.g. "h264"
probe.frame_timestamps        # tuple of per-frame PTS in seconds (dedicated ffprobe scan)
probe.exact_frame_count       # authoritative count from a packet scan
probe.packet_stats            # PacketStats: cfr/vfr, cbr/vbr, per-second fps/bitrate curves
```



## Checking your FFmpeg install

```python
from your_package.command_build import FFMMPEG_CommandBuilder

cmd = FFMMPEG_CommandBuilder()
print(cmd.get_ffmpeg_version())              # via `ffmpeg -version`
print(cmd.get_ffmpeg_version_via_ffprobe())   # via `ffprobe -show_program_version`
```

## Module layout

| Module | Responsibility |
|---|---|
| `video_processor.py` | `VideoProcessor` — frame extraction + timestamp alignment |
| `video_probe.py` | `VideoProbe` — lazy, cached metadata/timing/packet-stats probing |
| `command_build.py` | `FFMMPEG_CommandBuilder` — resolves `ffmpeg`/`ffprobe` binaries and versions |
| `extraction_enum.py` | `ExtractionRotation`, `ExtractionFPSmode` enums |
| `video_types.py` | Data types: `CodedDimensions`, `DisplayDimensions`, `VideoGeometry`, `RotationResult`, `RotationSource`, `PacketStats` |
| `_ffprobe.py` | Low-level `ffprobe` call wrappers: `probe_streams`, `probe_packets`, `probe_frame_timestamps` |

> Replace `your_package` above with the actual import path once the package is named/installed in your project.
