renoise-kit 0.5.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. renoise_kit-0.5.0/LICENSE +21 -0
  2. renoise_kit-0.5.0/PKG-INFO +307 -0
  3. renoise_kit-0.5.0/README.md +271 -0
  4. renoise_kit-0.5.0/pyproject.toml +47 -0
  5. renoise_kit-0.5.0/renoise_kit.egg-info/PKG-INFO +307 -0
  6. renoise_kit-0.5.0/renoise_kit.egg-info/SOURCES.txt +49 -0
  7. renoise_kit-0.5.0/renoise_kit.egg-info/dependency_links.txt +1 -0
  8. renoise_kit-0.5.0/renoise_kit.egg-info/entry_points.txt +2 -0
  9. renoise_kit-0.5.0/renoise_kit.egg-info/requires.txt +33 -0
  10. renoise_kit-0.5.0/renoise_kit.egg-info/top_level.txt +1 -0
  11. renoise_kit-0.5.0/rn/__init__.py +2 -0
  12. renoise_kit-0.5.0/rn/__main__.py +5 -0
  13. renoise_kit-0.5.0/rn/align.py +92 -0
  14. renoise_kit-0.5.0/rn/assemble.py +602 -0
  15. renoise_kit-0.5.0/rn/assets.py +125 -0
  16. renoise_kit-0.5.0/rn/audio.py +79 -0
  17. renoise_kit-0.5.0/rn/captions.py +343 -0
  18. renoise_kit-0.5.0/rn/capture.py +97 -0
  19. renoise_kit-0.5.0/rn/cli.py +557 -0
  20. renoise_kit-0.5.0/rn/compare.py +115 -0
  21. renoise_kit-0.5.0/rn/cuts.py +81 -0
  22. renoise_kit-0.5.0/rn/editops.py +252 -0
  23. renoise_kit-0.5.0/rn/edl.py +898 -0
  24. renoise_kit-0.5.0/rn/export.py +200 -0
  25. renoise_kit-0.5.0/rn/faces.py +135 -0
  26. renoise_kit-0.5.0/rn/ff.py +183 -0
  27. renoise_kit-0.5.0/rn/fonts.py +87 -0
  28. renoise_kit-0.5.0/rn/frames.py +205 -0
  29. renoise_kit-0.5.0/rn/fx/__init__.py +223 -0
  30. renoise_kit-0.5.0/rn/fx/fx.js +150 -0
  31. renoise_kit-0.5.0/rn/listen.py +199 -0
  32. renoise_kit-0.5.0/rn/matte.py +58 -0
  33. renoise_kit-0.5.0/rn/qa.py +212 -0
  34. renoise_kit-0.5.0/rn/ref.py +74 -0
  35. renoise_kit-0.5.0/rn/ref_guide.md +245 -0
  36. renoise_kit-0.5.0/rn/signal.py +190 -0
  37. renoise_kit-0.5.0/rn/text.py +112 -0
  38. renoise_kit-0.5.0/setup.cfg +4 -0
  39. renoise_kit-0.5.0/tests/test_captions.py +158 -0
  40. renoise_kit-0.5.0/tests/test_capture.py +83 -0
  41. renoise_kit-0.5.0/tests/test_edl_v3.py +183 -0
  42. renoise_kit-0.5.0/tests/test_faces.py +109 -0
  43. renoise_kit-0.5.0/tests/test_fx.py +87 -0
  44. renoise_kit-0.5.0/tests/test_listen.py +56 -0
  45. renoise_kit-0.5.0/tests/test_rn.py +314 -0
  46. renoise_kit-0.5.0/tests/test_v3_commands.py +110 -0
  47. renoise_kit-0.5.0/tests/test_v4_commands.py +120 -0
  48. renoise_kit-0.5.0/tests/test_v5_composite.py +85 -0
  49. renoise_kit-0.5.0/tests/test_v5_data.py +129 -0
  50. renoise_kit-0.5.0/tests/test_v5_measure.py +171 -0
  51. renoise_kit-0.5.0/tests/test_v5_time.py +95 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Renoise
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,307 @@
1
+ Metadata-Version: 2.4
2
+ Name: renoise-kit
3
+ Version: 0.5.0
4
+ Summary: rn: frame-exact probing, contact sheets, EDL assembly, finishing and QA for AI video edits
5
+ License-Expression: MIT
6
+ Project-URL: Homepage, https://renoise.ai
7
+ Project-URL: Source, https://github.com/renoise-ai/renoise-kit
8
+ Requires-Python: >=3.11
9
+ Description-Content-Type: text/markdown
10
+ License-File: LICENSE
11
+ Requires-Dist: pillow>=10.1
12
+ Requires-Dist: numpy
13
+ Provides-Extra: listen
14
+ Requires-Dist: librosa; extra == "listen"
15
+ Requires-Dist: matplotlib; extra == "listen"
16
+ Provides-Extra: transcribe
17
+ Requires-Dist: faster-whisper; extra == "transcribe"
18
+ Provides-Extra: faces
19
+ Requires-Dist: opencv-python-headless>=4.8; extra == "faces"
20
+ Provides-Extra: capture
21
+ Requires-Dist: playwright; extra == "capture"
22
+ Provides-Extra: captions
23
+ Requires-Dist: pysubs2; extra == "captions"
24
+ Provides-Extra: fonts
25
+ Requires-Dist: fonttools; extra == "fonts"
26
+ Provides-Extra: matte
27
+ Requires-Dist: rembg[cpu]; extra == "matte"
28
+ Provides-Extra: export
29
+ Requires-Dist: opentimelineio; extra == "export"
30
+ Requires-Dist: otio-fcpx-xml-adapter; extra == "export"
31
+ Requires-Dist: otio-fcp-adapter; extra == "export"
32
+ Requires-Dist: pysubs2; extra == "export"
33
+ Provides-Extra: all
34
+ Requires-Dist: renoise-kit[captions,capture,export,faces,fonts,listen,matte,transcribe]; extra == "all"
35
+ Dynamic: license-file
36
+
37
+ # renoise-kit · `rn`
38
+
39
+ **English** · [简体中文](README.zh-CN.md)
40
+
41
+ The mechanical, error-prone half of editing video, as one command-line tool: frame-exact probing, contact sheets you
42
+ can actually look at, audio turned into numbers, code rendered into frames, an edit decision list (EDL) assembled
43
+ frame-exactly, delivery finishing, QA and export to other editors.
44
+
45
+ `rn` measures and guarantees the mechanics. Every creative decision stays yours, or your agent's. It was built for AI
46
+ agents editing generated video in the [Renoise](https://renoise.ai) media sandbox, and the same tool runs on your
47
+ machine.
48
+
49
+ - **Measure.** Turns media into facts a model can read: frames, shots, beats, word times, faces, on-screen text,
50
+ per-frame signals.
51
+ - **Render.** Turns any code into frame-exact pictures, alpha included: HTML/canvas pages, numpy, Manim, Blender. They
52
+ all meet in one format, a PNG sequence.
53
+ - **Assemble and verify.** Builds a frame-exact cut from an EDL, then checks it against the delivery standard.
54
+ - **No named effects, no auto-editing.** The EDL adds general capabilities (keyframes, anchors, masks, blend modes, raw
55
+ ffmpeg filters), never a menu of looks. A common effect comes as readable source you can call, change or ignore.
56
+
57
+ ## Install
58
+
59
+ `rn` drives the system's ffmpeg and ffprobe, version 7 or later, built with libass for burned-in subtitles.
60
+
61
+ | | macOS | Debian / Ubuntu |
62
+ |---|---|---|
63
+ | ffmpeg with libass | `brew install ffmpeg-full`, then `export PATH="$(brew --prefix ffmpeg-full)/bin:$PATH"` (keg-only; Homebrew's plain `ffmpeg` has no libass) | `apt install ffmpeg fontconfig` |
64
+ | OCR for `rn text` (optional) | comes with `ffmpeg-full` | `apt install tesseract-ocr tesseract-ocr-chi-sim` |
65
+
66
+ ```sh
67
+ uv tool install 'renoise-kit[all]' # or: pipx install 'renoise-kit[all]'
68
+ playwright install chromium # only for rn capture
69
+ rn --version
70
+ ```
71
+
72
+ The core install is Pillow and numpy. Heavier dependencies come as extras, so you can install only what you use. When
73
+ a command needs a missing extra, its error line names the extra to install.
74
+
75
+ | Extra | For | Installs |
76
+ |---|---|---|
77
+ | `listen` | `rn listen`, `rn ref`, `rn signal -o` | librosa, matplotlib |
78
+ | `transcribe` | `rn listen --transcribe` | faster-whisper |
79
+ | `faces` | `rn faces` | OpenCV (headless); the YuNet model is fetched once into `~/.cache/rn` and checked against its sha256 |
80
+ | `capture` | `rn capture` | Playwright |
81
+ | `captions` | `rn captions` | pysubs2 |
82
+ | `fonts` | `rn fonts --licenses` | fontTools |
83
+ | `matte` | `rn matte` | rembg (onnxruntime); models download on first use |
84
+ | `export` | `rn export` | OpenTimelineIO and its Final Cut / FCP 7 adapters |
85
+ | `all` | everything above | |
86
+
87
+ ## A first cut
88
+
89
+ Starting from two generated shots, a music bed, a whoosh and an end card:
90
+
91
+ ```sh
92
+ rn sheet shots/s2.mp4 --fps 4 # look at a take: labelled frames, small enough to view
93
+ rn listen music.wav --json music.json # loudness, beats, tempo
94
+ rn capture title.html -o title --frames 90 --size 1080x300 # an HTML title, frame by frame, transparent
95
+ ```
96
+
97
+ `title.html` draws frame `i` when asked. The frame number is the time, so any frame order or number of shards gives
98
+ the same pixels:
99
+
100
+ ```html
101
+ <canvas id=c></canvas><script>
102
+ const x = c.getContext('2d'); c.width = RN.width; c.height = RN.height;
103
+ window.renderFrame = i => {
104
+ x.clearRect(0, 0, c.width, c.height);
105
+ x.globalAlpha = Math.min(1, i / 10);
106
+ x.fillStyle = '#fff'; x.font = '700 120px sans-serif'; x.textAlign = 'center';
107
+ x.fillText('RENOISE', c.width / 2, 160);
108
+ };</script>
109
+ ```
110
+
111
+ `edit.json` puts it together:
112
+
113
+ ```json
114
+ {
115
+ "output": {"width": 1080, "height": 1920, "fps": 30},
116
+ "tracks": [
117
+ {"clips": [
118
+ {"id": "open", "src": "shots/s1.mp4", "in": 0.4, "out": 3.2},
119
+ {"id": "hero", "src": "shots/s2.mp4", "ranges": [[0, 1.5], [2.5, 4]],
120
+ "transition": {"type": "fade", "duration": 0.3}},
121
+ {"id": "end", "image": "card.png", "duration": 1.5, "fit": "contain"}
122
+ ]},
123
+ {"clips": [
124
+ {"id": "title", "frames": "title/%05d.png", "fps": 30, "y": 1500,
125
+ "at": {"clip": "hero", "offset": 0.2}, "until": {"clip_end": "hero"}}
126
+ ]}
127
+ ],
128
+ "audio": [
129
+ {"src": "music.wav", "role": "music", "pivot": 12.0, "at": {"clip": "hero"}, "fade_out": 1},
130
+ {"src": "whoosh.wav", "role": "sfx", "pivot": 0.18, "at": {"clip": "hero"}}
131
+ ],
132
+ "mix": {"lufs": -14, "cut_fade": 0.01}
133
+ }
134
+ ```
135
+
136
+ What each part does:
137
+
138
+ - **Main track.** It plays back to back. `hero` plays two spans of one take, dissolving in from `open`.
139
+ - **Title.** It follows the `hero` clip by id rather than by hand-computed seconds. If `open` gets longer, the title
140
+ moves with it.
141
+ - **Music.** `pivot` lands the music's 12 s point (say, the drop) on the cut into `hero`, and the head of the track is
142
+ trimmed away.
143
+ - **Whoosh.** Its peak lands on the same cut.
144
+
145
+ ```sh
146
+ rn assemble edit.json --dry-run # the laid-out timeline, frame by frame, nothing rendered
147
+ rn assemble edit.json -o draft.mp4 --preview # half-size draft, about 3x faster
148
+ rn assemble edit.json -o cut.mp4 # full render; unchanged clips are reused from .rn-cache
149
+ rn qa cut.mp4 --edl edit.json # exit 1 on FAIL; WARN means look at it
150
+ ```
151
+
152
+ ```text
153
+ timeline 1080x1920 30fps 210 frames 7.0s
154
+ open: frames 0..83
155
+ hero: frames 75..119 (xfade 9f)
156
+ hero.1: frames 120..164
157
+ end: frames 165..209
158
+ title: overlay frames 81..164
159
+ ...
160
+ WARN level_jumps: 6.0s -40->-13 dB, 6.5s -45->-19 dB
161
+ qa PASS: 0 failed, 1 warnings, 17 passed (cut.mp4)
162
+ ```
163
+
164
+ A WARN may be intended. Here the jumps are the music's own hard-hitting beats. You can waive it in the EDL with
165
+ `"accept": [{"check": "level_jumps", "note": "the music's hits"}]`, or with `rn qa --accept level_jumps`. A FAIL can
166
+ never be waived.
167
+
168
+ Make further changes by operation instead of rewriting the JSON:
169
+
170
+ ```sh
171
+ rn edit edit.json split open --at 1.5
172
+ rn edit edit.json set title meta.copy="new tagline"
173
+ rn edit edit.json delete end --lift
174
+ ```
175
+
176
+ ## Commands
177
+
178
+ `rn <command> --help` is the reference. Every command prints a short summary, and `--json FILE` writes the full result.
179
+
180
+ **Measure**
181
+
182
+ | Command | What it gives |
183
+ |---|---|
184
+ | `rn probe FILE` | Decoded frame count, rational fps, size, color tags, separate video and audio durations, faststart (JSON) |
185
+ | `rn frame FILE --index N \| --time T \| --last -o out.png` | One exact frame, e.g. a tail frame to continue a shot |
186
+ | `rn sheet FILE... [--fps R \| --every N \| --indices ... \| --around T,.. --diff]` | Contact sheets labelled with frame number and timecode, each under 125 KB |
187
+ | `rn cuts FILE [--sheet JPG]` | Shot boundaries by decoded frame, plus weaker "suspects" to check |
188
+ | `rn listen FILE [--transcribe]` | Loudness, silences, onsets, tempo with half/double candidates, beat times, Whisper word times, one picture |
189
+ | `rn signal FILE [-o PNG]` | Per-frame luma, motion, saturation, loudness, low/mid/high bands, onset strength; flash frames, clicks |
190
+ | `rn align A B` | Where recording B sits inside A, by sound: offset, `match`, `distinct` |
191
+ | `rn faces FILE` | Face boxes and landmarks per frame (measurement only) |
192
+ | `rn text FILE` | On-screen text lines, their boxes and when they are on screen (tesseract) |
193
+ | `rn matte FILE -o DIR` | Subject alpha per frame as grey PNGs, ready to use as an EDL `mask`; `--cutout` for RGBA |
194
+ | `rn ref FILE` | A reference breakdown: shots, sound, cut-to-beat sync, every frame on sheets, and a reading guide |
195
+
196
+ **Render**
197
+
198
+ | Command | What it gives |
199
+ |---|---|
200
+ | `rn capture page.html -o DIR --frames N --size WxH` | An HTML page rendered frame by frame (`window.renderFrame(i)`) to RGBA PNGs, sharded across Chromiums |
201
+ | `rn fx [--source]` | The effects library (grain, vignette, leak, flash, glitch, ...) as readable source for pages and numpy |
202
+ | `rn captions WORDS.json --size WxH -o caps.ass` | Your script aligned to Whisper timing; CJK chunking, balanced lines, safe areas, kept off faces |
203
+ | `rn fonts [--script ja]` | Installed families with the weights that really exist, and the scripts they cover |
204
+ | `rn assets search\|get` | Licensed SFX and overlay footage, checked against sha256 |
205
+
206
+ **Edit and assemble**
207
+
208
+ | Command | What it gives |
209
+ |---|---|
210
+ | `rn edit EDL ids\|set\|split\|delete\|insert\|move` | Changes an EDL by clip id. Each step is checked against the whole timeline before the file is written |
211
+ | `rn assemble EDL -o OUT [--preview] [--range A-B] [--placeholders]` | Renders the EDL frame-exactly. `--placeholders` turns missing shots into labelled cards |
212
+
213
+ **Check and deliver**
214
+
215
+ | Command | What it gives |
216
+ |---|---|
217
+ | `rn qa FILE [--edl EDL] [--accept CHECK[@T]]` | Delivery checks. Format: frames, fps, size, codec, color, faststart, fixed frame rate. Sound: A/V length, loudness, true peak, dead air, level jumps, clicks. Picture: black, frozen, bars |
218
+ | `rn compare A B [--cuts] [--video sbs.mp4]` | Two videos at the same times on one sheet, cut timing paired, optional side-by-side video |
219
+ | `rn finish FILE -o OUT [--height N]` | Brings any video to the delivery standard; `--height` makes a scaled copy |
220
+ | `rn export EDL -o cut.otio\|.fcpxml\|.xml [--srt]` | The edit as a project for Final Cut, Premiere, Resolve or any OTIO tool |
221
+
222
+ ## The EDL in one screen
223
+
224
+ - **Tracks.** `tracks[0]` is the main track: its clips play back to back. Later tracks are overlays, placed with `at`
225
+ in time and with `x / y / w / h` in space.
226
+ - **Audio items.** Items in `audio` are placed with `at`. `role` is `music`, `voice` or `sfx`; music ducks under voice
227
+ when `mix.duck_db` is set.
228
+ - **One time grid.** `output.fps` is the only clock. A clip of N seconds is exactly `round(N × fps)` frames.
229
+ - **Sources.** A clip's source is one of: `src` (video, or another `.json` EDL to nest), `frames` (a numbered PNG
230
+ sequence, alpha kept), `image`, or `color`.
231
+ - **Positions inside a source.** `in` and `out` take seconds, `{"frame": n}` or `{"shot": n}` (a shot found by
232
+ `rn cuts`). `ranges` plays several spans of one source in a row.
233
+ - **Placing on the timeline.** `at` and `until` take seconds or an anchor plus `offset`:
234
+ - `{"clip": "id"}` and `{"clip_end": "id"}`
235
+ - `{"marker": name}`
236
+ - `{"beat": n, "listen": file}` and `{"word": n, "listen": file}`
237
+
238
+ `pivot` places a point inside the source on the anchor instead of the source's start.
239
+ - **Keys.** Written as `[t, value, ease]`. Overlays animate `scale`, `x`, `y`, `rotate`, `opacity` and `speed`; the
240
+ main track `scale`, `x`, `y`, `rotate`, `speed` and `gain_db`; audio items `gain_db`.
241
+ - **Compositing.** `blend` takes `screen`, `multiply`, `add` and other blend modes. `mask` takes a full-frame matte, as
242
+ luma or alpha, optionally inverted. `vf` and `af` insert one raw ffmpeg filter chain.
243
+ - **Bookkeeping.** `id` and `meta` go on anything; `meta` is kept as given. `accept` waives qa warnings judged
244
+ intended.
245
+
246
+ The full schema is in `rn assemble --help` and [SPEC.md](SPEC.md) §4.
247
+
248
+ ## Conventions
249
+
250
+ - **Output.** A few summary lines on stdout. `--json FILE` holds the details. `rn probe` prints JSON directly.
251
+ - **Errors.** A failure exits non-zero. Its last line is `rn: error: <what to fix>`, written so you can relay it as is.
252
+ If ffmpeg is missing, the exit code is 2. `rn qa` exits 1 on FAIL.
253
+ - **Frame exactness.** Frames are picked and cut by decoded index or decode timestamp, never by input seeking. Every
254
+ render is checked against the expected frame count.
255
+ - **Viewing budget.** Sheets default to 125 KB, so four fit in one 512 KB look.
256
+ - **Cache.** Rendered slots and nested edits are stored by content in `.rn-cache/` next to the EDL. Moving or
257
+ renaming a clip reuses its render. `--no-cache` renders everything afresh. Delete the folder to reclaim space.
258
+ - **Environment variables.**
259
+ - `RN_CACHE`: cache folder
260
+ - `RN_YUNET_MODEL`: face model file
261
+ - `RN_ASSETS_URL` / `RN_ASSETS_CACHE`: asset library
262
+ - `U2NET_HOME`: rembg models
263
+
264
+ ## Delivery standard
265
+
266
+ `rn assemble` and `rn finish` produce this standard, and `rn qa` checks it:
267
+
268
+ - **Video.** H.264 High, yuv420p, BT.709 limited range (converted, not just tagged), square pixels, `moov` first. The
269
+ SPS sets `fixed_frame_rate_flag=1`; with 0, some players show a 30 fps video at half its length.
270
+ - **Audio.** AAC 48 kHz stereo at −14 LUFS (configurable), with the true peak measured on the **encoded** AAC at or
271
+ below −1 dBTP.
272
+ - **Length.** Audio is cut to the video by sample, so the container is never longer than the picture.
273
+
274
+ ## With agents
275
+
276
+ `rn` is designed to be called by a model. `--help` is the documentation and loads only when needed. One sandbox call
277
+ can chain several commands. Summaries are short, and errors say what to change. Sheets, `listen`, `signal` and `align`
278
+ exist because a model cannot watch or listen; check the result through them rather than through the format alone.
279
+ `rn qa` passing does not mean the edit is good.
280
+
281
+ In the Renoise media sandbox, `rn` comes preinstalled at the version pinned by the sandbox template.
282
+
283
+ ## Development
284
+
285
+ ```sh
286
+ uv run pytest -q # Python 3.12 (as the sandbox); needs ffmpeg with libass; playwright install chromium for capture tests
287
+ ```
288
+
289
+ CI runs on Debian trixie with ffmpeg 7.1 (the same base as the sandbox) and on macOS with ffmpeg-full. The tests
290
+ generate their own media.
291
+
292
+ To release:
293
+
294
+ 1. Bump `__version__` in `rn/__init__.py`.
295
+ 2. Tag the commit:
296
+
297
+ ```sh
298
+ git tag v$(sed -n "s/^__version__ = '\(.*\)'/\1/p" rn/__init__.py) && git push --tags
299
+ ```
300
+
301
+ `.github/workflows/release.yml` then publishes the tag to PyPI through trusted publishing.
302
+
303
+ Design notes, the full EDL contract and the roadmap are in [SPEC.md](SPEC.md) (in Chinese).
304
+
305
+ ## License
306
+
307
+ MIT
@@ -0,0 +1,271 @@
1
+ # renoise-kit · `rn`
2
+
3
+ **English** · [简体中文](README.zh-CN.md)
4
+
5
+ The mechanical, error-prone half of editing video, as one command-line tool: frame-exact probing, contact sheets you
6
+ can actually look at, audio turned into numbers, code rendered into frames, an edit decision list (EDL) assembled
7
+ frame-exactly, delivery finishing, QA and export to other editors.
8
+
9
+ `rn` measures and guarantees the mechanics. Every creative decision stays yours, or your agent's. It was built for AI
10
+ agents editing generated video in the [Renoise](https://renoise.ai) media sandbox, and the same tool runs on your
11
+ machine.
12
+
13
+ - **Measure.** Turns media into facts a model can read: frames, shots, beats, word times, faces, on-screen text,
14
+ per-frame signals.
15
+ - **Render.** Turns any code into frame-exact pictures, alpha included: HTML/canvas pages, numpy, Manim, Blender. They
16
+ all meet in one format, a PNG sequence.
17
+ - **Assemble and verify.** Builds a frame-exact cut from an EDL, then checks it against the delivery standard.
18
+ - **No named effects, no auto-editing.** The EDL adds general capabilities (keyframes, anchors, masks, blend modes, raw
19
+ ffmpeg filters), never a menu of looks. A common effect comes as readable source you can call, change or ignore.
20
+
21
+ ## Install
22
+
23
+ `rn` drives the system's ffmpeg and ffprobe, version 7 or later, built with libass for burned-in subtitles.
24
+
25
+ | | macOS | Debian / Ubuntu |
26
+ |---|---|---|
27
+ | ffmpeg with libass | `brew install ffmpeg-full`, then `export PATH="$(brew --prefix ffmpeg-full)/bin:$PATH"` (keg-only; Homebrew's plain `ffmpeg` has no libass) | `apt install ffmpeg fontconfig` |
28
+ | OCR for `rn text` (optional) | comes with `ffmpeg-full` | `apt install tesseract-ocr tesseract-ocr-chi-sim` |
29
+
30
+ ```sh
31
+ uv tool install 'renoise-kit[all]' # or: pipx install 'renoise-kit[all]'
32
+ playwright install chromium # only for rn capture
33
+ rn --version
34
+ ```
35
+
36
+ The core install is Pillow and numpy. Heavier dependencies come as extras, so you can install only what you use. When
37
+ a command needs a missing extra, its error line names the extra to install.
38
+
39
+ | Extra | For | Installs |
40
+ |---|---|---|
41
+ | `listen` | `rn listen`, `rn ref`, `rn signal -o` | librosa, matplotlib |
42
+ | `transcribe` | `rn listen --transcribe` | faster-whisper |
43
+ | `faces` | `rn faces` | OpenCV (headless); the YuNet model is fetched once into `~/.cache/rn` and checked against its sha256 |
44
+ | `capture` | `rn capture` | Playwright |
45
+ | `captions` | `rn captions` | pysubs2 |
46
+ | `fonts` | `rn fonts --licenses` | fontTools |
47
+ | `matte` | `rn matte` | rembg (onnxruntime); models download on first use |
48
+ | `export` | `rn export` | OpenTimelineIO and its Final Cut / FCP 7 adapters |
49
+ | `all` | everything above | |
50
+
51
+ ## A first cut
52
+
53
+ Starting from two generated shots, a music bed, a whoosh and an end card:
54
+
55
+ ```sh
56
+ rn sheet shots/s2.mp4 --fps 4 # look at a take: labelled frames, small enough to view
57
+ rn listen music.wav --json music.json # loudness, beats, tempo
58
+ rn capture title.html -o title --frames 90 --size 1080x300 # an HTML title, frame by frame, transparent
59
+ ```
60
+
61
+ `title.html` draws frame `i` when asked. The frame number is the time, so any frame order or number of shards gives
62
+ the same pixels:
63
+
64
+ ```html
65
+ <canvas id=c></canvas><script>
66
+ const x = c.getContext('2d'); c.width = RN.width; c.height = RN.height;
67
+ window.renderFrame = i => {
68
+ x.clearRect(0, 0, c.width, c.height);
69
+ x.globalAlpha = Math.min(1, i / 10);
70
+ x.fillStyle = '#fff'; x.font = '700 120px sans-serif'; x.textAlign = 'center';
71
+ x.fillText('RENOISE', c.width / 2, 160);
72
+ };</script>
73
+ ```
74
+
75
+ `edit.json` puts it together:
76
+
77
+ ```json
78
+ {
79
+ "output": {"width": 1080, "height": 1920, "fps": 30},
80
+ "tracks": [
81
+ {"clips": [
82
+ {"id": "open", "src": "shots/s1.mp4", "in": 0.4, "out": 3.2},
83
+ {"id": "hero", "src": "shots/s2.mp4", "ranges": [[0, 1.5], [2.5, 4]],
84
+ "transition": {"type": "fade", "duration": 0.3}},
85
+ {"id": "end", "image": "card.png", "duration": 1.5, "fit": "contain"}
86
+ ]},
87
+ {"clips": [
88
+ {"id": "title", "frames": "title/%05d.png", "fps": 30, "y": 1500,
89
+ "at": {"clip": "hero", "offset": 0.2}, "until": {"clip_end": "hero"}}
90
+ ]}
91
+ ],
92
+ "audio": [
93
+ {"src": "music.wav", "role": "music", "pivot": 12.0, "at": {"clip": "hero"}, "fade_out": 1},
94
+ {"src": "whoosh.wav", "role": "sfx", "pivot": 0.18, "at": {"clip": "hero"}}
95
+ ],
96
+ "mix": {"lufs": -14, "cut_fade": 0.01}
97
+ }
98
+ ```
99
+
100
+ What each part does:
101
+
102
+ - **Main track.** It plays back to back. `hero` plays two spans of one take, dissolving in from `open`.
103
+ - **Title.** It follows the `hero` clip by id rather than by hand-computed seconds. If `open` gets longer, the title
104
+ moves with it.
105
+ - **Music.** `pivot` lands the music's 12 s point (say, the drop) on the cut into `hero`, and the head of the track is
106
+ trimmed away.
107
+ - **Whoosh.** Its peak lands on the same cut.
108
+
109
+ ```sh
110
+ rn assemble edit.json --dry-run # the laid-out timeline, frame by frame, nothing rendered
111
+ rn assemble edit.json -o draft.mp4 --preview # half-size draft, about 3x faster
112
+ rn assemble edit.json -o cut.mp4 # full render; unchanged clips are reused from .rn-cache
113
+ rn qa cut.mp4 --edl edit.json # exit 1 on FAIL; WARN means look at it
114
+ ```
115
+
116
+ ```text
117
+ timeline 1080x1920 30fps 210 frames 7.0s
118
+ open: frames 0..83
119
+ hero: frames 75..119 (xfade 9f)
120
+ hero.1: frames 120..164
121
+ end: frames 165..209
122
+ title: overlay frames 81..164
123
+ ...
124
+ WARN level_jumps: 6.0s -40->-13 dB, 6.5s -45->-19 dB
125
+ qa PASS: 0 failed, 1 warnings, 17 passed (cut.mp4)
126
+ ```
127
+
128
+ A WARN may be intended. Here the jumps are the music's own hard-hitting beats. You can waive it in the EDL with
129
+ `"accept": [{"check": "level_jumps", "note": "the music's hits"}]`, or with `rn qa --accept level_jumps`. A FAIL can
130
+ never be waived.
131
+
132
+ Make further changes by operation instead of rewriting the JSON:
133
+
134
+ ```sh
135
+ rn edit edit.json split open --at 1.5
136
+ rn edit edit.json set title meta.copy="new tagline"
137
+ rn edit edit.json delete end --lift
138
+ ```
139
+
140
+ ## Commands
141
+
142
+ `rn <command> --help` is the reference. Every command prints a short summary, and `--json FILE` writes the full result.
143
+
144
+ **Measure**
145
+
146
+ | Command | What it gives |
147
+ |---|---|
148
+ | `rn probe FILE` | Decoded frame count, rational fps, size, color tags, separate video and audio durations, faststart (JSON) |
149
+ | `rn frame FILE --index N \| --time T \| --last -o out.png` | One exact frame, e.g. a tail frame to continue a shot |
150
+ | `rn sheet FILE... [--fps R \| --every N \| --indices ... \| --around T,.. --diff]` | Contact sheets labelled with frame number and timecode, each under 125 KB |
151
+ | `rn cuts FILE [--sheet JPG]` | Shot boundaries by decoded frame, plus weaker "suspects" to check |
152
+ | `rn listen FILE [--transcribe]` | Loudness, silences, onsets, tempo with half/double candidates, beat times, Whisper word times, one picture |
153
+ | `rn signal FILE [-o PNG]` | Per-frame luma, motion, saturation, loudness, low/mid/high bands, onset strength; flash frames, clicks |
154
+ | `rn align A B` | Where recording B sits inside A, by sound: offset, `match`, `distinct` |
155
+ | `rn faces FILE` | Face boxes and landmarks per frame (measurement only) |
156
+ | `rn text FILE` | On-screen text lines, their boxes and when they are on screen (tesseract) |
157
+ | `rn matte FILE -o DIR` | Subject alpha per frame as grey PNGs, ready to use as an EDL `mask`; `--cutout` for RGBA |
158
+ | `rn ref FILE` | A reference breakdown: shots, sound, cut-to-beat sync, every frame on sheets, and a reading guide |
159
+
160
+ **Render**
161
+
162
+ | Command | What it gives |
163
+ |---|---|
164
+ | `rn capture page.html -o DIR --frames N --size WxH` | An HTML page rendered frame by frame (`window.renderFrame(i)`) to RGBA PNGs, sharded across Chromiums |
165
+ | `rn fx [--source]` | The effects library (grain, vignette, leak, flash, glitch, ...) as readable source for pages and numpy |
166
+ | `rn captions WORDS.json --size WxH -o caps.ass` | Your script aligned to Whisper timing; CJK chunking, balanced lines, safe areas, kept off faces |
167
+ | `rn fonts [--script ja]` | Installed families with the weights that really exist, and the scripts they cover |
168
+ | `rn assets search\|get` | Licensed SFX and overlay footage, checked against sha256 |
169
+
170
+ **Edit and assemble**
171
+
172
+ | Command | What it gives |
173
+ |---|---|
174
+ | `rn edit EDL ids\|set\|split\|delete\|insert\|move` | Changes an EDL by clip id. Each step is checked against the whole timeline before the file is written |
175
+ | `rn assemble EDL -o OUT [--preview] [--range A-B] [--placeholders]` | Renders the EDL frame-exactly. `--placeholders` turns missing shots into labelled cards |
176
+
177
+ **Check and deliver**
178
+
179
+ | Command | What it gives |
180
+ |---|---|
181
+ | `rn qa FILE [--edl EDL] [--accept CHECK[@T]]` | Delivery checks. Format: frames, fps, size, codec, color, faststart, fixed frame rate. Sound: A/V length, loudness, true peak, dead air, level jumps, clicks. Picture: black, frozen, bars |
182
+ | `rn compare A B [--cuts] [--video sbs.mp4]` | Two videos at the same times on one sheet, cut timing paired, optional side-by-side video |
183
+ | `rn finish FILE -o OUT [--height N]` | Brings any video to the delivery standard; `--height` makes a scaled copy |
184
+ | `rn export EDL -o cut.otio\|.fcpxml\|.xml [--srt]` | The edit as a project for Final Cut, Premiere, Resolve or any OTIO tool |
185
+
186
+ ## The EDL in one screen
187
+
188
+ - **Tracks.** `tracks[0]` is the main track: its clips play back to back. Later tracks are overlays, placed with `at`
189
+ in time and with `x / y / w / h` in space.
190
+ - **Audio items.** Items in `audio` are placed with `at`. `role` is `music`, `voice` or `sfx`; music ducks under voice
191
+ when `mix.duck_db` is set.
192
+ - **One time grid.** `output.fps` is the only clock. A clip of N seconds is exactly `round(N × fps)` frames.
193
+ - **Sources.** A clip's source is one of: `src` (video, or another `.json` EDL to nest), `frames` (a numbered PNG
194
+ sequence, alpha kept), `image`, or `color`.
195
+ - **Positions inside a source.** `in` and `out` take seconds, `{"frame": n}` or `{"shot": n}` (a shot found by
196
+ `rn cuts`). `ranges` plays several spans of one source in a row.
197
+ - **Placing on the timeline.** `at` and `until` take seconds or an anchor plus `offset`:
198
+ - `{"clip": "id"}` and `{"clip_end": "id"}`
199
+ - `{"marker": name}`
200
+ - `{"beat": n, "listen": file}` and `{"word": n, "listen": file}`
201
+
202
+ `pivot` places a point inside the source on the anchor instead of the source's start.
203
+ - **Keys.** Written as `[t, value, ease]`. Overlays animate `scale`, `x`, `y`, `rotate`, `opacity` and `speed`; the
204
+ main track `scale`, `x`, `y`, `rotate`, `speed` and `gain_db`; audio items `gain_db`.
205
+ - **Compositing.** `blend` takes `screen`, `multiply`, `add` and other blend modes. `mask` takes a full-frame matte, as
206
+ luma or alpha, optionally inverted. `vf` and `af` insert one raw ffmpeg filter chain.
207
+ - **Bookkeeping.** `id` and `meta` go on anything; `meta` is kept as given. `accept` waives qa warnings judged
208
+ intended.
209
+
210
+ The full schema is in `rn assemble --help` and [SPEC.md](SPEC.md) §4.
211
+
212
+ ## Conventions
213
+
214
+ - **Output.** A few summary lines on stdout. `--json FILE` holds the details. `rn probe` prints JSON directly.
215
+ - **Errors.** A failure exits non-zero. Its last line is `rn: error: <what to fix>`, written so you can relay it as is.
216
+ If ffmpeg is missing, the exit code is 2. `rn qa` exits 1 on FAIL.
217
+ - **Frame exactness.** Frames are picked and cut by decoded index or decode timestamp, never by input seeking. Every
218
+ render is checked against the expected frame count.
219
+ - **Viewing budget.** Sheets default to 125 KB, so four fit in one 512 KB look.
220
+ - **Cache.** Rendered slots and nested edits are stored by content in `.rn-cache/` next to the EDL. Moving or
221
+ renaming a clip reuses its render. `--no-cache` renders everything afresh. Delete the folder to reclaim space.
222
+ - **Environment variables.**
223
+ - `RN_CACHE`: cache folder
224
+ - `RN_YUNET_MODEL`: face model file
225
+ - `RN_ASSETS_URL` / `RN_ASSETS_CACHE`: asset library
226
+ - `U2NET_HOME`: rembg models
227
+
228
+ ## Delivery standard
229
+
230
+ `rn assemble` and `rn finish` produce this standard, and `rn qa` checks it:
231
+
232
+ - **Video.** H.264 High, yuv420p, BT.709 limited range (converted, not just tagged), square pixels, `moov` first. The
233
+ SPS sets `fixed_frame_rate_flag=1`; with 0, some players show a 30 fps video at half its length.
234
+ - **Audio.** AAC 48 kHz stereo at −14 LUFS (configurable), with the true peak measured on the **encoded** AAC at or
235
+ below −1 dBTP.
236
+ - **Length.** Audio is cut to the video by sample, so the container is never longer than the picture.
237
+
238
+ ## With agents
239
+
240
+ `rn` is designed to be called by a model. `--help` is the documentation and loads only when needed. One sandbox call
241
+ can chain several commands. Summaries are short, and errors say what to change. Sheets, `listen`, `signal` and `align`
242
+ exist because a model cannot watch or listen; check the result through them rather than through the format alone.
243
+ `rn qa` passing does not mean the edit is good.
244
+
245
+ In the Renoise media sandbox, `rn` comes preinstalled at the version pinned by the sandbox template.
246
+
247
+ ## Development
248
+
249
+ ```sh
250
+ uv run pytest -q # Python 3.12 (as the sandbox); needs ffmpeg with libass; playwright install chromium for capture tests
251
+ ```
252
+
253
+ CI runs on Debian trixie with ffmpeg 7.1 (the same base as the sandbox) and on macOS with ffmpeg-full. The tests
254
+ generate their own media.
255
+
256
+ To release:
257
+
258
+ 1. Bump `__version__` in `rn/__init__.py`.
259
+ 2. Tag the commit:
260
+
261
+ ```sh
262
+ git tag v$(sed -n "s/^__version__ = '\(.*\)'/\1/p" rn/__init__.py) && git push --tags
263
+ ```
264
+
265
+ `.github/workflows/release.yml` then publishes the tag to PyPI through trusted publishing.
266
+
267
+ Design notes, the full EDL contract and the roadmap are in [SPEC.md](SPEC.md) (in Chinese).
268
+
269
+ ## License
270
+
271
+ MIT
@@ -0,0 +1,47 @@
1
+ [project]
2
+ name = "renoise-kit"
3
+ dynamic = ["version"]
4
+ description = "rn: frame-exact probing, contact sheets, EDL assembly, finishing and QA for AI video edits"
5
+ readme = "README.md"
6
+ license = "MIT"
7
+ license-files = ["LICENSE"]
8
+ requires-python = ">=3.11"
9
+ dependencies = ["pillow>=10.1", "numpy"]
10
+
11
+ # The media sandbox installs everything; locally pick what you use. ffmpeg/ffprobe (with libass) come from the system.
12
+ [project.optional-dependencies]
13
+ listen = ["librosa", "matplotlib"]
14
+ transcribe = ["faster-whisper"]
15
+ faces = ["opencv-python-headless>=4.8"]
16
+ capture = ["playwright"]
17
+ captions = ["pysubs2"]
18
+ fonts = ["fonttools"]
19
+ matte = ["rembg[cpu]"]
20
+ export = ["opentimelineio", "otio-fcpx-xml-adapter", "otio-fcp-adapter", "pysubs2"]
21
+ all = ["renoise-kit[listen,transcribe,faces,capture,captions,fonts,matte,export]"]
22
+
23
+ # uv run pytest. faster-whisper and rembg are left out (large models); their tests skip, as OCR does without tesseract.
24
+ [dependency-groups]
25
+ dev = ["pytest", "librosa", "matplotlib", "opencv-python-headless>=4.8", "pysubs2", "playwright", "fonttools", "opentimelineio",
26
+ "otio-fcpx-xml-adapter", "otio-fcp-adapter"]
27
+
28
+ [project.urls]
29
+ Homepage = "https://renoise.ai"
30
+ Source = "https://github.com/renoise-ai/renoise-kit"
31
+
32
+ [project.scripts]
33
+ rn = "rn.cli:main"
34
+
35
+ [build-system]
36
+ requires = ["setuptools>=77"] # SPDX license expression
37
+ build-backend = "setuptools.build_meta"
38
+
39
+ [tool.setuptools]
40
+ packages = ["rn", "rn.fx"]
41
+
42
+ [tool.setuptools.package-data]
43
+ "rn" = ["ref_guide.md"]
44
+ "rn.fx" = ["fx.js"]
45
+
46
+ [tool.setuptools.dynamic]
47
+ version = { attr = "rn.__version__" }