framerudiments 0.1.3__tar.gz → 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {framerudiments-0.1.3 → framerudiments-0.2.0}/MANIFEST.in +3 -1
- {framerudiments-0.1.3/src/framerudiments.egg-info → framerudiments-0.2.0}/PKG-INFO +32 -3
- {framerudiments-0.1.3 → framerudiments-0.2.0}/README.md +31 -2
- framerudiments-0.2.0/docs/beat-alignment.md +56 -0
- framerudiments-0.2.0/docs/media/beat-alignment-evidence.json +173 -0
- framerudiments-0.2.0/docs/media/beat-alignment.mp4 +0 -0
- framerudiments-0.2.0/docs/media/beat-alignment.png +0 -0
- framerudiments-0.2.0/docs/media/demo-transcript.md +15 -0
- framerudiments-0.2.0/docs/media/effects-pipeline.svg +29 -0
- framerudiments-0.2.0/docs/media/full-frame-comparison.png +0 -0
- framerudiments-0.2.0/docs/media/full-frame-demo.mp4 +0 -0
- framerudiments-0.2.0/docs/media/media-evidence.json +50 -0
- {framerudiments-0.1.3 → framerudiments-0.2.0}/docs/project-format.md +22 -1
- {framerudiments-0.1.3 → framerudiments-0.2.0}/docs/visualisers.md +29 -2
- framerudiments-0.2.0/docs/workflow-media.md +15 -0
- {framerudiments-0.1.3 → framerudiments-0.2.0}/integration/test_visualisers_gpu.py +42 -0
- {framerudiments-0.1.3 → framerudiments-0.2.0}/pyproject.toml +1 -1
- {framerudiments-0.1.3 → framerudiments-0.2.0}/src/framerudiments/__init__.py +1 -1
- framerudiments-0.2.0/src/framerudiments/beat_alignment.py +57 -0
- {framerudiments-0.1.3 → framerudiments-0.2.0}/src/framerudiments/project.py +21 -4
- {framerudiments-0.1.3 → framerudiments-0.2.0}/src/framerudiments/projectm_backend.py +25 -3
- {framerudiments-0.1.3 → framerudiments-0.2.0}/src/framerudiments/visualiser_assets/butterchurn-worker.mjs +11 -5
- {framerudiments-0.1.3 → framerudiments-0.2.0/src/framerudiments.egg-info}/PKG-INFO +32 -3
- {framerudiments-0.1.3 → framerudiments-0.2.0}/src/framerudiments.egg-info/SOURCES.txt +12 -0
- framerudiments-0.2.0/tests/test_beat_alignment.py +144 -0
- {framerudiments-0.1.3 → framerudiments-0.2.0}/AGENTS.md +0 -0
- {framerudiments-0.1.3 → framerudiments-0.2.0}/LICENSE +0 -0
- {framerudiments-0.1.3 → framerudiments-0.2.0}/docs/extraction.md +0 -0
- {framerudiments-0.1.3 → framerudiments-0.2.0}/docs/rudiment-renderers.md +0 -0
- {framerudiments-0.1.3 → framerudiments-0.2.0}/docs/spectrum.md +0 -0
- {framerudiments-0.1.3 → framerudiments-0.2.0}/setup.cfg +0 -0
- {framerudiments-0.1.3 → framerudiments-0.2.0}/src/framerudiments/__main__.py +0 -0
- {framerudiments-0.1.3 → framerudiments-0.2.0}/src/framerudiments/atlas.py +0 -0
- {framerudiments-0.1.3 → framerudiments-0.2.0}/src/framerudiments/choreography.py +0 -0
- {framerudiments-0.1.3 → framerudiments-0.2.0}/src/framerudiments/cli.py +0 -0
- {framerudiments-0.1.3 → framerudiments-0.2.0}/src/framerudiments/compositor.py +0 -0
- {framerudiments-0.1.3 → framerudiments-0.2.0}/src/framerudiments/demo.py +0 -0
- {framerudiments-0.1.3 → framerudiments-0.2.0}/src/framerudiments/effects.py +0 -0
- {framerudiments-0.1.3 → framerudiments-0.2.0}/src/framerudiments/encoding.py +0 -0
- {framerudiments-0.1.3 → framerudiments-0.2.0}/src/framerudiments/flight.py +0 -0
- {framerudiments-0.1.3 → framerudiments-0.2.0}/src/framerudiments/io.py +0 -0
- {framerudiments-0.1.3 → framerudiments-0.2.0}/src/framerudiments/motion.py +0 -0
- {framerudiments-0.1.3 → framerudiments-0.2.0}/src/framerudiments/render.py +0 -0
- {framerudiments-0.1.3 → framerudiments-0.2.0}/src/framerudiments/rudiment_renderers.py +0 -0
- {framerudiments-0.1.3 → framerudiments-0.2.0}/src/framerudiments/spectrum.py +0 -0
- {framerudiments-0.1.3 → framerudiments-0.2.0}/src/framerudiments/stemlab.py +0 -0
- {framerudiments-0.1.3 → framerudiments-0.2.0}/src/framerudiments/subtitles.py +0 -0
- {framerudiments-0.1.3 → framerudiments-0.2.0}/src/framerudiments/timeline.py +0 -0
- {framerudiments-0.1.3 → framerudiments-0.2.0}/src/framerudiments/visualiser_assets/butterchurn-page.js +0 -0
- {framerudiments-0.1.3 → framerudiments-0.2.0}/src/framerudiments/visualiser_assets/feedback.json +0 -0
- {framerudiments-0.1.3 → framerudiments-0.2.0}/src/framerudiments/visualiser_assets/feedback.milk +0 -0
- {framerudiments-0.1.3 → framerudiments-0.2.0}/src/framerudiments/visualiser_assets/julia.json +0 -0
- {framerudiments-0.1.3 → framerudiments-0.2.0}/src/framerudiments/visualiser_assets/julia.milk +0 -0
- {framerudiments-0.1.3 → framerudiments-0.2.0}/src/framerudiments/visualiser_assets/kaleidoscope.json +0 -0
- {framerudiments-0.1.3 → framerudiments-0.2.0}/src/framerudiments/visualiser_assets/kaleidoscope.milk +0 -0
- {framerudiments-0.1.3 → framerudiments-0.2.0}/src/framerudiments/visualiser_assets/package-lock.json +0 -0
- {framerudiments-0.1.3 → framerudiments-0.2.0}/src/framerudiments/visualiser_assets/package.json +0 -0
- {framerudiments-0.1.3 → framerudiments-0.2.0}/src/framerudiments/visualiser_setup.py +0 -0
- {framerudiments-0.1.3 → framerudiments-0.2.0}/src/framerudiments/visualisers.py +0 -0
- {framerudiments-0.1.3 → framerudiments-0.2.0}/src/framerudiments.egg-info/dependency_links.txt +0 -0
- {framerudiments-0.1.3 → framerudiments-0.2.0}/src/framerudiments.egg-info/entry_points.txt +0 -0
- {framerudiments-0.1.3 → framerudiments-0.2.0}/src/framerudiments.egg-info/requires.txt +0 -0
- {framerudiments-0.1.3 → framerudiments-0.2.0}/src/framerudiments.egg-info/top_level.txt +0 -0
- {framerudiments-0.1.3 → framerudiments-0.2.0}/tests/test_adapters.py +0 -0
- {framerudiments-0.1.3 → framerudiments-0.2.0}/tests/test_extracted_helpers.py +0 -0
- {framerudiments-0.1.3 → framerudiments-0.2.0}/tests/test_flight.py +0 -0
- {framerudiments-0.1.3 → framerudiments-0.2.0}/tests/test_lyrics.py +0 -0
- {framerudiments-0.1.3 → framerudiments-0.2.0}/tests/test_pipeline.py +0 -0
- {framerudiments-0.1.3 → framerudiments-0.2.0}/tests/test_rudiment_renderers.py +0 -0
- {framerudiments-0.1.3 → framerudiments-0.2.0}/tests/test_spectrum.py +0 -0
- {framerudiments-0.1.3 → framerudiments-0.2.0}/tests/test_timeline.py +0 -0
- {framerudiments-0.1.3 → framerudiments-0.2.0}/tests/test_visualisers.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: framerudiments
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Deterministic Shorts and lyric visualisers using DanceRudiments and StemLab evidence.
|
|
5
5
|
Author: Kieran Simkin
|
|
6
6
|
License-Expression: MIT
|
|
@@ -29,6 +29,19 @@ FrameRudiments renders Shorts, Canvas-style clips and full lyric visualisers
|
|
|
29
29
|
from one traceable timeline. It is the offline Python renderer in the
|
|
30
30
|
[DanceFlow ecosystem](https://kieransimkin.co.uk/danceflow/).
|
|
31
31
|
|
|
32
|
+
**0.2.0** accepts source-bound StemLab BPM/first-beat readings through
|
|
33
|
+
`clock.beat_alignment`. Procedural motion follows the fractional beat phase
|
|
34
|
+
after excerpt/repeat mapping. Audio, lyric/cue timestamps and captured
|
|
35
|
+
recordings retain their original source clock. See [beat alignment](docs/beat-alignment.md).
|
|
36
|
+
|
|
37
|
+

|
|
38
|
+
|
|
39
|
+
[Play the four-second demonstration](docs/media/beat-alignment.mp4): original
|
|
40
|
+
120 BPM clicks start at 0.125 seconds; the right movement uses the -16 pip
|
|
41
|
+
reading. Text and recorded bands keep source time. This synthetic example
|
|
42
|
+
demonstrates clock composition; it does not establish song-analysis accuracy.
|
|
43
|
+
[Encoded-frame verification and hashes](docs/media/beat-alignment-evidence.json).
|
|
44
|
+
|
|
32
45
|
It consolidates the shared Python/Pillow/FFmpeg mechanisms developed across
|
|
33
46
|
the existing Gilt Trip, Blame Putin, Clay, Paper Dreams, Free Danny Tommo,
|
|
34
47
|
Silly Sausage Britain and Moderate or Good renderers. Release-specific artwork,
|
|
@@ -172,8 +185,8 @@ analysis outputs into the source distribution.
|
|
|
172
185
|
|
|
173
186
|
## Source, contribution and releases
|
|
174
187
|
|
|
175
|
-
[Canonical repository](https://github.com/kieransimkin/FrameRudiments)
|
|
176
|
-
[Python package](https://pypi.org/project/framerudiments/)
|
|
188
|
+
[Canonical repository](https://github.com/kieransimkin/FrameRudiments) ·
|
|
189
|
+
[Python package](https://pypi.org/project/framerudiments/) ·
|
|
177
190
|
[Issues and improvements](https://github.com/kieransimkin/FrameRudiments/issues)
|
|
178
191
|
|
|
179
192
|
Read [CONTRIBUTING.md](CONTRIBUTING.md) before changing the engine. Improvements
|
|
@@ -213,3 +226,19 @@ Optional runtime setup is explicit; the base renderer still works without Node,
|
|
|
213
226
|
a browser or projectM. Feedback is sequential, and GPU output can vary by driver.
|
|
214
227
|
Inspect processed/encoded glyphs and safe zones; pre-effect text bounds cannot
|
|
215
228
|
prove final legibility.
|
|
229
|
+
|
|
230
|
+
Version 0.1.4 preserves portrait geometry in pinned projectM legacy presets through a recorded square overscan/crop mapping; the upstream engine remains unmodified. Neutral portrait/landscape/square mapping is tested for both backends.
|
|
231
|
+
|
|
232
|
+

|
|
233
|
+
|
|
234
|
+
Actual FrameRudiments 0.1.4 candidate output: timed captions and native moving
|
|
235
|
+
sprites/atlas before composition effects (left) and after both real engines
|
|
236
|
+
(right). Synthetic oscillator audio and example words; this is a software demo.
|
|
237
|
+
[Play the four-second demonstration](https://raw.githubusercontent.com/kieransimkin/FrameRudiments/v0.1.4/docs/media/full-frame-demo.mp4)
|
|
238
|
+
and [read its transcript](https://github.com/kieransimkin/FrameRudiments/blob/v0.1.4/docs/media/demo-transcript.md).
|
|
239
|
+
Playback is user-controlled; no automatic media playback is required.
|
|
240
|
+
|
|
241
|
+

|
|
242
|
+
|
|
243
|
+
Generated vector illustration of the processing order. The comparison above
|
|
244
|
+
and linked video are captured renderer output; the diagram is explanatory.
|
|
@@ -4,6 +4,19 @@ FrameRudiments renders Shorts, Canvas-style clips and full lyric visualisers
|
|
|
4
4
|
from one traceable timeline. It is the offline Python renderer in the
|
|
5
5
|
[DanceFlow ecosystem](https://kieransimkin.co.uk/danceflow/).
|
|
6
6
|
|
|
7
|
+
**0.2.0** accepts source-bound StemLab BPM/first-beat readings through
|
|
8
|
+
`clock.beat_alignment`. Procedural motion follows the fractional beat phase
|
|
9
|
+
after excerpt/repeat mapping. Audio, lyric/cue timestamps and captured
|
|
10
|
+
recordings retain their original source clock. See [beat alignment](docs/beat-alignment.md).
|
|
11
|
+
|
|
12
|
+

|
|
13
|
+
|
|
14
|
+
[Play the four-second demonstration](docs/media/beat-alignment.mp4): original
|
|
15
|
+
120 BPM clicks start at 0.125 seconds; the right movement uses the -16 pip
|
|
16
|
+
reading. Text and recorded bands keep source time. This synthetic example
|
|
17
|
+
demonstrates clock composition; it does not establish song-analysis accuracy.
|
|
18
|
+
[Encoded-frame verification and hashes](docs/media/beat-alignment-evidence.json).
|
|
19
|
+
|
|
7
20
|
It consolidates the shared Python/Pillow/FFmpeg mechanisms developed across
|
|
8
21
|
the existing Gilt Trip, Blame Putin, Clay, Paper Dreams, Free Danny Tommo,
|
|
9
22
|
Silly Sausage Britain and Moderate or Good renderers. Release-specific artwork,
|
|
@@ -147,8 +160,8 @@ analysis outputs into the source distribution.
|
|
|
147
160
|
|
|
148
161
|
## Source, contribution and releases
|
|
149
162
|
|
|
150
|
-
[Canonical repository](https://github.com/kieransimkin/FrameRudiments)
|
|
151
|
-
[Python package](https://pypi.org/project/framerudiments/)
|
|
163
|
+
[Canonical repository](https://github.com/kieransimkin/FrameRudiments) ·
|
|
164
|
+
[Python package](https://pypi.org/project/framerudiments/) ·
|
|
152
165
|
[Issues and improvements](https://github.com/kieransimkin/FrameRudiments/issues)
|
|
153
166
|
|
|
154
167
|
Read [CONTRIBUTING.md](CONTRIBUTING.md) before changing the engine. Improvements
|
|
@@ -188,3 +201,19 @@ Optional runtime setup is explicit; the base renderer still works without Node,
|
|
|
188
201
|
a browser or projectM. Feedback is sequential, and GPU output can vary by driver.
|
|
189
202
|
Inspect processed/encoded glyphs and safe zones; pre-effect text bounds cannot
|
|
190
203
|
prove final legibility.
|
|
204
|
+
|
|
205
|
+
Version 0.1.4 preserves portrait geometry in pinned projectM legacy presets through a recorded square overscan/crop mapping; the upstream engine remains unmodified. Neutral portrait/landscape/square mapping is tested for both backends.
|
|
206
|
+
|
|
207
|
+

|
|
208
|
+
|
|
209
|
+
Actual FrameRudiments 0.1.4 candidate output: timed captions and native moving
|
|
210
|
+
sprites/atlas before composition effects (left) and after both real engines
|
|
211
|
+
(right). Synthetic oscillator audio and example words; this is a software demo.
|
|
212
|
+
[Play the four-second demonstration](https://raw.githubusercontent.com/kieransimkin/FrameRudiments/v0.1.4/docs/media/full-frame-demo.mp4)
|
|
213
|
+
and [read its transcript](https://github.com/kieransimkin/FrameRudiments/blob/v0.1.4/docs/media/demo-transcript.md).
|
|
214
|
+
Playback is user-controlled; no automatic media playback is required.
|
|
215
|
+
|
|
216
|
+

|
|
217
|
+
|
|
218
|
+
Generated vector illustration of the processing order. The comparison above
|
|
219
|
+
and linked video are captured renderer output; the diagram is explanatory.
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Beat alignment from saved StemLab evidence
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+
|
|
5
|
+
[Play the four-second example](media/beat-alignment.mp4). Original synthetic
|
|
6
|
+
120 BPM clicks begin at 0.125 seconds. The left native bounce shows the original
|
|
7
|
+
grid; the right follows the -16 pip reading. The bands retain their recording
|
|
8
|
+
clock. Text changes at the original LRC times: “Signals into motion” at 0,
|
|
9
|
+
“Words into light” at 1.25, “Keep the clock in sight” at 2.50, then clears at
|
|
10
|
+
3.75 seconds. The synthetic text is a caption fixture, not sung lyrics.
|
|
11
|
+
[Encoded verification, source identity and hashes](media/beat-alignment-evidence.json).
|
|
12
|
+
|
|
13
|
+
Generate a source-bound reading from saved StemLab evidence, reviewing canonical,
|
|
14
|
+
combined, fitted and half/double-time interpretations and individual detectors:
|
|
15
|
+
|
|
16
|
+
```sh
|
|
17
|
+
stemlab beat-offset RESULTS --output NEW --audio EXACT-MASTER.wav \
|
|
18
|
+
--alignment-analysis consensus --alignment-bpm-basis combined_fit
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Use `canonical` only when canonical BPM has an established authority. Retain the
|
|
22
|
+
full comparison report; a good beat-grid fit is not a listening or downbeat
|
|
23
|
+
confirmation. A normal half-time reading does not mean a detector failed.
|
|
24
|
+
|
|
25
|
+
Pin the resulting file exactly as every other project input:
|
|
26
|
+
|
|
27
|
+
```json
|
|
28
|
+
"clock": {
|
|
29
|
+
"authority": "Reviewed StemLab consensus constant-grid fit",
|
|
30
|
+
"status": "estimated",
|
|
31
|
+
"phase_status": "estimated",
|
|
32
|
+
"beat_alignment": {"path": "beat-alignment.json", "sha256": "EXACT_FILE_SHA256"}
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
The adapter validates the master SHA-256, source sample rate/frame bounds,
|
|
37
|
+
64-pip units, detector/report identities and exact rational clock equation.
|
|
38
|
+
An explicit BPM must agree. Do not also supply `origin` or `beat_grid`; the
|
|
39
|
+
reading owns this clock. An estimated BPM cannot become `confirmed` through
|
|
40
|
+
project metadata, and phase authority stays separate.
|
|
41
|
+
|
|
42
|
+
The existing BeatClock uses the first source beat as its origin. At that instant
|
|
43
|
+
the procedural clock is pip zero; before it, pips can be negative. Excerpts and
|
|
44
|
+
repeats map source time first and apply phase once. Keep the original master,
|
|
45
|
+
LRC, CUE and captured recording clocks unchanged. A recording's captured beat
|
|
46
|
+
zero and integer samples remain authoritative, including Fourier dimensions.
|
|
47
|
+
|
|
48
|
+
Missing, mismatched or poor evidence remains a review hold. A constant-grid
|
|
49
|
+
reading is not proof that a variable-tempo song fits one BPM. Existing projects
|
|
50
|
+
without a reading retain their explicit clock and report unverified phase when
|
|
51
|
+
no phase status was supplied.
|
|
52
|
+
|
|
53
|
+
Reproduce the actual fixture with `python examples/beat-alignment/generate.py
|
|
54
|
+
NEW-DIRECTORY --font /path/to/font.ttf`, then use normal `plan`, `preflight` and
|
|
55
|
+
`render` commands. Keep technical encoded QA separate from attended listening,
|
|
56
|
+
creative approval and any platform publication.
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
{
|
|
2
|
+
"context": "Actual FrameRudiments 0.2.0 encoded synthetic clock-composition example",
|
|
3
|
+
"frames": 240,
|
|
4
|
+
"fps": 60,
|
|
5
|
+
"dimensions": [
|
|
6
|
+
360,
|
|
7
|
+
640
|
|
8
|
+
],
|
|
9
|
+
"duration_seconds": 4,
|
|
10
|
+
"source_audio_sha256": "83e47effac0c8cf6c6c8c70f3babec9b55f61fcea0ba1a97822528e85b8ffe8c",
|
|
11
|
+
"first_beat_seconds": 0.125,
|
|
12
|
+
"offset_pips": -16,
|
|
13
|
+
"pips_per_beat": 64,
|
|
14
|
+
"screenshot_frame": 75,
|
|
15
|
+
"screenshot_source_seconds": 1.25,
|
|
16
|
+
"verification": {
|
|
17
|
+
"probe": {
|
|
18
|
+
"streams": [
|
|
19
|
+
{
|
|
20
|
+
"index": 0,
|
|
21
|
+
"codec_name": "h264",
|
|
22
|
+
"codec_long_name": "H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10",
|
|
23
|
+
"profile": "High",
|
|
24
|
+
"codec_type": "video",
|
|
25
|
+
"codec_tag_string": "avc1",
|
|
26
|
+
"codec_tag": "0x31637661",
|
|
27
|
+
"mime_codec_string": "avc1.64001f",
|
|
28
|
+
"width": 360,
|
|
29
|
+
"height": 640,
|
|
30
|
+
"coded_width": 368,
|
|
31
|
+
"coded_height": 640,
|
|
32
|
+
"has_b_frames": 2,
|
|
33
|
+
"pix_fmt": "yuv420p",
|
|
34
|
+
"level": 31,
|
|
35
|
+
"color_range": "tv",
|
|
36
|
+
"color_space": "bt709",
|
|
37
|
+
"chroma_location": "left",
|
|
38
|
+
"field_order": "progressive",
|
|
39
|
+
"refs": 4,
|
|
40
|
+
"is_avc": "true",
|
|
41
|
+
"nal_length_size": "4",
|
|
42
|
+
"id": "0x1",
|
|
43
|
+
"r_frame_rate": "60/1",
|
|
44
|
+
"avg_frame_rate": "60/1",
|
|
45
|
+
"time_base": "1/15360",
|
|
46
|
+
"start_pts": 0,
|
|
47
|
+
"start_time": "0.000000",
|
|
48
|
+
"duration_ts": 61440,
|
|
49
|
+
"duration": "4.000000",
|
|
50
|
+
"bit_rate": "108766",
|
|
51
|
+
"bits_per_raw_sample": "8",
|
|
52
|
+
"nb_frames": "240",
|
|
53
|
+
"nb_read_frames": "240",
|
|
54
|
+
"extradata_size": 49,
|
|
55
|
+
"disposition": {
|
|
56
|
+
"default": 1,
|
|
57
|
+
"dub": 0,
|
|
58
|
+
"original": 0,
|
|
59
|
+
"comment": 0,
|
|
60
|
+
"lyrics": 0,
|
|
61
|
+
"karaoke": 0,
|
|
62
|
+
"forced": 0,
|
|
63
|
+
"hearing_impaired": 0,
|
|
64
|
+
"visual_impaired": 0,
|
|
65
|
+
"clean_effects": 0,
|
|
66
|
+
"attached_pic": 0,
|
|
67
|
+
"timed_thumbnails": 0,
|
|
68
|
+
"non_diegetic": 0,
|
|
69
|
+
"captions": 0,
|
|
70
|
+
"descriptions": 0,
|
|
71
|
+
"metadata": 0,
|
|
72
|
+
"dependent": 0,
|
|
73
|
+
"still_image": 0,
|
|
74
|
+
"multilayer": 0
|
|
75
|
+
},
|
|
76
|
+
"tags": {
|
|
77
|
+
"language": "und",
|
|
78
|
+
"handler_name": "VideoHandler",
|
|
79
|
+
"encoder": "Lavc63.1.101 libx264"
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"index": 1,
|
|
84
|
+
"codec_name": "aac",
|
|
85
|
+
"codec_long_name": "AAC (Advanced Audio Coding)",
|
|
86
|
+
"profile": "LC",
|
|
87
|
+
"codec_type": "audio",
|
|
88
|
+
"codec_tag_string": "mp4a",
|
|
89
|
+
"codec_tag": "0x6134706d",
|
|
90
|
+
"mime_codec_string": "mp4a.40.2",
|
|
91
|
+
"sample_fmt": "fltp",
|
|
92
|
+
"sample_rate": "48000",
|
|
93
|
+
"channels": 2,
|
|
94
|
+
"channel_layout": "stereo",
|
|
95
|
+
"bits_per_sample": 0,
|
|
96
|
+
"initial_padding": 1024,
|
|
97
|
+
"id": "0x2",
|
|
98
|
+
"r_frame_rate": "0/0",
|
|
99
|
+
"avg_frame_rate": "0/0",
|
|
100
|
+
"time_base": "1/48000",
|
|
101
|
+
"start_pts": 0,
|
|
102
|
+
"start_time": "0.000000",
|
|
103
|
+
"duration_ts": 192000,
|
|
104
|
+
"duration": "4.000000",
|
|
105
|
+
"bit_rate": "35574",
|
|
106
|
+
"nb_frames": "189",
|
|
107
|
+
"nb_read_frames": "188",
|
|
108
|
+
"extradata_size": 5,
|
|
109
|
+
"disposition": {
|
|
110
|
+
"default": 1,
|
|
111
|
+
"dub": 0,
|
|
112
|
+
"original": 0,
|
|
113
|
+
"comment": 0,
|
|
114
|
+
"lyrics": 0,
|
|
115
|
+
"karaoke": 0,
|
|
116
|
+
"forced": 0,
|
|
117
|
+
"hearing_impaired": 0,
|
|
118
|
+
"visual_impaired": 0,
|
|
119
|
+
"clean_effects": 0,
|
|
120
|
+
"attached_pic": 0,
|
|
121
|
+
"timed_thumbnails": 0,
|
|
122
|
+
"non_diegetic": 0,
|
|
123
|
+
"captions": 0,
|
|
124
|
+
"descriptions": 0,
|
|
125
|
+
"metadata": 0,
|
|
126
|
+
"dependent": 0,
|
|
127
|
+
"still_image": 0,
|
|
128
|
+
"multilayer": 0
|
|
129
|
+
},
|
|
130
|
+
"tags": {
|
|
131
|
+
"language": "und",
|
|
132
|
+
"handler_name": "SoundHandler"
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
],
|
|
136
|
+
"format": {
|
|
137
|
+
"nb_streams": 2,
|
|
138
|
+
"nb_programs": 0,
|
|
139
|
+
"nb_stream_groups": 0,
|
|
140
|
+
"format_name": "mov,mp4,m4a,3gp,3g2,mj2",
|
|
141
|
+
"format_long_name": "QuickTime / MOV",
|
|
142
|
+
"start_time": "0.000000",
|
|
143
|
+
"duration": "4.000000",
|
|
144
|
+
"size": "79895",
|
|
145
|
+
"bit_rate": "159790",
|
|
146
|
+
"probe_score": 100,
|
|
147
|
+
"tags": {
|
|
148
|
+
"major_brand": "isom",
|
|
149
|
+
"minor_version": "512",
|
|
150
|
+
"compatible_brands": "isomiso2avc1mp41",
|
|
151
|
+
"encoder": "Lavf63.1.101"
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
"full_decode": "passed",
|
|
156
|
+
"decoded_timestamps": "CFR from zero; all frames checked",
|
|
157
|
+
"audio_duration_error_seconds": 0.0,
|
|
158
|
+
"mp4_atoms": [
|
|
159
|
+
"ftyp",
|
|
160
|
+
"moov",
|
|
161
|
+
"free",
|
|
162
|
+
"mdat"
|
|
163
|
+
],
|
|
164
|
+
"listening_approved": false,
|
|
165
|
+
"platform_safe_zone_verified": false
|
|
166
|
+
},
|
|
167
|
+
"description": "Left: original pip-zero movement. Right: source-bound aligned movement. Bottom: captured recording keeps source clock. Text changes at original LRC timestamps. Soundtrack: original synthetic clicks.",
|
|
168
|
+
"limits": "Synthetic score, not detector accuracy, canonical-song authority or attended listening approval. Generated illustration omitted because actual rendered output demonstrates the composition.",
|
|
169
|
+
"files": {
|
|
170
|
+
"beat-alignment.mp4": "7e8a62a67fcc2967540121bd848263d2eff2bea02ca77e0b01f3be2966d45ccb",
|
|
171
|
+
"beat-alignment.png": "1755f09c51382cf04adbe0b18ce702036807e8b595633cc10cd116bfb9c5f88a"
|
|
172
|
+
}
|
|
173
|
+
}
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Synthetic visualiser demonstration transcript
|
|
2
|
+
|
|
3
|
+
Four seconds at 60 fps, 360 by 640 pixels. This is synthetic oscillator audio,
|
|
4
|
+
not a performed song. FrameRudiments identity text stays in the upper section.
|
|
5
|
+
A DanceRudiments bounce moves the round check-mark sprite; a circular native
|
|
6
|
+
motion moves a four-cell, beat-animated triangle atlas. Four native recording
|
|
7
|
+
bars appear below. Both engines apply feedback to the complete image.
|
|
8
|
+
|
|
9
|
+
- 0.00-1.25 seconds: Signals into motion.
|
|
10
|
+
- 1.25-2.50 seconds: Words into light.
|
|
11
|
+
- 2.50-3.75 seconds: Keep the clock in sight.
|
|
12
|
+
- 3.75-4.00 seconds: caption cleared; shapes and feedback remain.
|
|
13
|
+
|
|
14
|
+
The still compares the original composed frame with the encoded processed
|
|
15
|
+
frame at output time 2.00 seconds. See media-evidence.json for exact hashes.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="1000" height="280" viewBox="0 0 1000 280" role="img" aria-labelledby="title desc">
|
|
2
|
+
<title id="title">Illustration of FrameRudiments full-frame effects</title>
|
|
3
|
+
<desc id="desc">Artwork, native sprites, rudiment views and lyrics are composed before optional Butterchurn and projectM stages, followed by encoding. Both effects receive PCM from the same soundtrack edits.</desc>
|
|
4
|
+
<defs><marker id="arrow" markerWidth="8" markerHeight="8" refX="6" refY="4" orient="auto"><path d="M0 0L8 4L0 8Z" fill="#76d4cf"/></marker></defs>
|
|
5
|
+
<rect width="1000" height="280" rx="18" fill="#101b2e"/>
|
|
6
|
+
<g font-family="sans-serif" fill="#f5eddf" text-anchor="middle">
|
|
7
|
+
<text x="500" y="32" font-size="18">Processing-order illustration — not a screenshot</text>
|
|
8
|
+
<g stroke="#76d4cf" stroke-width="2" fill="#192a40">
|
|
9
|
+
<rect x="20" y="65" width="250" height="110" rx="12"/>
|
|
10
|
+
<rect x="320" y="65" width="210" height="110" rx="12"/>
|
|
11
|
+
<rect x="580" y="65" width="210" height="110" rx="12"/>
|
|
12
|
+
<rect x="840" y="65" width="140" height="110" rx="12"/>
|
|
13
|
+
</g>
|
|
14
|
+
<text x="145" y="93" font-size="20">Compose full frame</text>
|
|
15
|
+
<text x="145" y="120" font-size="15">Artwork · native sprites/atlases</text>
|
|
16
|
+
<text x="145" y="145" font-size="15">Rudiment views · lyrics</text>
|
|
17
|
+
<text x="425" y="100" font-size="22">Butterchurn</text>
|
|
18
|
+
<text x="425" y="130" font-size="16">Optional warp + feedback</text>
|
|
19
|
+
<text x="425" y="153" font-size="14">Includes text and sprites</text>
|
|
20
|
+
<text x="685" y="100" font-size="22">projectM</text>
|
|
21
|
+
<text x="685" y="130" font-size="16">Optional warp + feedback</text>
|
|
22
|
+
<text x="685" y="153" font-size="14">Includes text and sprites</text>
|
|
23
|
+
<text x="910" y="105" font-size="22">Encode</text>
|
|
24
|
+
<text x="910" y="137" font-size="16">Verified MP4</text>
|
|
25
|
+
<text x="500" y="225" font-size="17">Exact output-frame clock + causal PCM from the same trim/repeat/fade chain</text>
|
|
26
|
+
<text x="500" y="255" font-size="14">Choose either engine or change stage order. No clean lyric/sprite overlay is added after effects.</text>
|
|
27
|
+
</g>
|
|
28
|
+
<g stroke="#76d4cf" stroke-width="3" marker-end="url(#arrow)"><path d="M278 120H308"/><path d="M538 120H568"/><path d="M798 120H828"/></g>
|
|
29
|
+
</svg>
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"context": "Actual FrameRudiments0.1.4 candidate render; source91e69c1; synthetic words/tone/art only",
|
|
3
|
+
"frames": 240,
|
|
4
|
+
"fps": 60,
|
|
5
|
+
"dimensions": [
|
|
6
|
+
360,
|
|
7
|
+
640
|
|
8
|
+
],
|
|
9
|
+
"full_decode": "passed",
|
|
10
|
+
"source_time_seconds": 2,
|
|
11
|
+
"engines": [
|
|
12
|
+
{
|
|
13
|
+
"engine": "butterchurn",
|
|
14
|
+
"preset": "9dc533150c3807697041399961802a7c6056a8de01922bcf07440dabea33385f",
|
|
15
|
+
"runtime_version": "2.6.7"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"engine": "projectm",
|
|
19
|
+
"preset": "815d9c4e1574908b67aff3c7d735dc2e24b01ac1f03ceb582f268965a91d5785",
|
|
20
|
+
"runtime_version": "4.2.0-dev"
|
|
21
|
+
}
|
|
22
|
+
],
|
|
23
|
+
"portrait_canvas": [
|
|
24
|
+
640,
|
|
25
|
+
640
|
|
26
|
+
],
|
|
27
|
+
"frame_size": [
|
|
28
|
+
360,
|
|
29
|
+
640
|
|
30
|
+
],
|
|
31
|
+
"illustration": "effects-pipeline.svg is a generated explanatory diagram, not screenshot evidence",
|
|
32
|
+
"files": {
|
|
33
|
+
"demo-transcript.md": {
|
|
34
|
+
"sha256": "e331c33d2940290845484d0d17674461168f0ba4ebc8d156be02521754e97bab",
|
|
35
|
+
"bytes": 778
|
|
36
|
+
},
|
|
37
|
+
"effects-pipeline.svg": {
|
|
38
|
+
"sha256": "73671c92921a02d0017e2d7bb33bff4527311cd60672483b6c469ba7fc52c19c",
|
|
39
|
+
"bytes": 2283
|
|
40
|
+
},
|
|
41
|
+
"full-frame-comparison.png": {
|
|
42
|
+
"sha256": "fe53c0e80dfdbb52b48ae55b8bf0330793b24c76b6644d0cdad444e91f6e05ee",
|
|
43
|
+
"bytes": 88048
|
|
44
|
+
},
|
|
45
|
+
"full-frame-demo.mp4": {
|
|
46
|
+
"sha256": "5014dd1e467a73be1f17b18c03e55872c739178fdab5cdb4ad62e629019909e1",
|
|
47
|
+
"bytes": 285522
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -11,7 +11,7 @@ the inputs are checked again before and after a render.
|
|
|
11
11
|
| `purpose` | `short` (120 ms endpoint fades by default), `visualiser`, or `canvas` (no fades by default). |
|
|
12
12
|
| `width`, `height`, `fps`, `fps_reason` | Even dimensions, fps defaults to 60; other rates need a reason. No platform compliance is inferred. |
|
|
13
13
|
| `audio` | `source` file reference, verified `sample_rate`, start-inclusive `start_sample`, end-exclusive `end_sample`, optional positive integer `repeats` and `fade_seconds`. |
|
|
14
|
-
| `clock` | `bpm` or `
|
|
14
|
+
| `clock` | `bpm`, `beat_grid` or source-bound `beat_alignment` file reference; `authority`, `status` (`confirmed`, `estimated`, `synthetic`), optional `origin` in source seconds and `phase_status`. A reading owns its BPM/origin and cannot be combined with `beat_grid` or `origin`. |
|
|
15
15
|
| `font` | Pinned TTF/OTF file for authored text. |
|
|
16
16
|
| `lyrics`, `cues` | Separate LRC references; `.cue` files are also supported for structural cues. |
|
|
17
17
|
| `canonical_lyrics` | Optional pinned plain text whose performed nonempty lines must exactly match the LRC. Bracketed structural lines in this comparison file are excluded. |
|
|
@@ -30,6 +30,13 @@ control their appearance. `mapping` amplitudes are `dx`, `dy`, `rotation`
|
|
|
30
30
|
and `scale_axis` select index 0,1,2 of the motion projection. Defaults are
|
|
31
31
|
0,1,2,2 respectively. Set `y_axis: 0` to map a one-dimensional bounce vertically.
|
|
32
32
|
|
|
33
|
+

|
|
34
|
+
|
|
35
|
+
[Watch the synthetic clock example](media/beat-alignment.mp4) and follow the
|
|
36
|
+
[source-bound alignment setup](beat-alignment.md). Its first click at 0.125
|
|
37
|
+
seconds maps to pip zero; the screenshot is encoded frame 75 at source 1.25
|
|
38
|
+
seconds. [Verification and media hashes](media/beat-alignment-evidence.json).
|
|
39
|
+
|
|
33
40
|
Atlas configuration is `{columns, rows, count, cycle_ticks, phase_beats?}`.
|
|
34
41
|
The atlas must divide into exact cells. One beat is 16 ticks; cycle ticks are
|
|
35
42
|
explicitly authored rather than silently quantised. Cycles use a continuous
|
|
@@ -87,3 +94,17 @@ for the `id`, `engine`, `preset`, `input_mix`, `seed` and `trusted_preset` field
|
|
|
87
94
|
runtime setup and sequential feedback contract. Both engines process all existing
|
|
88
95
|
layers, including native moving sprites/atlases, captions and lyric art, before
|
|
89
96
|
encoding. Existing projects without `effects` keep the original composition path.
|
|
97
|
+
|
|
98
|
+

|
|
99
|
+
|
|
100
|
+
Actual FrameRudiments 0.1.4 candidate output: timed captions and native moving
|
|
101
|
+
sprites/atlas before composition effects (left) and after both real engines
|
|
102
|
+
(right). Synthetic oscillator audio and example words; this is a software demo.
|
|
103
|
+
[Play the four-second demonstration](https://raw.githubusercontent.com/kieransimkin/FrameRudiments/v0.1.4/docs/media/full-frame-demo.mp4)
|
|
104
|
+
and [read its transcript](https://github.com/kieransimkin/FrameRudiments/blob/v0.1.4/docs/media/demo-transcript.md).
|
|
105
|
+
Playback is user-controlled; no automatic media playback is required.
|
|
106
|
+
|
|
107
|
+

|
|
108
|
+
|
|
109
|
+
Generated vector illustration of the processing order. The comparison above
|
|
110
|
+
and linked video are captured renderer output; the diagram is explanatory.
|
|
@@ -94,7 +94,8 @@ the next warp input. This touches pinned internal properties, so changing the
|
|
|
94
94
|
Butterchurn version requires adapter review and real-engine acceptance tests.
|
|
95
95
|
projectM uses the public 4.2 burn-in/FBO/time APIs and one zero-time allocation
|
|
96
96
|
warmup. Preset selection is locked; failures abort instead of retaining an idle
|
|
97
|
-
or previous preset unnoticed.
|
|
97
|
+
or previous preset unnoticed. Native error/fallback log messages are captured
|
|
98
|
+
through the pinned 4.2 API; malformed warp/composite shaders abort too.
|
|
98
99
|
|
|
99
100
|
`effects-audio.f32` is stereo native-rate PCM decoded with the **same shared
|
|
100
101
|
FFmpeg trim, repeat and qsin fade filters** as the encoded soundtrack. Each
|
|
@@ -120,9 +121,21 @@ cross-platform bit-exact contract.
|
|
|
120
121
|
|
|
121
122
|
The headless helper creates its own temporary profile, blocks external requests,
|
|
122
123
|
and closes its page/browser through Puppeteer on success and failure before
|
|
123
|
-
removing only that owned profile.
|
|
124
|
+
removing only that owned profile. Bounded retries handle transient Windows file
|
|
125
|
+
locks after browser closure; an unresolved cleanup failure aborts the render. It does not attach to a signed-in browser.
|
|
124
126
|
projectM destroys its instance, textures, framebuffer and hidden GLFW window.
|
|
125
127
|
|
|
128
|
+
## Portrait mapping
|
|
129
|
+
|
|
130
|
+
In 0.1.4+, the pinned projectM legacy VideoEcho path's portrait stretch is
|
|
131
|
+
avoided with a square overscan canvas: the whole input frame is placed at its
|
|
132
|
+
original pixel size in the centre, processed by the unmodified engine, and
|
|
133
|
+
cropped back to the requested dimensions. The manifest records frame/canvas
|
|
134
|
+
sizes and mapping. Landscape/square projectM and Butterchurn use native viewports.
|
|
135
|
+
Presets use the engine canvas coordinates, so material can move through the
|
|
136
|
+
overscan margin. No post-effect lyric or sprite overlay is drawn. Neutral mapping
|
|
137
|
+
is tested in portrait, landscape and square formats for both engines.
|
|
138
|
+
|
|
126
139
|
## External presets and potential problems
|
|
127
140
|
|
|
128
141
|
A preset may instead be a pinned `{ "path": "...", "sha256": "..." }` file
|
|
@@ -148,3 +161,17 @@ wheel does not bundle the native library, browser or npm modules. When
|
|
|
148
161
|
redistributing those runtimes, retain their own licences and corresponding
|
|
149
162
|
source/relinking obligations. See the upstream [Butterchurn repository](https://github.com/jberg/butterchurn)
|
|
150
163
|
and [pinned projectM source](https://github.com/projectM-visualizer/projectm/tree/349b7cc2db312d47cef6be24c6856ced6643a385).
|
|
164
|
+
|
|
165
|
+

|
|
166
|
+
|
|
167
|
+
Actual FrameRudiments 0.1.4 candidate output: timed captions and native moving
|
|
168
|
+
sprites/atlas before composition effects (left) and after both real engines
|
|
169
|
+
(right). Synthetic oscillator audio and example words; this is a software demo.
|
|
170
|
+
[Play the four-second demonstration](https://raw.githubusercontent.com/kieransimkin/FrameRudiments/v0.1.4/docs/media/full-frame-demo.mp4)
|
|
171
|
+
and [read its transcript](https://github.com/kieransimkin/FrameRudiments/blob/v0.1.4/docs/media/demo-transcript.md).
|
|
172
|
+
Playback is user-controlled; no automatic media playback is required.
|
|
173
|
+
|
|
174
|
+

|
|
175
|
+
|
|
176
|
+
Generated vector illustration of the processing order. The comparison above
|
|
177
|
+
and linked video are captured renderer output; the diagram is explanatory.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
## FrameRudiments visual examples
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+
|
|
5
|
+
Actual synthetic FrameRudiments 0.1.4 candidate output: lyrics, native moving
|
|
6
|
+
sprites and a beat-animated atlas all pass through both real visualisers.
|
|
7
|
+
[Play the four-second demo](https://raw.githubusercontent.com/kieransimkin/FrameRudiments/v0.1.4/docs/media/full-frame-demo.mp4)
|
|
8
|
+
with [transcript](https://github.com/kieransimkin/FrameRudiments/blob/v0.1.4/docs/media/demo-transcript.md).
|
|
9
|
+
|
|
10
|
+

|
|
11
|
+
|
|
12
|
+
The diagram illustrates composition order; the still and video are captured
|
|
13
|
+
renderer output. Use the exact runtime pins and project format in the
|
|
14
|
+
[visualiser guide](https://github.com/kieransimkin/FrameRudiments/blob/v0.1.4/docs/visualisers.md).
|
|
15
|
+
Inspect encoded glyphs/safe zones; pre-effect bounds do not prove final fit.
|
|
@@ -91,3 +91,45 @@ def test_butterchurn_shader_failure_closes_browser(tmp_path):
|
|
|
91
91
|
def test_projectm_missing_preset_fails_instead_of_idle_fallback(tmp_path):
|
|
92
92
|
with pytest.raises(RuntimeError, match="preset failed"):
|
|
93
93
|
ProjectMBackend((160, 240), 60, tmp_path / "does-not-exist.milk", .8, 0, tmp_path)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@pytest.mark.parametrize("size", [(180, 320), (320, 180), (240, 240)])
|
|
97
|
+
@pytest.mark.parametrize("engine,factory,ext", [("projectm", ProjectMBackend, "milk"), ("butterchurn", ButterchurnBackend, "json")])
|
|
98
|
+
def test_neutral_mapping_preserves_source_pixel_geometry(tmp_path, size, engine, factory, ext):
|
|
99
|
+
# Disable all artistic transforms to separate host geometry from preset choices.
|
|
100
|
+
milk = ('[preset00]\nfDecay=1\nfGammaAdj=1\nfWaveAlpha=0\nfShader=0\nmv_a=0\n'
|
|
101
|
+
'ob_size=0\nib_size=0\nwarp=0\nzoom=1\nfZoomExponent=1\nrot=0\nsx=1\nsy=1\n'
|
|
102
|
+
'dx=0\ndy=0\nfVideoEchoAlpha=0\n')
|
|
103
|
+
preset = dict(baseVals=dict(decay=1, gammaadj=1, wave_a=0, shader=0, mv_a=0,
|
|
104
|
+
ob_size=0, ib_size=0, warp=0, zoom=1, zoomexp=1, rot=0, sx=1, sy=1, dx=0, dy=0, echo_alpha=0),
|
|
105
|
+
shapes=[], waves=[], init_eqs_str='', frame_eqs_str='', pixel_eqs_str='', warp='', comp='')
|
|
106
|
+
target = tmp_path / f'neutral.{ext}'
|
|
107
|
+
target.write_text(milk if engine == 'projectm' else json.dumps(preset), encoding='utf-8')
|
|
108
|
+
image = Image.new('RGBA', size, 'black')
|
|
109
|
+
width, height = size
|
|
110
|
+
expected = (width//2-20, height//2-20, width//2+21, height//2+21)
|
|
111
|
+
draw = ImageDraw.Draw(image)
|
|
112
|
+
draw.rectangle((expected[0], expected[1], expected[2]-1, expected[3]-1), fill='white')
|
|
113
|
+
draw.rectangle((8, 8, 24, 24), fill='red')
|
|
114
|
+
backend = factory(size, 60, target, 1, 0, tmp_path)
|
|
115
|
+
try:
|
|
116
|
+
output = backend.process(image, SimpleNamespace(frame=0, output_seconds=0), array('f', [0]*2048))
|
|
117
|
+
red, green, blue = output.convert('RGB').split()
|
|
118
|
+
white = ImageChops.darker(ImageChops.darker(red, green), blue).point(lambda value: 255 if value > 80 else 0)
|
|
119
|
+
assert all(abs(a-b) <= 2 for a, b in zip(white.getbbox(), expected))
|
|
120
|
+
assert output.getpixel((16, 16))[0] > 100 and output.getpixel((16, 16))[2] < 10
|
|
121
|
+
assert output.size == size
|
|
122
|
+
finally:
|
|
123
|
+
backend.close()
|
|
124
|
+
assert all(backend.evidence['cleanup'].values())
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
@pytest.mark.parametrize("kind", ["WARP", "COMP"])
|
|
128
|
+
def test_projectm_shader_error_cannot_silently_fall_back(tmp_path, kind):
|
|
129
|
+
preset = (ASSETS / "feedback.milk").read_text()
|
|
130
|
+
preset = (f"MILKDROP_PRESET_VERSION=201\nPSVERSION_{kind}=2\n" + preset +
|
|
131
|
+
f"{kind.lower()}_1=shader_body {{ ret = THIS_IS_NOT_VALID; }}\n")
|
|
132
|
+
path = tmp_path / "broken.milk"
|
|
133
|
+
path.write_text(preset, encoding="utf-8")
|
|
134
|
+
with pytest.raises(RuntimeError, match="projectM preset failed"):
|
|
135
|
+
ProjectMBackend((160, 240), 60, path, .8, 0, tmp_path)
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "framerudiments"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.2.0"
|
|
8
8
|
description = "Deterministic Shorts and lyric visualisers using DanceRudiments and StemLab evidence."
|
|
9
9
|
requires-python = ">=3.11"
|
|
10
10
|
readme = "README.md"
|