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.
- vidai_rec-0.2.0/PKG-INFO +138 -0
- vidai_rec-0.2.0/README.md +95 -0
- vidai_rec-0.2.0/pyproject.toml +55 -0
- vidai_rec-0.2.0/setup.cfg +4 -0
- vidai_rec-0.2.0/tests/test_analyze.py +29 -0
- vidai_rec-0.2.0/tests/test_anchors.py +45 -0
- vidai_rec-0.2.0/tests/test_edit.py +48 -0
- vidai_rec-0.2.0/tests/test_lab.py +55 -0
- vidai_rec-0.2.0/tests/test_live.py +308 -0
- vidai_rec-0.2.0/tests/test_native.py +50 -0
- vidai_rec-0.2.0/tests/test_recorder.py +40 -0
- vidai_rec-0.2.0/tests/test_render.py +79 -0
- vidai_rec-0.2.0/tests/test_service.py +35 -0
- vidai_rec-0.2.0/tests/test_studio.py +110 -0
- vidai_rec-0.2.0/vidai/__init__.py +11 -0
- vidai_rec-0.2.0/vidai/analyze.py +125 -0
- vidai_rec-0.2.0/vidai/anchors.py +226 -0
- vidai_rec-0.2.0/vidai/assets/icon.png +0 -0
- vidai_rec-0.2.0/vidai/assets/icon_square.png +0 -0
- vidai_rec-0.2.0/vidai/brief.py +75 -0
- vidai_rec-0.2.0/vidai/capture.py +348 -0
- vidai_rec-0.2.0/vidai/cli.py +149 -0
- vidai_rec-0.2.0/vidai/edit.py +231 -0
- vidai_rec-0.2.0/vidai/export.py +48 -0
- vidai_rec-0.2.0/vidai/ffmpeg.py +125 -0
- vidai_rec-0.2.0/vidai/gui.py +502 -0
- vidai_rec-0.2.0/vidai/lab/__init__.py +205 -0
- vidai_rec-0.2.0/vidai/lab/examples.py +107 -0
- vidai_rec-0.2.0/vidai/live/bus.py +110 -0
- vidai_rec-0.2.0/vidai/live/guide.py +88 -0
- vidai_rec-0.2.0/vidai/live/learn.py +184 -0
- vidai_rec-0.2.0/vidai/live/pipeline.py +611 -0
- vidai_rec-0.2.0/vidai/live/processors.py +410 -0
- vidai_rec-0.2.0/vidai/live/rules.py +110 -0
- vidai_rec-0.2.0/vidai/live/sensors.py +397 -0
- vidai_rec-0.2.0/vidai/mcp_server.py +42 -0
- vidai_rec-0.2.0/vidai/native/__init__.py +143 -0
- vidai_rec-0.2.0/vidai/native/fastops.c +96 -0
- vidai_rec-0.2.0/vidai/overlays.py +136 -0
- vidai_rec-0.2.0/vidai/preview.py +48 -0
- vidai_rec-0.2.0/vidai/recorder.py +253 -0
- vidai_rec-0.2.0/vidai/render.py +245 -0
- vidai_rec-0.2.0/vidai/service.py +386 -0
- vidai_rec-0.2.0/vidai/session.py +341 -0
- vidai_rec-0.2.0/vidai/testing.py +26 -0
- vidai_rec-0.2.0/vidai_rec.egg-info/PKG-INFO +138 -0
- vidai_rec-0.2.0/vidai_rec.egg-info/SOURCES.txt +49 -0
- vidai_rec-0.2.0/vidai_rec.egg-info/dependency_links.txt +1 -0
- vidai_rec-0.2.0/vidai_rec.egg-info/entry_points.txt +2 -0
- vidai_rec-0.2.0/vidai_rec.egg-info/requires.txt +30 -0
- vidai_rec-0.2.0/vidai_rec.egg-info/top_level.txt +1 -0
vidai_rec-0.2.0/PKG-INFO
ADDED
|
@@ -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,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
|