framerudiments 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. framerudiments-0.1.0/AGENTS.md +16 -0
  2. framerudiments-0.1.0/LICENSE +21 -0
  3. framerudiments-0.1.0/MANIFEST.in +3 -0
  4. framerudiments-0.1.0/PKG-INFO +173 -0
  5. framerudiments-0.1.0/README.md +151 -0
  6. framerudiments-0.1.0/docs/extraction.md +28 -0
  7. framerudiments-0.1.0/docs/project-format.md +70 -0
  8. framerudiments-0.1.0/pyproject.toml +38 -0
  9. framerudiments-0.1.0/setup.cfg +4 -0
  10. framerudiments-0.1.0/src/framerudiments/__init__.py +7 -0
  11. framerudiments-0.1.0/src/framerudiments/__main__.py +3 -0
  12. framerudiments-0.1.0/src/framerudiments/atlas.py +44 -0
  13. framerudiments-0.1.0/src/framerudiments/choreography.py +30 -0
  14. framerudiments-0.1.0/src/framerudiments/cli.py +54 -0
  15. framerudiments-0.1.0/src/framerudiments/compositor.py +278 -0
  16. framerudiments-0.1.0/src/framerudiments/demo.py +68 -0
  17. framerudiments-0.1.0/src/framerudiments/effects.py +51 -0
  18. framerudiments-0.1.0/src/framerudiments/encoding.py +111 -0
  19. framerudiments-0.1.0/src/framerudiments/flight.py +157 -0
  20. framerudiments-0.1.0/src/framerudiments/io.py +66 -0
  21. framerudiments-0.1.0/src/framerudiments/motion.py +137 -0
  22. framerudiments-0.1.0/src/framerudiments/project.py +124 -0
  23. framerudiments-0.1.0/src/framerudiments/render.py +111 -0
  24. framerudiments-0.1.0/src/framerudiments/stemlab.py +36 -0
  25. framerudiments-0.1.0/src/framerudiments/subtitles.py +45 -0
  26. framerudiments-0.1.0/src/framerudiments/timeline.py +132 -0
  27. framerudiments-0.1.0/src/framerudiments.egg-info/PKG-INFO +173 -0
  28. framerudiments-0.1.0/src/framerudiments.egg-info/SOURCES.txt +36 -0
  29. framerudiments-0.1.0/src/framerudiments.egg-info/dependency_links.txt +1 -0
  30. framerudiments-0.1.0/src/framerudiments.egg-info/entry_points.txt +2 -0
  31. framerudiments-0.1.0/src/framerudiments.egg-info/requires.txt +8 -0
  32. framerudiments-0.1.0/src/framerudiments.egg-info/top_level.txt +1 -0
  33. framerudiments-0.1.0/tests/test_adapters.py +64 -0
  34. framerudiments-0.1.0/tests/test_extracted_helpers.py +26 -0
  35. framerudiments-0.1.0/tests/test_flight.py +31 -0
  36. framerudiments-0.1.0/tests/test_lyrics.py +45 -0
  37. framerudiments-0.1.0/tests/test_pipeline.py +127 -0
  38. framerudiments-0.1.0/tests/test_timeline.py +54 -0
