Metadata-Version: 2.4
Name: bfvt
Version: 0.1.0
Summary: Black-frame video trimmer: detect long black + silent stretches and remove all but the first moment of each.
Author: smolkai
License: MIT
Keywords: video,ffmpeg,opencv,trim,black-frame,silence
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Multimedia :: Video
Classifier: Environment :: Console
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: numpy>=1.23
Requires-Dist: opencv-python-headless>=4.7
Provides-Extra: test
Requires-Dist: pytest>=7.0; extra == "test"
Dynamic: license-file

# bfvt — black-frame video trimmer

Scan a video, find long stretches that are **simultaneously black and silent**,
and remove all but the first moment of each — then write a re-encoded, trimmed
file. Built for chopping the dead air out of recordings: the leader/trailer
black, the gap between segments, the "I forgot to stop the screen recorder"
tail.

Tiny stray bright pixels (codec ringing, a dead-pixel speck, a faint watermark)
are still treated as black, so a frame doesn't get disqualified just because a
handful of pixels aren't perfectly `#000000`.

## How it decides what to cut

1. **Black detection (OpenCV).** Frames are sampled (default 10 fps). A frame
   counts as *black* when the fraction of pixels brighter than a luma threshold
   is at or below a small ratio (default 0.2%) — that ratio is the "ignore
   stray pixels" knob.
2. **Silence detection (ffmpeg `silencedetect`).** Audio below a noise floor
   (default −30 dB) is silence. Videos with no audio track are treated as
   wholly silent, so black detection alone drives the cut.
3. **Interval algebra (pure, deterministic).** The black intervals are
   intersected with the silent intervals. Each overlapping run that lasts at
   least `--min-duration` (default 5 s) is trimmed, **keeping the first
   `--keep-head` seconds** (default 0.25 s) so a hard chapter cut doesn't become
   a jarring jump. Everything else in the run is removed.
4. **Re-encode (ffmpeg).** The kept segments are concatenated via a
   `trim`/`atrim` + `concat` filtergraph and re-encoded (libx264 + AAC).

Given the same input and flags, the output is deterministic.

## Install

```bash
pip install .
# or, for development + tests:
pip install -e ".[test]"
```

Requires `ffmpeg` and `ffprobe` on `PATH`. OpenCV is pulled in via
`opencv-python-headless`.

## Usage

```bash
# Trim, writing alongside the input as <name>.trimmed.mp4
bfvt input.mp4

# Choose the output and the threshold
bfvt input.mp4 -o clean.mp4 --min-duration 5 --keep-head 0.25

# See what WOULD be cut without re-encoding
bfvt input.mp4 --dry-run

# Trim on darkness alone (ignore audio)
bfvt input.mp4 --ignore-audio
```

### Key options

| Flag | Default | Meaning |
| --- | --- | --- |
| `--min-duration SEC` | `5.0` | Minimum black+silent run before anything is trimmed |
| `--keep-head SEC` | `0.25` | Seconds at the start of each run to preserve |
| `--luma-threshold N` | `32` | 0–255 luma above which a pixel is "bright" |
| `--bright-ratio R` | `0.002` | Max fraction of bright pixels for a frame to still be black |
| `--sample-fps FPS` | `10` | Frame sampling rate for black detection |
| `--noise-db DB` | `-30` | Audio below this dB is silence |
| `--ignore-audio` | off | Trim on darkness only |
| `--crf` / `--preset` | `18` / `medium` | libx264 quality/speed |
| `--dry-run` | off | Report cuts, write nothing |

## Library

```python
from bfvt import TrimConfig, analyze, run

cfg = TrimConfig(min_duration=5.0, keep_head=0.25)
res = analyze("input.mp4", cfg)      # inspect res.black / res.silent / res.cuts
run("input.mp4", "out.mp4", cfg)     # do the trim
```

The interval logic is exposed directly (`merge_segments`,
`intersect_segments`, `black_silent_cuts`, `keep_segments_from_cuts`) and is
pure/dependency-free.

## Tests

```bash
pytest -q                  # all tests (integration generates a real clip)
pytest -q -m "not integration"   # pure unit tests only, no ffmpeg/OpenCV needed
```

The unit tests cover the interval algebra, the black-frame pixel classifier
(including the stray-pixel tolerance), the `silencedetect` log parser, and the
ffmpeg filtergraph builder. The integration test synthesizes a
blue → black/silent → green clip with ffmpeg, runs the whole pipeline, and
asserts the black stretch is detected and removed.

## License

MIT — see [LICENSE](LICENSE).
