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.
- framerudiments-0.1.0/AGENTS.md +16 -0
- framerudiments-0.1.0/LICENSE +21 -0
- framerudiments-0.1.0/MANIFEST.in +3 -0
- framerudiments-0.1.0/PKG-INFO +173 -0
- framerudiments-0.1.0/README.md +151 -0
- framerudiments-0.1.0/docs/extraction.md +28 -0
- framerudiments-0.1.0/docs/project-format.md +70 -0
- framerudiments-0.1.0/pyproject.toml +38 -0
- framerudiments-0.1.0/setup.cfg +4 -0
- framerudiments-0.1.0/src/framerudiments/__init__.py +7 -0
- framerudiments-0.1.0/src/framerudiments/__main__.py +3 -0
- framerudiments-0.1.0/src/framerudiments/atlas.py +44 -0
- framerudiments-0.1.0/src/framerudiments/choreography.py +30 -0
- framerudiments-0.1.0/src/framerudiments/cli.py +54 -0
- framerudiments-0.1.0/src/framerudiments/compositor.py +278 -0
- framerudiments-0.1.0/src/framerudiments/demo.py +68 -0
- framerudiments-0.1.0/src/framerudiments/effects.py +51 -0
- framerudiments-0.1.0/src/framerudiments/encoding.py +111 -0
- framerudiments-0.1.0/src/framerudiments/flight.py +157 -0
- framerudiments-0.1.0/src/framerudiments/io.py +66 -0
- framerudiments-0.1.0/src/framerudiments/motion.py +137 -0
- framerudiments-0.1.0/src/framerudiments/project.py +124 -0
- framerudiments-0.1.0/src/framerudiments/render.py +111 -0
- framerudiments-0.1.0/src/framerudiments/stemlab.py +36 -0
- framerudiments-0.1.0/src/framerudiments/subtitles.py +45 -0
- framerudiments-0.1.0/src/framerudiments/timeline.py +132 -0
- framerudiments-0.1.0/src/framerudiments.egg-info/PKG-INFO +173 -0
- framerudiments-0.1.0/src/framerudiments.egg-info/SOURCES.txt +36 -0
- framerudiments-0.1.0/src/framerudiments.egg-info/dependency_links.txt +1 -0
- framerudiments-0.1.0/src/framerudiments.egg-info/entry_points.txt +2 -0
- framerudiments-0.1.0/src/framerudiments.egg-info/requires.txt +8 -0
- framerudiments-0.1.0/src/framerudiments.egg-info/top_level.txt +1 -0
- framerudiments-0.1.0/tests/test_adapters.py +64 -0
- framerudiments-0.1.0/tests/test_extracted_helpers.py +26 -0
- framerudiments-0.1.0/tests/test_flight.py +31 -0
- framerudiments-0.1.0/tests/test_lyrics.py +45 -0
- framerudiments-0.1.0/tests/test_pipeline.py +127 -0
- 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,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,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
|