pycvr 0.1.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 (39) hide show
  1. pycvr-0.1.0/CHANGELOG.md +35 -0
  2. pycvr-0.1.0/LICENSE +21 -0
  3. pycvr-0.1.0/MANIFEST.in +4 -0
  4. pycvr-0.1.0/PKG-INFO +357 -0
  5. pycvr-0.1.0/README.md +320 -0
  6. pycvr-0.1.0/examples/basic.py +36 -0
  7. pycvr-0.1.0/examples/sample.py +30 -0
  8. pycvr-0.1.0/examples/to_gif.py +27 -0
  9. pycvr-0.1.0/pyproject.toml +61 -0
  10. pycvr-0.1.0/setup.cfg +4 -0
  11. pycvr-0.1.0/src/cvr/__init__.py +34 -0
  12. pycvr-0.1.0/src/cvr/__main__.py +5 -0
  13. pycvr-0.1.0/src/cvr/cli.py +189 -0
  14. pycvr-0.1.0/src/cvr/encoders.py +130 -0
  15. pycvr-0.1.0/src/cvr/fonts.py +121 -0
  16. pycvr-0.1.0/src/cvr/gui/__init__.py +5 -0
  17. pycvr-0.1.0/src/cvr/gui/__main__.py +5 -0
  18. pycvr-0.1.0/src/cvr/gui/app.py +1049 -0
  19. pycvr-0.1.0/src/cvr/gui/settings.py +69 -0
  20. pycvr-0.1.0/src/cvr/gui/widgets.py +232 -0
  21. pycvr-0.1.0/src/cvr/highlight.py +205 -0
  22. pycvr-0.1.0/src/cvr/palette.py +237 -0
  23. pycvr-0.1.0/src/cvr/players.py +141 -0
  24. pycvr-0.1.0/src/cvr/py.typed +0 -0
  25. pycvr-0.1.0/src/cvr/renderer.py +650 -0
  26. pycvr-0.1.0/src/cvr/themes.py +220 -0
  27. pycvr-0.1.0/src/pycvr.egg-info/PKG-INFO +357 -0
  28. pycvr-0.1.0/src/pycvr.egg-info/SOURCES.txt +37 -0
  29. pycvr-0.1.0/src/pycvr.egg-info/dependency_links.txt +1 -0
  30. pycvr-0.1.0/src/pycvr.egg-info/entry_points.txt +5 -0
  31. pycvr-0.1.0/src/pycvr.egg-info/requires.txt +10 -0
  32. pycvr-0.1.0/src/pycvr.egg-info/top_level.txt +1 -0
  33. pycvr-0.1.0/tests/conftest.py +37 -0
  34. pycvr-0.1.0/tests/test_cli.py +40 -0
  35. pycvr-0.1.0/tests/test_gui.py +29 -0
  36. pycvr-0.1.0/tests/test_highlight.py +42 -0
  37. pycvr-0.1.0/tests/test_palette.py +76 -0
  38. pycvr-0.1.0/tests/test_renderer.py +139 -0
  39. pycvr-0.1.0/tests/test_themes.py +64 -0
