renoise-kit 0.5.0__py3-none-any.whl
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.
- renoise_kit-0.5.0.dist-info/METADATA +307 -0
- renoise_kit-0.5.0.dist-info/RECORD +33 -0
- renoise_kit-0.5.0.dist-info/WHEEL +5 -0
- renoise_kit-0.5.0.dist-info/entry_points.txt +2 -0
- renoise_kit-0.5.0.dist-info/licenses/LICENSE +21 -0
- renoise_kit-0.5.0.dist-info/top_level.txt +1 -0
- rn/__init__.py +2 -0
- rn/__main__.py +5 -0
- rn/align.py +92 -0
- rn/assemble.py +602 -0
- rn/assets.py +125 -0
- rn/audio.py +79 -0
- rn/captions.py +343 -0
- rn/capture.py +97 -0
- rn/cli.py +557 -0
- rn/compare.py +115 -0
- rn/cuts.py +81 -0
- rn/editops.py +252 -0
- rn/edl.py +898 -0
- rn/export.py +200 -0
- rn/faces.py +135 -0
- rn/ff.py +183 -0
- rn/fonts.py +87 -0
- rn/frames.py +205 -0
- rn/fx/__init__.py +223 -0
- rn/fx/fx.js +150 -0
- rn/listen.py +199 -0
- rn/matte.py +58 -0
- rn/qa.py +212 -0
- rn/ref.py +74 -0
- rn/ref_guide.md +245 -0
- rn/signal.py +190 -0
- rn/text.py +112 -0
|
@@ -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,33 @@
|
|
|
1
|
+
renoise_kit-0.5.0.dist-info/licenses/LICENSE,sha256=UrTmgBkpFSS63iC4X_Vt1YEglPvBt2X5dJ_YOWr_0rc,1064
|
|
2
|
+
rn/__init__.py,sha256=JyGbOqFS5uX5HY6tfc-EQRbjR90F_MuoZC27A8Vsrp8,132
|
|
3
|
+
rn/__main__.py,sha256=E6Gls0DNz8GQK2K-kOUIx8cYhgANW_CH54VKrfCfs14,52
|
|
4
|
+
rn/align.py,sha256=fiEa4isdj6sZ1awk2jcGR77cMLPMC8sYrtF5PUB4kOw,5111
|
|
5
|
+
rn/assemble.py,sha256=kZzUGrxNsKdhXO0e7DPtMjwHERpMcFVJncMTp5QsNPA,33658
|
|
6
|
+
rn/assets.py,sha256=eO9bHmJKoNQuy8SINI_IFRljKbCYjC-W7AkNx3HXTAs,5187
|
|
7
|
+
rn/audio.py,sha256=PZcy5C8bSu53w9kwl9FNKoyqwKIPr8LxHy45645p8EA,4286
|
|
8
|
+
rn/captions.py,sha256=sx4IJVF5bkdpBpTunOaLErtASqDoV_UN9Kcl7Th8-g0,18792
|
|
9
|
+
rn/capture.py,sha256=kuuCrinwOETX2RWDVGm66HFDC44FnkDf3Xo0-G9SfSg,5161
|
|
10
|
+
rn/cli.py,sha256=0p1xykHpr8QYFNgQzPh5vle9HDpVkdJOGUCDKFb3MG0,39519
|
|
11
|
+
rn/compare.py,sha256=-ELBwu8igLRbgDCJWYUkIWHbsWpyt36A1L3RA-PQDs0,6491
|
|
12
|
+
rn/cuts.py,sha256=hvHNjK5AzVzppHBIFZ82mrJxzny-sgBfMt7-AmC91uA,3977
|
|
13
|
+
rn/editops.py,sha256=l2J0gSi8XDKv7wO28KWG2wfEOjItZx5jYD82Q6Jw4BI,11138
|
|
14
|
+
rn/edl.py,sha256=d6-DUoSkNd0ge9W9EM5ZnYNtV01IMP84OP7DjeTw8HA,46805
|
|
15
|
+
rn/export.py,sha256=HxpayyLpG4qNaZH3Kgo6KsNV3LNyBrmLECTJGMnHQrM,10725
|
|
16
|
+
rn/faces.py,sha256=5I4ck2Mtwe_Sh8eT5WFntM2npiPM1yZF2Yr-4KcQ0HM,7054
|
|
17
|
+
rn/ff.py,sha256=puTeMvB4r5qshpqqGrzA9LPOID633PhS2t8KtJucL7o,8319
|
|
18
|
+
rn/fonts.py,sha256=pL-6YxUf0KUB8G5Cx8BX9VLRbczM4oqajWESAND_y8s,4029
|
|
19
|
+
rn/frames.py,sha256=b-pzMYv1NB6ectMgyq_r-qq6aJI7PwHo5ACyCNlqeOw,10343
|
|
20
|
+
rn/listen.py,sha256=QuVPPzXw9RPkFsV15tySdUUAVUdIvdxaGomifjgxYws,10232
|
|
21
|
+
rn/matte.py,sha256=uka85o-5tVCVP_h0N7qX3RRjc3g9Lq_kvKH49sxR9Mo,2759
|
|
22
|
+
rn/qa.py,sha256=MpSz8nFDVP0xOKzxS2DRppVAcYvYPQHIw6V2gPhAndI,13328
|
|
23
|
+
rn/ref.py,sha256=xEFc2lFMU6pu-PQxlbEt1YLEvz2v6eyTqOdoIHuHt9o,4139
|
|
24
|
+
rn/ref_guide.md,sha256=5DD0XKANkHbPpmpnKCNaHcFyLVxiSqM9uiQ7LjodZ-E,16072
|
|
25
|
+
rn/signal.py,sha256=0g7iuc3KKwfc_2p6T_b4wx3UfH6ykLIfdySL1C-vp9A,9336
|
|
26
|
+
rn/text.py,sha256=F00CLac4yVUu0jKcoPaMsyLdlRZ5tJdw5rhWUf66fD4,5446
|
|
27
|
+
rn/fx/__init__.py,sha256=2CG8uWi3XvKn9Q78VNqbTKc69q8_2-vqjX_usMcRoWk,8742
|
|
28
|
+
rn/fx/fx.js,sha256=eNvKSTZBKH3CT9doMGhy_1B4DMYiAaJfPPXsIQ5fRuE,8805
|
|
29
|
+
renoise_kit-0.5.0.dist-info/METADATA,sha256=FXkFuQcRjEjmEkMCiFiRYtP6W7H3jspwLOQYa3LHfwU,15010
|
|
30
|
+
renoise_kit-0.5.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
31
|
+
renoise_kit-0.5.0.dist-info/entry_points.txt,sha256=gHfCS6a60H1bKsOKWiWwE_c33y68bss5n5RsxjqdKbM,35
|
|
32
|
+
renoise_kit-0.5.0.dist-info/top_level.txt,sha256=ijW4XJ8V9ycQoEbMh6E26F5Jh9CYx-UNrWrIJxiuyaQ,3
|
|
33
|
+
renoise_kit-0.5.0.dist-info/RECORD,,
|
|
@@ -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 @@
|
|
|
1
|
+
rn
|
rn/__init__.py
ADDED
rn/__main__.py
ADDED
rn/align.py
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"""rn align: where one recording sits inside another, by their sound. Finds a song inside a reference edit, syncs camera
|
|
2
|
+
angles or a separately recorded voice, checks picture against sound. Measurement only: it reports the offset and how sure
|
|
3
|
+
it is; placing anything is yours (e.g. an audio item's "at", or "in" on a clip).
|
|
4
|
+
"""
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
|
|
9
|
+
from .ff import RnError, probe
|
|
10
|
+
from .signal import pcm
|
|
11
|
+
|
|
12
|
+
COARSE_RATE = 200 # Hz envelope for the global search (5 ms)
|
|
13
|
+
FINE_RATE = 8000 # Hz waveform for refinement (0.125 ms)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _envelope(samples: np.ndarray, rate: int) -> np.ndarray:
|
|
17
|
+
"""Onset-like envelope: log energy rises, which survive different mixes, levels and codecs better than waveforms."""
|
|
18
|
+
hop = rate // COARSE_RATE
|
|
19
|
+
frames = len(samples) // hop
|
|
20
|
+
energy = np.log1p(1000 * np.sqrt(np.mean(samples[:frames * hop].reshape(frames, hop) ** 2, axis=1)))
|
|
21
|
+
rise = np.maximum(np.diff(energy, prepend=energy[:1]), 0)
|
|
22
|
+
return (rise - rise.mean()) / (rise.std() or 1.0)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _lags(a: np.ndarray, b: np.ndarray) -> tuple[np.ndarray, np.ndarray]:
|
|
26
|
+
"""Correlation of a with b at every lag (B's start inside A, in samples)."""
|
|
27
|
+
size = 1 << int(np.ceil(np.log2(len(a) + len(b))))
|
|
28
|
+
corr = np.fft.irfft(np.fft.rfft(a, size) * np.conj(np.fft.rfft(b, size)), size)
|
|
29
|
+
lags = np.concatenate([np.arange(0, len(a)), np.arange(-len(b) + 1, 0)])
|
|
30
|
+
values = np.concatenate([corr[:len(a)], corr[size - len(b) + 1:]])
|
|
31
|
+
return lags, values
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def align_command(path_a: str, path_b: str, *, max_offset: float | None) -> dict:
|
|
35
|
+
for path in (path_a, path_b):
|
|
36
|
+
if not probe(path)['audio']:
|
|
37
|
+
raise RnError(f'{path}: no audio to align by')
|
|
38
|
+
a, b = pcm(path_a, FINE_RATE), pcm(path_b, FINE_RATE)
|
|
39
|
+
if min(len(a), len(b)) < FINE_RATE:
|
|
40
|
+
raise RnError('both recordings need at least 1 s of sound')
|
|
41
|
+
env_a, env_b = _envelope(a, FINE_RATE), _envelope(b, FINE_RATE)
|
|
42
|
+
lags, values = _lags(env_a, env_b)
|
|
43
|
+
# Normalise by the overlap so short overlaps at the edges do not win on their own.
|
|
44
|
+
overlap = np.minimum(len(env_a) - np.maximum(lags, 0), len(env_b) + np.minimum(lags, 0)).clip(min=1)
|
|
45
|
+
score = values / overlap
|
|
46
|
+
usable = overlap >= min(len(env_a), len(env_b)) * 0.25
|
|
47
|
+
if max_offset is not None:
|
|
48
|
+
usable &= np.abs(lags) <= max_offset * COARSE_RATE
|
|
49
|
+
if not usable.any():
|
|
50
|
+
raise RnError('no offset leaves enough overlap (at least a quarter of the shorter recording)')
|
|
51
|
+
score = np.where(usable, score, -np.inf)
|
|
52
|
+
best = int(np.argmax(score))
|
|
53
|
+
lag = int(lags[best])
|
|
54
|
+
# Confidence: the best peak against the best one at least 0.25 s away (a beat-periodic song has near-equal peaks).
|
|
55
|
+
away = usable & (np.abs(lags - lag) > COARSE_RATE // 4)
|
|
56
|
+
runner = float(score[away].max()) if away.any() else 0.0
|
|
57
|
+
peak = float(score[best])
|
|
58
|
+
# Refine on the waveform within +-10 ms of the coarse lag.
|
|
59
|
+
coarse = lag / COARSE_RATE
|
|
60
|
+
start_a = max(0, int(coarse * FINE_RATE))
|
|
61
|
+
start_b = max(0, int(-coarse * FINE_RATE))
|
|
62
|
+
length = min(len(a) - start_a, len(b) - start_b, 30 * FINE_RATE)
|
|
63
|
+
window = FINE_RATE // 100
|
|
64
|
+
seg_b = b[start_b:start_b + length]
|
|
65
|
+
best_fine, best_value = 0, -np.inf
|
|
66
|
+
if length > 2 * window:
|
|
67
|
+
seg_a = a[max(0, start_a - window):start_a + length + window]
|
|
68
|
+
fine_lags, fine_values = _lags(seg_a, seg_b)
|
|
69
|
+
pad = start_a - max(0, start_a - window)
|
|
70
|
+
mask = np.abs(fine_lags - pad) <= window
|
|
71
|
+
if mask.any():
|
|
72
|
+
pick = np.argmax(np.where(mask, fine_values, -np.inf))
|
|
73
|
+
best_fine, best_value = int(fine_lags[pick]) - pad, float(fine_values[pick])
|
|
74
|
+
offset = round(coarse + best_fine / FINE_RATE, 4)
|
|
75
|
+
# How alike the two are where they overlap at that offset (Pearson on the waveform, up to 30 s).
|
|
76
|
+
shift_a = max(0, round(offset * FINE_RATE))
|
|
77
|
+
shift_b = max(0, round(-offset * FINE_RATE))
|
|
78
|
+
span = min(len(a) - shift_a, len(b) - shift_b, 30 * FINE_RATE)
|
|
79
|
+
match = float(np.corrcoef(a[shift_a:shift_a + span], b[shift_b:shift_b + span])[0, 1]) if span > FINE_RATE // 10 else 0.0
|
|
80
|
+
info_a = probe(path_a)
|
|
81
|
+
fps = info_a['video']['fps_float'] if info_a['video'] and not info_a['video']['still'] else None
|
|
82
|
+
return {'a': path_a, 'b': path_b, 'offset': offset, 'offset_frames': round(offset * fps) if fps else None,
|
|
83
|
+
'match': round(match, 3), 'distinct': round(max(0.0, min(1.0, (peak - runner) / peak)) if peak > 0 else 0.0, 3)}
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def summary_lines(result: dict) -> list[str]:
|
|
87
|
+
frames = f' ({result["offset_frames"]} frames of A)' if result['offset_frames'] is not None else ''
|
|
88
|
+
where = 'starts' if result['offset'] >= 0 else 'started'
|
|
89
|
+
return [f'B {where} at {result["offset"]:+.4f}s on A\'s clock{frames}: A time = B time + offset',
|
|
90
|
+
f'match {result["match"]} (waveform correlation where they overlap: near 1 the same sound, ~0 unrelated; a different '
|
|
91
|
+
f'mix of the same song reads lower), distinct {result["distinct"]} (how far ahead of the best offset 0.25 s or more '
|
|
92
|
+
'away; low for repetitive music, whose next bar looks the same)']
|