renoise-kit 0.5.0__tar.gz → 0.6.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.
- {renoise_kit-0.5.0/renoise_kit.egg-info → renoise_kit-0.6.0}/PKG-INFO +31 -17
- {renoise_kit-0.5.0 → renoise_kit-0.6.0}/README.md +30 -16
- {renoise_kit-0.5.0 → renoise_kit-0.6.0}/pyproject.toml +1 -1
- {renoise_kit-0.5.0 → renoise_kit-0.6.0/renoise_kit.egg-info}/PKG-INFO +31 -17
- {renoise_kit-0.5.0 → renoise_kit-0.6.0}/renoise_kit.egg-info/SOURCES.txt +2 -0
- {renoise_kit-0.5.0 → renoise_kit-0.6.0}/rn/__init__.py +1 -1
- {renoise_kit-0.5.0 → renoise_kit-0.6.0}/rn/assemble.py +4 -2
- {renoise_kit-0.5.0 → renoise_kit-0.6.0}/rn/assets.py +1 -1
- {renoise_kit-0.5.0 → renoise_kit-0.6.0}/rn/audio.py +1 -1
- {renoise_kit-0.5.0 → renoise_kit-0.6.0}/rn/captions.py +9 -5
- {renoise_kit-0.5.0 → renoise_kit-0.6.0}/rn/capture.py +2 -2
- {renoise_kit-0.5.0 → renoise_kit-0.6.0}/rn/cli.py +33 -17
- {renoise_kit-0.5.0 → renoise_kit-0.6.0}/rn/editops.py +14 -5
- {renoise_kit-0.5.0 → renoise_kit-0.6.0}/rn/edl.py +177 -49
- {renoise_kit-0.5.0 → renoise_kit-0.6.0}/rn/ff.py +21 -3
- renoise_kit-0.6.0/rn/fonts.py +154 -0
- {renoise_kit-0.5.0 → renoise_kit-0.6.0}/rn/listen.py +2 -2
- {renoise_kit-0.5.0 → renoise_kit-0.6.0}/rn/qa.py +3 -3
- {renoise_kit-0.5.0 → renoise_kit-0.6.0}/rn/ref.py +1 -1
- {renoise_kit-0.5.0 → renoise_kit-0.6.0}/rn/text.py +2 -2
- {renoise_kit-0.5.0 → renoise_kit-0.6.0}/tests/test_captions.py +9 -2
- {renoise_kit-0.5.0 → renoise_kit-0.6.0}/tests/test_capture.py +6 -6
- renoise_kit-0.6.0/tests/test_edit_v2.py +95 -0
- {renoise_kit-0.5.0 → renoise_kit-0.6.0}/tests/test_edl_v3.py +9 -8
- {renoise_kit-0.5.0 → renoise_kit-0.6.0}/tests/test_fx.py +1 -1
- renoise_kit-0.6.0/tests/test_portability.py +56 -0
- {renoise_kit-0.5.0 → renoise_kit-0.6.0}/tests/test_rn.py +8 -7
- {renoise_kit-0.5.0 → renoise_kit-0.6.0}/tests/test_v3_commands.py +3 -3
- {renoise_kit-0.5.0 → renoise_kit-0.6.0}/tests/test_v4_commands.py +5 -5
- {renoise_kit-0.5.0 → renoise_kit-0.6.0}/tests/test_v5_data.py +12 -12
- {renoise_kit-0.5.0 → renoise_kit-0.6.0}/tests/test_v5_measure.py +8 -8
- {renoise_kit-0.5.0 → renoise_kit-0.6.0}/tests/test_v5_time.py +2 -2
- renoise_kit-0.5.0/rn/fonts.py +0 -87
- {renoise_kit-0.5.0 → renoise_kit-0.6.0}/LICENSE +0 -0
- {renoise_kit-0.5.0 → renoise_kit-0.6.0}/renoise_kit.egg-info/dependency_links.txt +0 -0
- {renoise_kit-0.5.0 → renoise_kit-0.6.0}/renoise_kit.egg-info/entry_points.txt +0 -0
- {renoise_kit-0.5.0 → renoise_kit-0.6.0}/renoise_kit.egg-info/requires.txt +0 -0
- {renoise_kit-0.5.0 → renoise_kit-0.6.0}/renoise_kit.egg-info/top_level.txt +0 -0
- {renoise_kit-0.5.0 → renoise_kit-0.6.0}/rn/__main__.py +0 -0
- {renoise_kit-0.5.0 → renoise_kit-0.6.0}/rn/align.py +0 -0
- {renoise_kit-0.5.0 → renoise_kit-0.6.0}/rn/compare.py +0 -0
- {renoise_kit-0.5.0 → renoise_kit-0.6.0}/rn/cuts.py +0 -0
- {renoise_kit-0.5.0 → renoise_kit-0.6.0}/rn/export.py +0 -0
- {renoise_kit-0.5.0 → renoise_kit-0.6.0}/rn/faces.py +0 -0
- {renoise_kit-0.5.0 → renoise_kit-0.6.0}/rn/frames.py +0 -0
- {renoise_kit-0.5.0 → renoise_kit-0.6.0}/rn/fx/__init__.py +0 -0
- {renoise_kit-0.5.0 → renoise_kit-0.6.0}/rn/fx/fx.js +0 -0
- {renoise_kit-0.5.0 → renoise_kit-0.6.0}/rn/matte.py +0 -0
- {renoise_kit-0.5.0 → renoise_kit-0.6.0}/rn/ref_guide.md +0 -0
- {renoise_kit-0.5.0 → renoise_kit-0.6.0}/rn/signal.py +0 -0
- {renoise_kit-0.5.0 → renoise_kit-0.6.0}/setup.cfg +0 -0
- {renoise_kit-0.5.0 → renoise_kit-0.6.0}/tests/test_faces.py +0 -0
- {renoise_kit-0.5.0 → renoise_kit-0.6.0}/tests/test_listen.py +0 -0
- {renoise_kit-0.5.0 → renoise_kit-0.6.0}/tests/test_v5_composite.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: renoise-kit
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.0
|
|
4
4
|
Summary: rn: frame-exact probing, contact sheets, EDL assembly, finishing and QA for AI video edits
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
Project-URL: Homepage, https://renoise.ai
|
|
@@ -58,17 +58,20 @@ machine.
|
|
|
58
58
|
|
|
59
59
|
`rn` drives the system's ffmpeg and ffprobe, version 7 or later, built with libass for burned-in subtitles.
|
|
60
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` |
|
|
61
|
+
| | macOS | Debian / Ubuntu | Windows 10 / 11 |
|
|
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` | `winget install Gyan.FFmpeg` (the full build, libass and fontconfig included), then open a new terminal |
|
|
64
|
+
| OCR for `rn text` (optional) | comes with `ffmpeg-full` | `apt install tesseract-ocr tesseract-ocr-chi-sim` | `winget install UB-Mannheim.TesseractOCR`, then add `C:\Program Files\Tesseract-OCR` to `PATH` |
|
|
65
65
|
|
|
66
66
|
```sh
|
|
67
|
-
uv tool install
|
|
67
|
+
uv tool install "renoise-kit[all]" # or: pipx install "renoise-kit[all]"
|
|
68
68
|
playwright install chromium # only for rn capture
|
|
69
69
|
rn --version
|
|
70
70
|
```
|
|
71
71
|
|
|
72
|
+
The same commands work in a POSIX shell, PowerShell and cmd (hence the double quotes). On Windows `rn fonts` reads the
|
|
73
|
+
system font folders directly when fontconfig is not installed, and `rn` writes UTF-8 when its output goes to a pipe.
|
|
74
|
+
|
|
72
75
|
The core install is Pillow and numpy. Heavier dependencies come as extras, so you can install only what you use. When
|
|
73
76
|
a command needs a missing extra, its error line names the extra to install.
|
|
74
77
|
|
|
@@ -112,16 +115,17 @@ window.renderFrame = i => {
|
|
|
112
115
|
|
|
113
116
|
```json
|
|
114
117
|
{
|
|
118
|
+
"version": 2,
|
|
115
119
|
"output": {"width": 1080, "height": 1920, "fps": 30},
|
|
116
120
|
"tracks": [
|
|
117
121
|
{"clips": [
|
|
118
122
|
{"id": "open", "src": "shots/s1.mp4", "in": 0.4, "out": 3.2},
|
|
119
123
|
{"id": "hero", "src": "shots/s2.mp4", "ranges": [[0, 1.5], [2.5, 4]],
|
|
120
124
|
"transition": {"type": "fade", "duration": 0.3}},
|
|
121
|
-
{"id": "end", "image": "card.png", "duration": 1.5, "fit": "contain"}
|
|
125
|
+
{"id": "end", "image": "card.png", "duration": 1.5, "style": {"object-fit": "contain"}}
|
|
122
126
|
]},
|
|
123
127
|
{"clips": [
|
|
124
|
-
{"id": "title", "frames": "title/%05d.png", "fps": 30, "
|
|
128
|
+
{"id": "title", "frames": "title/%05d.png", "fps": 30, "style": {"top": 1500},
|
|
125
129
|
"at": {"clip": "hero", "offset": 0.2}, "until": {"clip_end": "hero"}}
|
|
126
130
|
]}
|
|
127
131
|
],
|
|
@@ -207,7 +211,7 @@ rn edit edit.json delete end --lift
|
|
|
207
211
|
|
|
208
212
|
| Command | What it gives |
|
|
209
213
|
|---|---|
|
|
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 |
|
|
214
|
+
| `rn edit EDL ids\|set\|split\|delete\|insert\|move\|upgrade` | Changes an EDL by clip id. Each step is checked against the whole timeline before the file is written |
|
|
211
215
|
| `rn assemble EDL -o OUT [--preview] [--range A-B] [--placeholders]` | Renders the EDL frame-exactly. `--placeholders` turns missing shots into labelled cards |
|
|
212
216
|
|
|
213
217
|
**Check and deliver**
|
|
@@ -221,8 +225,14 @@ rn edit edit.json delete end --lift
|
|
|
221
225
|
|
|
222
226
|
## The EDL in one screen
|
|
223
227
|
|
|
228
|
+
- **Version.** `"version": 2`. A file without it uses the version-1 names (`x`, `y`, `w`, `h`, `fit`, `blend`) and is
|
|
229
|
+
upgraded on load; `rn edit FILE upgrade` rewrites it.
|
|
224
230
|
- **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 `
|
|
231
|
+
in time and with `style` in space.
|
|
232
|
+
- **Style.** Placement and look under CSS names and units: `left`, `top`, `width`, `height` (px, `"90px"`, or `"50%"`
|
|
233
|
+
of the frame), `opacity`, `scale`, `rotate` (degrees), `object-fit` (`cover`, `contain`, `fill`) and
|
|
234
|
+
`mix-blend-mode` (the CSS modes). The main track fills the frame, so it takes only `object-fit`, `left`, `top`,
|
|
235
|
+
`scale` and `rotate`.
|
|
226
236
|
- **Audio items.** Items in `audio` are placed with `at`. `role` is `music`, `voice` or `sfx`; music ducks under voice
|
|
227
237
|
when `mix.duck_db` is set.
|
|
228
238
|
- **One time grid.** `output.fps` is the only clock. A clip of N seconds is exactly `round(N × fps)` frames.
|
|
@@ -236,14 +246,17 @@ rn edit edit.json delete end --lift
|
|
|
236
246
|
- `{"beat": n, "listen": file}` and `{"word": n, "listen": file}`
|
|
237
247
|
|
|
238
248
|
`pivot` places a point inside the source on the anchor instead of the source's start.
|
|
239
|
-
- **Keys.** Written as `[t, value, ease]
|
|
240
|
-
main track `scale`, `
|
|
241
|
-
|
|
242
|
-
|
|
249
|
+
- **Keys.** Written as `[t, value, ease]`: the moving form of a style value. Overlays animate `scale`, `left`, `top`,
|
|
250
|
+
`rotate`, `opacity` and `speed`; the main track `scale`, `left`, `top`, `rotate`, `speed` and `gain_db`; audio items
|
|
251
|
+
`gain_db`.
|
|
252
|
+
- **Compositing.** `mask` takes a full-frame matte, as luma or alpha, optionally inverted. `vf` and `af` insert one raw
|
|
253
|
+
ffmpeg filter chain.
|
|
243
254
|
- **Bookkeeping.** `id` and `meta` go on anything; `meta` is kept as given. `accept` waives qa warnings judged
|
|
244
255
|
intended.
|
|
245
256
|
|
|
246
|
-
The full schema is
|
|
257
|
+
The full schema is [schema/edit.schema.json](schema/edit.schema.json), with `rn assemble --help` and [SPEC.md](SPEC.md)
|
|
258
|
+
§4. Fields it marks `x-rn-only` (raw filters, masks, image sequences, nested edits, beat and word anchors, delivery
|
|
259
|
+
loudness, subtitles) are rn's own; another program that renders the rest of the format previews what rn delivers.
|
|
247
260
|
|
|
248
261
|
## Conventions
|
|
249
262
|
|
|
@@ -286,8 +299,9 @@ In the Renoise media sandbox, `rn` comes preinstalled at the version pinned by t
|
|
|
286
299
|
uv run pytest -q # Python 3.12 (as the sandbox); needs ffmpeg with libass; playwright install chromium for capture tests
|
|
287
300
|
```
|
|
288
301
|
|
|
289
|
-
CI runs on Debian trixie with ffmpeg 7.1 (the same base as the sandbox)
|
|
290
|
-
|
|
302
|
+
CI runs on Debian trixie with ffmpeg 7.1 (the same base as the sandbox), on macOS with ffmpeg-full and on Windows with
|
|
303
|
+
gyan.dev's full build (with Windows' own default encoding, so a file read without `encoding='utf-8'` fails there). The
|
|
304
|
+
tests generate their own media.
|
|
291
305
|
|
|
292
306
|
To release:
|
|
293
307
|
|
|
@@ -22,17 +22,20 @@ machine.
|
|
|
22
22
|
|
|
23
23
|
`rn` drives the system's ffmpeg and ffprobe, version 7 or later, built with libass for burned-in subtitles.
|
|
24
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` |
|
|
25
|
+
| | macOS | Debian / Ubuntu | Windows 10 / 11 |
|
|
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` | `winget install Gyan.FFmpeg` (the full build, libass and fontconfig included), then open a new terminal |
|
|
28
|
+
| OCR for `rn text` (optional) | comes with `ffmpeg-full` | `apt install tesseract-ocr tesseract-ocr-chi-sim` | `winget install UB-Mannheim.TesseractOCR`, then add `C:\Program Files\Tesseract-OCR` to `PATH` |
|
|
29
29
|
|
|
30
30
|
```sh
|
|
31
|
-
uv tool install
|
|
31
|
+
uv tool install "renoise-kit[all]" # or: pipx install "renoise-kit[all]"
|
|
32
32
|
playwright install chromium # only for rn capture
|
|
33
33
|
rn --version
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
+
The same commands work in a POSIX shell, PowerShell and cmd (hence the double quotes). On Windows `rn fonts` reads the
|
|
37
|
+
system font folders directly when fontconfig is not installed, and `rn` writes UTF-8 when its output goes to a pipe.
|
|
38
|
+
|
|
36
39
|
The core install is Pillow and numpy. Heavier dependencies come as extras, so you can install only what you use. When
|
|
37
40
|
a command needs a missing extra, its error line names the extra to install.
|
|
38
41
|
|
|
@@ -76,16 +79,17 @@ window.renderFrame = i => {
|
|
|
76
79
|
|
|
77
80
|
```json
|
|
78
81
|
{
|
|
82
|
+
"version": 2,
|
|
79
83
|
"output": {"width": 1080, "height": 1920, "fps": 30},
|
|
80
84
|
"tracks": [
|
|
81
85
|
{"clips": [
|
|
82
86
|
{"id": "open", "src": "shots/s1.mp4", "in": 0.4, "out": 3.2},
|
|
83
87
|
{"id": "hero", "src": "shots/s2.mp4", "ranges": [[0, 1.5], [2.5, 4]],
|
|
84
88
|
"transition": {"type": "fade", "duration": 0.3}},
|
|
85
|
-
{"id": "end", "image": "card.png", "duration": 1.5, "fit": "contain"}
|
|
89
|
+
{"id": "end", "image": "card.png", "duration": 1.5, "style": {"object-fit": "contain"}}
|
|
86
90
|
]},
|
|
87
91
|
{"clips": [
|
|
88
|
-
{"id": "title", "frames": "title/%05d.png", "fps": 30, "
|
|
92
|
+
{"id": "title", "frames": "title/%05d.png", "fps": 30, "style": {"top": 1500},
|
|
89
93
|
"at": {"clip": "hero", "offset": 0.2}, "until": {"clip_end": "hero"}}
|
|
90
94
|
]}
|
|
91
95
|
],
|
|
@@ -171,7 +175,7 @@ rn edit edit.json delete end --lift
|
|
|
171
175
|
|
|
172
176
|
| Command | What it gives |
|
|
173
177
|
|---|---|
|
|
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 |
|
|
178
|
+
| `rn edit EDL ids\|set\|split\|delete\|insert\|move\|upgrade` | Changes an EDL by clip id. Each step is checked against the whole timeline before the file is written |
|
|
175
179
|
| `rn assemble EDL -o OUT [--preview] [--range A-B] [--placeholders]` | Renders the EDL frame-exactly. `--placeholders` turns missing shots into labelled cards |
|
|
176
180
|
|
|
177
181
|
**Check and deliver**
|
|
@@ -185,8 +189,14 @@ rn edit edit.json delete end --lift
|
|
|
185
189
|
|
|
186
190
|
## The EDL in one screen
|
|
187
191
|
|
|
192
|
+
- **Version.** `"version": 2`. A file without it uses the version-1 names (`x`, `y`, `w`, `h`, `fit`, `blend`) and is
|
|
193
|
+
upgraded on load; `rn edit FILE upgrade` rewrites it.
|
|
188
194
|
- **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 `
|
|
195
|
+
in time and with `style` in space.
|
|
196
|
+
- **Style.** Placement and look under CSS names and units: `left`, `top`, `width`, `height` (px, `"90px"`, or `"50%"`
|
|
197
|
+
of the frame), `opacity`, `scale`, `rotate` (degrees), `object-fit` (`cover`, `contain`, `fill`) and
|
|
198
|
+
`mix-blend-mode` (the CSS modes). The main track fills the frame, so it takes only `object-fit`, `left`, `top`,
|
|
199
|
+
`scale` and `rotate`.
|
|
190
200
|
- **Audio items.** Items in `audio` are placed with `at`. `role` is `music`, `voice` or `sfx`; music ducks under voice
|
|
191
201
|
when `mix.duck_db` is set.
|
|
192
202
|
- **One time grid.** `output.fps` is the only clock. A clip of N seconds is exactly `round(N × fps)` frames.
|
|
@@ -200,14 +210,17 @@ rn edit edit.json delete end --lift
|
|
|
200
210
|
- `{"beat": n, "listen": file}` and `{"word": n, "listen": file}`
|
|
201
211
|
|
|
202
212
|
`pivot` places a point inside the source on the anchor instead of the source's start.
|
|
203
|
-
- **Keys.** Written as `[t, value, ease]
|
|
204
|
-
main track `scale`, `
|
|
205
|
-
|
|
206
|
-
|
|
213
|
+
- **Keys.** Written as `[t, value, ease]`: the moving form of a style value. Overlays animate `scale`, `left`, `top`,
|
|
214
|
+
`rotate`, `opacity` and `speed`; the main track `scale`, `left`, `top`, `rotate`, `speed` and `gain_db`; audio items
|
|
215
|
+
`gain_db`.
|
|
216
|
+
- **Compositing.** `mask` takes a full-frame matte, as luma or alpha, optionally inverted. `vf` and `af` insert one raw
|
|
217
|
+
ffmpeg filter chain.
|
|
207
218
|
- **Bookkeeping.** `id` and `meta` go on anything; `meta` is kept as given. `accept` waives qa warnings judged
|
|
208
219
|
intended.
|
|
209
220
|
|
|
210
|
-
The full schema is
|
|
221
|
+
The full schema is [schema/edit.schema.json](schema/edit.schema.json), with `rn assemble --help` and [SPEC.md](SPEC.md)
|
|
222
|
+
§4. Fields it marks `x-rn-only` (raw filters, masks, image sequences, nested edits, beat and word anchors, delivery
|
|
223
|
+
loudness, subtitles) are rn's own; another program that renders the rest of the format previews what rn delivers.
|
|
211
224
|
|
|
212
225
|
## Conventions
|
|
213
226
|
|
|
@@ -250,8 +263,9 @@ In the Renoise media sandbox, `rn` comes preinstalled at the version pinned by t
|
|
|
250
263
|
uv run pytest -q # Python 3.12 (as the sandbox); needs ffmpeg with libass; playwright install chromium for capture tests
|
|
251
264
|
```
|
|
252
265
|
|
|
253
|
-
CI runs on Debian trixie with ffmpeg 7.1 (the same base as the sandbox)
|
|
254
|
-
|
|
266
|
+
CI runs on Debian trixie with ffmpeg 7.1 (the same base as the sandbox), on macOS with ffmpeg-full and on Windows with
|
|
267
|
+
gyan.dev's full build (with Windows' own default encoding, so a file read without `encoding='utf-8'` fails there). The
|
|
268
|
+
tests generate their own media.
|
|
255
269
|
|
|
256
270
|
To release:
|
|
257
271
|
|
|
@@ -23,7 +23,7 @@ all = ["renoise-kit[listen,transcribe,faces,capture,captions,fonts,matte,export]
|
|
|
23
23
|
# uv run pytest. faster-whisper and rembg are left out (large models); their tests skip, as OCR does without tesseract.
|
|
24
24
|
[dependency-groups]
|
|
25
25
|
dev = ["pytest", "librosa", "matplotlib", "opencv-python-headless>=4.8", "pysubs2", "playwright", "fonttools", "opentimelineio",
|
|
26
|
-
"otio-fcpx-xml-adapter", "otio-fcp-adapter"]
|
|
26
|
+
"otio-fcpx-xml-adapter", "otio-fcp-adapter", "jsonschema"]
|
|
27
27
|
|
|
28
28
|
[project.urls]
|
|
29
29
|
Homepage = "https://renoise.ai"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: renoise-kit
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.0
|
|
4
4
|
Summary: rn: frame-exact probing, contact sheets, EDL assembly, finishing and QA for AI video edits
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
Project-URL: Homepage, https://renoise.ai
|
|
@@ -58,17 +58,20 @@ machine.
|
|
|
58
58
|
|
|
59
59
|
`rn` drives the system's ffmpeg and ffprobe, version 7 or later, built with libass for burned-in subtitles.
|
|
60
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` |
|
|
61
|
+
| | macOS | Debian / Ubuntu | Windows 10 / 11 |
|
|
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` | `winget install Gyan.FFmpeg` (the full build, libass and fontconfig included), then open a new terminal |
|
|
64
|
+
| OCR for `rn text` (optional) | comes with `ffmpeg-full` | `apt install tesseract-ocr tesseract-ocr-chi-sim` | `winget install UB-Mannheim.TesseractOCR`, then add `C:\Program Files\Tesseract-OCR` to `PATH` |
|
|
65
65
|
|
|
66
66
|
```sh
|
|
67
|
-
uv tool install
|
|
67
|
+
uv tool install "renoise-kit[all]" # or: pipx install "renoise-kit[all]"
|
|
68
68
|
playwright install chromium # only for rn capture
|
|
69
69
|
rn --version
|
|
70
70
|
```
|
|
71
71
|
|
|
72
|
+
The same commands work in a POSIX shell, PowerShell and cmd (hence the double quotes). On Windows `rn fonts` reads the
|
|
73
|
+
system font folders directly when fontconfig is not installed, and `rn` writes UTF-8 when its output goes to a pipe.
|
|
74
|
+
|
|
72
75
|
The core install is Pillow and numpy. Heavier dependencies come as extras, so you can install only what you use. When
|
|
73
76
|
a command needs a missing extra, its error line names the extra to install.
|
|
74
77
|
|
|
@@ -112,16 +115,17 @@ window.renderFrame = i => {
|
|
|
112
115
|
|
|
113
116
|
```json
|
|
114
117
|
{
|
|
118
|
+
"version": 2,
|
|
115
119
|
"output": {"width": 1080, "height": 1920, "fps": 30},
|
|
116
120
|
"tracks": [
|
|
117
121
|
{"clips": [
|
|
118
122
|
{"id": "open", "src": "shots/s1.mp4", "in": 0.4, "out": 3.2},
|
|
119
123
|
{"id": "hero", "src": "shots/s2.mp4", "ranges": [[0, 1.5], [2.5, 4]],
|
|
120
124
|
"transition": {"type": "fade", "duration": 0.3}},
|
|
121
|
-
{"id": "end", "image": "card.png", "duration": 1.5, "fit": "contain"}
|
|
125
|
+
{"id": "end", "image": "card.png", "duration": 1.5, "style": {"object-fit": "contain"}}
|
|
122
126
|
]},
|
|
123
127
|
{"clips": [
|
|
124
|
-
{"id": "title", "frames": "title/%05d.png", "fps": 30, "
|
|
128
|
+
{"id": "title", "frames": "title/%05d.png", "fps": 30, "style": {"top": 1500},
|
|
125
129
|
"at": {"clip": "hero", "offset": 0.2}, "until": {"clip_end": "hero"}}
|
|
126
130
|
]}
|
|
127
131
|
],
|
|
@@ -207,7 +211,7 @@ rn edit edit.json delete end --lift
|
|
|
207
211
|
|
|
208
212
|
| Command | What it gives |
|
|
209
213
|
|---|---|
|
|
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 |
|
|
214
|
+
| `rn edit EDL ids\|set\|split\|delete\|insert\|move\|upgrade` | Changes an EDL by clip id. Each step is checked against the whole timeline before the file is written |
|
|
211
215
|
| `rn assemble EDL -o OUT [--preview] [--range A-B] [--placeholders]` | Renders the EDL frame-exactly. `--placeholders` turns missing shots into labelled cards |
|
|
212
216
|
|
|
213
217
|
**Check and deliver**
|
|
@@ -221,8 +225,14 @@ rn edit edit.json delete end --lift
|
|
|
221
225
|
|
|
222
226
|
## The EDL in one screen
|
|
223
227
|
|
|
228
|
+
- **Version.** `"version": 2`. A file without it uses the version-1 names (`x`, `y`, `w`, `h`, `fit`, `blend`) and is
|
|
229
|
+
upgraded on load; `rn edit FILE upgrade` rewrites it.
|
|
224
230
|
- **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 `
|
|
231
|
+
in time and with `style` in space.
|
|
232
|
+
- **Style.** Placement and look under CSS names and units: `left`, `top`, `width`, `height` (px, `"90px"`, or `"50%"`
|
|
233
|
+
of the frame), `opacity`, `scale`, `rotate` (degrees), `object-fit` (`cover`, `contain`, `fill`) and
|
|
234
|
+
`mix-blend-mode` (the CSS modes). The main track fills the frame, so it takes only `object-fit`, `left`, `top`,
|
|
235
|
+
`scale` and `rotate`.
|
|
226
236
|
- **Audio items.** Items in `audio` are placed with `at`. `role` is `music`, `voice` or `sfx`; music ducks under voice
|
|
227
237
|
when `mix.duck_db` is set.
|
|
228
238
|
- **One time grid.** `output.fps` is the only clock. A clip of N seconds is exactly `round(N × fps)` frames.
|
|
@@ -236,14 +246,17 @@ rn edit edit.json delete end --lift
|
|
|
236
246
|
- `{"beat": n, "listen": file}` and `{"word": n, "listen": file}`
|
|
237
247
|
|
|
238
248
|
`pivot` places a point inside the source on the anchor instead of the source's start.
|
|
239
|
-
- **Keys.** Written as `[t, value, ease]
|
|
240
|
-
main track `scale`, `
|
|
241
|
-
|
|
242
|
-
|
|
249
|
+
- **Keys.** Written as `[t, value, ease]`: the moving form of a style value. Overlays animate `scale`, `left`, `top`,
|
|
250
|
+
`rotate`, `opacity` and `speed`; the main track `scale`, `left`, `top`, `rotate`, `speed` and `gain_db`; audio items
|
|
251
|
+
`gain_db`.
|
|
252
|
+
- **Compositing.** `mask` takes a full-frame matte, as luma or alpha, optionally inverted. `vf` and `af` insert one raw
|
|
253
|
+
ffmpeg filter chain.
|
|
243
254
|
- **Bookkeeping.** `id` and `meta` go on anything; `meta` is kept as given. `accept` waives qa warnings judged
|
|
244
255
|
intended.
|
|
245
256
|
|
|
246
|
-
The full schema is
|
|
257
|
+
The full schema is [schema/edit.schema.json](schema/edit.schema.json), with `rn assemble --help` and [SPEC.md](SPEC.md)
|
|
258
|
+
§4. Fields it marks `x-rn-only` (raw filters, masks, image sequences, nested edits, beat and word anchors, delivery
|
|
259
|
+
loudness, subtitles) are rn's own; another program that renders the rest of the format previews what rn delivers.
|
|
247
260
|
|
|
248
261
|
## Conventions
|
|
249
262
|
|
|
@@ -286,8 +299,9 @@ In the Renoise media sandbox, `rn` comes preinstalled at the version pinned by t
|
|
|
286
299
|
uv run pytest -q # Python 3.12 (as the sandbox); needs ffmpeg with libass; playwright install chromium for capture tests
|
|
287
300
|
```
|
|
288
301
|
|
|
289
|
-
CI runs on Debian trixie with ffmpeg 7.1 (the same base as the sandbox)
|
|
290
|
-
|
|
302
|
+
CI runs on Debian trixie with ffmpeg 7.1 (the same base as the sandbox), on macOS with ffmpeg-full and on Windows with
|
|
303
|
+
gyan.dev's full build (with Windows' own default encoding, so a file read without `encoding='utf-8'` fails there). The
|
|
304
|
+
tests generate their own media.
|
|
291
305
|
|
|
292
306
|
To release:
|
|
293
307
|
|
|
@@ -36,10 +36,12 @@ rn/fx/__init__.py
|
|
|
36
36
|
rn/fx/fx.js
|
|
37
37
|
tests/test_captions.py
|
|
38
38
|
tests/test_capture.py
|
|
39
|
+
tests/test_edit_v2.py
|
|
39
40
|
tests/test_edl_v3.py
|
|
40
41
|
tests/test_faces.py
|
|
41
42
|
tests/test_fx.py
|
|
42
43
|
tests/test_listen.py
|
|
44
|
+
tests/test_portability.py
|
|
43
45
|
tests/test_rn.py
|
|
44
46
|
tests/test_v3_commands.py
|
|
45
47
|
tests/test_v4_commands.py
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""Renoise kit (`rn`, PyPI `renoise-kit`). Released on its own; the media sandbox template pins a version."""
|
|
2
|
-
__version__ = '0.
|
|
2
|
+
__version__ = '0.6.0'
|
|
@@ -211,7 +211,9 @@ def _join(edit: Edit, slots: list[Path], work: Path) -> Path:
|
|
|
211
211
|
'-map', '[v]', *INTERMEDIATE, '-r', str(edit.fps), '-y', str(piece)])
|
|
212
212
|
pieces.append(piece)
|
|
213
213
|
listing = work / 'main.txt'
|
|
214
|
-
|
|
214
|
+
# Forward slashes work on every OS (a backslash is the demuxer's escape character); a quote closes and reopens.
|
|
215
|
+
quoted = (piece.resolve().as_posix().replace("'", "'\\''") for piece in pieces)
|
|
216
|
+
listing.write_text(''.join(f"file '{path}'\n" for path in quoted), encoding='utf-8')
|
|
215
217
|
return listing
|
|
216
218
|
|
|
217
219
|
|
|
@@ -467,7 +469,7 @@ def _cached(cache: Path | None, key: str, suffix: str, work: Path, render) -> Pa
|
|
|
467
469
|
def _fingerprint(path: Path, placeholders: bool) -> str:
|
|
468
470
|
"""A nested edit's render depends on its text and, recursively, on every file it reads."""
|
|
469
471
|
edit = load(path, placeholders=placeholders)
|
|
470
|
-
parts = [__version__, path.read_text(), placeholders]
|
|
472
|
+
parts = [__version__, path.read_text(encoding='utf-8'), placeholders]
|
|
471
473
|
for clip in edit.base + edit.overlays:
|
|
472
474
|
parts.append(_fingerprint(clip.source, placeholders) if clip.kind == 'edit' else _files(clip.source, clip.kind == 'sequence'))
|
|
473
475
|
parts += [_files(item.source) for item in edit.audio] + [_files(edit.subtitles)]
|
|
@@ -56,7 +56,7 @@ def fetch_checked(source: str, digest: str, cached: Path, *, name: str) -> Path:
|
|
|
56
56
|
if hasher.hexdigest() != digest:
|
|
57
57
|
partial.unlink()
|
|
58
58
|
raise RnError(f'{name}: checksum mismatch, refusing the download')
|
|
59
|
-
partial.
|
|
59
|
+
partial.replace(cached) # rename() refuses to overwrite on Windows
|
|
60
60
|
return cached
|
|
61
61
|
|
|
62
62
|
|
|
@@ -20,7 +20,7 @@ MAX_LIMITER_PASSES = 6
|
|
|
20
20
|
def measure(path: str | Path, *, stream: str = 'a:0') -> dict:
|
|
21
21
|
"""Integrated loudness (LUFS), loudness range and true peak (dBTP) via ebur128."""
|
|
22
22
|
result = subprocess.run(['ffmpeg', '-hide_banner', '-nostdin', '-i', str(path), '-map', f'0:{stream}',
|
|
23
|
-
'-af', 'ebur128=peak=true:framelog=quiet', '-f', 'null', '-'], capture_output=True, text=True)
|
|
23
|
+
'-af', 'ebur128=peak=true:framelog=quiet', '-f', 'null', '-'], capture_output=True, text=True, encoding='utf-8', errors='replace')
|
|
24
24
|
if result.returncode != 0:
|
|
25
25
|
raise RnError(f'loudness measurement failed: {result.stderr.strip().splitlines()[-1:]}')
|
|
26
26
|
summary = result.stderr[result.stderr.rfind('Summary:'):]
|
|
@@ -7,6 +7,7 @@ from __future__ import annotations
|
|
|
7
7
|
|
|
8
8
|
import json
|
|
9
9
|
import re
|
|
10
|
+
import sys
|
|
10
11
|
import unicodedata
|
|
11
12
|
from difflib import SequenceMatcher
|
|
12
13
|
from pathlib import Path
|
|
@@ -27,7 +28,9 @@ OPENERS = set('「『(([“‘《〈【')
|
|
|
27
28
|
SENTENCE_END = set('。!?.!?…‼⁇')
|
|
28
29
|
SOFT_BREAK = set(',、;:,;:—')
|
|
29
30
|
CLOSERS = set('」』))]”’》〉】"\'')
|
|
30
|
-
FONTS = {'zh': 'Noto Sans CJK SC', 'yue': 'Noto Sans CJK SC', 'ja': 'Noto Sans CJK JP', 'ko': 'Noto Sans CJK KR'}
|
|
31
|
+
FONTS = {'zh': 'Noto Sans CJK SC', 'yue': 'Noto Sans CJK SC', 'ja': 'Noto Sans CJK JP', 'ko': 'Noto Sans CJK KR', '': 'Noto Sans'}
|
|
32
|
+
# Windows ships no Noto face and libass would fall back silently, so name the faces every Windows has.
|
|
33
|
+
WINDOWS_FONTS = {'zh': 'Microsoft YaHei', 'yue': 'Microsoft YaHei', 'ja': 'Yu Gothic', 'ko': 'Malgun Gothic', '': 'Segoe UI'}
|
|
31
34
|
PAUSE_BREAK = 0.5 # seconds of silence that ends a caption
|
|
32
35
|
|
|
33
36
|
|
|
@@ -220,7 +223,7 @@ def captions_command(words_json: str, *, script: str | None = None, faces_json:
|
|
|
220
223
|
if width <= 0 or height <= 0 or max_lines < 1 or min_duration > max_duration:
|
|
221
224
|
raise RnError('captions: width/height must be positive, max_lines >= 1, min_duration <= max_duration')
|
|
222
225
|
try:
|
|
223
|
-
listen = json.loads(Path(words_json).read_text(encoding='utf-8'))
|
|
226
|
+
listen = json.loads(Path(words_json).read_text(encoding='utf-8-sig'))
|
|
224
227
|
except (OSError, ValueError) as error:
|
|
225
228
|
raise RnError(f'cannot read words json {words_json}: {error}') from error
|
|
226
229
|
words = [w for w in listen.get('words', []) if str(w.get('word', '')).strip()]
|
|
@@ -234,7 +237,7 @@ def captions_command(words_json: str, *, script: str | None = None, faces_json:
|
|
|
234
237
|
alignment = None
|
|
235
238
|
if script is not None:
|
|
236
239
|
try:
|
|
237
|
-
script_text = Path(script).read_text(encoding='utf-8')
|
|
240
|
+
script_text = Path(script).read_text(encoding='utf-8-sig')
|
|
238
241
|
except OSError as error:
|
|
239
242
|
raise RnError(f'cannot read script {script}: {error}') from error
|
|
240
243
|
script_units = _script_units(script_text, cjk)
|
|
@@ -251,7 +254,8 @@ def captions_command(words_json: str, *, script: str | None = None, faces_json:
|
|
|
251
254
|
max_chars = max_chars or max(4, round(BASE_CHARS['cjk' if cjk else 'latin'] * width / 1080))
|
|
252
255
|
em_per_char = 1.0 if cjk else LATIN_EM
|
|
253
256
|
font_size = round(min(height * (0.03 if height > width else 0.05), safe_width / (max_chars * em_per_char)))
|
|
254
|
-
|
|
257
|
+
fonts = WINDOWS_FONTS if sys.platform == 'win32' else FONTS
|
|
258
|
+
font_name = fonts.get(language.split('-')[0].lower(), fonts['']) if language else fonts['zh' if cjk else '']
|
|
255
259
|
|
|
256
260
|
chunks = _chunk(units, max_chars=max_chars, max_lines=max_lines, min_duration=min_duration, max_duration=max_duration, cjk=cjk)
|
|
257
261
|
for chunk in chunks:
|
|
@@ -263,7 +267,7 @@ def captions_command(words_json: str, *, script: str | None = None, faces_json:
|
|
|
263
267
|
scale = (1.0, 1.0)
|
|
264
268
|
if faces_json:
|
|
265
269
|
try:
|
|
266
|
-
faces = json.loads(Path(faces_json).read_text(encoding='utf-8'))
|
|
270
|
+
faces = json.loads(Path(faces_json).read_text(encoding='utf-8-sig'))
|
|
267
271
|
except (OSError, ValueError) as error:
|
|
268
272
|
raise RnError(f'cannot read faces json {faces_json}: {error}') from error
|
|
269
273
|
frames = faces.get('frames', [])
|
|
@@ -22,7 +22,7 @@ FRAME_TIMEOUT_MS = 20_000
|
|
|
22
22
|
def _init_script(fps: float, frames: int, width: int, height: int, shard: int) -> str:
|
|
23
23
|
from .fx import js_path
|
|
24
24
|
meta = json.dumps({'fps': fps, 'frames': frames, 'width': width, 'height': height, 'shard': shard})
|
|
25
|
-
return f'window.RN = {meta};\n' + js_path().read_text()
|
|
25
|
+
return f'window.RN = {meta};\n' + js_path().read_text(encoding='utf-8')
|
|
26
26
|
|
|
27
27
|
|
|
28
28
|
def _shard(args: tuple) -> dict:
|
|
@@ -64,7 +64,7 @@ def capture_command(page: str, *, out: str, frames: int, width: int, height: int
|
|
|
64
64
|
try:
|
|
65
65
|
import playwright # noqa: F401
|
|
66
66
|
except ImportError:
|
|
67
|
-
raise RnError(
|
|
67
|
+
raise RnError('rn capture needs playwright (extra "capture"): uv tool install "renoise-kit[all]" && playwright install chromium') from None
|
|
68
68
|
out_dir = Path(out)
|
|
69
69
|
out_dir.mkdir(parents=True, exist_ok=True)
|
|
70
70
|
for stale in out_dir.glob('*.png'):
|
|
@@ -9,6 +9,7 @@ from __future__ import annotations
|
|
|
9
9
|
|
|
10
10
|
import argparse
|
|
11
11
|
import json
|
|
12
|
+
import os
|
|
12
13
|
import shutil
|
|
13
14
|
import sys
|
|
14
15
|
from pathlib import Path
|
|
@@ -214,20 +215,21 @@ def _parser() -> argparse.ArgumentParser:
|
|
|
214
215
|
'ducking, LUFS target and encoded true peak <= limit. Output: H.264 High yuv420p bt709, moov first.\n'
|
|
215
216
|
'The EDL handles the mechanical assembly; render anything creative with your own code (rn capture for\n'
|
|
216
217
|
'HTML/canvas, numpy + rn.fx, Manim, Blender) to a PNG sequence or alpha clip and place it in the EDL.\n\n'
|
|
217
|
-
'edit.json (paths relative to it; times in seconds;
|
|
218
|
-
'
|
|
218
|
+
'edit.json version 2 (paths relative to it; times in seconds; # marks notes below, not JSON; a "note"\n'
|
|
219
|
+
'field is allowed anywhere; schema: schema/edit.schema.json in the renoise-kit repository):\n'
|
|
219
220
|
'{\n'
|
|
221
|
+
' "version": 2,\n'
|
|
220
222
|
' "output": {"width": 1080, "height": 1920, "fps": 30, "background": "#000000", "crf": 18},\n'
|
|
221
223
|
' "tracks": [\n'
|
|
222
224
|
' {"clips": [ # track 0 = main track, clips back to back\n'
|
|
223
|
-
' {"src": "s1.mp4", "in": 0.4, "out": 3.2
|
|
225
|
+
' {"src": "s1.mp4", "in": 0.4, "out": 3.2},\n'
|
|
224
226
|
' {"src": "s2.mp4", "in": {"frame": 6}, "duration": 2.5, "speed": 0.5, "audio": false,\n'
|
|
225
227
|
' "transition": {"type": "fade", "duration": 0.4}}, # any xfade name: dissolve, wipeleft, slideup...\n'
|
|
226
|
-
' {"image": "card.png", "duration": 1.5, "fit": "contain"},\n'
|
|
228
|
+
' {"image": "card.png", "duration": 1.5, "style": {"object-fit": "contain"}},\n'
|
|
227
229
|
' {"color": "#000000", "duration": 0.5}]},\n'
|
|
228
|
-
' {"clips": [ # later tracks = overlays,
|
|
229
|
-
' {"image": "title.png", "at": 0.5, "duration": 2, "
|
|
230
|
-
' "
|
|
230
|
+
' {"clips": [ # later tracks = overlays, placed in time and space\n'
|
|
231
|
+
' {"image": "title.png", "at": 0.5, "duration": 2, "fade_in": 0.2, "fade_out": 0.2,\n'
|
|
232
|
+
' "style": {"left": 90, "top": "73%", "width": 900, "opacity": 0.9, "mix-blend-mode": "screen"}}]},\n'
|
|
231
233
|
' {"clips": [{"frames": "title/%05d.png", "fps": 30, "at": {"clip": 1, "offset": 0.2},\n'
|
|
232
234
|
' "until": {"clip_end": 1}, "keys": {"scale": [[0, 0.9], [0.3, 1, "out3"]], "opacity": [[0, 0], [0.2, 1]]}}]}],\n'
|
|
233
235
|
' "markers": {"drop": 4.5}, # named times for anchors\n'
|
|
@@ -236,21 +238,27 @@ def _parser() -> argparse.ArgumentParser:
|
|
|
236
238
|
' "subtitles": {"file": "captions.ass"},\n'
|
|
237
239
|
' "mix": {"lufs": -14, "true_peak": -1.0, "duck_db": 10} # duck music under voice\n'
|
|
238
240
|
'}\n'
|
|
241
|
+
'"style" uses CSS names: overlays left/top/width/height (px: 90 or "90px"; "50%" of the frame), opacity,\n'
|
|
242
|
+
'scale, rotate (degrees: 5 or "5deg"), object-fit (cover | contain | fill), mix-blend-mode (normal multiply\n'
|
|
243
|
+
'screen overlay darken lighten color-dodge color-burn hard-light soft-light difference exclusion\n'
|
|
244
|
+
'plus-lighter); the main track fills the frame: object-fit, left/top, scale, rotate.\n'
|
|
239
245
|
'Positions: in/out take seconds, {"frame": n} or {"shot": n, "offset": s} (shots from rn cuts).\n'
|
|
240
246
|
'Overlay "at"/"until" and audio "at" take seconds or an anchor + "offset": {"clip": i or "id"},\n'
|
|
241
247
|
'{"clip_end": i or "id"} (main-track clip as laid out, or an earlier overlay by id), {"marker": name},\n'
|
|
242
248
|
'{"frame": n}, {"beat": n, "listen": f.json}, {"word": n, "listen": f.json, "edge": "start"|"end"}.\n'
|
|
243
|
-
'Keys [t, value, ease?] (t from the clip start; eases linear in out inout in3 out3 inout3 hold)
|
|
244
|
-
'main track scale/
|
|
245
|
-
'gain_db; overlays scale/
|
|
246
|
-
'Keep main-track scale >= 1: edges smear.\n'
|
|
249
|
+
'Keys [t, value, ease?] (t from the clip start; eases linear in out inout in3 out3 inout3 hold), the\n'
|
|
250
|
+
'moving form of a style value: main track scale/left/top/rotate (left, top: px the frame moves), speed\n'
|
|
251
|
+
'(a ramp; drops the clip\'s sound), gain_db; overlays scale/left/top/rotate/opacity/speed (scale and rotate\n'
|
|
252
|
+
'about the layer centre); audio gain_db. Keep main-track scale >= 1: edges smear.\n'
|
|
247
253
|
'Any clip or audio item: "id" (anchors, rn edit), "meta" (anything, kept as is), "vf" / "af" (one raw\n'
|
|
248
254
|
'ffmpeg filter chain, e.g. "eq=saturation=1.2,unsharp"; af on main-track clips and audio).\n'
|
|
249
255
|
'Main track: "ranges": [[in, out], ...] plays spans of one source back to back; "src": "part.json"\n'
|
|
250
|
-
'plays another
|
|
251
|
-
'
|
|
252
|
-
'
|
|
253
|
-
'
|
|
256
|
+
'plays another edit. Overlays: "mask": {"frames"|"image"|"src": ..., "mode": "luma"|"alpha", "invert": bool}\n'
|
|
257
|
+
'(full frame, e.g. from rn matte). Overlays and audio: "pivot": s puts that point of the source (a hit, a\n'
|
|
258
|
+
'downbeat) on "at". "mix": {"cut_fade": 0.01} fades every hard cut\'s own sound. "accept": [{"check", "at",\n'
|
|
259
|
+
'"note"}] waives qa warnings judged intended. A file without "version" uses the version-1 names (x, y, w,\n'
|
|
260
|
+
'h, fit, blend; main-track keys x/y as fractions of the frame) and is upgraded on load; rn edit FILE\n'
|
|
261
|
+
'upgrade rewrites it.')
|
|
254
262
|
assemble.add_argument('edl')
|
|
255
263
|
assemble.add_argument('-o', '--out', default='out.mp4')
|
|
256
264
|
assemble.add_argument('--dry-run', action='store_true', help='validate and print the timeline without rendering')
|
|
@@ -277,6 +285,7 @@ def _parser() -> argparse.ArgumentParser:
|
|
|
277
285
|
edit.add_argument('edl')
|
|
278
286
|
ops = edit.add_subparsers(dest='op', required=True, metavar='op')
|
|
279
287
|
ops.add_parser('ids', help='give every clip and audio item without an id one (c1.. main, o1.. overlays, a1.. audio)')
|
|
288
|
+
ops.add_parser('upgrade', help='rewrite a version-1 edit.json under version-2 names (style, keys left/top)')
|
|
280
289
|
op_set = ops.add_parser('set', help='set fields: key=value (JSON when it parses), dotted keys reach into objects')
|
|
281
290
|
op_set.add_argument('ref')
|
|
282
291
|
op_set.add_argument('assignments', nargs='*', metavar='key=value')
|
|
@@ -442,6 +451,8 @@ def _run_summarized(args) -> tuple[dict, list[str]]:
|
|
|
442
451
|
path = Path(args.edl)
|
|
443
452
|
if args.op == 'ids':
|
|
444
453
|
result = editops.ids_op(path)
|
|
454
|
+
elif args.op == 'upgrade':
|
|
455
|
+
result = editops.upgrade_op(path)
|
|
445
456
|
elif args.op == 'set':
|
|
446
457
|
result = editops.set_op(path, args.ref, args.assignments, args.unset)
|
|
447
458
|
elif args.op == 'split':
|
|
@@ -492,6 +503,11 @@ def _run_summarized(args) -> tuple[dict, list[str]]:
|
|
|
492
503
|
|
|
493
504
|
|
|
494
505
|
def main(argv: list[str] | None = None) -> int:
|
|
506
|
+
if sys.platform == 'win32' and not os.environ.get('PYTHONIOENCODING'):
|
|
507
|
+
# A pipe (an agent reading rn) would get the ANSI code page, which cannot hold every file name or caption.
|
|
508
|
+
for stream in (sys.stdout, sys.stderr):
|
|
509
|
+
if not stream.isatty() and hasattr(stream, 'reconfigure'):
|
|
510
|
+
stream.reconfigure(encoding='utf-8', errors='replace')
|
|
495
511
|
args = _parser().parse_args(argv)
|
|
496
512
|
for tool in ('ffmpeg', 'ffprobe'):
|
|
497
513
|
if not shutil.which(tool):
|
|
@@ -536,7 +552,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
536
552
|
except ModuleNotFoundError as error:
|
|
537
553
|
extra = EXTRAS.get(args.command, 'all')
|
|
538
554
|
print(f"rn: error: rn {args.command} needs the Python module {error.name} (extra \"{extra}\"): "
|
|
539
|
-
|
|
555
|
+
'uv tool install "renoise-kit[all]"', file=sys.stderr)
|
|
540
556
|
return 1
|
|
541
557
|
except Exception as error: # a kit bug: keep the traceback for the report, end on the usual one-liner
|
|
542
558
|
import traceback
|
|
@@ -545,7 +561,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
545
561
|
return 1
|
|
546
562
|
if args.json:
|
|
547
563
|
Path(args.json).parent.mkdir(parents=True, exist_ok=True)
|
|
548
|
-
Path(args.json).write_text(json.dumps(result, indent=1, ensure_ascii=False, default=str))
|
|
564
|
+
Path(args.json).write_text(json.dumps(result, indent=1, ensure_ascii=False, default=str), encoding='utf-8')
|
|
549
565
|
if args.command in SUMMARIZED:
|
|
550
566
|
print('\n'.join(lines))
|
|
551
567
|
else:
|