Metadata-Version: 2.4
Name: shot-detection
Version: 0.1.1
Summary: Standalone Python shot boundary detection package for splitting videos into shots
Project-URL: Homepage, https://github.com/Seeknetic/shot-detection-python
Project-URL: Repository, https://github.com/Seeknetic/shot-detection-python
Author: Seeknetic
License-Expression: Apache-2.0
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Multimedia :: Video
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Typing :: Typed
Requires-Python: >=3.9
Requires-Dist: numpy<3,>=1.24
Requires-Dist: onnxruntime<2,>=1.17
Requires-Dist: typing-extensions<5,>=4.9
Provides-Extra: dev
Requires-Dist: pytest<9,>=8; extra == 'dev'
Description-Content-Type: text/markdown

# Shot Detection Python

[![PyPI version](https://img.shields.io/pypi/v/shot-detection.svg)](https://pypi.org/project/shot-detection/)

Standalone Python package for shot boundary detection.

It takes a video file, runs TransNetV2-style ONNX inference on low-resolution RGB frames, and returns shot ranges with millisecond timestamps.

Built by [Seeknetic](https://www.seeknetic.com/). If you want to make video shots searchable and enable more professional video understanding workflows, visit [seeknetic.com](https://www.seeknetic.com/).

## What it does

- Probes the input video with `ffprobe`
- Extracts low-resolution frames with `ffmpeg`
- Runs sliding-window ONNX inference
- Finds shot boundaries
- Converts them into `{start_ms, end_ms}` shot segments

## Installation

```bash
pip install shot-detection
```

Import from `shot_detection`:

```python
from shot_detection import ShotDetector
```

You also need:

- `ffmpeg`
- `ffprobe`

On first run, the package downloads the default model automatically:

- URL: `https://download.shotai.io/model/shot-detection/transnetv2_open_fp16.onnx`
- cache dir:
  - Linux/macOS: `~/.cache/shot-detection/models/`
  - Windows: `%LOCALAPPDATA%\\shot-detection\\models\\`

You can override the cache root with `SHOT_DETECTION_CACHE_DIR`.

## Usage

```python
from shot_detection import ShotDetector

detector = ShotDetector()
shots = detector.detect("/path/to/video.mp4")

for shot in shots:
    print(shot.start_ms, shot.end_ms)
```

## Advanced usage

```python
from shot_detection import detect_shots

shots = detect_shots(
    video_path="/path/to/video.mp4",
    threshold=0.5,
    min_shot_duration_ms=500,
)
```

## Custom model path

```python
from shot_detection import ShotDetector

detector = ShotDetector(model_path="/path/to/custom-transnetv2.onnx")
```

## Notes

- The package expects the ONNX model input to accept 100-frame windows at `48x27` RGB.
- `ffmpeg` decode is adaptive: it prefers system-native hardware decoding when available and falls back to software decoding automatically.
- CUDA is intentionally not part of the default decode plan.

## Integration with Seeknetic SDK

If you want to run shot-level embedding or tagging jobs after boundary detection, you can combine this package with the [Seeknetic Python SDK](https://pypi.org/project/seeknetic/).

For the full workflow, see [docs/seeknetic-sdk-integration.md](docs/seeknetic-sdk-integration.md).

Install the SDK separately:

```bash
pip install seeknetic
```

Set `SEEKNETIC_API_KEY` before calling the SDK:

```bash
export SEEKNETIC_API_KEY="your_api_key"
```

A paid Seeknetic account and API access are required. You can get an API key from [accounts.seeknetic.com](https://accounts.seeknetic.com/).

### Quick example

```python
import os
import uuid

from seeknetic import Seeknetic
from shot_detection import ShotDetector

video_path = "/path/to/video.mp4"
shots = ShotDetector().detect(video_path)
client = Seeknetic(api_key=os.environ["SEEKNETIC_API_KEY"])
submitted_requests = {}

for shot in shots:
    request_id = str(uuid.uuid4())
    submitted_requests[shot.index] = {
        "request_id": request_id,
        "start_ms": shot.start_ms,
        "end_ms": shot.end_ms,
    }

    upload = client.preprocess_and_upload(
        video_path=video_path,
        service="embedding",
        request_id=request_id,
        start_ms=shot.start_ms,
        end_ms=shot.end_ms,
    )

    client.video_embedding.encode_video.submit_async(
        tensor={
            "video_input_key": upload.r2_keys["video_input"],
            "audio_input_key": upload.r2_keys["audio_input"],
        },
        request_id=upload.request_id,
    )

print(submitted_requests)
```

For tagging, switch `service="embedding"` to `service="tagging"` and use `client.video_tagging.submit_async_job(...)`.