@@ -0,0 +1,35 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0 (2026-10-24)
4
+
5
+ First release as an installable package. The rendered frames are pixel for pixel what the single
6
+ `CVR.py` produced, apart from the line number and highlighting changes below.
7
+
8
+ ### Added
9
+
10
+ * `pip install` support, with a `cvr` command line tool and a `cvr-gui` launcher.
11
+ * A Python API: `cvr.render()`, `cvr.Renderer`, `cvr.load_theme()`, `cvr.list_themes()` and
12
+ `cvr.Palette`.
13
+ * `Renderer.frames()` yields each frame as a numpy array, for GIFs or your own pipeline.
14
+ * Render from a string with `source=` and pick the lexer with `language=`.
15
+ * `Palette.replace()` and hex strings for custom colours.
16
+ * Tests and a GitHub Actions workflow.
17
+
18
+ ### Changed
19
+
20
+ * The code is split into modules under `src/cvr/`. `python CVR.py` still opens the app.
21
+ * ffmpeg now comes with the package through `imageio-ffmpeg`, so exact theme colours work without
22
+ installing anything else. OpenCV is optional (`pip install pycvr[opencv]`).
23
+ * App settings live in the user config folder (`%APPDATA%\cvr`, `~/Library/Application Support/cvr`
24
+ or `~/.config/cvr`). An existing `typewriter_settings.json` is picked up on first launch.
25
+ * Line numbers appear as each line is typed, instead of the whole gutter being numbered from
26
+ the first frame.
27
+ * The preview uses the same highlighter as the render, so their colours agree.
28
+ * A failed render shows the reason in the app instead of pointing at the console.
29
+ * A failed or interrupted render deletes its partial file.
30
+
31
+ ### Fixed
32
+
33
+ * A file that started with blank lines was highlighted one line out, because Pygments dropped the
34
+ leading newlines.
35
+ * Text the highlighter could not place fell back to Dark+ grey instead of the theme's foreground.
pycvr-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Anish Bhattarai
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,4 @@
1
+ # Extra files for the source distribution, so its tests run and examples ship.
2
+ include CHANGELOG.md
3
+ recursive-include tests *.py
4
+ recursive-include examples *.py
pycvr-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,357 @@
1
+ Metadata-Version: 2.4
2
+ Name: pycvr
3
+ Version: 0.1.0
4
+ Summary: Turn source code into a typewriter-style video, rendered in your real VS Code colour theme.
5
+ Author: Anish Bhattarai
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/anishbhattarai-asm/CVR
8
+ Project-URL: Source, https://github.com/anishbhattarai-asm/CVR
9
+ Project-URL: Changelog, https://github.com/anishbhattarai-asm/CVR/blob/main/CHANGELOG.md
10
+ Project-URL: Issues, https://github.com/anishbhattarai-asm/CVR/issues
11
+ Keywords: code,video,typewriter,vscode,theme,syntax-highlighting,animation
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Environment :: Console
14
+ Classifier: Environment :: X11 Applications
15
+ Classifier: Environment :: Win32 (MS Windows)
16
+ Classifier: Environment :: MacOS X
17
+ Classifier: Intended Audience :: Developers
18
+ Classifier: Intended Audience :: Education
19
+ Classifier: Operating System :: OS Independent
20
+ Classifier: Programming Language :: Python :: 3
21
+ Classifier: Programming Language :: Python :: 3 :: Only
22
+ Classifier: Topic :: Multimedia :: Video
23
+ Classifier: Topic :: Software Development :: Documentation
24
+ Classifier: Typing :: Typed
25
+ Requires-Python: >=3.9
26
+ Description-Content-Type: text/markdown
27
+ License-File: LICENSE
28
+ Requires-Dist: numpy>=1.21
29
+ Requires-Dist: pillow>=9.2
30
+ Requires-Dist: pygments>=2.10
31
+ Requires-Dist: imageio-ffmpeg>=0.4.7
32
+ Provides-Extra: opencv
33
+ Requires-Dist: opencv-python>=4.5; extra == "opencv"
34
+ Provides-Extra: dev
35
+ Requires-Dist: pytest>=7; extra == "dev"
36
+ Dynamic: license-file
37
+
38
+ # CVR
39
+
40
+ Turn a source file into a typewriter style video, rendered in your real VS Code colour theme.
41
+
42
+ ![A Python file being typed out in the Dark 2026 theme](https://raw.githubusercontent.com/anishbhattarai-asm/CVR/main/docs/demo.gif)
43
+
44
+ CVR reads the themes VS Code has installed on your machine, resolves their colours the same way
45
+ VS Code does, and paints every frame with them. The result is a video that looks like your editor,
46
+ not like an approximation of it.
47
+
48
+ Use it three ways: a desktop app with a live preview, a `cvr` command for scripts and batch jobs,
49
+ or a Python library you can build on.
50
+
51
+ ## Features
52
+
53
+ * Reads installed VS Code themes straight from disk. Every built in theme plus anything a theme
54
+ extension contributes, or any theme json you point it at.
55
+ * Colours resolved from the theme json, including the `include` chain and TextMate scope
56
+ precedence, so `keyword.control`, `entity.name.function` and the rest land on the exact values
57
+ VS Code uses.
58
+ * Syntax highlighting through Pygments for any language it can lex.
59
+ * Editor tab across the top showing the file name, drawn in the theme tab colours.
60
+ * Fixed target duration. Ask for 30 seconds and the video is 30 seconds, no matter how long the
61
+ file is.
62
+ * Instant indentation, the way an editor indents, instead of typing leading spaces one at a time.
63
+ * Line numbers, cursor blink, smooth scrolling that follows the cursor, and a scrollbar.
64
+ * Exact theme colours in the output, with ffmpeg bundled so there is nothing else to install.
65
+ * Frames available as numpy arrays, so you can make GIFs or feed your own pipeline.
66
+
67
+ ## Install
68
+
69
+ Python 3.9 or newer.
70
+
71
+ ```
72
+ pip install pycvr
73
+ ```
74
+
75
+ The package is called `pycvr` on PyPI; you import it as `cvr` and run it as `cvr`.
76
+
77
+ For the latest code from GitHub instead:
78
+
79
+ ```
80
+ pip install git+https://github.com/anishbhattarai-asm/CVR
81
+ ```
82
+
83
+ Or from a clone, which also lets you run the app without installing:
84
+
85
+ ```
86
+ git clone https://github.com/anishbhattarai-asm/CVR
87
+ cd CVR
88
+ pip install .
89
+ ```
90
+
91
+ This pulls in numpy, Pillow, Pygments and `imageio-ffmpeg`, which ships an ffmpeg binary. An
92
+ ffmpeg already on your `PATH` is used first.
93
+
94
+ The app needs Tkinter. It ships with Python on Windows and macOS. On Debian or Ubuntu install it
95
+ with `sudo apt install python3-tk`. The command line tool and the library work without it.
96
+
97
+ ## Quick start
98
+
99
+ ### The app
100
+
101
+ ```
102
+ cvr
103
+ ```
104
+
105
+ `cvr-gui` does the same without a console window, and from a clone without installing,
106
+ `pip install -r requirements.txt` then `python CVR.py`.
107
+
108
+ ![The CVR app with the theme list and live preview](https://raw.githubusercontent.com/anishbhattarai-asm/CVR/main/docs/app.png)
109
+
110
+ 1. **File**: pick the source file and where the video should go.
111
+ 2. **Theme**: choose a colour theme. The preview updates as you select.
112
+ 3. **Video**: resolution, frame rate, font size, line numbers, file name tab, colour accuracy.
113
+ 4. **Animation**: typing speed, cursor behaviour, scrolling, instant indentation.
114
+ 5. **Duration**: set a target length, or drive it with the per character and per line delays.
115
+
116
+ Press **Start rendering**. Progress and an estimate appear at the bottom, and **Cancel** stops it.
117
+ When the render is done a notification appears in the corner; click it to play the video in the
118
+ player of your choice.
119
+
120
+ ### The command line
121
+
122
+ ```
123
+ cvr render app.py # app_typewriter.mp4, your VS Code theme
124
+ cvr render app.py -o demo.mp4 -t "Dark Modern" -d 30
125
+ cvr render main.rs -r 1920x1080 --fps 60 --font-size 28
126
+ cvr render notes.txt --language python --style typewriter --open
127
+ cvr themes # list installed themes, * marks the default
128
+ cvr themes light
129
+ ```
130
+
131
+ | Option | Default | |
132
+ |---|---|---|
133
+ | `-o`, `--output PATH` | `FILE_typewriter.mp4` | Where to write the video. |
134
+ | `-t`, `--theme NAME` | the theme VS Code is set to | An installed theme's name (case does not matter) or a theme json path. |
135
+ | `-d`, `--duration SECONDS` | none | Fixed length. Without it, the delays decide. |
136
+ | `-r`, `--resolution WxH` | `1280x720` | Frame size. |
137
+ | `--fps N` | `30` | Frames per second. |
138
+ | `--font PATH`, `--font-size PX` | system monospace, `18` | Font file and size. |
139
+ | `--style` | `smooth` | `smooth`, `fast`, or `typewriter` (slower, underline cursor). |
140
+ | `--char-delay`, `--line-delay` | `0.05`, `0.30` | Seconds after each character and each line. |
141
+ | `--language NAME` | from the file name | Pygments lexer name. |
142
+ | `--no-line-numbers`, `--no-tab`, `--no-cursor`, `--no-blink` | | Hide parts of the editor. |
143
+ | `--no-follow`, `--no-smooth-scroll` | | Change how the view scrolls. |
144
+ | `--no-highlight` | | Plain text. |
145
+ | `--type-indent` | | Type leading spaces instead of indenting instantly. |
146
+ | `--compatible` | | Video Windows Media Player can open. See [Colour accuracy](#colour-accuracy). |
147
+ | `--open` | | Play the video when it is done. |
148
+ | `-q`, `--quiet` / `-v`, `--verbose` | | No progress bar / print timing details. |
149
+
150
+ `python -m cvr` works anywhere `cvr` does.
151
+
152
+ ### Python
153
+
154
+ ```python
155
+ import cvr
156
+
157
+ cvr.render("app.py", "app.mp4", theme="Monokai", duration=20)
158
+ ```
159
+
160
+ ## Python API
161
+
162
+ ### `cvr.render()`
163
+
164
+ ```python
165
+ cvr.render(code_path=None, output_path=None, *, theme=None, duration=None,
166
+ source=None, language=None, progress=None, cancel=None, **options) -> str
167
+ ```
168
+
169
+ Renders and returns the path of the video.
170
+
171
+ * `theme` is an installed theme's name, a theme json path, a `cvr.Palette`, or `None` for the
172
+ theme VS Code is currently set to (falling back to the bundled Dark+).
173
+ * `duration` fixes the length in seconds. Leave it out to let the delays decide.
174
+ * `source` renders a string instead of reading `code_path`. `code_path` can still be given to set
175
+ the tab label and pick the lexer.
176
+ * `language` picks the Pygments lexer by name, such as `"python"` or `"rust"`.
177
+ * `progress(percent, total, status, eta_seconds)` is called as frames are written. `total` is
178
+ always 100.
179
+ * `cancel` is a `threading.Event` (anything with `is_set()`). Setting it stops the render and
180
+ deletes the partial file.
181
+ * Everything else goes to `cvr.Renderer`.
182
+
183
+ It raises `cvr.RenderError` when the render fails, `cvr.RenderCancelled` when it is cancelled,
184
+ `LookupError` for a theme that is not installed, and `FileNotFoundError` for a missing source file.
185
+
186
+ ```python
187
+ import threading
188
+ import cvr
189
+
190
+ stop = threading.Event()
191
+
192
+ def show(percent, _total, status, eta):
193
+ print(f"\r{percent:5.1f}% {status} ETA {eta:.0f}s", end="")
194
+
195
+ cvr.render(source='print("hello")\n', language="python", output_path="hello.mp4",
196
+ duration=5, progress=show, cancel=stop)
197
+ ```
198
+
199
+ ### `cvr.Renderer`
200
+
201
+ The class behind `render()`. Construction reads and lays out the code; nothing is written until
202
+ you call `render()`.
203
+
204
+ ```python
205
+ r = cvr.Renderer("app.py", "app.mp4", palette=cvr.load_theme("Dark Modern"),
206
+ resolution=(1920, 1080), fps=60, font_size=28)
207
+ print(r.estimated_duration(), "seconds,", r.total_frames(), "frames")
208
+ r.render() # returns the path, or False with the reason in r.error
209
+ ```
210
+
211
+ | Option | Default | |
212
+ |---|---|---|
213
+ | `code_path` | | Source file. `str` or `pathlib.Path`. |
214
+ | `output_path` | `<name>_typewriter.mp4` in the current folder | Video to write. |
215
+ | `palette` | bundled Dark+ | A `cvr.Palette`. |
216
+ | `resolution` | `(1280, 720)` | Frame size in pixels. |
217
+ | `fps` | `30` | Frames per second. |
218
+ | `font_path` | system monospace | A TrueType font file. The default is the first one found of Cascadia Code or Consolas on Windows, Courier New or Menlo on macOS, Liberation Mono or DejaVu Sans Mono on Linux. |
219
+ | `font_size` | `18` | In pixels. |
220
+ | `char_delay` | `0.05` | Seconds after each typed character. |
221
+ | `line_delay` | `0.30` | Seconds after each line. |
222
+ | `target_duration` | `0` | Fixed length in seconds. Scales both delays and keeps their ratio. `0` turns it off. |
223
+ | `animation_type` | `"smooth"` | `"smooth"` uses the delays above. `"fast"` and `"typewriter"` replace them with presets; `"typewriter"` also draws an underline cursor. |
224
+ | `show_line_numbers` | `True` | Line number gutter. |
225
+ | `show_filename_bar` | `True` | Editor tab with the file name. |
226
+ | `show_cursor`, `cursor_blink` | `True`, `True` | The cursor, and whether it blinks. |
227
+ | `follow_cursor`, `smooth_scroll` | `True`, `True` | Scroll to keep the cursor in view, gliding rather than jumping. |
228
+ | `syntax_highlighting` | `True` | `False` draws everything in the foreground colour. |
229
+ | `instant_indent` | `True` | Indentation appears at once instead of being typed. |
230
+ | `exact_colour` | `True` | H.264 4:4:4 with untouched colours. See [Colour accuracy](#colour-accuracy). |
231
+ | `source` | `None` | Code as a string. |
232
+ | `language` | `None` | Pygments lexer name. |
233
+
234
+ ### Frames
235
+
236
+ `Renderer.frames()` yields every frame in order as an RGB `uint8` numpy array of shape
237
+ `(height, width, 3)`, so you can write a GIF, add an overlay, or hand them to another library.
238
+ `create_frame(line, char)` draws a single frame on its own.
239
+
240
+ ```python
241
+ from PIL import Image
242
+ import cvr
243
+
244
+ r = cvr.Renderer("app.py", palette=cvr.load_theme(), resolution=(800, 450),
245
+ fps=20, target_duration=10)
246
+ frames = [Image.fromarray(f) for f in r.frames()]
247
+ frames[0].save("app.gif", save_all=True, append_images=frames[1:], duration=50, loop=0)
248
+ ```
249
+
250
+ [`examples/to_gif.py`](https://github.com/anishbhattarai-asm/CVR/blob/main/examples/to_gif.py) is the script that made the GIF at the top of this page.
251
+
252
+ ### Themes and palettes
253
+
254
+ ```python
255
+ cvr.list_themes() # [ThemeInfo(label, path), ...] for every installed theme
256
+ cvr.default_theme() # the one VS Code is set to
257
+ cvr.find_theme("monokai") # ThemeInfo or None; ignores case
258
+ cvr.load_theme("Solarized Dark") # Palette; also takes a json path, or None for the default
259
+
260
+ pal = cvr.Palette.from_file("my-theme.json")
261
+ pal = cvr.load_theme().replace(bg="#000000", cursor="#FFCC00")
262
+ cvr.DEFAULT_PALETTE # the bundled Dark+, no VS Code needed
263
+ ```
264
+
265
+ A `Palette` holds every colour the renderer uses as an RGB tuple: `bg`, `fg`, `comment`,
266
+ `string`, `number`, `keyword_ctrl`, `storage`, `function`, `type_`, `variable`, the gutter, the
267
+ cursor, the tab colours and a few more. `Palette.FIELDS` lists them all. Any of them can be passed
268
+ as a `"#RRGGBB"` string.
269
+
270
+ Light themes are labelled with a `(light)` suffix, as in the app's list.
271
+
272
+ ### Logging
273
+
274
+ The library reports through the standard `logging` module under the `cvr` logger and prints
275
+ nothing by itself. `logging.basicConfig(level=logging.INFO)` shows the timing each render settles
276
+ on.
277
+
278
+ [`examples/basic.py`](https://github.com/anishbhattarai-asm/CVR/blob/main/examples/basic.py) walks through the common cases.
279
+
280
+ ## Colour accuracy
281
+
282
+ H.264 stores colour as YUV, and converting RGB to YUV and back costs a level or two on the dark
283
+ tones a code theme is built from. The **Exact theme colours** option in the Video tab (`--compatible`
284
+ on the command line, `exact_colour` in Python) controls the trade off.
285
+
286
+ **On**, the default. Colours match the preview exactly and coloured text edges stay sharp. Plays
287
+ in VLC, mpv and browsers.
288
+
289
+ **Off**. Colours land about one level off on dark tones. Plays in everything, including Windows
290
+ Media Player.
291
+
292
+ Windows Media Player cannot open H.264 4:4:4 and reports "unsupported encoding settings". Turn the
293
+ option off if you need a file it will play.
294
+
295
+ Both modes need ffmpeg, which `imageio-ffmpeg` provides. If neither it nor a system ffmpeg is
296
+ available, CVR falls back to OpenCV (`pip install "pycvr[opencv]"`), which shifts
297
+ dark tones slightly.
298
+
299
+ ## How theme loading works
300
+
301
+ 1. Find every VS Code extension folder, both the bundled ones next to the VS Code install and the
302
+ ones under your user profile.
303
+ 2. Read each `package.json`, collect what `contributes.themes` declares, and resolve any localised
304
+ label through `package.nls.json`.
305
+ 3. Parse the chosen theme, which is json with comments and trailing commas, and follow its
306
+ `include` chain so a theme built on another inherits correctly.
307
+ 4. Resolve TextMate scopes with VS Code rules: the longest matching selector wins, and a later
308
+ rule beats an earlier one at the same specificity.
309
+ 5. Map Pygments token types onto the resolved colours.
310
+
311
+ Identifier colouring is the one place this cannot be exact. VS Code colours identifiers with
312
+ semantic tokens from a language server, while CVR uses a lexer. Structural tokens such as
313
+ keywords, strings, numbers, comments, functions and classes match, but the odd identifier can land
314
+ on a different role.
315
+
316
+ Without VS Code installed, `cvr themes` lists only the bundled Dark+ palette, and any theme json
317
+ you have can still be loaded by path.
318
+
319
+ ## Settings
320
+
321
+ The app remembers its choices in `settings.json` under `%APPDATA%\cvr` on Windows,
322
+ `~/Library/Application Support/cvr` on macOS, and `~/.config/cvr` on Linux. Set `CVR_SETTINGS` to
323
+ use another file. A `typewriter_settings.json` from an older version is read on first launch and
324
+ carried over.
325
+
326
+ ## Project layout
327
+
328
+ ```
329
+ src/cvr/
330
+ __init__.py public API
331
+ renderer.py Renderer, render(), timing
332
+ palette.py Palette and theme json parsing
333
+ themes.py finding installed VS Code themes
334
+ highlight.py Pygments tokens to theme colours
335
+ fonts.py font lookup and text measuring
336
+ encoders.py ffmpeg and OpenCV video writers
337
+ players.py finding installed video players
338
+ cli.py the cvr command
339
+ gui/ the Tkinter app: app.py, widgets.py, settings.py
340
+ tests/ pytest suite
341
+ examples/ library examples
342
+ CVR.py runs the app from a clone without installing
343
+ ```
344
+
345
+ ## Development
346
+
347
+ ```
348
+ pip install -e ".[dev]"
349
+ pytest
350
+ ```
351
+
352
+ The tests render small videos, so they need ffmpeg, which the install provides. Tests that need
353
+ Tkinter or OpenCV are skipped when those are missing.
354
+
355
+ ## Licence
356
+
357
+ MIT. See [LICENSE](https://github.com/anishbhattarai-asm/CVR/blob/main/LICENSE).