frameproof 0.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. frameproof-0.4.0/LICENSE +21 -0
  2. frameproof-0.4.0/PKG-INFO +201 -0
  3. frameproof-0.4.0/README.md +175 -0
  4. frameproof-0.4.0/frameproof/__init__.py +24 -0
  5. frameproof-0.4.0/frameproof/__main__.py +487 -0
  6. frameproof-0.4.0/frameproof/analyze.py +293 -0
  7. frameproof-0.4.0/frameproof/assets/agents/frameproof-adversary.md +66 -0
  8. frameproof-0.4.0/frameproof/assets/skill/SKILL.md +171 -0
  9. frameproof-0.4.0/frameproof/budget.py +83 -0
  10. frameproof-0.4.0/frameproof/extract.py +110 -0
  11. frameproof-0.4.0/frameproof/fetch.py +147 -0
  12. frameproof-0.4.0/frameproof/index.py +300 -0
  13. frameproof-0.4.0/frameproof/ocr.py +126 -0
  14. frameproof-0.4.0/frameproof/probe.py +81 -0
  15. frameproof-0.4.0/frameproof/py.typed +0 -0
  16. frameproof-0.4.0/frameproof/report.py +107 -0
  17. frameproof-0.4.0/frameproof/select.py +383 -0
  18. frameproof-0.4.0/frameproof/transcribe.py +226 -0
  19. frameproof-0.4.0/frameproof/util.py +79 -0
  20. frameproof-0.4.0/frameproof/verify.py +302 -0
  21. frameproof-0.4.0/frameproof.egg-info/PKG-INFO +201 -0
  22. frameproof-0.4.0/frameproof.egg-info/SOURCES.txt +30 -0
  23. frameproof-0.4.0/frameproof.egg-info/dependency_links.txt +1 -0
  24. frameproof-0.4.0/frameproof.egg-info/entry_points.txt +2 -0
  25. frameproof-0.4.0/frameproof.egg-info/requires.txt +10 -0
  26. frameproof-0.4.0/frameproof.egg-info/top_level.txt +1 -0
  27. frameproof-0.4.0/pyproject.toml +49 -0
  28. frameproof-0.4.0/setup.cfg +4 -0
  29. frameproof-0.4.0/tests/test_budget_and_parsing.py +104 -0
  30. frameproof-0.4.0/tests/test_packaging.py +46 -0
  31. frameproof-0.4.0/tests/test_select.py +260 -0
  32. frameproof-0.4.0/tests/test_verify.py +161 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Edvard Grishin
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,201 @@
1
+ Metadata-Version: 2.4
2
+ Name: frameproof
3
+ Version: 0.4.0
4
+ Summary: Агент смотрит видео без слепых зон и доказывает тайм-кодом, что именно он видел
5
+ Author: Edvard Grishin
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/edvardgrishin27/frameproof
8
+ Project-URL: Issues, https://github.com/edvardgrishin27/frameproof/issues
9
+ Keywords: video,claude-code,agent,transcript,keyframes,ffmpeg,whisper
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Environment :: Console
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Topic :: Multimedia :: Video
15
+ Requires-Python: >=3.10
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE
18
+ Requires-Dist: numpy>=1.24
19
+ Provides-Extra: net
20
+ Requires-Dist: yt-dlp>=2024.1.1; extra == "net"
21
+ Provides-Extra: mlx
22
+ Requires-Dist: mlx-whisper>=0.4; extra == "mlx"
23
+ Provides-Extra: dev
24
+ Requires-Dist: pytest>=7; extra == "dev"
25
+ Dynamic: license-file
26
+
27
+ # frameproof
28
+
29
+ **Your coding agent did not watch that video. It guessed.**
30
+
31
+ Ask Claude Code to "watch this tutorial" and it samples frames on a scene-change
32
+ threshold. On a screencast that threshold cannot fire. A tool may warn that coverage is
33
+ sparse, but it will not tell you WHERE the hole is — so the agent cannot tell "few frames"
34
+ from "no frames for twenty minutes straight".
35
+
36
+ Measured on a real 38-minute tutorial, the most popular tool in this niche extracted
37
+ **17 frames by default**, with a **20-minute 51-second gap**. `frameproof` extracted 220
38
+ with a **14-second** maximum gap — first command, no flags.
39
+
40
+ The full table, including their best mode where they lead on coverage, is in
41
+ [bench/RESULTS.md](bench/RESULTS.md). Hiding it would be less interesting.
42
+
43
+ ```bash
44
+ pip install frameproof
45
+ frameproof index "https://youtube.com/watch?v=..." --ocr
46
+ ```
47
+
48
+ ## Why the threshold cannot work
49
+
50
+ ffmpeg's `scene` filter measures the **mean** delta across the whole frame. Measured on
51
+ ffmpeg 8.0.1 with real terminal colours (`#cccccc` on `#1e1e1e`, 640×360):
52
+
53
+ | what changed on screen | scene score | threshold 0.3 |
54
+ |---|---|---|
55
+ | one full-width line of text | 0.0579 | no |
56
+ | three lines | 0.149 | no |
57
+ | half the screen | 0.745 | yes |
58
+
59
+ Real glyphs cover 10–15 % of a line's area, so a typed command scores around **0.006 —
60
+ off by a factor of about 40**. Lowering the threshold does not help: what rescues a
61
+ screencast buries a fast-cut video under thousands of frames.
62
+
63
+ `frameproof` measures the **fraction of changed pixels per grid cell**, calibrated
64
+ against each cell's own baseline. A cell that moves constantly — the presenter's
65
+ webcam, a running timer, a cursor — is suppressed automatically. A cell that is quiet
66
+ most of the time and then changes is an event.
67
+
68
+ ## The guarantee
69
+
70
+ **No stretch of the timeline is left without a frame for longer than `--max-gap`
71
+ seconds** (15 by default). When detectors stay silent, frames are placed on a grid.
72
+ Coverage is not a matter of picking a lucky threshold.
73
+
74
+ And when the guarantee cannot be met, the tool **says so**:
75
+
76
+ ```
77
+ покрытие: 97 % — 2 участка без кадров (57 с). НЕ утверждай, что показано на экране в них.
78
+ БЕЗ КАДРА 25:30 – 25:59 (29 с)
79
+ ```
80
+
81
+ Silent blindness is worse than an honest "I did not look here".
82
+
83
+ ## Three commands, on purpose
84
+
85
+ | command | what it does | images |
86
+ |---|---|---|
87
+ | `index` | builds the index, prints coverage | none |
88
+ | `search` | searches speech **and on-screen text** | none |
89
+ | `frames` | returns images | yes — the only one |
90
+
91
+ If search could return pictures, the savings would vanish on the first query. A frame
92
+ at 1280×720 costs about 1196 visual tokens; the transcript of an hour is about 50 KB.
93
+ Most questions are answered without loading a single image.
94
+
95
+ ```bash
96
+ frameproof search "openrouter" --out ~/.frameproof/hermes
97
+ # [9:57 / f0050] screen: ... OpenRouter • дешевле напрямую ...
98
+
99
+ frameproof frames --at 18:38 --out ~/.frameproof/hermes
100
+ # [18:38 / f0097] .../frames/f0097.jpg (1196 токенов)
101
+ ```
102
+
103
+ ## Two speed tiers
104
+
105
+ ```bash
106
+ frameproof index <url> --fast # 1 second
107
+ frameproof index <url> # 32 seconds, frames land better
108
+ ```
109
+
110
+ `--fast` takes candidates from keyframes instead of decoding the whole video.
111
+ Measured on a 38-minute tutorial:
112
+
113
+ | mode | frames | reliable on-screen terms | per frame | time |
114
+ |---|---|---|---|---|
115
+ | `--fast` | 231 | 672 | 2.9 | **1.1 s** |
116
+ | default | 225 | **789** | **3.5** | 32 s |
117
+
118
+ The fast tier returns 85 % of the information for 3 % of the time. The trade is honest:
119
+ frames land where the encoder put a keyframe, not where the thought on screen finished.
120
+
121
+ The frame budget scales with duration instead of being a constant: a one-minute clip
122
+ gets 40, a 38-minute tutorial 231, a three-hour lecture 600.
123
+
124
+
125
+ ## A citation you can check
126
+
127
+ `[18:38 / f0097]` is not decoration. It points at a row of the index, and arithmetic checks it:
128
+
129
+ ```bash
130
+ frameproof verify answer.md --out ~/.frameproof/hermes
131
+ ```
132
+
133
+ ```
134
+ ✗ [20:00 / f9999] The memory architecture diagram is on screen.
135
+ FAIL FRAME_NOT_FOUND: no frame f9999 in the index — the reference is invented
136
+ ✗ [5:00 / f0097] Here he opens the router settings.
137
+ FAIL TIME_MISMATCH: the tag says 5:00, frame f0097 was taken at 18:38
138
+ ? [29:31 / f0160] A list of ten skills is shown.
139
+ WARN NEVER_OPENED: the frame exists but was never requested —
140
+ the claim was made without looking
141
+ ```
142
+
143
+ Six checks, zero model calls: does the frame exist · does the timestamp match · does the
144
+ moment fall in a coverage gap · **was the frame ever served to the agent** · does the quoted
145
+ string appear in the frame's OCR · does it appear in nearby speech.
146
+
147
+ ## A blind second look
148
+
149
+ Meaning is beyond arithmetic. For that there is a separate subagent that sees **only the
150
+ frame and the claim** — not the user's question, not the author's reasoning, not the rest
151
+ of the answer. Its job is to refute.
152
+
153
+ ```bash
154
+ frameproof verify answer.md --out <index> --plan # tasks carrying no context at all
155
+ ```
156
+
157
+ It runs **only when explicitly asked**. Refuted claims are **flagged, not hidden**: measured
158
+ adversarial panels raise false alarms on up to a third of correct claims, so the call stays
159
+ with the human.
160
+
161
+
162
+ ## Install
163
+
164
+ ```bash
165
+ pip install frameproof # core
166
+ pip install "frameproof[net]" # + yt-dlp for links
167
+ pip install "frameproof[mlx]" # + fast local transcription on Apple Silicon
168
+
169
+ frameproof doctor # check what is available
170
+ frameproof install # install the skill into Claude Code
171
+ ```
172
+
173
+ ```bash
174
+ npx skills add edvardgrishin27/frameproof -g # Codex, Cursor, Copilot, others
175
+ ```
176
+
177
+ > We have **not** verified this outside Claude Code. The `SKILL.md` format is portable and
178
+ > the manifests are in place, but we will not claim support we did not test — see [CLAIMS.md](CLAIMS.md).
179
+
180
+ Requires `ffmpeg`. Everything else is optional and degrades gracefully.
181
+ **No API keys, ever.** Subtitles come free from `yt-dlp`; when there are none,
182
+ transcription runs locally.
183
+
184
+ ## Use in Claude Code
185
+
186
+ After `frameproof install`, just ask: *"watch this video and tell me which command he
187
+ shows at 4:12"*. The skill enforces one rule the agent cannot skip:
188
+
189
+ > Never claim what was on screen without having seen a frame. Every statement about
190
+ > the screen carries a `[MM:SS / fNNNN]` tag so a human can check it.
191
+
192
+ ## Honest limits
193
+
194
+ The full list is in [CLAIMS.md](CLAIMS.md). The short version: this tool guarantees
195
+ *coverage*, not that no change was ever missed; OCR is for **finding** frames, not for
196
+ reading code verbatim; and the benchmark is one video of the class where the gap is
197
+ widest.
198
+
199
+ Russian documentation: [README.ru.md](README.ru.md)
200
+
201
+ MIT
@@ -0,0 +1,175 @@
1
+ # frameproof
2
+
3
+ **Your coding agent did not watch that video. It guessed.**
4
+
5
+ Ask Claude Code to "watch this tutorial" and it samples frames on a scene-change
6
+ threshold. On a screencast that threshold cannot fire. A tool may warn that coverage is
7
+ sparse, but it will not tell you WHERE the hole is — so the agent cannot tell "few frames"
8
+ from "no frames for twenty minutes straight".
9
+
10
+ Measured on a real 38-minute tutorial, the most popular tool in this niche extracted
11
+ **17 frames by default**, with a **20-minute 51-second gap**. `frameproof` extracted 220
12
+ with a **14-second** maximum gap — first command, no flags.
13
+
14
+ The full table, including their best mode where they lead on coverage, is in
15
+ [bench/RESULTS.md](bench/RESULTS.md). Hiding it would be less interesting.
16
+
17
+ ```bash
18
+ pip install frameproof
19
+ frameproof index "https://youtube.com/watch?v=..." --ocr
20
+ ```
21
+
22
+ ## Why the threshold cannot work
23
+
24
+ ffmpeg's `scene` filter measures the **mean** delta across the whole frame. Measured on
25
+ ffmpeg 8.0.1 with real terminal colours (`#cccccc` on `#1e1e1e`, 640×360):
26
+
27
+ | what changed on screen | scene score | threshold 0.3 |
28
+ |---|---|---|
29
+ | one full-width line of text | 0.0579 | no |
30
+ | three lines | 0.149 | no |
31
+ | half the screen | 0.745 | yes |
32
+
33
+ Real glyphs cover 10–15 % of a line's area, so a typed command scores around **0.006 —
34
+ off by a factor of about 40**. Lowering the threshold does not help: what rescues a
35
+ screencast buries a fast-cut video under thousands of frames.
36
+
37
+ `frameproof` measures the **fraction of changed pixels per grid cell**, calibrated
38
+ against each cell's own baseline. A cell that moves constantly — the presenter's
39
+ webcam, a running timer, a cursor — is suppressed automatically. A cell that is quiet
40
+ most of the time and then changes is an event.
41
+
42
+ ## The guarantee
43
+
44
+ **No stretch of the timeline is left without a frame for longer than `--max-gap`
45
+ seconds** (15 by default). When detectors stay silent, frames are placed on a grid.
46
+ Coverage is not a matter of picking a lucky threshold.
47
+
48
+ And when the guarantee cannot be met, the tool **says so**:
49
+
50
+ ```
51
+ покрытие: 97 % — 2 участка без кадров (57 с). НЕ утверждай, что показано на экране в них.
52
+ БЕЗ КАДРА 25:30 – 25:59 (29 с)
53
+ ```
54
+
55
+ Silent blindness is worse than an honest "I did not look here".
56
+
57
+ ## Three commands, on purpose
58
+
59
+ | command | what it does | images |
60
+ |---|---|---|
61
+ | `index` | builds the index, prints coverage | none |
62
+ | `search` | searches speech **and on-screen text** | none |
63
+ | `frames` | returns images | yes — the only one |
64
+
65
+ If search could return pictures, the savings would vanish on the first query. A frame
66
+ at 1280×720 costs about 1196 visual tokens; the transcript of an hour is about 50 KB.
67
+ Most questions are answered without loading a single image.
68
+
69
+ ```bash
70
+ frameproof search "openrouter" --out ~/.frameproof/hermes
71
+ # [9:57 / f0050] screen: ... OpenRouter • дешевле напрямую ...
72
+
73
+ frameproof frames --at 18:38 --out ~/.frameproof/hermes
74
+ # [18:38 / f0097] .../frames/f0097.jpg (1196 токенов)
75
+ ```
76
+
77
+ ## Two speed tiers
78
+
79
+ ```bash
80
+ frameproof index <url> --fast # 1 second
81
+ frameproof index <url> # 32 seconds, frames land better
82
+ ```
83
+
84
+ `--fast` takes candidates from keyframes instead of decoding the whole video.
85
+ Measured on a 38-minute tutorial:
86
+
87
+ | mode | frames | reliable on-screen terms | per frame | time |
88
+ |---|---|---|---|---|
89
+ | `--fast` | 231 | 672 | 2.9 | **1.1 s** |
90
+ | default | 225 | **789** | **3.5** | 32 s |
91
+
92
+ The fast tier returns 85 % of the information for 3 % of the time. The trade is honest:
93
+ frames land where the encoder put a keyframe, not where the thought on screen finished.
94
+
95
+ The frame budget scales with duration instead of being a constant: a one-minute clip
96
+ gets 40, a 38-minute tutorial 231, a three-hour lecture 600.
97
+
98
+
99
+ ## A citation you can check
100
+
101
+ `[18:38 / f0097]` is not decoration. It points at a row of the index, and arithmetic checks it:
102
+
103
+ ```bash
104
+ frameproof verify answer.md --out ~/.frameproof/hermes
105
+ ```
106
+
107
+ ```
108
+ ✗ [20:00 / f9999] The memory architecture diagram is on screen.
109
+ FAIL FRAME_NOT_FOUND: no frame f9999 in the index — the reference is invented
110
+ ✗ [5:00 / f0097] Here he opens the router settings.
111
+ FAIL TIME_MISMATCH: the tag says 5:00, frame f0097 was taken at 18:38
112
+ ? [29:31 / f0160] A list of ten skills is shown.
113
+ WARN NEVER_OPENED: the frame exists but was never requested —
114
+ the claim was made without looking
115
+ ```
116
+
117
+ Six checks, zero model calls: does the frame exist · does the timestamp match · does the
118
+ moment fall in a coverage gap · **was the frame ever served to the agent** · does the quoted
119
+ string appear in the frame's OCR · does it appear in nearby speech.
120
+
121
+ ## A blind second look
122
+
123
+ Meaning is beyond arithmetic. For that there is a separate subagent that sees **only the
124
+ frame and the claim** — not the user's question, not the author's reasoning, not the rest
125
+ of the answer. Its job is to refute.
126
+
127
+ ```bash
128
+ frameproof verify answer.md --out <index> --plan # tasks carrying no context at all
129
+ ```
130
+
131
+ It runs **only when explicitly asked**. Refuted claims are **flagged, not hidden**: measured
132
+ adversarial panels raise false alarms on up to a third of correct claims, so the call stays
133
+ with the human.
134
+
135
+
136
+ ## Install
137
+
138
+ ```bash
139
+ pip install frameproof # core
140
+ pip install "frameproof[net]" # + yt-dlp for links
141
+ pip install "frameproof[mlx]" # + fast local transcription on Apple Silicon
142
+
143
+ frameproof doctor # check what is available
144
+ frameproof install # install the skill into Claude Code
145
+ ```
146
+
147
+ ```bash
148
+ npx skills add edvardgrishin27/frameproof -g # Codex, Cursor, Copilot, others
149
+ ```
150
+
151
+ > We have **not** verified this outside Claude Code. The `SKILL.md` format is portable and
152
+ > the manifests are in place, but we will not claim support we did not test — see [CLAIMS.md](CLAIMS.md).
153
+
154
+ Requires `ffmpeg`. Everything else is optional and degrades gracefully.
155
+ **No API keys, ever.** Subtitles come free from `yt-dlp`; when there are none,
156
+ transcription runs locally.
157
+
158
+ ## Use in Claude Code
159
+
160
+ After `frameproof install`, just ask: *"watch this video and tell me which command he
161
+ shows at 4:12"*. The skill enforces one rule the agent cannot skip:
162
+
163
+ > Never claim what was on screen without having seen a frame. Every statement about
164
+ > the screen carries a `[MM:SS / fNNNN]` tag so a human can check it.
165
+
166
+ ## Honest limits
167
+
168
+ The full list is in [CLAIMS.md](CLAIMS.md). The short version: this tool guarantees
169
+ *coverage*, not that no change was ever missed; OCR is for **finding** frames, not for
170
+ reading code verbatim; and the benchmark is one video of the class where the gap is
171
+ widest.
172
+
173
+ Russian documentation: [README.ru.md](README.ru.md)
174
+
175
+ MIT
@@ -0,0 +1,24 @@
1
+ """frameproof — агент смотрит видео и может доказать, что именно он видел.
2
+
3
+ Два обещания, которых нет у аналогов:
4
+
5
+ 1. НЕТ СЛЕПЫХ ЗОН. Кадры отбираются по доле изменившихся пикселей в ячейках сетки,
6
+ а не по средней дельте всего кадра, плюс жёсткая страховка «ни одного промежутка
7
+ длиннее N секунд». Наивный `select='gt(scene,0.3)'` на скринкасте промахивается
8
+ примерно в 40 раз и молча пропускает целые минуты.
9
+
10
+ 2. ЧЕСТНЫЙ ОТЧЁТ. Инструмент вслух говорит, какое покрытие получилось и где кадров
11
+ нет. В этих участках агенту запрещено утверждать, что было на экране.
12
+
13
+ 3. МЕТКА ПРОВЕРЯЕМА. `[MM:SS / fNNNN]` — не оформление, а ссылка на строку индекса.
14
+ Механический аудит без всякой модели ловит выдуманный кадр, разъехавшийся тайм-код,
15
+ утверждение о кадре, который агенту не выдавали, и цитату, которой на экране нет.
16
+ Что механика проверить не может — отдаётся слепому субагенту: он видит только кадр
17
+ и утверждение, без вопроса и без чужих рассуждений.
18
+
19
+ Работает полностью офлайн: yt-dlp + ffmpeg + локальная расшифровка. Ключи не нужны.
20
+ """
21
+
22
+ __version__ = "0.4.0"
23
+
24
+ __all__ = ["__version__"]