vidai-rec 0.2.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 (51) hide show
  1. vidai_rec-0.2.0/PKG-INFO +138 -0
  2. vidai_rec-0.2.0/README.md +95 -0
  3. vidai_rec-0.2.0/pyproject.toml +55 -0
  4. vidai_rec-0.2.0/setup.cfg +4 -0
  5. vidai_rec-0.2.0/tests/test_analyze.py +29 -0
  6. vidai_rec-0.2.0/tests/test_anchors.py +45 -0
  7. vidai_rec-0.2.0/tests/test_edit.py +48 -0
  8. vidai_rec-0.2.0/tests/test_lab.py +55 -0
  9. vidai_rec-0.2.0/tests/test_live.py +308 -0
  10. vidai_rec-0.2.0/tests/test_native.py +50 -0
  11. vidai_rec-0.2.0/tests/test_recorder.py +40 -0
  12. vidai_rec-0.2.0/tests/test_render.py +79 -0
  13. vidai_rec-0.2.0/tests/test_service.py +35 -0
  14. vidai_rec-0.2.0/tests/test_studio.py +110 -0
  15. vidai_rec-0.2.0/vidai/__init__.py +11 -0
  16. vidai_rec-0.2.0/vidai/analyze.py +125 -0
  17. vidai_rec-0.2.0/vidai/anchors.py +226 -0
  18. vidai_rec-0.2.0/vidai/assets/icon.png +0 -0
  19. vidai_rec-0.2.0/vidai/assets/icon_square.png +0 -0
  20. vidai_rec-0.2.0/vidai/brief.py +75 -0
  21. vidai_rec-0.2.0/vidai/capture.py +348 -0
  22. vidai_rec-0.2.0/vidai/cli.py +149 -0
  23. vidai_rec-0.2.0/vidai/edit.py +231 -0
  24. vidai_rec-0.2.0/vidai/export.py +48 -0
  25. vidai_rec-0.2.0/vidai/ffmpeg.py +125 -0
  26. vidai_rec-0.2.0/vidai/gui.py +502 -0
  27. vidai_rec-0.2.0/vidai/lab/__init__.py +205 -0
  28. vidai_rec-0.2.0/vidai/lab/examples.py +107 -0
  29. vidai_rec-0.2.0/vidai/live/bus.py +110 -0
  30. vidai_rec-0.2.0/vidai/live/guide.py +88 -0
  31. vidai_rec-0.2.0/vidai/live/learn.py +184 -0
  32. vidai_rec-0.2.0/vidai/live/pipeline.py +611 -0
  33. vidai_rec-0.2.0/vidai/live/processors.py +410 -0
  34. vidai_rec-0.2.0/vidai/live/rules.py +110 -0
  35. vidai_rec-0.2.0/vidai/live/sensors.py +397 -0
  36. vidai_rec-0.2.0/vidai/mcp_server.py +42 -0
  37. vidai_rec-0.2.0/vidai/native/__init__.py +143 -0
  38. vidai_rec-0.2.0/vidai/native/fastops.c +96 -0
  39. vidai_rec-0.2.0/vidai/overlays.py +136 -0
  40. vidai_rec-0.2.0/vidai/preview.py +48 -0
  41. vidai_rec-0.2.0/vidai/recorder.py +253 -0
  42. vidai_rec-0.2.0/vidai/render.py +245 -0
  43. vidai_rec-0.2.0/vidai/service.py +386 -0
  44. vidai_rec-0.2.0/vidai/session.py +341 -0
  45. vidai_rec-0.2.0/vidai/testing.py +26 -0
  46. vidai_rec-0.2.0/vidai_rec.egg-info/PKG-INFO +138 -0
  47. vidai_rec-0.2.0/vidai_rec.egg-info/SOURCES.txt +49 -0
  48. vidai_rec-0.2.0/vidai_rec.egg-info/dependency_links.txt +1 -0
  49. vidai_rec-0.2.0/vidai_rec.egg-info/entry_points.txt +2 -0
  50. vidai_rec-0.2.0/vidai_rec.egg-info/requires.txt +30 -0
  51. vidai_rec-0.2.0/vidai_rec.egg-info/top_level.txt +1 -0