@@ -0,0 +1,16 @@
1
+ # FrameRudiments development
2
+
3
+ Read README.md, docs/project-format.md and docs/extraction.md first. Preserve
4
+ source audio/clock identity and separation of technical QA from perceptual
5
+ approval. Use released DanceRudiments APIs and saved StemLab outputs; do not
6
+ copy movement definitions or DSP. Keep creative scene decisions in adapters.
7
+
8
+ Update README and relevant docs with feature changes. Add focused regression
9
+ coverage, run ruff and pytest with FFmpeg/font integration enabled, build wheel
10
+ and sdist, and smoke-test the installed wheel before a release. Keep credentials,
11
+ private media, song text, local paths and campaign evidence out of distributions.
12
+ Respect immutable tags, public deployment gates and explicit name ownership.
13
+ Submit focused improvements to https://github.com/kieransimkin/FrameRudiments/pulls.
14
+ Validate exact-head CI and immutable matching releases; do not rewrite tags or
15
+ assets. GitHub and PyPI publication are separately verified states. Website:
16
+ https://kieransimkin.co.uk/danceflow/.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Kieran Simkin
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 copies
9
+ of the Software, and to permit persons to whom the Software is furnished to do
10
+ 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,3 @@
1
+ include README.md LICENSE AGENTS.md
2
+ recursive-include docs *.md
3
+ recursive-include tests *.py
@@ -0,0 +1,173 @@
1
+ Metadata-Version: 2.4
2
+ Name: framerudiments
3
+ Version: 0.1.0
4
+ Summary: Deterministic Shorts and lyric visualisers using DanceRudiments and StemLab evidence.
5
+ Author: Kieran Simkin
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://kieransimkin.co.uk/danceflow/
8
+ Project-URL: Repository, https://github.com/kieransimkin/FrameRudiments
9
+ Project-URL: Issues, https://github.com/kieransimkin/FrameRudiments/issues
10
+ Project-URL: Documentation, https://github.com/kieransimkin/FrameRudiments#readme
11
+ Requires-Python: >=3.11
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ Requires-Dist: Pillow>=10
15
+ Requires-Dist: dancerudiments<0.4,>=0.3.0
16
+ Provides-Extra: dev
17
+ Requires-Dist: pytest>=8; extra == "dev"
18
+ Requires-Dist: build>=1.2; extra == "dev"
19
+ Requires-Dist: ruff>=0.9; extra == "dev"
20
+ Requires-Dist: twine>=6; extra == "dev"
21
+ Dynamic: license-file
22
+
23
+ # FrameRudiments
24
+
25
+ FrameRudiments renders Shorts, Canvas-style clips and full lyric visualisers
26
+ from one traceable timeline. It is the offline Python renderer in the
27
+ [DanceFlow ecosystem](https://kieransimkin.co.uk/danceflow/).
28
+
29
+ It consolidates the shared Python/Pillow/FFmpeg mechanisms developed across
30
+ the existing Gilt Trip, Blame Putin, Clay, Paper Dreams, Free Danny Tommo,
31
+ Silly Sausage Britain and Moderate or Good renderers. Release-specific artwork,
32
+ scene layouts and creative decisions belong in project files or Python scene
33
+ hooks. Historical render scripts and approved outputs remain preserved.
34
+
35
+ ## Install and run
36
+
37
+ Python 3.11+ and FFmpeg with libx264/AAC are required. Install the package into
38
+ its own virtual environment. Published packages use `python -m pip install framerudiments`;
39
+ from a source checkout use `python -m pip install .`:
40
+
41
+ ```sh
42
+ python -m pip install .
43
+ framerudiments --version
44
+ framerudiments demo demo --font /path/to/a/font.ttf
45
+ framerudiments plan demo/project.json --output demo-plan.json
46
+ framerudiments preflight demo/project.json --output preflight --ffmpeg /path/to/ffmpeg
47
+ framerudiments render demo/project.json --output render --ffmpeg /path/to/ffmpeg
48
+ ```
49
+
50
+ Use a new output directory each time. `--ffprobe` overrides FFmpeg's sibling
51
+ ffprobe. Environment alternatives are `FRAMERUDIMENTS_FFMPEG` and
52
+ `FRAMERUDIMENTS_FFPROBE`. Fonts are explicit hashed inputs; no platform-specific
53
+ font or FFmpeg location is hardcoded into the engine. The synthetic demo creates
54
+ its own tone, sprite, LRC, CUE and four-dimensional recording.
55
+
56
+ ## Capabilities
57
+
58
+ * Portrait, landscape and square frame-addressed Pillow composition, at native
59
+ constant 60 fps by default. Other frame rates require a recorded reason.
60
+ * Sample-addressed source excerpts and exact repetitions; no time-stretch,
61
+ resampling, normalization or remix. The master is never changed. Short
62
+ derivatives default to 120 ms quarter-sine endpoint fades; set
63
+ `audio.fade_seconds: 0` to preserve an approved raw seam. Visualisers and
64
+ Canvas-style projects default to no fades. AAC encoding is lossy.
65
+ * One caption/cue ledger for actual raster transitions, JSON, SRT and ASS.
66
+ Retains signed frame error, same-frame ordering, explicit clears, carry-in
67
+ lyrics and repeat boundaries. Accepts UTF-8 LRC with offsets/multiple stamps
68
+ and CD CUE INDEX 01 at 75 subframes/s. Section labels never become lyrics.
69
+ * Canonical lyric text comparison, exact input hashes, inert text, bounded text
70
+ wrapping and fit-or-fail behavior. Optional previous/current/next captions
71
+ and exact-text-associated lyric artwork (planes, tablets, signs, etc.).
72
+ * Native DanceRudiments catalogue sampling at integer pips; optional explicit
73
+ interpolation of adjacent native samples for legacy compositor parity.
74
+ Preserves the distinction between 64 pips/beat and 16 DanceMoves ticks/beat.
75
+ * Saved StemLab beat grids, accepted `stemlab.loops.v1` selections, explicitly
76
+ selected `stemlab.dancerudiments.candidates.v1` matches, and native
77
+ `dancerudiments.recording` files. No model execution or copied DSP.
78
+ Match-window extrapolation needs an explicit choice. N-dimensional
79
+ recordings retain their own source clock and never wrap outside capture.
80
+ * Transparent sprites, atlas cycles in musical ticks, cue/time-gated layers,
81
+ position/rotation/scale mappings with selectable axes, same-pose directional
82
+ trails, masks, background contain/cover and N-dimensional signed bars.
83
+ Independent composite motion channels preserve separate bounce/sway/tilt.
84
+ All transformed sprite alpha extents are checked against declared bounds
85
+ on every frame. Custom scene drawing has its own review obligations.
86
+ * The existing seeded paper-glider flight model and connected-alpha sprite
87
+ cleaner, plus a general frame-addressed lyric-art entrance/hold/exit helper.
88
+ The flight model is stylized animation; it does not simulate real aerodynamics
89
+ or establish musical synchronization by itself.
90
+ * H.264/yuv420p + AAC MP4 with fast start, full decoding, exact decoded frame
91
+ count and every decoded timestamp checked for CFR. Audio length, sample
92
+ rate and channel count are checked against the source. Includes encoded QA
93
+ stills, a poster, phone preview, input/output hashes, commands and logs.
94
+
95
+ ## Project format
96
+
97
+ Start from `framerudiments demo` and replace its synthetic assets. Every file
98
+ reference has `path` and `sha256`; paths are relative to the project JSON unless
99
+ absolute. See [docs/project-format.md](docs/project-format.md) for all fields and
100
+ [docs/extraction.md](docs/extraction.md) for source mechanisms and boundaries.
101
+
102
+ ```python
103
+ from framerudiments import Project, render
104
+
105
+ project = Project.load("release/project.json")
106
+
107
+ def scene(image, context):
108
+ # Existing custom drawing consumes frame, source_seconds, output_seconds,
109
+ # beat, lyric and cue. Return a frame-sized RGBA image.
110
+ return image
111
+
112
+ render(project, "release/new-render", scene=scene, ffmpeg="/path/to/ffmpeg")
113
+ ```
114
+
115
+ Pin any assets used by a custom scene with `project.ref({"path": ..., "sha256": ...})`
116
+ before rendering. The hook is supplied by trusted Python code, never imported
117
+ or executed from a project JSON. Do not reproduce clock, sampling, caption,
118
+ encode or verification loops in release adapters.
119
+
120
+ ## Evidence and limits
121
+
122
+ `plan` is preparation, `preflight` checks all composed frames without encoding,
123
+ and `render` produces **technical-QA passed; creative and attended listening
124
+ review pending**. No command uploads, schedules or publishes anything.
125
+ Automatic checks cannot establish sung-word accuracy, pleasant motion, a
126
+ musical loop seam, platform preview acceptance, rights or public eligibility.
127
+ Caption boxes are composition constraints, not current official platform
128
+ safe-zone verification. Review the exact encoded file and destination preview.
129
+
130
+ Constant-BPM and saved variable-grid clocks record their authority/status;
131
+ an estimate never becomes artist confirmation. Beat grids extrapolate from
132
+ their first/last interval outside their covered extent; constrain projects to
133
+ supported intervals where that is unsuitable. A ranked match does not prove
134
+ whole-song alignment. Recording source/axis provenance is retained; the author
135
+ must select the correct master/stem association. No live WordPress timing,
136
+ sensor or plugin configuration is changed by offline rendering.
137
+
138
+ All text in `captions.ass` is a portable timing sidecar with neutral styling;
139
+ the raster compositor remains the authority for the approved layout.
140
+
141
+ ## Development
142
+
143
+ ```sh
144
+ python -m pip install -e '.[dev]'
145
+ ruff check .
146
+ pytest
147
+ python -m build
148
+ ```
149
+
150
+ Set `FRAMERUDIMENTS_TEST_FONT` and `FRAMERUDIMENTS_FFMPEG` to run all integration
151
+ tests. Without them, corresponding checks report skipped. Tests cover native
152
+ sampling, non-looping recordings, input identity, lyric clears, excerpt/repeat
153
+ boundaries, text/alpha overflow and actual encoded portrait/landscape/square
154
+ outputs. Use synthetic fixtures; never put songs, personal data or private
155
+ analysis outputs into the source distribution.
156
+
157
+ ## Source, contribution and releases
158
+
159
+ [Canonical repository](https://github.com/kieransimkin/FrameRudiments) ·
160
+ [Python package](https://pypi.org/project/framerudiments/) ·
161
+ [Issues and improvements](https://github.com/kieransimkin/FrameRudiments/issues)
162
+
163
+ Read [CONTRIBUTING.md](CONTRIBUTING.md) before changing the engine. Improvements
164
+ belong in focused pull requests to the canonical repository, with current README,
165
+ meaningful tests and reproducible synthetic examples.
166
+
167
+ The GitHub Actions CI runs the complete render suite on Linux and Windows with
168
+ Python 3.11 and 3.13. The release workflow validates the immutable `v<version>` tag,
169
+ re-runs tests, builds/checks wheel and source distribution, performs a clean wheel
170
+ render and publishes exact checksum-labelled assets. It refuses to overwrite
171
+ an existing GitHub release. The optional PyPI job uses the `pypi` environment and
172
+ a separately configured trusted publisher; no API token is stored in this project.
173
+ A GitHub release and a PyPI listing are separately verified outcomes.
@@ -0,0 +1,151 @@
1
+ # FrameRudiments
2
+
3
+ FrameRudiments renders Shorts, Canvas-style clips and full lyric visualisers
4
+ from one traceable timeline. It is the offline Python renderer in the
5
+ [DanceFlow ecosystem](https://kieransimkin.co.uk/danceflow/).
6
+
7
+ It consolidates the shared Python/Pillow/FFmpeg mechanisms developed across
8
+ the existing Gilt Trip, Blame Putin, Clay, Paper Dreams, Free Danny Tommo,
9
+ Silly Sausage Britain and Moderate or Good renderers. Release-specific artwork,
10
+ scene layouts and creative decisions belong in project files or Python scene
11
+ hooks. Historical render scripts and approved outputs remain preserved.
12
+
13
+ ## Install and run
14
+
15
+ Python 3.11+ and FFmpeg with libx264/AAC are required. Install the package into
16
+ its own virtual environment. Published packages use `python -m pip install framerudiments`;
17
+ from a source checkout use `python -m pip install .`:
18
+
19
+ ```sh
20
+ python -m pip install .
21
+ framerudiments --version
22
+ framerudiments demo demo --font /path/to/a/font.ttf
23
+ framerudiments plan demo/project.json --output demo-plan.json
24
+ framerudiments preflight demo/project.json --output preflight --ffmpeg /path/to/ffmpeg
25
+ framerudiments render demo/project.json --output render --ffmpeg /path/to/ffmpeg
26
+ ```
27
+
28
+ Use a new output directory each time. `--ffprobe` overrides FFmpeg's sibling
29
+ ffprobe. Environment alternatives are `FRAMERUDIMENTS_FFMPEG` and
30
+ `FRAMERUDIMENTS_FFPROBE`. Fonts are explicit hashed inputs; no platform-specific
31
+ font or FFmpeg location is hardcoded into the engine. The synthetic demo creates
32
+ its own tone, sprite, LRC, CUE and four-dimensional recording.
33
+
34
+ ## Capabilities
35
+
36
+ * Portrait, landscape and square frame-addressed Pillow composition, at native
37
+ constant 60 fps by default. Other frame rates require a recorded reason.
38
+ * Sample-addressed source excerpts and exact repetitions; no time-stretch,
39
+ resampling, normalization or remix. The master is never changed. Short
40
+ derivatives default to 120 ms quarter-sine endpoint fades; set
41
+ `audio.fade_seconds: 0` to preserve an approved raw seam. Visualisers and
42
+ Canvas-style projects default to no fades. AAC encoding is lossy.
43
+ * One caption/cue ledger for actual raster transitions, JSON, SRT and ASS.
44
+ Retains signed frame error, same-frame ordering, explicit clears, carry-in
45
+ lyrics and repeat boundaries. Accepts UTF-8 LRC with offsets/multiple stamps
46
+ and CD CUE INDEX 01 at 75 subframes/s. Section labels never become lyrics.
47
+ * Canonical lyric text comparison, exact input hashes, inert text, bounded text
48
+ wrapping and fit-or-fail behavior. Optional previous/current/next captions
49
+ and exact-text-associated lyric artwork (planes, tablets, signs, etc.).
50
+ * Native DanceRudiments catalogue sampling at integer pips; optional explicit
51
+ interpolation of adjacent native samples for legacy compositor parity.
52
+ Preserves the distinction between 64 pips/beat and 16 DanceMoves ticks/beat.
53
+ * Saved StemLab beat grids, accepted `stemlab.loops.v1` selections, explicitly
54
+ selected `stemlab.dancerudiments.candidates.v1` matches, and native
55
+ `dancerudiments.recording` files. No model execution or copied DSP.
56
+ Match-window extrapolation needs an explicit choice. N-dimensional
57
+ recordings retain their own source clock and never wrap outside capture.
58
+ * Transparent sprites, atlas cycles in musical ticks, cue/time-gated layers,
59
+ position/rotation/scale mappings with selectable axes, same-pose directional
60
+ trails, masks, background contain/cover and N-dimensional signed bars.
61
+ Independent composite motion channels preserve separate bounce/sway/tilt.
62
+ All transformed sprite alpha extents are checked against declared bounds
63
+ on every frame. Custom scene drawing has its own review obligations.
64
+ * The existing seeded paper-glider flight model and connected-alpha sprite
65
+ cleaner, plus a general frame-addressed lyric-art entrance/hold/exit helper.
66
+ The flight model is stylized animation; it does not simulate real aerodynamics
67
+ or establish musical synchronization by itself.
68
+ * H.264/yuv420p + AAC MP4 with fast start, full decoding, exact decoded frame
69
+ count and every decoded timestamp checked for CFR. Audio length, sample
70
+ rate and channel count are checked against the source. Includes encoded QA
71
+ stills, a poster, phone preview, input/output hashes, commands and logs.
72
+
73
+ ## Project format
74
+
75
+ Start from `framerudiments demo` and replace its synthetic assets. Every file
76
+ reference has `path` and `sha256`; paths are relative to the project JSON unless
77
+ absolute. See [docs/project-format.md](docs/project-format.md) for all fields and
78
+ [docs/extraction.md](docs/extraction.md) for source mechanisms and boundaries.
79
+
80
+ ```python
81
+ from framerudiments import Project, render
82
+
83
+ project = Project.load("release/project.json")
84
+
85
+ def scene(image, context):
86
+ # Existing custom drawing consumes frame, source_seconds, output_seconds,
87
+ # beat, lyric and cue. Return a frame-sized RGBA image.
88
+ return image
89
+
90
+ render(project, "release/new-render", scene=scene, ffmpeg="/path/to/ffmpeg")
91
+ ```
92
+
93
+ Pin any assets used by a custom scene with `project.ref({"path": ..., "sha256": ...})`
94
+ before rendering. The hook is supplied by trusted Python code, never imported
95
+ or executed from a project JSON. Do not reproduce clock, sampling, caption,
96
+ encode or verification loops in release adapters.
97
+
98
+ ## Evidence and limits
99
+
100
+ `plan` is preparation, `preflight` checks all composed frames without encoding,
101
+ and `render` produces **technical-QA passed; creative and attended listening
102
+ review pending**. No command uploads, schedules or publishes anything.
103
+ Automatic checks cannot establish sung-word accuracy, pleasant motion, a
104
+ musical loop seam, platform preview acceptance, rights or public eligibility.
105
+ Caption boxes are composition constraints, not current official platform
106
+ safe-zone verification. Review the exact encoded file and destination preview.
107
+
108
+ Constant-BPM and saved variable-grid clocks record their authority/status;
109
+ an estimate never becomes artist confirmation. Beat grids extrapolate from
110
+ their first/last interval outside their covered extent; constrain projects to
111
+ supported intervals where that is unsuitable. A ranked match does not prove
112
+ whole-song alignment. Recording source/axis provenance is retained; the author
113
+ must select the correct master/stem association. No live WordPress timing,
114
+ sensor or plugin configuration is changed by offline rendering.
115
+
116
+ All text in `captions.ass` is a portable timing sidecar with neutral styling;
117
+ the raster compositor remains the authority for the approved layout.
118
+
119
+ ## Development
120
+
121
+ ```sh
122
+ python -m pip install -e '.[dev]'
123
+ ruff check .
124
+ pytest
125
+ python -m build
126
+ ```
127
+
128
+ Set `FRAMERUDIMENTS_TEST_FONT` and `FRAMERUDIMENTS_FFMPEG` to run all integration
129
+ tests. Without them, corresponding checks report skipped. Tests cover native
130
+ sampling, non-looping recordings, input identity, lyric clears, excerpt/repeat
131
+ boundaries, text/alpha overflow and actual encoded portrait/landscape/square
132
+ outputs. Use synthetic fixtures; never put songs, personal data or private
133
+ analysis outputs into the source distribution.
134
+
135
+ ## Source, contribution and releases
136
+
137
+ [Canonical repository](https://github.com/kieransimkin/FrameRudiments) ·
138
+ [Python package](https://pypi.org/project/framerudiments/) ·
139
+ [Issues and improvements](https://github.com/kieransimkin/FrameRudiments/issues)
140
+
141
+ Read [CONTRIBUTING.md](CONTRIBUTING.md) before changing the engine. Improvements
142
+ belong in focused pull requests to the canonical repository, with current README,
143
+ meaningful tests and reproducible synthetic examples.
144
+
145
+ The GitHub Actions CI runs the complete render suite on Linux and Windows with
146
+ Python 3.11 and 3.13. The release workflow validates the immutable `v<version>` tag,
147
+ re-runs tests, builds/checks wheel and source distribution, performs a clean wheel
148
+ render and publishes exact checksum-labelled assets. It refuses to overwrite
149
+ an existing GitHub release. The optional PyPI job uses the `pypi` environment and
150
+ a separately configured trusted publisher; no API token is stored in this project.
151
+ A GitHub release and a PyPI listing are separately verified outcomes.
@@ -0,0 +1,28 @@
1
+ # Extraction and ownership
2
+
3
+ This package consolidates reusable render mechanisms. It does not copy private
4
+ song assets or every historical campaign script into the distribution. Creative
5
+ scene drawing remains release-owned and can use the shared Python scene hook.
6
+
7
+ | Existing renderer family | Shared functionality now supplied by FrameRudiments |
8
+ | --- | --- |
9
+ | Gilt Trip Shorts/Canvas | Native table identity, source-time 64-pip sampling, layer placement, alpha bounds, lyric-frame ledger, endpoint fades, FFmpeg encode/probe/decode, poster/preview. |
10
+ | Blame Putin Shorts/full visualiser | Direct native catalogue sampling, saved StemLab beat grid, explicit match source/window/phase, cue-gated layers, full-duration programmes, static caption plates. |
11
+ | Clay Shorts/full film | Pinned exact source intervals and repeats, canonical LRC mapping, named native motions, no-fade option, text fit, layered transparent art, lyric tablets/signs, retained raw source provenance. |
12
+ | Paper Dreams Shorts/full film | Sprite sheets, connected-alpha component cleaning, exact-text lyric artwork, masks, alpha manipulation, extracted seeded flight model, generalized lyric-art entrance/hold/exit and per-frame scene hook for fog/custom drawing. Narrative/asset decisions stay in its adapter. |
13
+ | Silly Sausage Britain | Atlas cycles, body/sprite transforms, masks, same-pose directional trails, generated lyric artwork, bounded compositing. Specific dancers, gull routes and smoke artwork stay in its adapter. |
14
+ | Free Danny Tommo / Patriotic Revolution | Reusable beat clock, sprite/lyric composition, pinned sources, transparent layering, rendered/encoded QA evidence. Protest/crown/identity assets stay private to their releases. |
15
+ | Moderate or Good | Timed-caption concepts, separate structural cues, SRT/ASS export, full/short project modes and background composition; old estimated timings remain historical evidence. |
16
+
17
+ The local migration inventory records original paths, hashes, imports and
18
+ function names. It is private evidence outside the package. Original scripts
19
+ are preserved; they are not silently redirected, deleted or asserted to have
20
+ been regenerated. Approved existing films retain their original generators.
21
+ New adaptations can remove their duplicated clock/caption/encoder loops and
22
+ call the shared API while retaining scene drawing and provenance locally.
23
+
24
+ StemLab owns analysis, accepted-loop evidence, beat grids, matching and recorded
25
+ signals. DanceRudiments owns motion functions, schema validation and native
26
+ recording playback. FrameRudiments owns offline timeline adaptation, composition,
27
+ encoding and technical render QA. DanceMoves remains the live web runtime.
28
+ Short Shifter remains outside DanceFlow.
@@ -0,0 +1,70 @@
1
+ # Project format v1
2
+
3
+ The schema identifier is `framerudiments.project.v1`. Unknown fields are errors.
4
+ All file references are `{ "path": "relative-or-absolute", "sha256": "64 hex digits" }`.
5
+ Hash each final input, including the font; a changed file fails preparation and
6
+ the inputs are checked again before and after a render.
7
+
8
+ | Field | Meaning |
9
+ | --- | --- |
10
+ | `title`, `artist`, `notes` | Private project description; text appears only through `identity`. |
11
+ | `purpose` | `short` (120 ms endpoint fades by default), `visualiser`, or `canvas` (no fades by default). |
12
+ | `width`, `height`, `fps`, `fps_reason` | Even dimensions, fps defaults to 60; other rates need a reason. No platform compliance is inferred. |
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 `beat_grid` file reference; `authority`, `status` (`confirmed`, `estimated`, `synthetic`), optional `origin` in source seconds and `phase_status`. |
15
+ | `font` | Pinned TTF/OTF file for authored text. |
16
+ | `lyrics`, `cues` | Separate LRC references; `.cue` files are also supported for structural cues. |
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. |
18
+ | `background` | `colour`, optional pinned `image` and `fit` (`cover` or `contain`). |
19
+ | `captions` | `box` [left,top,right,bottom], `font_size`, optional `min_font_size`, `colour`, `panel`, `stroke`, `line_spacing`, `mode` (`current` or `neighbours`). |
20
+ | `identity` | List of `{text, style}`; each style uses the same text-block fields as captions. |
21
+ | `layers` | Ordered sprite/bar layers described below. |
22
+ | `lyric_art` | `{items: [{text, asset}], placement: <sprite fields>, fallback: "text" or "error"}`. Exact text lookup preserves canonical lyric association. `error` requires every visible lyric to have an asset. |
23
+ | `stemlab_loop` | `{file: <pinned loops report>, id: <accepted loop id>}`; imports native bounds/rate, verifies source SHA, rejects conflicting explicit audio values. Does not approve a seam. |
24
+
25
+ Sprites use `id`, `asset`, `center` [x,y], `width` and optional `bounds`
26
+ [left,top,right,bottom]. Optional `crop_alpha`, `opacity`, `motion`, `mapping`,
27
+ `atlas`, `trail`, source-time `start_seconds`/`end_seconds` and `cue_labels`
28
+ control their appearance. `mapping` amplitudes are `dx`, `dy`, `rotation`
29
+ (degrees), `scale` (magnitude less than one); `x_axis`, `y_axis`, `rotation_axis`
30
+ and `scale_axis` select index 0,1,2 of the motion projection. Defaults are
31
+ 0,1,2,2 respectively. Set `y_axis: 0` to map a one-dimensional bounce vertically.
32
+
33
+ Atlas configuration is `{columns, rows, count, cycle_ticks, phase_beats?}`.
34
+ The atlas must divide into exact cells. One beat is 16 ticks; cycle ticks are
35
+ explicitly authored rather than silently quantised. Cycles use a continuous
36
+ beat clock across repeated source excerpts. `trail` accepts `offset` [x,y],
37
+ `opacity` and `radius`, with the expanded result included in bounds checks.
38
+
39
+ Motion kinds:
40
+
41
+ * `composite`: `channels` is a list of one-to-three `{motion, axis}` entries;
42
+ each output axis samples its own native motion route. Use this for independent
43
+ sway, bounce and tilt rather than conflating their periods or phases.
44
+ * `named`: native `pattern`, optional `rate`, `phase_beats`, `origin_beat`.
45
+ * `table`: pinned `file` containing an existing native sample export and
46
+ `pattern`. Requires 64-pip, version, binary-hash and exact period identity.
47
+ * `match`: pinned StemLab report `file`, explicit zero-based `source_index`
48
+ and `match_index`, optional `source_offset_seconds` for a documented excerpt
49
+ report. Uses the selected pattern/phase and source-window origin. Extrapolation
50
+ requires `allow_outside_match: true`; it never implies approval.
51
+ * `recording`: pinned native recording `file`, optional one-to-three named
52
+ `axes`. Its BPM, beat zero and negative start pip stay authoritative. Outside
53
+ capture `outside` is `error` by default, or explicitly `neutral`. Rate,
54
+ phase and interpolation overrides are rejected.
55
+
56
+ `interpolate: true` on named/table/match routes explicitly blends adjacent
57
+ integer native samples for old compositor parity. Default sampling is native
58
+ integer-pip. Named/table `repeat_clock` is `continuous` by default; `source`
59
+ replays the source phase each cycle. Recorded and matched motion always use
60
+ absolute source time.
61
+
62
+ A layer with `type: "bars"`, `bounds` and a recording motion displays every
63
+ dimension on a signed -1..+1 scale, including Fourier buckets. It does not
64
+ extract frequencies or generate envelopes: StemLab supplies that evidence.
65
+
66
+ The Python `scene(image, FrameContext)` hook supports release-owned custom
67
+ flight physics, fog, backgrounds and choreography. Pure/random-access hooks
68
+ are easiest to test; a stateful simulation must advance by encoded frame in
69
+ a deterministic order and own its collision/occlusion QA. Custom drawing is
70
+ not automatically covered by sprite bounds. All external assets must be pinned.
@@ -0,0 +1,38 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "framerudiments"
7
+ version = "0.1.0"
8
+ description = "Deterministic Shorts and lyric visualisers using DanceRudiments and StemLab evidence."
9
+ requires-python = ">=3.11"
10
+ readme = "README.md"
11
+ license = "MIT"
12
+ authors = [{name = "Kieran Simkin"}]
13
+ dependencies = ["Pillow>=10", "dancerudiments>=0.3.0,<0.4"]
14
+
15
+ [project.optional-dependencies]
16
+ dev = ["pytest>=8", "build>=1.2", "ruff>=0.9", "twine>=6"]
17
+
18
+ [project.urls]
19
+ Homepage = "https://kieransimkin.co.uk/danceflow/"
20
+ Repository = "https://github.com/kieransimkin/FrameRudiments"
21
+ Issues = "https://github.com/kieransimkin/FrameRudiments/issues"
22
+ Documentation = "https://github.com/kieransimkin/FrameRudiments#readme"
23
+
24
+ [project.scripts]
25
+ framerudiments = "framerudiments.cli:main"
26
+
27
+ [tool.setuptools.packages.find]
28
+ where = ["src"]
29
+
30
+ [tool.pytest.ini_options]
31
+ testpaths = ["tests"]
32
+ pythonpath = ["src"]
33
+
34
+ [tool.ruff]
35
+ line-length = 110
36
+
37
+ [tool.ruff.lint]
38
+ select = ["E4", "E7", "E9", "F"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,7 @@
1
+ """Offline frame composition on a traceable musical clock."""
2
+ __version__ = "0.1.0"
3
+
4
+ from .project import Project
5
+ from .render import render, preflight
6
+
7
+ __all__ = ["Project", "render", "preflight", "__version__"]
@@ -0,0 +1,3 @@
1
+ from .cli import main
2
+
3
+ raise SystemExit(main())
@@ -0,0 +1,44 @@
1
+ """Connected alpha-component cleaning extracted from the sprite preparation tools."""
2
+ from collections import deque
3
+ from PIL import Image
4
+
5
+ def components(alpha: Image.Image, threshold: int = 18) -> list[list[tuple[int, int]]]:
6
+ width, height = alpha.size
7
+ pixels = alpha.load()
8
+ seen: set[tuple[int, int]] = set()
9
+ result: list[list[tuple[int, int]]] = []
10
+ for y in range(height):
11
+ for x in range(width):
12
+ if (x, y) in seen or pixels[x, y] < threshold:
13
+ continue
14
+ queue = deque([(x, y)])
15
+ seen.add((x, y))
16
+ group: list[tuple[int, int]] = []
17
+ while queue:
18
+ px, py = queue.popleft()
19
+ group.append((px, py))
20
+ for ny in range(max(0, py - 1), min(height, py + 2)):
21
+ for nx in range(max(0, px - 1), min(width, px + 2)):
22
+ if (nx, ny) not in seen and pixels[nx, ny] >= threshold:
23
+ seen.add((nx, ny))
24
+ queue.append((nx, ny))
25
+ result.append(group)
26
+ return sorted(result, key=len, reverse=True)
27
+
28
+ def isolate_one_object(cell: Image.Image) -> tuple[Image.Image, int, int]:
29
+ rgba = cell.convert("RGBA")
30
+ groups = components(rgba.getchannel("A"))
31
+ if not groups:
32
+ raise ValueError("atlas cell contains no opaque object")
33
+ keep = set(groups[0])
34
+ removed = sum(len(group) for group in groups[1:])
35
+ src = rgba.load()
36
+ for y in range(rgba.height):
37
+ for x in range(rgba.width):
38
+ if (x, y) not in keep:
39
+ r, g, b, _ = src[x, y]
40
+ src[x, y] = (r, g, b, 0)
41
+ post = [group for group in components(rgba.getchannel("A")) if len(group) >= 8]
42
+ if len(post) != 1:
43
+ raise ValueError(f"cleaned cell has {len(post)} meaningful objects")
44
+ return rgba, len(groups), removed
@@ -0,0 +1,30 @@
1
+ """Frame-addressed entrance/hold/exit mechanics extracted from lyric-plane rendering."""
2
+ from .timeline import half_up
3
+ from .io import number, integer
4
+
5
+
6
+ def lyric_placements(events, frame, *, fps, bpm, transition_ticks=8, width=1920, centre=960,
7
+ margin=650, programme_frames):
8
+ """Enter by onset, hold to explicit end_frame, then leave; no copied song BPM."""
9
+ fps = integer(fps, "fps", minimum=1)
10
+ bpm = number(bpm, "bpm", minimum=.00001)
11
+ transition = max(1, half_up(number(transition_ticks, "transition ticks", minimum=0) * 3.75 / bpm * fps))
12
+ for index, event in enumerate(events):
13
+ onset = event["frame"]
14
+ if "end_frame" not in event:
15
+ raise ValueError("Lyric-art choreography requires explicit end_frame")
16
+ following = event["end_frame"]
17
+ if following < onset:
18
+ raise ValueError("Lyric-art end precedes onset")
19
+ enter, end = max(0, onset - transition), min(programme_frames, following + transition)
20
+ if not enter <= frame < end:
21
+ continue
22
+ if frame < onset:
23
+ u = 1 - (1 - (frame - enter) / max(1, onset - enter)) ** 3
24
+ x, phase = -margin + (centre + margin) * u, "enter"
25
+ elif frame < following:
26
+ x, phase = centre, "hold"
27
+ else:
28
+ u = 1 - (1 - min(1, (frame - following) / transition)) ** 3
29
+ x, phase = centre + (width + margin - centre) * u, "exit"
30
+ yield index, round(x), phase