framesig
Find when something happens on screen — in any video, from any game or source — by its pixel signature.
framesig doesn't know what a "kill" or a "death screen" looks like — and it doesn't need to. You describe an event as a region of the frame + a colour/brightness signature in a few lines of YAML, and framesig scans the video and hands you back the timestamps where that signature appears. Red flash in the HUD, a coloured kill-feed row, a fade to black, a hard cut — same tiny config, no model, no training, no per-game code.
Above: framesig watching four independent regions of a synthetic clip. Boxes light up the instant their signature fires; the number is the live score.
Why it's useful
- Source-agnostic. It only ever reasons about pixels in a rectangle, never about the game. The same tool works on League, CS, a slideshow, or security footage.
- Declarative. Events live in YAML, not code. Tweak a region or a threshold, re-run — no recompiling, no re-scanning (see caching below).
- Cheap to re-tune. Scanning a video is the expensive part; framesig caches the raw per-frame scores, so changing a threshold is instant.
- Self-contained demo.
framesig demorenders its own test clip with ffmpeg, scans it, and charts the result — no external video or model to download.
Features
- Four built-in detectors:
channel_dominance,color_fraction,brightness,scene_change. - Regions in resolution-independent fractions (or absolute pixels).
- Frame sub-sampling at a configurable rate for speed.
- JSON event output, with peak time, peak/mean score, duration and sample count per event.
- On-disk score cache keyed so that changing a threshold reuses the scan, while changing a detector invalidates it.
- A pure-OpenCV score-timeline chart renderer (no matplotlib dependency).
- Clean Python API and a
framesigCLI.
Install
framesig needs only numpy, opencv-python-headless and PyYAML. (ffmpeg on your PATH is optional — it's used only to generate the sample clip in gen-sample / demo; scanning real videos does not need it.)
git clone https://github.com/ferinazumaDEV/framesig
cd framesig
python -m venv .venv && . .venv/bin/activate
pip install -e ".[dev]"
Quickstart
Render a synthetic test clip, scan it, and chart the detections in one command:
$ framesig demo
[1/3] rendering synthetic clip with ffmpeg...
[2/3] scanning for pixel signatures...
sample.mp4 640x360 15.0s 150 samples @ 10.0 fps (scan)
7 event(s) across 4 signature(s)
death_screen: 2
[ 2.00s -> 2.40s] peak 1.00 @ 2.00s (5 samples)
[ 7.50s -> 7.90s] peak 1.00 @ 7.50s (5 samples)
kill_feed: 3
[ 4.00s -> 4.30s] peak 0.98 @ 4.00s (4 samples)
[ 4.80s -> 5.00s] peak 0.98 @ 4.80s (3 samples)
[ 9.40s -> 9.80s] peak 0.98 @ 9.40s (5 samples)
white_flash: 1
[ 11.00s -> 11.30s] peak 0.98 @ 11.00s (4 samples)
scene_cut: 1
[ 13.00s -> 13.00s] peak 0.35 @ 13.00s (1 samples)
[3/3] rendering score-timeline chart...
done. outputs in framesig_demo/
Every event lands exactly where the clip was painted — two death flashes, three kill-feed rows, one white flash, one scene cut. The chart it writes:
Each lane is one signature: the blue curve is the raw score, the red dashed line is the threshold, the green bands are the detected events.
Usage
CLI
$ framesig gen-sample sample.mp4
wrote sample.mp4 (640x360, 15s)
$ framesig scan sample.mp4 -c examples/flash.yaml -o events.json
sample.mp4 640x360 15.0s 150 samples @ 10.0 fps (scan)
7 event(s) across 4 signature(s)
death_screen: 2
[ 2.00s -> 2.40s] peak 1.00 @ 2.00s (5 samples)
...
wrote events.json
Run it again and the scan is served from cache — note the (cache) tag:
$ framesig scan sample.mp4 -c examples/flash.yaml -o events.json
sample.mp4 640x360 15.0s 150 samples @ 10.0 fps (cache)
events.json (one signature shown):
{
"video": "sample.mp4",
"meta": { "native_fps": 30.0, "frame_count": 450, "width": 640, "height": 360,
"sample_fps": 10.0, "step": 3, "sample_period": 0.1,
"samples": 150, "duration": 15.0 },
"from_cache": false,
"events": {
"death_screen": [
{ "signature": "death_screen", "start": 2.0, "end": 2.4, "duration": 0.5,
"peak_t": 2.0, "peak_score": 1.0, "mean_score": 1.0, "samples": 5 }
]
}
}
Handy flags: --sample-fps N (override sampling rate), --no-cache, --chart timeline.png, -q.
Config
A signature is a detector applied to a region, plus rules for turning the score timeline into discrete events:
sample_fps: 10 # analyse ~10 frames per second of video
cache_dir: .framesig_cache # optional; default is a .framesig_cache
# folder next to the video
regions: # bounds are fractions of the frame by default
hud_top: { x: 0.00, y: 0.00, w: 1.00, h: 0.55 }
kill_feed: { x: 0.08, y: 0.74, w: 0.84, h: 0.18 }
minimap: { x: 1500, y: 800, w: 400, h: 250, unit: pixels } # absolute pixels
signatures:
- name: death_screen
region: hud_top
detector: channel_dominance # "relative red", robust to compression
params: { channel: red, gain: 2.0 }
threshold: 0.30 # score >= 0.30 counts as active
min_duration: 0.15 # drop blips shorter than 0.15 s
merge_gap: 0.25 # bridge flickers up to 0.25 s apart
- name: kill_feed
region: kill_feed
detector: color_fraction # % of red pixels in the band (two HSV ranges: red wraps hue)
params:
hsv_low: [0, 120, 70]
hsv_high: [10, 255, 255]
hsv_low2: [170, 120, 70]
hsv_high2: [179, 255, 255]
threshold: 0.20
See examples/flash.yaml for the full four-signature config.
Python API
from framesig import load_config, scan_video, detect_all
config = load_config("examples/flash.yaml")
result = scan_video("sample.mp4", config) # scores get cached on disk
events = detect_all(config, result) # applying thresholds is free
for name, evs in events.items():
for e in evs:
print(f"{name}: {e.peak_t:.2f}s (score {e.peak_score:.2f})")
death_screen: 2.00s (score 1.00)
death_screen: 7.50s (score 1.00)
kill_feed: 4.00s (score 0.98)
...
Detectors
| detector | measures | good for | key params |
|---|---|---|---|
channel_dominance |
how much one BGR channel beats the other two | red death/kill flashes; survives compression, ignores brightness | channel, gain |
color_fraction |
fraction of pixels inside one or more HSV ranges | coloured HUD elements (kill feed, objective banners) | hsv_low/high, hsv_low2/high2 |
brightness |
mean luminance | white flashes (invert: false), fades to black (invert: true) |
invert |
scene_change |
mean absolute difference from the previous sampled frame | hard cuts, big transitions | — |
framesig detectors lists them at runtime.
How it works
video ──▶ sub-sample frames ──▶ crop each region ──▶ detector score in [0,1]
│
per-frame score timelines
│
┌─────────────────────┴─── cached on disk ───┐
▼ │
threshold · merge_gap · min_duration │
▼ │
events ◀── re-run with new thresholds, free ◀────┘
- Sample. The scanner walks the video once and keeps roughly
sample_fpsframes per second. - Score. Each signature crops its region and asks its detector for one number in
[0, 1]. - Cache. Those score timelines are written to
.framesig_cache/, keyed by a fingerprint of the video and of the score-relevant config (sampling rate, regions, detector params) — but not thresholds. So re-tuning a threshold is a cache hit; changing a detector transparently invalidates it. - Detect. Thresholding turns each timeline into events: consecutive active samples become a run, nearby runs merge (
merge_gap), too-short runs are dropped (min_duration).
Testing
$ pytest
88 passed
The suite includes an end-to-end test that renders the synthetic clip with ffmpeg and asserts framesig recovers exactly the events baked into it, at the right timestamps — plus unit tests for every detector, the event logic, config validation, the scanner's error paths and the cache.
Sibling tools
framesig is one of a set of small, dependency-light tools I build and maintain in the open — focused utilities that each do one job well and turn messy input into clean, structured output. If framesig fits into your pipeline, these siblings share the same engineering-first philosophy:
- The GEO Handbook — the open reference on getting content cited by AI answer engines (ChatGPT, Perplexity, Google AI Overviews, Gemini, Copilot).
- typedout — reliable structured output from any LLM: schema-validated JSON with tolerant repair and retries.
- politeclient — a polite, bulletproof HTTP client for Python: retries with backoff, per-host rate-limiting, caching, pagination.
- scaffld — scaffold fully-wired Python projects (tests, CI, pre-commit, license) from templates, with a TUI.
- Hub & writing: zentimes.es.
By ferinazumaDEV.
License
MIT — see LICENSE.
Built by Fernando (@ferinazumaDEV).
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file framesig-0.1.0.tar.gz.
File metadata
- Download URL: framesig-0.1.0.tar.gz
- Upload date:
- Size: 37.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.11.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e9d0eb9f3b8aff971972afdcf04a71b424f47415e1a4c5f194ffb2a68c50b252
|
|
| MD5 |
1fc3bd7fc1e3df10b8d339b80ff0d56f
|
|
| BLAKE2b-256 |
d0fa540b9d3b9baee2ec3d6a109435965dbad4a0ff497ef2b0d77921faa4e995
|
File details
Details for the file framesig-0.1.0-py3-none-any.whl.
File metadata
- Download URL: framesig-0.1.0-py3-none-any.whl
- Upload date:
- Size: 30.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.11.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a8022dbfafbff650b87283537f606b1b1344b0d963a5a61ec499f633f12028cf
|
|
| MD5 |
8811d059daabcd004c6331af8e9fd22a
|
|
| BLAKE2b-256 |
5ec8328d47dc0d8ef17ed44ecc87a0976ffa6bb9202a6317ac5301c22d88c210
|