@@ -0,0 +1,138 @@
1
+ Metadata-Version: 2.4
2
+ Name: vidai-rec
3
+ Version: 0.2.0
4
+ Summary: Record, understand and improve your videos by talking to Claude (under development)
5
+ Author-email: AlsammanAlsamman <sammanmahmoud@gmail.com>
6
+ Maintainer-email: AlsammanAlsamman <sammanmahmoud@gmail.com>
7
+ Project-URL: Homepage, https://github.com/AlsammanAlsamman/VidAI
8
+ Project-URL: Issues, https://github.com/AlsammanAlsamman/VidAI/issues
9
+ Keywords: video,recording,editing,claude,youtube,speech-to-text,ffmpeg
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: End Users/Desktop
12
+ Classifier: Operating System :: POSIX :: Linux
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Topic :: Multimedia :: Video :: Capture
15
+ Classifier: Topic :: Multimedia :: Video :: Non-Linear Editor
16
+ Requires-Python: >=3.10
17
+ Description-Content-Type: text/markdown
18
+ Requires-Dist: numpy
19
+ Requires-Dist: pillow
20
+ Requires-Dist: pydantic>=2
21
+ Requires-Dist: imageio-ffmpeg
22
+ Requires-Dist: opencv-python-headless
23
+ Requires-Dist: customtkinter
24
+ Requires-Dist: faster-whisper
25
+ Requires-Dist: rapidocr-onnxruntime
26
+ Provides-Extra: record
27
+ Requires-Dist: obsws-python; extra == "record"
28
+ Requires-Dist: pynput; extra == "record"
29
+ Provides-Extra: mcp
30
+ Requires-Dist: mcp>=2; extra == "mcp"
31
+ Provides-Extra: arabic
32
+ Requires-Dist: arabic-reshaper; extra == "arabic"
33
+ Requires-Dist: python-bidi; extra == "arabic"
34
+ Provides-Extra: dev
35
+ Requires-Dist: pytest; extra == "dev"
36
+ Provides-Extra: all
37
+ Requires-Dist: obsws-python; extra == "all"
38
+ Requires-Dist: pynput; extra == "all"
39
+ Requires-Dist: mcp>=2; extra == "all"
40
+ Requires-Dist: arabic-reshaper; extra == "all"
41
+ Requires-Dist: python-bidi; extra == "all"
42
+ Requires-Dist: pytest; extra == "all"
43
+
44
+ <p align="center">
45
+ <img src="https://raw.githubusercontent.com/AlsammanAlsamman/VidAI/master/assets/icon.png" alt="VidAI" width="260">
46
+ </p>
47
+
48
+ <h1 align="center">VidAI</h1>
49
+
50
+ <p align="center"><b>Record, understand and improve your videos — by talking to Claude.</b></p>
51
+
52
+ <p align="center">
53
+ <img src="https://img.shields.io/badge/status-under%20development-ff3b5c" alt="status: under development">
54
+ <a href="https://pypi.org/project/vidai-rec/"><img src="https://img.shields.io/pypi/v/vidai-rec?color=8b6cff&label=pypi%20vidai-rec" alt="PyPI vidai-rec"></a>
55
+ <img src="https://img.shields.io/badge/python-3.10%2B-34d399" alt="python 3.10+">
56
+ <img src="https://img.shields.io/badge/platform-Linux-ffbf00" alt="Linux">
57
+ <img src="https://img.shields.io/badge/tests-50%20passing-34d399" alt="50 tests passing">
58
+ </p>
59
+
60
+ > [!WARNING]
61
+ > **VidAI is under active development.** It works end to end today (record → live effects → edit → YouTube
62
+ > export), but features, commands and file formats may still change.
63
+
64
+ VidAI is a **video plugin for Claude Code**. Say `vidai`, answer a few questions, and record. While you record,
65
+ VidAI measures small, useful statistics ("anchors") and applies live effects; you can talk to it
66
+ (*"VidAI, zoom in"*, *"VidAI, add a title that says Hello"*). Afterwards Claude uses the anchors to edit the
67
+ video and exports a YouTube-ready file. Everything runs locally and for free.
68
+
69
+ ---
70
+
71
+ ## How it works
72
+
73
+ <p align="center">
74
+ <a href="https://raw.githubusercontent.com/AlsammanAlsamman/VidAI/master/assets/flowchart/vidai_flow.svg"><img src="https://raw.githubusercontent.com/AlsammanAlsamman/VidAI/master/assets/flowchart/vidai_flow.png" alt="VidAI flow: brief, anchor setup, record, live, anchors, understand, edit plan, render" width="100%"></a>
75
+ </p>
76
+
77
+ <sub>Generated by <code>assets/flowchart/flowchart.js</code> (pure Node → SVG → PNG via headless Chrome): <code>node assets/flowchart/flowchart.js</code></sub>
78
+
79
+ ---
80
+
81
+ ## What it can do now
82
+
83
+ | | |
84
+ |---|---|
85
+ | 🎙️ **Brief** | Claude asks what the video is about, the language, style, length and sections, then decides what to track |
86
+ | 🎥 **Record** | Its own recorder: camera, screen, or screen + camera, with mic · live preview · countdown · crash-safe files |
87
+ | 📊 **Live anchors** | Pauses, speech (speech-to-text), text on screen (OCR), motion, scene changes, active window, markers |
88
+ | 🗣️ **Voice** | *“VidAI record / stop / mark / mistake / new section … / important / zoom in / zoom out / captions on”* — anything else goes to Claude (English for now) |
89
+ | ✨ **Live effects** | Text, logos, arrows and boxes, zoom, blur, live captions — plus new effects Claude writes on the spot (e.g. an object that follows your hand) |
90
+ | 🤔 **Thinking icon** | A little animated VidAI eye bounces in the corner while Claude works on your request |
91
+ | 🧠 **Instant models** | Teach it something during the recording (*“VidAI label yes”*), correct it (*“VidAI wrong”*); it learns immediately |
92
+ | ✂️ **Editing** | Remove pauses and failed takes, add titles, arrows, zooms, subtitles, chapters, audio clean-up — without touching the original |
93
+ | ⚡ **Fast** | Renders in parallel chunks split inside silences; hot loops in C |
94
+ | 📺 **YouTube export** | MP4 + subtitles (`.srt`) + a description with chapters |
95
+
96
+ ---
97
+
98
+ ## Quick start
99
+
100
+ ```bash
101
+ pip install vidai-rec
102
+ ```
103
+
104
+ Working on VidAI itself (tests, Claude Code skill and MCP config included):
105
+
106
+ ```bash
107
+ git clone https://github.com/AlsammanAlsamman/VidAI && cd VidAI
108
+ python3 -m venv .venv && .venv/bin/pip install -e ".[all]"
109
+ .venv/bin/python -m pytest -q # optional: run the tests
110
+ ```
111
+
112
+ Then open Claude Code inside the folder and say **vidai**:
113
+
114
+ ```bash
115
+ claude
116
+ > vidai
117
+ ```
118
+
119
+ Claude interviews you, opens the **VidAI Recorder**, and edits the video when you are done.
120
+ Prefer the terminal? `vidai studio` · `vidai analyze video.mkv` · `vidai plan video.mkv remove_gaps` · `vidai render video.mkv`
121
+
122
+ **Requirements:** Linux (X11), a webcam and mic (PipeWire/PulseAudio), `gcc` for the C speed-ups (optional).
123
+ ffmpeg is bundled; the speech model downloads on first use.
124
+
125
+ ---
126
+
127
+ ## Coming next
128
+
129
+ - Speech-based editing: remove filler words, keep the best take automatically
130
+ - Built-in tracking: hands, face, objects — effects that follow them
131
+ - Arabic voice commands and on-screen text
132
+ - More layouts, transitions and music
133
+
134
+ ---
135
+
136
+ <p align="center">
137
+ Made by <b>AlsammanAlsamman</b> · <a href="mailto:sammanmahmoud@gmail.com">sammanmahmoud@gmail.com</a>
138
+ </p>
@@ -0,0 +1,95 @@
1
+ <p align="center">
2
+ <img src="https://raw.githubusercontent.com/AlsammanAlsamman/VidAI/master/assets/icon.png" alt="VidAI" width="260">
3
+ </p>
4
+
5
+ <h1 align="center">VidAI</h1>
6
+
7
+ <p align="center"><b>Record, understand and improve your videos — by talking to Claude.</b></p>
8
+
9
+ <p align="center">
10
+ <img src="https://img.shields.io/badge/status-under%20development-ff3b5c" alt="status: under development">
11
+ <a href="https://pypi.org/project/vidai-rec/"><img src="https://img.shields.io/pypi/v/vidai-rec?color=8b6cff&label=pypi%20vidai-rec" alt="PyPI vidai-rec"></a>
12
+ <img src="https://img.shields.io/badge/python-3.10%2B-34d399" alt="python 3.10+">
13
+ <img src="https://img.shields.io/badge/platform-Linux-ffbf00" alt="Linux">
14
+ <img src="https://img.shields.io/badge/tests-50%20passing-34d399" alt="50 tests passing">
15
+ </p>
16
+
17
+ > [!WARNING]
18
+ > **VidAI is under active development.** It works end to end today (record → live effects → edit → YouTube
19
+ > export), but features, commands and file formats may still change.
20
+
21
+ VidAI is a **video plugin for Claude Code**. Say `vidai`, answer a few questions, and record. While you record,
22
+ VidAI measures small, useful statistics ("anchors") and applies live effects; you can talk to it
23
+ (*"VidAI, zoom in"*, *"VidAI, add a title that says Hello"*). Afterwards Claude uses the anchors to edit the
24
+ video and exports a YouTube-ready file. Everything runs locally and for free.
25
+
26
+ ---
27
+
28
+ ## How it works
29
+
30
+ <p align="center">
31
+ <a href="https://raw.githubusercontent.com/AlsammanAlsamman/VidAI/master/assets/flowchart/vidai_flow.svg"><img src="https://raw.githubusercontent.com/AlsammanAlsamman/VidAI/master/assets/flowchart/vidai_flow.png" alt="VidAI flow: brief, anchor setup, record, live, anchors, understand, edit plan, render" width="100%"></a>
32
+ </p>
33
+
34
+ <sub>Generated by <code>assets/flowchart/flowchart.js</code> (pure Node → SVG → PNG via headless Chrome): <code>node assets/flowchart/flowchart.js</code></sub>
35
+
36
+ ---
37
+
38
+ ## What it can do now
39
+
40
+ | | |
41
+ |---|---|
42
+ | 🎙️ **Brief** | Claude asks what the video is about, the language, style, length and sections, then decides what to track |
43
+ | 🎥 **Record** | Its own recorder: camera, screen, or screen + camera, with mic · live preview · countdown · crash-safe files |
44
+ | 📊 **Live anchors** | Pauses, speech (speech-to-text), text on screen (OCR), motion, scene changes, active window, markers |
45
+ | 🗣️ **Voice** | *“VidAI record / stop / mark / mistake / new section … / important / zoom in / zoom out / captions on”* — anything else goes to Claude (English for now) |
46
+ | ✨ **Live effects** | Text, logos, arrows and boxes, zoom, blur, live captions — plus new effects Claude writes on the spot (e.g. an object that follows your hand) |
47
+ | 🤔 **Thinking icon** | A little animated VidAI eye bounces in the corner while Claude works on your request |
48
+ | 🧠 **Instant models** | Teach it something during the recording (*“VidAI label yes”*), correct it (*“VidAI wrong”*); it learns immediately |
49
+ | ✂️ **Editing** | Remove pauses and failed takes, add titles, arrows, zooms, subtitles, chapters, audio clean-up — without touching the original |
50
+ | ⚡ **Fast** | Renders in parallel chunks split inside silences; hot loops in C |
51
+ | 📺 **YouTube export** | MP4 + subtitles (`.srt`) + a description with chapters |
52
+
53
+ ---
54
+
55
+ ## Quick start
56
+
57
+ ```bash
58
+ pip install vidai-rec
59
+ ```
60
+
61
+ Working on VidAI itself (tests, Claude Code skill and MCP config included):
62
+
63
+ ```bash
64
+ git clone https://github.com/AlsammanAlsamman/VidAI && cd VidAI
65
+ python3 -m venv .venv && .venv/bin/pip install -e ".[all]"
66
+ .venv/bin/python -m pytest -q # optional: run the tests
67
+ ```
68
+
69
+ Then open Claude Code inside the folder and say **vidai**:
70
+
71
+ ```bash
72
+ claude
73
+ > vidai
74
+ ```
75
+
76
+ Claude interviews you, opens the **VidAI Recorder**, and edits the video when you are done.
77
+ Prefer the terminal? `vidai studio` · `vidai analyze video.mkv` · `vidai plan video.mkv remove_gaps` · `vidai render video.mkv`
78
+
79
+ **Requirements:** Linux (X11), a webcam and mic (PipeWire/PulseAudio), `gcc` for the C speed-ups (optional).
80
+ ffmpeg is bundled; the speech model downloads on first use.
81
+
82
+ ---
83
+
84
+ ## Coming next
85
+
86
+ - Speech-based editing: remove filler words, keep the best take automatically
87
+ - Built-in tracking: hands, face, objects — effects that follow them
88
+ - Arabic voice commands and on-screen text
89
+ - More layouts, transitions and music
90
+
91
+ ---
92
+
93
+ <p align="center">
94
+ Made by <b>AlsammanAlsamman</b> · <a href="mailto:sammanmahmoud@gmail.com">sammanmahmoud@gmail.com</a>
95
+ </p>
@@ -0,0 +1,55 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "vidai-rec"
7
+ version = "0.2.0"
8
+ description = "Record, understand and improve your videos by talking to Claude (under development)"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ authors = [{ name = "AlsammanAlsamman", email = "sammanmahmoud@gmail.com" }]
12
+ maintainers = [{ name = "AlsammanAlsamman", email = "sammanmahmoud@gmail.com" }]
13
+ keywords = ["video", "recording", "editing", "claude", "youtube", "speech-to-text", "ffmpeg"]
14
+ classifiers = [
15
+ "Development Status :: 3 - Alpha",
16
+ "Intended Audience :: End Users/Desktop",
17
+ "Operating System :: POSIX :: Linux",
18
+ "Programming Language :: Python :: 3",
19
+ "Topic :: Multimedia :: Video :: Capture",
20
+ "Topic :: Multimedia :: Video :: Non-Linear Editor",
21
+ ]
22
+ dependencies = [
23
+ "numpy",
24
+ "pillow",
25
+ "pydantic>=2",
26
+ "imageio-ffmpeg",
27
+ "opencv-python-headless",
28
+ "customtkinter",
29
+ "faster-whisper",
30
+ "rapidocr-onnxruntime",
31
+ ]
32
+
33
+ [project.optional-dependencies]
34
+ record = ["obsws-python", "pynput"]
35
+ mcp = ["mcp>=2"]
36
+ arabic = ["arabic-reshaper", "python-bidi"]
37
+ dev = ["pytest"]
38
+ all = ["obsws-python", "pynput", "mcp>=2", "arabic-reshaper", "python-bidi", "pytest"]
39
+
40
+ [project.urls]
41
+ Homepage = "https://github.com/AlsammanAlsamman/VidAI"
42
+ Issues = "https://github.com/AlsammanAlsamman/VidAI/issues"
43
+
44
+ [project.scripts]
45
+ vidai = "vidai.cli:main"
46
+
47
+ [tool.setuptools.packages.find]
48
+ include = ["vidai*"]
49
+
50
+ [tool.setuptools.package-data]
51
+ vidai = ["assets/*.png", "native/*.c"]
52
+
53
+ [tool.pytest.ini_options]
54
+ testpaths = ["tests"]
55
+ markers = ["slow: runs Whisper on real speech (downloads a model on first run)"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,29 @@
1
+ import pytest
2
+
3
+ from vidai.analyze import analyze
4
+ from vidai.testing import DURATION, SCENE_CUT, SILENCES
5
+
6
+
7
+ def test_analyze_finds_silences_and_scene_cut(video):
8
+ a = analyze(video, workers=3)
9
+ assert a.duration == pytest.approx(DURATION, abs=0.1)
10
+ sil = [(s.start, s.end) for s in a.segments_of("silence")]
11
+ assert len(sil) == len(SILENCES)
12
+ for (s, e), (es, ee) in zip(sil, SILENCES):
13
+ assert s == pytest.approx(es, abs=0.2) and e == pytest.approx(ee, abs=0.2)
14
+ cuts = [e.t for e in a.events_of("scene_change")]
15
+ assert len(cuts) == 1 and cuts[0] == pytest.approx(SCENE_CUT, abs=0.6)
16
+ assert len(a.series["audio_level"].values) == pytest.approx(DURATION * 10, abs=2)
17
+
18
+
19
+ def test_parallel_and_serial_analysis_agree(video):
20
+ a1 = analyze(video, workers=1)
21
+ a3 = analyze(video, workers=3, anchors=None)
22
+ assert [(s.start, s.end) for s in a1.segments_of("silence")] == [(s.start, s.end) for s in a3.segments_of("silence")]
23
+ assert [e.t for e in a1.events_of("scene_change")] == [e.t for e in a3.events_of("scene_change")]
24
+
25
+
26
+ def test_only_selected_stats(video):
27
+ a = analyze(video, stats=["silence"])
28
+ assert "audio_level" not in a.series and "motion" not in a.series
29
+ assert a.segments_of("silence")
@@ -0,0 +1,45 @@
1
+ from vidai import AnchorFile, Brief, Event, Segment, select_stats
2
+
3
+
4
+ def test_select_stats_depends_on_brief():
5
+ screen = select_stats(Brief(style="screencast"))
6
+ cam = select_stats(Brief(style="talking_head"))
7
+ assert {"silence", "markers", "scene_change", "input_activity"} <= set(screen)
8
+ assert "motion" in cam and "input_activity" not in cam
9
+ assert all(isinstance(r, str) and r for r in screen.values())
10
+
11
+
12
+ def _anchors():
13
+ a = AnchorFile(video="x.mp4", duration=60.0)
14
+ a.add_segments([Segment(start=18.0, end=21.0, kind="silence"), Segment(start=41.0, end=42.0, kind="silence")])
15
+ a.add_events([Event(t=30.5, kind="scene_change")])
16
+ return a
17
+
18
+
19
+ def test_split_points_snap_to_silence():
20
+ a = _anchors()
21
+ assert a.split_points(3) == [19.5, 41.5]
22
+ chunks = a.chunks(3)
23
+ assert chunks[0][0] == 0 and chunks[-1][1] == 60.0
24
+ assert all(b > a_ for a_, b in chunks)
25
+
26
+
27
+ def test_split_points_fall_back_to_scene_change_then_target():
28
+ a = AnchorFile(video="x.mp4", duration=60.0)
29
+ a.add_events([Event(t=31.0, kind="scene_change")])
30
+ assert a.split_points(2) == [31.0]
31
+ assert AnchorFile(video="x", duration=60.0).split_points(2) == [30.0]
32
+ assert AnchorFile(video="x", duration=6.0).split_points(3) == []
33
+
34
+
35
+ def test_roundtrip_and_queries(tmp_path):
36
+ a = _anchors()
37
+ a.brief = Brief(title="t", language="ar+en")
38
+ p = a.save(tmp_path / "x.mp4.anchors.json")
39
+ b = AnchorFile.load(p)
40
+ assert b.model_dump() == a.model_dump()
41
+ assert b.brief.languages == ["ar", "en"]
42
+ assert len(b.segments_of("silence", 20, 50)) == 2
43
+ at = b.at(30.0)
44
+ assert at["events"][0]["kind"] == "scene_change"
45
+ assert b.summary()["segments"]["silence"]["count"] == 2
@@ -0,0 +1,48 @@
1
+ import pytest
2
+
3
+ from vidai import AnchorFile, Chapter, Cut, EditPlan, Event, Segment, Shape, Text
4
+ from vidai.analyze import analyze
5
+
6
+
7
+ def test_keep_ranges_and_time_mapping():
8
+ p = EditPlan(source="x.mp4", duration=20.0)
9
+ p.add(Cut(start=5, end=8), Cut(start=7, end=9), {"op": "cut", "start": 15, "end": 25})
10
+ assert p.keep_ranges() == [(0.0, 5.0), (9.0, 15.0)]
11
+ assert p.output_duration() == 11.0
12
+ assert p.map_time(4.0) == 4.0 and p.map_time(10.0) == 6.0
13
+ assert p.map_time(6.0) is None and p.map_time_after(6.0) == 5.0
14
+
15
+
16
+ def test_plan_json_roundtrip(tmp_path):
17
+ p = EditPlan(source="x.mp4", duration=10.0)
18
+ p.add(Text(start=0, end=1, text="hi", position=(0.2, 0.3)), Shape(start=1, end=2, shape="circle"),
19
+ Chapter(t=0, title="Intro"), {"op": "zoom", "start": 1, "end": 2}, {"op": "audio", "filter": "denoise"},
20
+ {"op": "model", "name": "m"}, {"op": "subtitle", "start": 0, "end": 1, "text": "x"})
21
+ q = EditPlan.load(p.save(tmp_path / "p.json"))
22
+ assert q.model_dump() == p.model_dump()
23
+
24
+
25
+ def test_remove_gaps_uses_anchors(video):
26
+ a = analyze(video)
27
+ p = EditPlan(source=str(video), duration=a.duration)
28
+ assert p.remove_gaps(a, min_gap=0.8, keep=0.3) == 3
29
+ # 3 gaps of 3, 2, 3 s, each keeps 0.3 s
30
+ assert p.output_duration() == pytest.approx(30 - (2.7 + 1.7 + 2.7), abs=0.5)
31
+
32
+
33
+ def test_cut_mistakes():
34
+ a = AnchorFile(video="x", duration=30.0)
35
+ a.add_segments([Segment(start=4, end=5, kind="silence"), Segment(start=10, end=12, kind="silence")])
36
+ a.add_events([Event(t=9.8, kind="markers", data={"type": "mistake"})])
37
+ p = EditPlan(source="x", duration=30.0)
38
+ assert p.cut_mistakes(a) == 1
39
+ c = p.of(Cut)[0]
40
+ assert c.start == 5.0 and c.end == 11.0
41
+
42
+
43
+ def test_chapters_from_anchors():
44
+ a = AnchorFile(video="x", duration=100.0)
45
+ a.add_events([Event(t=5, kind="scene_change"), Event(t=30, kind="scene_change"), Event(t=60, kind="scene_change")])
46
+ p = EditPlan(source="x", duration=100.0)
47
+ assert p.chapters_from_anchors(a, ["Intro", "Setup", "Demo"]) == 3
48
+ assert [c.t for c in p.of(Chapter)] == [0.0, 30, 60]
@@ -0,0 +1,55 @@
1
+ import numpy as np
2
+ import pytest
3
+
4
+ from vidai import lab
5
+ from vidai.lab.examples import ColorMatch, LogisticFrameClassifier
6
+
7
+
8
+ def _color_data(seed=0):
9
+ rng = np.random.default_rng(seed)
10
+ X = rng.integers(0, 256, (4000, 3)).astype(np.uint8)
11
+ xf = X.astype(np.float32)
12
+ Y = np.clip(0.8 * xf[:, [2, 1, 0]] + 30 + 0.3 * (xf ** 2) / 255, 0, 255).astype(np.uint8) # swap + curve
13
+ return (X[:3000], Y[:3000]), (X[3000:], Y[3000:])
14
+
15
+
16
+ def test_train_until_suitable_iterates_until_target():
17
+ tr, va = _color_data()
18
+
19
+ def adjust(hp, metrics, r):
20
+ return {**hp, "degree": 2} # linear is not enough -> add curve terms
21
+
22
+ rep = lab.train_until_suitable(ColorMatch, tr, va, "mae", 3.0, higher_is_better=False,
23
+ hparams={"degree": 1}, adjust=adjust, save_as="color_fix")
24
+ assert rep.suitable and len(rep.rounds) == 2
25
+ assert rep.rounds[0]["metrics"]["mae"] > 3.0 >= rep.rounds[1]["metrics"]["mae"]
26
+ m, spec = lab.load_model("color_fix")
27
+ assert spec.hparams["degree"] == 2
28
+ assert np.abs(m.predict(va[0]).astype(int) - va[1].astype(int)).mean() <= 3.0
29
+ assert [x["name"] for x in lab.list_models()] == ["color_fix"]
30
+
31
+
32
+ def test_not_saved_when_not_suitable():
33
+ tr, va = _color_data()
34
+ rep = lab.train_until_suitable(ColorMatch, tr, va, "mae", 0.01, higher_is_better=False, save_as="nope",
35
+ max_rounds=3)
36
+ assert not rep.suitable and rep.saved_to is None and len(rep.rounds) == 1 # no adjust -> stop
37
+ with pytest.raises(KeyError):
38
+ lab.load_model("nope")
39
+
40
+
41
+ def test_classifier_and_code_file_model(tmp_path):
42
+ code = tmp_path / "bright.py"
43
+ code.write_text(
44
+ "from vidai.lab.examples import LogisticFrameClassifier\n"
45
+ "class Bright(LogisticFrameClassifier):\n pass\n")
46
+ rng = np.random.default_rng(1)
47
+ X = rng.random((400, 16)).astype(np.float32)
48
+ y = (X.mean(1) > 0.5).astype(int)
49
+ cls = lab._import_class(f"{code}:Bright")
50
+ rep = lab.train_until_suitable(cls, (X[:300], y[:300]), (X[300:], y[300:]), "accuracy", 0.9,
51
+ save_as="bright", code_file=code)
52
+ assert rep.suitable
53
+ m, spec = lab.load_model("bright")
54
+ assert spec.class_path == "model.py:Bright"
55
+ assert (m.predict(X[300:]) == y[300:]).mean() >= 0.9