cutan 0.0.2__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.
- cutan-0.0.2/.gitattributes +1 -0
- cutan-0.0.2/.gitignore +176 -0
- cutan-0.0.2/CLAUDE.md +42 -0
- cutan-0.0.2/LICENSE +21 -0
- cutan-0.0.2/PKG-INFO +64 -0
- cutan-0.0.2/README.md +35 -0
- cutan-0.0.2/conftest.py +265 -0
- cutan-0.0.2/cutan/__init__.py +80 -0
- cutan-0.0.2/cutan/audio/__init__.py +50 -0
- cutan-0.0.2/cutan/audio/injectable_lipsync.py +123 -0
- cutan-0.0.2/cutan/audio/offline_lipsync.py +126 -0
- cutan-0.0.2/cutan/audio/rhubarb_lipsync.py +152 -0
- cutan-0.0.2/cutan/audio/whisper_lipsync.py +143 -0
- cutan-0.0.2/cutan/bench.py +244 -0
- cutan-0.0.2/cutan/characters/__init__.py +125 -0
- cutan-0.0.2/cutan/characters/brows.py +504 -0
- cutan-0.0.2/cutan/characters/checks.py +610 -0
- cutan-0.0.2/cutan/characters/cli.py +629 -0
- cutan-0.0.2/cutan/characters/colour_roles.py +176 -0
- cutan-0.0.2/cutan/characters/dicebear.py +238 -0
- cutan-0.0.2/cutan/characters/drawn.py +94 -0
- cutan-0.0.2/cutan/characters/factory.py +2237 -0
- cutan-0.0.2/cutan/characters/idle.py +220 -0
- cutan-0.0.2/cutan/characters/licenses.py +385 -0
- cutan-0.0.2/cutan/characters/methods.py +708 -0
- cutan-0.0.2/cutan/characters/mouth_set.py +235 -0
- cutan-0.0.2/cutan/characters/play.py +1244 -0
- cutan-0.0.2/cutan/characters/promote.py +189 -0
- cutan-0.0.2/cutan/characters/record.py +167 -0
- cutan-0.0.2/cutan/characters/registration.py +239 -0
- cutan-0.0.2/cutan/characters/schema.py +921 -0
- cutan-0.0.2/cutan/characters/silhouette.py +155 -0
- cutan-0.0.2/cutan/characters/svg_utils.py +346 -0
- cutan-0.0.2/cutan/characters/validate.py +946 -0
- cutan-0.0.2/cutan/characters/vocabulary.py +194 -0
- cutan-0.0.2/cutan/compile/__init__.py +1 -0
- cutan-0.0.2/cutan/compile/coarticulate.py +445 -0
- cutan-0.0.2/cutan/compile/gaze.py +147 -0
- cutan-0.0.2/cutan/compile/lowering.py +109 -0
- cutan-0.0.2/cutan/compile/passes.py +2581 -0
- cutan-0.0.2/cutan/conftest.py +8 -0
- cutan-0.0.2/cutan/data/dicebear_9x_styles.json +38 -0
- cutan-0.0.2/cutan/expression/__init__.py +80 -0
- cutan-0.0.2/cutan/expression/axes.py +122 -0
- cutan-0.0.2/cutan/expression/binding.py +326 -0
- cutan-0.0.2/cutan/expression/blendshapes.py +125 -0
- cutan-0.0.2/cutan/expression/presets.py +203 -0
- cutan-0.0.2/cutan/expression/provider.py +210 -0
- cutan-0.0.2/cutan/expression/registration.py +187 -0
- cutan-0.0.2/cutan/genre.py +220 -0
- cutan-0.0.2/cutan/impacts/__init__.py +89 -0
- cutan-0.0.2/cutan/impacts/cli.py +166 -0
- cutan-0.0.2/cutan/impacts/clip.py +603 -0
- cutan-0.0.2/cutan/impacts/objects.py +255 -0
- cutan-0.0.2/cutan/impacts/performance.py +257 -0
- cutan-0.0.2/cutan/impacts/stroke.py +252 -0
- cutan-0.0.2/cutan/impacts/truth.py +368 -0
- cutan-0.0.2/cutan/library.py +318 -0
- cutan-0.0.2/cutan/nw.py +58 -0
- cutan-0.0.2/cutan/runtime/__init__.py +1 -0
- cutan-0.0.2/cutan/runtime/visuals.js +129 -0
- cutan-0.0.2/cutan/verify/__init__.py +1 -0
- cutan-0.0.2/cutan/verify/style.py +1194 -0
- cutan-0.0.2/examples/character_gallery/.gitignore +4 -0
- cutan-0.0.2/examples/character_gallery/README.md +70 -0
- cutan-0.0.2/examples/character_gallery/build.py +276 -0
- cutan-0.0.2/examples/character_gallery/cartoon/.gitignore +6 -0
- cutan-0.0.2/examples/character_gallery/cartoon/scene.md +66 -0
- cutan-0.0.2/examples/character_gallery/videos/cartoon.mp4 +0 -0
- cutan-0.0.2/examples/park_bench_cartoon/README.md +43 -0
- cutan-0.0.2/examples/park_bench_cartoon/ir/scene.json +121 -0
- cutan-0.0.2/examples/park_bench_cartoon/scene.md +66 -0
- cutan-0.0.2/examples/promote_demo/README.md +81 -0
- cutan-0.0.2/examples/promote_demo/assets/characters/raw_maya/raw_maya.svg +88 -0
- cutan-0.0.2/examples/promote_demo/build.py +51 -0
- cutan-0.0.2/examples/promote_demo/ir/scene.json +64 -0
- cutan-0.0.2/examples/promote_demo/scene.md +39 -0
- cutan-0.0.2/examples/single_character/README.md +11 -0
- cutan-0.0.2/examples/single_character/ir/scene.json +102 -0
- cutan-0.0.2/examples/single_character/scene.md +29 -0
- cutan-0.0.2/examples/walk_demo/ir/scene.json +204 -0
- cutan-0.0.2/examples/walk_demo/scene.md +82 -0
- cutan-0.0.2/misc/CHANGELOG.md +6 -0
- cutan-0.0.2/misc/bench/corpus/aa_probe/assets/characters/probe-rig/character.json +10 -0
- cutan-0.0.2/misc/bench/corpus/aa_probe/scene.md +49 -0
- cutan-0.0.2/misc/bench/corpus/dialogue/assets/characters/talker-rig/character.json +10 -0
- cutan-0.0.2/misc/bench/corpus/dialogue/ir/scene.json +163 -0
- cutan-0.0.2/misc/bench/corpus/dialogue/scene.md +36 -0
- cutan-0.0.2/misc/bench/corpus/expressions/assets/characters/face/character.json +822 -0
- cutan-0.0.2/misc/bench/corpus/expressions/assets/characters/face/face.svg +5 -0
- cutan-0.0.2/misc/bench/corpus/expressions/assets/characters/face/parts/arm_l.svg +2 -0
- cutan-0.0.2/misc/bench/corpus/expressions/assets/characters/face/parts/arm_r.svg +2 -0
- cutan-0.0.2/misc/bench/corpus/expressions/assets/characters/face/parts/brow_l.svg +2 -0
- cutan-0.0.2/misc/bench/corpus/expressions/assets/characters/face/parts/brow_r.svg +2 -0
- cutan-0.0.2/misc/bench/corpus/expressions/assets/characters/face/parts/eye_l_closed.svg +2 -0
- cutan-0.0.2/misc/bench/corpus/expressions/assets/characters/face/parts/eye_l_open.svg +2 -0
- cutan-0.0.2/misc/bench/corpus/expressions/assets/characters/face/parts/eye_r_closed.svg +2 -0
- cutan-0.0.2/misc/bench/corpus/expressions/assets/characters/face/parts/eye_r_open.svg +2 -0
- cutan-0.0.2/misc/bench/corpus/expressions/assets/characters/face/parts/head.svg +5 -0
- cutan-0.0.2/misc/bench/corpus/expressions/assets/characters/face/parts/leg_l.svg +2 -0
- cutan-0.0.2/misc/bench/corpus/expressions/assets/characters/face/parts/leg_r.svg +2 -0
- cutan-0.0.2/misc/bench/corpus/expressions/assets/characters/face/parts/mouth/mouth_a.svg +2 -0
- cutan-0.0.2/misc/bench/corpus/expressions/assets/characters/face/parts/mouth/mouth_a_happy.svg +2 -0
- cutan-0.0.2/misc/bench/corpus/expressions/assets/characters/face/parts/mouth/mouth_a_sad.svg +2 -0
- cutan-0.0.2/misc/bench/corpus/expressions/assets/characters/face/parts/mouth/mouth_b.svg +2 -0
- cutan-0.0.2/misc/bench/corpus/expressions/assets/characters/face/parts/mouth/mouth_b_happy.svg +2 -0
- cutan-0.0.2/misc/bench/corpus/expressions/assets/characters/face/parts/mouth/mouth_b_sad.svg +2 -0
- cutan-0.0.2/misc/bench/corpus/expressions/assets/characters/face/parts/mouth/mouth_c.svg +2 -0
- cutan-0.0.2/misc/bench/corpus/expressions/assets/characters/face/parts/mouth/mouth_c_happy.svg +2 -0
- cutan-0.0.2/misc/bench/corpus/expressions/assets/characters/face/parts/mouth/mouth_c_sad.svg +2 -0
- cutan-0.0.2/misc/bench/corpus/expressions/assets/characters/face/parts/mouth/mouth_d.svg +2 -0
- cutan-0.0.2/misc/bench/corpus/expressions/assets/characters/face/parts/mouth/mouth_d_happy.svg +2 -0
- cutan-0.0.2/misc/bench/corpus/expressions/assets/characters/face/parts/mouth/mouth_d_sad.svg +2 -0
- cutan-0.0.2/misc/bench/corpus/expressions/assets/characters/face/parts/mouth/mouth_e.svg +2 -0
- cutan-0.0.2/misc/bench/corpus/expressions/assets/characters/face/parts/mouth/mouth_e_happy.svg +2 -0
- cutan-0.0.2/misc/bench/corpus/expressions/assets/characters/face/parts/mouth/mouth_e_sad.svg +2 -0
- cutan-0.0.2/misc/bench/corpus/expressions/assets/characters/face/parts/mouth/mouth_f.svg +2 -0
- cutan-0.0.2/misc/bench/corpus/expressions/assets/characters/face/parts/mouth/mouth_f_happy.svg +2 -0
- cutan-0.0.2/misc/bench/corpus/expressions/assets/characters/face/parts/mouth/mouth_f_sad.svg +2 -0
- cutan-0.0.2/misc/bench/corpus/expressions/assets/characters/face/parts/mouth/mouth_g.svg +2 -0
- cutan-0.0.2/misc/bench/corpus/expressions/assets/characters/face/parts/mouth/mouth_g_happy.svg +2 -0
- cutan-0.0.2/misc/bench/corpus/expressions/assets/characters/face/parts/mouth/mouth_g_sad.svg +2 -0
- cutan-0.0.2/misc/bench/corpus/expressions/assets/characters/face/parts/mouth/mouth_h.svg +2 -0
- cutan-0.0.2/misc/bench/corpus/expressions/assets/characters/face/parts/mouth/mouth_h_happy.svg +2 -0
- cutan-0.0.2/misc/bench/corpus/expressions/assets/characters/face/parts/mouth/mouth_h_sad.svg +2 -0
- cutan-0.0.2/misc/bench/corpus/expressions/assets/characters/face/parts/mouth/mouth_x.svg +2 -0
- cutan-0.0.2/misc/bench/corpus/expressions/assets/characters/face/parts/mouth/mouth_x_happy.svg +2 -0
- cutan-0.0.2/misc/bench/corpus/expressions/assets/characters/face/parts/mouth/mouth_x_sad.svg +2 -0
- cutan-0.0.2/misc/bench/corpus/expressions/assets/characters/face/parts/pupil_l.svg +2 -0
- cutan-0.0.2/misc/bench/corpus/expressions/assets/characters/face/parts/pupil_r.svg +2 -0
- cutan-0.0.2/misc/bench/corpus/expressions/assets/characters/face/parts/sclera_l.svg +2 -0
- cutan-0.0.2/misc/bench/corpus/expressions/assets/characters/face/parts/sclera_r.svg +2 -0
- cutan-0.0.2/misc/bench/corpus/expressions/assets/characters/face/parts/torso.svg +2 -0
- cutan-0.0.2/misc/bench/corpus/expressions/scene.md +212 -0
- cutan-0.0.2/misc/bench/corpus/graded_field/assets/characters/graded-field-rig/character.json +465 -0
- cutan-0.0.2/misc/bench/corpus/graded_field/assets/characters/graded-field-rig/parts/arm_l.svg +1 -0
- cutan-0.0.2/misc/bench/corpus/graded_field/assets/characters/graded-field-rig/parts/arm_r.svg +1 -0
- cutan-0.0.2/misc/bench/corpus/graded_field/assets/characters/graded-field-rig/parts/brow_l.svg +1 -0
- cutan-0.0.2/misc/bench/corpus/graded_field/assets/characters/graded-field-rig/parts/brow_r.svg +1 -0
- cutan-0.0.2/misc/bench/corpus/graded_field/assets/characters/graded-field-rig/parts/eye_l_open.svg +1 -0
- cutan-0.0.2/misc/bench/corpus/graded_field/assets/characters/graded-field-rig/parts/eye_r_open.svg +1 -0
- cutan-0.0.2/misc/bench/corpus/graded_field/assets/characters/graded-field-rig/parts/head.svg +1 -0
- cutan-0.0.2/misc/bench/corpus/graded_field/assets/characters/graded-field-rig/parts/leg_l.svg +1 -0
- cutan-0.0.2/misc/bench/corpus/graded_field/assets/characters/graded-field-rig/parts/leg_r.svg +1 -0
- cutan-0.0.2/misc/bench/corpus/graded_field/assets/characters/graded-field-rig/parts/mouth/mouth_a.svg +1 -0
- cutan-0.0.2/misc/bench/corpus/graded_field/assets/characters/graded-field-rig/parts/mouth/mouth_b.svg +1 -0
- cutan-0.0.2/misc/bench/corpus/graded_field/assets/characters/graded-field-rig/parts/mouth/mouth_c.svg +1 -0
- cutan-0.0.2/misc/bench/corpus/graded_field/assets/characters/graded-field-rig/parts/mouth/mouth_d.svg +1 -0
- cutan-0.0.2/misc/bench/corpus/graded_field/assets/characters/graded-field-rig/parts/mouth/mouth_e.svg +1 -0
- cutan-0.0.2/misc/bench/corpus/graded_field/assets/characters/graded-field-rig/parts/mouth/mouth_f.svg +1 -0
- cutan-0.0.2/misc/bench/corpus/graded_field/assets/characters/graded-field-rig/parts/mouth/mouth_g.svg +1 -0
- cutan-0.0.2/misc/bench/corpus/graded_field/assets/characters/graded-field-rig/parts/mouth/mouth_h.svg +1 -0
- cutan-0.0.2/misc/bench/corpus/graded_field/assets/characters/graded-field-rig/parts/mouth/mouth_x.svg +1 -0
- cutan-0.0.2/misc/bench/corpus/graded_field/assets/characters/graded-field-rig/parts/torso.svg +1 -0
- cutan-0.0.2/misc/bench/corpus/graded_field/scene.md +60 -0
- cutan-0.0.2/misc/bench/corpus/multi_shot/assets/characters/ada-rig/character.json +10 -0
- cutan-0.0.2/misc/bench/corpus/multi_shot/scene.md +64 -0
- cutan-0.0.2/misc/bench/corpus/saturated_outline/assets/characters/saturated-rig/character.json +465 -0
- cutan-0.0.2/misc/bench/corpus/saturated_outline/assets/characters/saturated-rig/parts/arm_l.svg +1 -0
- cutan-0.0.2/misc/bench/corpus/saturated_outline/assets/characters/saturated-rig/parts/arm_r.svg +1 -0
- cutan-0.0.2/misc/bench/corpus/saturated_outline/assets/characters/saturated-rig/parts/brow_l.svg +1 -0
- cutan-0.0.2/misc/bench/corpus/saturated_outline/assets/characters/saturated-rig/parts/brow_r.svg +1 -0
- cutan-0.0.2/misc/bench/corpus/saturated_outline/assets/characters/saturated-rig/parts/eye_l_open.svg +1 -0
- cutan-0.0.2/misc/bench/corpus/saturated_outline/assets/characters/saturated-rig/parts/eye_r_open.svg +1 -0
- cutan-0.0.2/misc/bench/corpus/saturated_outline/assets/characters/saturated-rig/parts/head.svg +1 -0
- cutan-0.0.2/misc/bench/corpus/saturated_outline/assets/characters/saturated-rig/parts/leg_l.svg +1 -0
- cutan-0.0.2/misc/bench/corpus/saturated_outline/assets/characters/saturated-rig/parts/leg_r.svg +1 -0
- cutan-0.0.2/misc/bench/corpus/saturated_outline/assets/characters/saturated-rig/parts/mouth/mouth_a.svg +1 -0
- cutan-0.0.2/misc/bench/corpus/saturated_outline/assets/characters/saturated-rig/parts/mouth/mouth_b.svg +1 -0
- cutan-0.0.2/misc/bench/corpus/saturated_outline/assets/characters/saturated-rig/parts/mouth/mouth_c.svg +1 -0
- cutan-0.0.2/misc/bench/corpus/saturated_outline/assets/characters/saturated-rig/parts/mouth/mouth_d.svg +1 -0
- cutan-0.0.2/misc/bench/corpus/saturated_outline/assets/characters/saturated-rig/parts/mouth/mouth_e.svg +1 -0
- cutan-0.0.2/misc/bench/corpus/saturated_outline/assets/characters/saturated-rig/parts/mouth/mouth_f.svg +1 -0
- cutan-0.0.2/misc/bench/corpus/saturated_outline/assets/characters/saturated-rig/parts/mouth/mouth_g.svg +1 -0
- cutan-0.0.2/misc/bench/corpus/saturated_outline/assets/characters/saturated-rig/parts/mouth/mouth_h.svg +1 -0
- cutan-0.0.2/misc/bench/corpus/saturated_outline/assets/characters/saturated-rig/parts/mouth/mouth_x.svg +1 -0
- cutan-0.0.2/misc/bench/corpus/saturated_outline/assets/characters/saturated-rig/parts/torso.svg +1 -0
- cutan-0.0.2/misc/bench/corpus/saturated_outline/scene.md +34 -0
- cutan-0.0.2/misc/bench/golden/aa_probe/bless-chromium140.0.7339.16.json +46 -0
- cutan-0.0.2/misc/bench/golden/aa_probe/f0000-chromium140.0.7339.16.png +0 -0
- cutan-0.0.2/misc/bench/golden/aa_probe/f0006-chromium140.0.7339.16.png +0 -0
- cutan-0.0.2/misc/bench/golden/dialogue/bless-chromium140.0.7339.16.json +46 -0
- cutan-0.0.2/misc/bench/golden/dialogue/f0000-chromium140.0.7339.16.png +0 -0
- cutan-0.0.2/misc/bench/golden/dialogue/f0014-chromium140.0.7339.16.png +0 -0
- cutan-0.0.2/misc/bench/golden/expressions/bless-chromium140.0.7339.16.json +113 -0
- cutan-0.0.2/misc/bench/golden/expressions/f0003-chromium140.0.7339.16.png +0 -0
- cutan-0.0.2/misc/bench/golden/expressions/f0009-chromium140.0.7339.16.png +0 -0
- cutan-0.0.2/misc/bench/golden/expressions/f0015-chromium140.0.7339.16.png +0 -0
- cutan-0.0.2/misc/bench/golden/expressions/f0021-chromium140.0.7339.16.png +0 -0
- cutan-0.0.2/misc/bench/golden/expressions/f0027-chromium140.0.7339.16.png +0 -0
- cutan-0.0.2/misc/bench/golden/expressions/f0033-chromium140.0.7339.16.png +0 -0
- cutan-0.0.2/misc/bench/golden/expressions/f0039-chromium140.0.7339.16.png +0 -0
- cutan-0.0.2/misc/bench/golden/expressions/f0045-chromium140.0.7339.16.png +0 -0
- cutan-0.0.2/misc/bench/golden/graded_field/bless-chromium140.0.7339.16.json +46 -0
- cutan-0.0.2/misc/bench/golden/graded_field/f0000-chromium140.0.7339.16.png +0 -0
- cutan-0.0.2/misc/bench/golden/graded_field/f0004-chromium140.0.7339.16.png +0 -0
- cutan-0.0.2/misc/bench/golden/multi_shot/bless-chromium140.0.7339.16.json +47 -0
- cutan-0.0.2/misc/bench/golden/multi_shot/f0000-chromium140.0.7339.16.png +0 -0
- cutan-0.0.2/misc/bench/golden/multi_shot/f0006-chromium140.0.7339.16.png +0 -0
- cutan-0.0.2/misc/bench/golden/promote_demo/bless-chromium140.0.7339.16.json +46 -0
- cutan-0.0.2/misc/bench/golden/promote_demo/f0000-chromium140.0.7339.16.png +0 -0
- cutan-0.0.2/misc/bench/golden/promote_demo/f0070-chromium140.0.7339.16.png +0 -0
- cutan-0.0.2/misc/bench/golden/saturated_outline/bless-chromium140.0.7339.16.json +46 -0
- cutan-0.0.2/misc/bench/golden/saturated_outline/f0000-chromium140.0.7339.16.png +0 -0
- cutan-0.0.2/misc/bench/golden/saturated_outline/f0006-chromium140.0.7339.16.png +0 -0
- cutan-0.0.2/misc/bench/golden/single_character/bless-chromium140.0.7339.16.json +46 -0
- cutan-0.0.2/misc/bench/golden/single_character/f0000-chromium140.0.7339.16.png +0 -0
- cutan-0.0.2/misc/bench/golden/single_character/f0024-chromium140.0.7339.16.png +0 -0
- cutan-0.0.2/misc/demos/README.md +56 -0
- cutan-0.0.2/misc/demos/build_demos.py +3283 -0
- cutan-0.0.2/pyproject.toml +200 -0
- cutan-0.0.2/tests/__init__.py +0 -0
- cutan-0.0.2/tests/_bench_compat.py +24 -0
- cutan-0.0.2/tests/_child_guard/sitecustomize.py +101 -0
- cutan-0.0.2/tests/_emotion_frames.py +63 -0
- cutan-0.0.2/tests/_fake_subprocess.py +135 -0
- cutan-0.0.2/tests/_lipsync_strips.py +81 -0
- cutan-0.0.2/tests/_node.py +117 -0
- cutan-0.0.2/tests/_render_seam.py +97 -0
- cutan-0.0.2/tests/_support.py +60 -0
- cutan-0.0.2/tests/_vision_cassettes.py +144 -0
- cutan-0.0.2/tests/conftest.py +1002 -0
- cutan-0.0.2/tests/fixtures/art/invisible_head.svg +19 -0
- cutan-0.0.2/tests/fixtures/characters/gale/character.json +594 -0
- cutan-0.0.2/tests/fixtures/characters/gale/parts/arm_l.svg +1 -0
- cutan-0.0.2/tests/fixtures/characters/gale/parts/arm_r.svg +1 -0
- cutan-0.0.2/tests/fixtures/characters/gale/parts/brow_l.svg +1 -0
- cutan-0.0.2/tests/fixtures/characters/gale/parts/brow_r.svg +1 -0
- cutan-0.0.2/tests/fixtures/characters/gale/parts/eye_l_closed.svg +1 -0
- cutan-0.0.2/tests/fixtures/characters/gale/parts/eye_l_open.svg +1 -0
- cutan-0.0.2/tests/fixtures/characters/gale/parts/eye_r_closed.svg +1 -0
- cutan-0.0.2/tests/fixtures/characters/gale/parts/eye_r_open.svg +1 -0
- cutan-0.0.2/tests/fixtures/characters/gale/parts/hand_fist.svg +1 -0
- cutan-0.0.2/tests/fixtures/characters/gale/parts/hand_palm.svg +1 -0
- cutan-0.0.2/tests/fixtures/characters/gale/parts/hand_point.svg +1 -0
- cutan-0.0.2/tests/fixtures/characters/gale/parts/head.svg +1 -0
- cutan-0.0.2/tests/fixtures/characters/gale/parts/leg_l.svg +1 -0
- cutan-0.0.2/tests/fixtures/characters/gale/parts/leg_r.svg +1 -0
- cutan-0.0.2/tests/fixtures/characters/gale/parts/mouth/mouth_a.svg +1 -0
- cutan-0.0.2/tests/fixtures/characters/gale/parts/mouth/mouth_b.svg +1 -0
- cutan-0.0.2/tests/fixtures/characters/gale/parts/mouth/mouth_c.svg +1 -0
- cutan-0.0.2/tests/fixtures/characters/gale/parts/mouth/mouth_d.svg +1 -0
- cutan-0.0.2/tests/fixtures/characters/gale/parts/mouth/mouth_e.svg +1 -0
- cutan-0.0.2/tests/fixtures/characters/gale/parts/mouth/mouth_f.svg +1 -0
- cutan-0.0.2/tests/fixtures/characters/gale/parts/mouth/mouth_g.svg +1 -0
- cutan-0.0.2/tests/fixtures/characters/gale/parts/mouth/mouth_h.svg +1 -0
- cutan-0.0.2/tests/fixtures/characters/gale/parts/mouth/mouth_x.svg +1 -0
- cutan-0.0.2/tests/fixtures/characters/gale/parts/torso.svg +1 -0
- cutan-0.0.2/tests/fixtures/characters/gale/parts/torso_left.svg +1 -0
- cutan-0.0.2/tests/fixtures/characters/gale/parts/torso_right.svg +1 -0
- cutan-0.0.2/tests/fixtures/runtime_visual_demo.js +9 -0
- cutan-0.0.2/tests/fixtures/vision_frames/emotions/afraid.png +0 -0
- cutan-0.0.2/tests/fixtures/vision_frames/emotions/angry.png +0 -0
- cutan-0.0.2/tests/fixtures/vision_frames/emotions/happy.png +0 -0
- cutan-0.0.2/tests/fixtures/vision_frames/emotions/neutral.png +0 -0
- cutan-0.0.2/tests/fixtures/vision_frames/emotions/sad.png +0 -0
- cutan-0.0.2/tests/fixtures/vision_frames/emotions/skeptical.png +0 -0
- cutan-0.0.2/tests/fixtures/vision_frames/emotions/surprised.png +0 -0
- cutan-0.0.2/tests/fixtures/vision_frames/emotions/thinking.png +0 -0
- cutan-0.0.2/tests/fixtures/vision_frames/lipsync/after/f0_frame001.png +0 -0
- cutan-0.0.2/tests/fixtures/vision_frames/lipsync/after/f1_frame004.png +0 -0
- cutan-0.0.2/tests/fixtures/vision_frames/lipsync/after/f2_frame007.png +0 -0
- cutan-0.0.2/tests/fixtures/vision_frames/lipsync/after/f3_frame010.png +0 -0
- cutan-0.0.2/tests/fixtures/vision_frames/lipsync/after/f4_frame013.png +0 -0
- cutan-0.0.2/tests/fixtures/vision_frames/lipsync/after/f5_frame016.png +0 -0
- cutan-0.0.2/tests/fixtures/vision_frames/lipsync/after/f6_frame019.png +0 -0
- cutan-0.0.2/tests/fixtures/vision_frames/lipsync/after/f7_frame022.png +0 -0
- cutan-0.0.2/tests/fixtures/vision_frames/lipsync/before/f0_frame001.png +0 -0
- cutan-0.0.2/tests/fixtures/vision_frames/lipsync/before/f1_frame004.png +0 -0
- cutan-0.0.2/tests/fixtures/vision_frames/lipsync/before/f2_frame007.png +0 -0
- cutan-0.0.2/tests/fixtures/vision_frames/lipsync/before/f3_frame010.png +0 -0
- cutan-0.0.2/tests/fixtures/vision_frames/lipsync/before/f4_frame013.png +0 -0
- cutan-0.0.2/tests/fixtures/vision_frames/lipsync/before/f5_frame016.png +0 -0
- cutan-0.0.2/tests/fixtures/vision_frames/lipsync/before/f6_frame019.png +0 -0
- cutan-0.0.2/tests/fixtures/vision_frames/lipsync/before/f7_frame022.png +0 -0
- cutan-0.0.2/tests/test_art_contract.py +242 -0
- cutan-0.0.2/tests/test_bench_capture.py +283 -0
- cutan-0.0.2/tests/test_bench_corpus.py +483 -0
- cutan-0.0.2/tests/test_blinks.py +489 -0
- cutan-0.0.2/tests/test_brow_acting.py +405 -0
- cutan-0.0.2/tests/test_carved_art_gaps.py +563 -0
- cutan-0.0.2/tests/test_character_variety.py +460 -0
- cutan-0.0.2/tests/test_character_voices.py +150 -0
- cutan-0.0.2/tests/test_characters.py +545 -0
- cutan-0.0.2/tests/test_coarticulation.py +524 -0
- cutan-0.0.2/tests/test_cutout_compile_visemes.py +217 -0
- cutan-0.0.2/tests/test_cutout_render.py +216 -0
- cutan-0.0.2/tests/test_demo_framing.py +109 -0
- cutan-0.0.2/tests/test_demo_gallery_contracts.py +53 -0
- cutan-0.0.2/tests/test_dialogue_emotion.py +72 -0
- cutan-0.0.2/tests/test_emotion_gaze_render.py +111 -0
- cutan-0.0.2/tests/test_emotion_judge.py +143 -0
- cutan-0.0.2/tests/test_examples_spend_gate.py +282 -0
- cutan-0.0.2/tests/test_expression.py +243 -0
- cutan-0.0.2/tests/test_expression_compose.py +661 -0
- cutan-0.0.2/tests/test_expression_goldens.py +68 -0
- cutan-0.0.2/tests/test_gaze.py +442 -0
- cutan-0.0.2/tests/test_impacts.py +498 -0
- cutan-0.0.2/tests/test_injectable_lipsync.py +107 -0
- cutan-0.0.2/tests/test_methods_compile.py +429 -0
- cutan-0.0.2/tests/test_multi_character_render.py +113 -0
- cutan-0.0.2/tests/test_nw.py +55 -0
- cutan-0.0.2/tests/test_offline_lipsync.py +68 -0
- cutan-0.0.2/tests/test_placeholder_limb_pivot.py +119 -0
- cutan-0.0.2/tests/test_play.py +507 -0
- cutan-0.0.2/tests/test_play_extent.py +219 -0
- cutan-0.0.2/tests/test_play_presets.py +445 -0
- cutan-0.0.2/tests/test_provider_swap.py +130 -0
- cutan-0.0.2/tests/test_pure_pose.py +409 -0
- cutan-0.0.2/tests/test_rhubarb_lipsync.py +33 -0
- cutan-0.0.2/tests/test_rhubarb_recognizer.py +190 -0
- cutan-0.0.2/tests/test_rig_builder_extraction.py +200 -0
- cutan-0.0.2/tests/test_rig_fidelity.py +223 -0
- cutan-0.0.2/tests/test_rig_layout.py +138 -0
- cutan-0.0.2/tests/test_scaffold.py +41 -0
- cutan-0.0.2/tests/test_skill_rig_paths.py +102 -0
- cutan-0.0.2/tests/test_style_lint.py +379 -0
- cutan-0.0.2/tests/test_style_specs.py +404 -0
- cutan-0.0.2/tests/test_swap_channels.py +965 -0
- cutan-0.0.2/tests/test_turnaround.py +751 -0
- cutan-0.0.2/tests/test_validate_whole_character_swaps.py +110 -0
- cutan-0.0.2/tests/test_walk_preset.py +248 -0
- cutan-0.0.2/tests/test_whisper_lipsync.py +100 -0
- cutan-0.0.2/tests/test_word_timings.py +215 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
*.ipynb linguist-documentation
|
cutan-0.0.2/.gitignore
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
.claude/handoffs/
|
|
2
|
+
.claude/scratch/
|
|
3
|
+
|
|
4
|
+
# Byte-compiled / optimized / DLL files
|
|
5
|
+
__pycache__/
|
|
6
|
+
*.py[cod]
|
|
7
|
+
*$py.class
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
.DS_Store
|
|
11
|
+
# C extensions
|
|
12
|
+
*.so
|
|
13
|
+
|
|
14
|
+
# TLS certificates
|
|
15
|
+
## Ignore all PEM files anywhere
|
|
16
|
+
*.pem
|
|
17
|
+
## Also ignore any certs directory
|
|
18
|
+
certs/
|
|
19
|
+
|
|
20
|
+
# Distribution / packaging
|
|
21
|
+
.Python
|
|
22
|
+
build/
|
|
23
|
+
develop-eggs/
|
|
24
|
+
dist/
|
|
25
|
+
downloads/
|
|
26
|
+
eggs/
|
|
27
|
+
.eggs/
|
|
28
|
+
lib/
|
|
29
|
+
lib64/
|
|
30
|
+
# ANCHORED. Unanchored, `parts/` matches at any depth — and this repo's
|
|
31
|
+
# character rigs keep their SVGs in `assets/characters/<ref>/parts/`, so the
|
|
32
|
+
# template's buildout rule silently excluded every one of them (measured:
|
|
33
|
+
# `git ls-files | grep parts/` returned ZERO). That is the an#33 failure shape
|
|
34
|
+
# exactly: a fixture that renders from a build product nobody tracks, and a
|
|
35
|
+
# clean checkout that draws a different picture with no error. Buildout's
|
|
36
|
+
# directory is at the repo root, so anchoring loses nothing.
|
|
37
|
+
/parts/
|
|
38
|
+
sdist/
|
|
39
|
+
var/
|
|
40
|
+
wheels/
|
|
41
|
+
*.egg-info/
|
|
42
|
+
.installed.cfg
|
|
43
|
+
*.egg
|
|
44
|
+
MANIFEST
|
|
45
|
+
_build
|
|
46
|
+
|
|
47
|
+
# PyInstaller
|
|
48
|
+
# Usually these files are written by a python script from a template
|
|
49
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
50
|
+
*.manifest
|
|
51
|
+
*.spec
|
|
52
|
+
|
|
53
|
+
# Installer logs
|
|
54
|
+
pip-log.txt
|
|
55
|
+
pip-delete-this-directory.txt
|
|
56
|
+
|
|
57
|
+
# Unit test / coverage reports
|
|
58
|
+
htmlcov/
|
|
59
|
+
.tox/
|
|
60
|
+
.coverage
|
|
61
|
+
.coverage.*
|
|
62
|
+
.cache
|
|
63
|
+
nosetests.xml
|
|
64
|
+
coverage.xml
|
|
65
|
+
*.cover
|
|
66
|
+
.hypothesis/
|
|
67
|
+
.pytest_cache/
|
|
68
|
+
|
|
69
|
+
# Translations
|
|
70
|
+
*.mo
|
|
71
|
+
*.pot
|
|
72
|
+
|
|
73
|
+
# Django stuff:
|
|
74
|
+
*.log
|
|
75
|
+
local_settings.py
|
|
76
|
+
db.sqlite3
|
|
77
|
+
|
|
78
|
+
# Flask stuff:
|
|
79
|
+
instance/
|
|
80
|
+
.webassets-cache
|
|
81
|
+
|
|
82
|
+
# Scrapy stuff:
|
|
83
|
+
.scrapy
|
|
84
|
+
|
|
85
|
+
# Sphinx documentation
|
|
86
|
+
docs/_build/
|
|
87
|
+
docs/*
|
|
88
|
+
|
|
89
|
+
# PyBuilder
|
|
90
|
+
target/
|
|
91
|
+
|
|
92
|
+
# Jupyter Notebook
|
|
93
|
+
.ipynb_checkpoints
|
|
94
|
+
|
|
95
|
+
# pyenv
|
|
96
|
+
.python-version
|
|
97
|
+
|
|
98
|
+
# celery beat schedule file
|
|
99
|
+
celerybeat-schedule
|
|
100
|
+
|
|
101
|
+
# SageMath parsed files
|
|
102
|
+
*.sage.py
|
|
103
|
+
|
|
104
|
+
# Environments
|
|
105
|
+
.env
|
|
106
|
+
.venv
|
|
107
|
+
env/
|
|
108
|
+
venv/
|
|
109
|
+
ENV/
|
|
110
|
+
env.bak/
|
|
111
|
+
venv.bak/
|
|
112
|
+
|
|
113
|
+
# Spyder project settings
|
|
114
|
+
.spyderproject
|
|
115
|
+
.spyproject
|
|
116
|
+
|
|
117
|
+
# Rope project settings
|
|
118
|
+
.ropeproject
|
|
119
|
+
|
|
120
|
+
# mkdocs documentation
|
|
121
|
+
/site
|
|
122
|
+
|
|
123
|
+
# mypy
|
|
124
|
+
.mypy_cache/
|
|
125
|
+
|
|
126
|
+
# PyCharm
|
|
127
|
+
.idea
|
|
128
|
+
|
|
129
|
+
# an per-project ephemera (renders, intermediates, agent state)
|
|
130
|
+
examples/*/output/
|
|
131
|
+
examples/*/artifacts/
|
|
132
|
+
examples/*/assets/
|
|
133
|
+
examples/*/.an/
|
|
134
|
+
examples/*/.anima/
|
|
135
|
+
examples/*/an.toml
|
|
136
|
+
examples/*/anima.toml
|
|
137
|
+
|
|
138
|
+
# The promote_demo example's input SVG is the whole point of the example
|
|
139
|
+
# — keep it tracked even though examples/*/assets/ is otherwise ignored.
|
|
140
|
+
!examples/promote_demo/assets/
|
|
141
|
+
!examples/promote_demo/assets/characters/
|
|
142
|
+
!examples/promote_demo/assets/characters/raw_maya/
|
|
143
|
+
!examples/promote_demo/assets/characters/raw_maya/*.svg
|
|
144
|
+
# But re-exclude the promote target — it's regenerated by build.py.
|
|
145
|
+
examples/promote_demo/assets/characters/maya-promoted/
|
|
146
|
+
|
|
147
|
+
# Agent worktrees (adversarial reviewers run isolated here so they cannot
|
|
148
|
+
# mutate the shared checkout). Never committed, and excluded from tooling
|
|
149
|
+
# below or ruff/pytest walk three extra copies of the whole repo.
|
|
150
|
+
.claude/worktrees/
|
|
151
|
+
|
|
152
|
+
# Session-boundary handoffs. Local working notes, not project docs — anything
|
|
153
|
+
# that should outlive a session belongs in an issue or misc/docs/ instead.
|
|
154
|
+
.claude/handoffs/
|
|
155
|
+
|
|
156
|
+
# an bench corpus (an#38): the fixtures are COMMITTED WHOLE — their pixels have
|
|
157
|
+
# to be a function of the repo alone — so only the per-render ephemera is
|
|
158
|
+
# ignored here. Note there is deliberately no `assets/` rule: that is what
|
|
159
|
+
# `examples/*/assets/` has, and it is why the corpus does not live there.
|
|
160
|
+
misc/bench/corpus/*/output/
|
|
161
|
+
misc/bench/corpus/*/artifacts/
|
|
162
|
+
misc/bench/corpus/*/.an/
|
|
163
|
+
misc/bench/corpus/*/ir/
|
|
164
|
+
# …except `dialogue`'s IR, which is the fixture: the bench renders with
|
|
165
|
+
# `auto_audio=False`, so the visemes a speaking scene needs must already be in
|
|
166
|
+
# its committed `ir/scene.json` (offline provider, deterministic), exactly as
|
|
167
|
+
# `examples/single_character/ir/scene.json` does. Without this carve-out the
|
|
168
|
+
# scene would be mute on a clean checkout and its "mid-line" golden a lie (an#96).
|
|
169
|
+
!misc/bench/corpus/dialogue/ir/
|
|
170
|
+
!misc/bench/corpus/dialogue/ir/scene.json
|
|
171
|
+
|
|
172
|
+
# `misc/demos/build_demos.py`'s output. Regenerable in a couple of minutes from
|
|
173
|
+
# the committed script, and the media is published as release assets rather than
|
|
174
|
+
# committed — `misc/` ships in the sdist, so a megabyte of GIFs would ride along
|
|
175
|
+
# with every `pip install`.
|
|
176
|
+
misc/demos/out/
|
cutan-0.0.2/CLAUDE.md
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Working in the cutan repo
|
|
2
|
+
|
|
3
|
+
`cutan` is the cut-out animation genre of [`an`](https://github.com/thorwhalen/an), split out of it per ADR 0001 (an#225, epic an#231). It depends on `an`; `an` never imports it (except through the registered hooks, `an.genres`).
|
|
4
|
+
|
|
5
|
+
## Read first
|
|
6
|
+
|
|
7
|
+
- `an`'s `CLAUDE.md` and `misc/docs/architecture_as_built.md` (§0a: core and genre, the seams this package plugs into). Decisions and the plan: `misc/docs/adr/0001-core-genre-split.md` and `misc/docs/plan_core_and_cutan_2026-10.md` in `an`.
|
|
8
|
+
- This repo's skills (`.claude/skills/`): `cutan` (downstream: authoring cut-out scenes), `cutan-style`, `cutan-art-package`, and `cutan-dev-{expression,lipsync,rig-contract,swap-channels}` for the areas of the same names. Read the matching `cutan-dev-*` before touching a face, lip-sync, the rig contract or swap channels; for a pixel, an encode flag or the camera, the `an-dev-render-pipeline` and `an-dev-stage` skills in `an`.
|
|
9
|
+
|
|
10
|
+
## Layout
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
cutan/genre.py CUTOUT, the Genre object (entry point cutout_animation = "cutan.genre:CUTOUT"); nw.py: the nw declaration
|
|
14
|
+
cutan/characters/ descriptor schema (CharacterDescriptor), factory, play resolution, methods, vocabulary, checks, CLI
|
|
15
|
+
cutan/expression/ expression presets, axes, bindings, the face provider
|
|
16
|
+
cutan/compile/ passes.py (the cut-out passes over an.stage.compile), lowering.py (play/expression lowering, swap declaration), coarticulate.py, gaze.py
|
|
17
|
+
cutan/audio/ lip-sync providers (offline, rhubarb, whisper, word timings)
|
|
18
|
+
cutan/impacts/ impact choreography and ground truth; cutan/verify/style.py: the style lint; cutan/library.py: the character analyser
|
|
19
|
+
cutan/runtime/ visuals.js: the procedural mouth and eye, registered through the stage runtime's anRegisterVisual
|
|
20
|
+
cutan/bench.py CUTOUT_FIXTURES and run_bench() over this repo's misc/bench/ corpus
|
|
21
|
+
tests/ examples/ misc/bench/{corpus,golden}/ misc/demos/ .claude/skills/
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Rules
|
|
25
|
+
|
|
26
|
+
- **Persisted identifiers never change** (`cutan/__init__.py` lists them): `renderer: cutout`, the genre slug `cutout_animation`, the `CharacterDescriptor` document kind and its `schema_version`, the `character` asset kind and `characters` store, the action kinds `play` and `expression`, the compiled document's wire shape, the provenance provider string `"an character factory"` (licence `cc0-1.0`), the lip-sync provider names, the swap set names (`viseme`, `eyelid`, `view`, `hands`, `body_facing`) and the visual kinds `mouth` and `eye`.
|
|
27
|
+
- **The pixel gate stays with the core.** The corpus contract hashes (`tests/test_expression_compose.py`) and the blessed golden frames (`misc/bench/golden/`) must not move without a bless record that says why. A PR here that can change a pixel is exercised against `an` main in CI. Never write that a rendering behaviour is "verified in CI" without saying where.
|
|
28
|
+
- **Import `an` freely; `an` imports nothing of `cutan`.** If the core needs something from this package, add a registry or hook in `an` first (a service, `ActionKind.lowering`, `EntityKind.swap_declaration`, a compile pass, a runtime script). The compile passes may import `an.stage.compile` helpers (underscore names included): a genre depending on the core is allowed.
|
|
29
|
+
- **A change to any module here changes the core's shot-cache key** (`an.stage.cache_key.genre_code_modules` keys every module of this package), so a cut-out shot re-renders after an upgrade. That is intended.
|
|
30
|
+
- `cutan.REQUIRED_AN_API_LEVEL` is the lowest `an.genres.API_LEVEL` this package runs against (instead of a version pin: `an`'s version is assigned by CI at merge). Raise it in the same change that uses a new seam.
|
|
31
|
+
- Local packages carry no version pins: `an`, not `an>=…`.
|
|
32
|
+
- One `CLI`: `cutan` adds no console script. Its commands are `an character …` and `an impacts …`.
|
|
33
|
+
- Shims in `an` (`an.characters`, `an.expression`, `an.impacts`, `an.audio.*_lipsync`, `an.verify.style`, `an.library.character`, `an.genres.cutout`, `an.genre`, `an.adapters.cutout.{coarticulate,gaze}`) are removed when no package under `$PP` imports an old path and 14 days have passed (checked by a script, not by memory).
|
|
34
|
+
- Decisions of the move (the lead's, 2026-10-02): the bench *runner* stays in `an` (generic); skill names `cutan-style` and `cutan-dev-*`, with stubs of the old names left in `an` for one cycle; `an.genres.API_LEVEL` instead of a version floor.
|
|
35
|
+
|
|
36
|
+
## Tests
|
|
37
|
+
|
|
38
|
+
`pytest` from the repo root runs the tests and the doctests (`--doctest-modules` over `cutan/`, as CI does). The render tests are gated like `an`'s (`tests/conftest.py`): they need a headless browser and `ffmpeg`, and skip with a stated reason where those are absent. Tests that load a committed project in place depend on `scene.md` and `ir/scene.json` having the same age, which a fresh checkout gives them.
|
|
39
|
+
|
|
40
|
+
## Data
|
|
41
|
+
|
|
42
|
+
The genre's library root is `~/.local/share/cutan` (`CUTAN_HOME` overrides it), resolved by `an.library.root.library_root(package="cutan")`. Nothing in this repository holds assets or project data beyond the committed corpus and examples.
|
cutan-0.0.2/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Thor Whalen
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
cutan-0.0.2/PKG-INFO
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: cutan
|
|
3
|
+
Version: 0.0.2
|
|
4
|
+
Summary: Cut-out animation for an: rigged characters, faces, lip-sync and styles, as a genre package on the an core.
|
|
5
|
+
Project-URL: Homepage, https://github.com/thorwhalen/cutan
|
|
6
|
+
Project-URL: Repository, https://github.com/thorwhalen/cutan
|
|
7
|
+
Project-URL: Documentation, https://thorwhalen.github.io/cutan
|
|
8
|
+
Author: Thor Whalen
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: an,animation,cut-out,cutout,lip-sync
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Topic :: Multimedia :: Graphics
|
|
15
|
+
Requires-Python: >=3.10
|
|
16
|
+
Requires-Dist: an
|
|
17
|
+
Requires-Dist: numpy
|
|
18
|
+
Requires-Dist: pillow
|
|
19
|
+
Requires-Dist: pyyaml
|
|
20
|
+
Provides-Extra: dev
|
|
21
|
+
Requires-Dist: an[stage]; extra == 'dev'
|
|
22
|
+
Requires-Dist: pytest-cov>=4.0; extra == 'dev'
|
|
23
|
+
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
24
|
+
Requires-Dist: ruff>=0.1.0; extra == 'dev'
|
|
25
|
+
Provides-Extra: docs
|
|
26
|
+
Requires-Dist: sphinx-rtd-theme>=1.0; extra == 'docs'
|
|
27
|
+
Requires-Dist: sphinx>=6.0; extra == 'docs'
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
|
|
30
|
+
# cutan
|
|
31
|
+
|
|
32
|
+
Cut-out animation for [`an`](https://github.com/thorwhalen/an): rigged characters, faces and expressions, lip-sync, swap sets and views, impacts, and cut-out styles, as a genre package on the `an` core.
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
pip install "an[cutout]" # an + the stage + cutan
|
|
36
|
+
an character new maya --offline
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
`an` finds `cutan` through the `an.genres` entry point and loads it with `an.genres.load()` (the CLI and `an.load(project)` do). The scene format, rendering and the asset library are `an`'s; see its README.
|
|
40
|
+
|
|
41
|
+
## What is here
|
|
42
|
+
|
|
43
|
+
`cutan` registers with the core:
|
|
44
|
+
|
|
45
|
+
- the `character` entity kind and the `play` and `expression` actions, the `[emotion]` dialogue sugar and the semantic checks that go with them (`cutan.genre`, `cutan.characters`, `cutan.expression`);
|
|
46
|
+
- the rig, face, viseme, blink, gaze and swap-pose compile passes over the `an.stage` compiler (`cutan.compile`), and the mouth and eye visuals of the stage runtime (`cutan/runtime/visuals.js`);
|
|
47
|
+
- the lip-sync providers `offline`, `rhubarb` and `whisper` (`cutan.audio`);
|
|
48
|
+
- locomotion, speech, blink and turn methods with their requirements and defaults, and the cut-out vocabulary;
|
|
49
|
+
- the `an character …` and `an impacts …` command namespaces;
|
|
50
|
+
- the style lint (`python -m cutan.verify.style`) and the style specs of the `cutan-style` skill.
|
|
51
|
+
|
|
52
|
+
The cut-out bench corpus (`misc/bench/`), `examples/` and the demo gallery (`misc/demos/`) live here too; `cutan.bench.run_bench()` runs the corpus through `an`'s bench runner.
|
|
53
|
+
|
|
54
|
+
## Names that never change
|
|
55
|
+
|
|
56
|
+
Moving the code renamed nothing that is stored. Scenes keep `renderer: cutout`, the genre keeps its slug `cutout_animation`, character descriptors keep their document kind and version, and asset ids keep their `character.` prefix. These names are in `cutan/__init__.py`. The old `an.characters`, `an.expression`, `an.impacts`, `an.audio.offline_lipsync`-style import paths still work in `an`, with a `MovedModuleWarning`.
|
|
57
|
+
|
|
58
|
+
## Where data lives
|
|
59
|
+
|
|
60
|
+
The genre's asset library and agent-made projects live under `~/.local/share/cutan` by default. Set `CUTAN_HOME` to move them. The core's own library (voices, sounds, fonts) stays under `an`'s root, and a project reads both as one search path.
|
|
61
|
+
|
|
62
|
+
## Working on cutan
|
|
63
|
+
|
|
64
|
+
Read `CLAUDE.md`. In short: `pip install -e . --no-deps` next to an editable `an`, then `pytest`. The tests that render need a headless browser (`playwright install chromium`) and `ffmpeg`.
|
cutan-0.0.2/README.md
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# cutan
|
|
2
|
+
|
|
3
|
+
Cut-out animation for [`an`](https://github.com/thorwhalen/an): rigged characters, faces and expressions, lip-sync, swap sets and views, impacts, and cut-out styles, as a genre package on the `an` core.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
pip install "an[cutout]" # an + the stage + cutan
|
|
7
|
+
an character new maya --offline
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
`an` finds `cutan` through the `an.genres` entry point and loads it with `an.genres.load()` (the CLI and `an.load(project)` do). The scene format, rendering and the asset library are `an`'s; see its README.
|
|
11
|
+
|
|
12
|
+
## What is here
|
|
13
|
+
|
|
14
|
+
`cutan` registers with the core:
|
|
15
|
+
|
|
16
|
+
- the `character` entity kind and the `play` and `expression` actions, the `[emotion]` dialogue sugar and the semantic checks that go with them (`cutan.genre`, `cutan.characters`, `cutan.expression`);
|
|
17
|
+
- the rig, face, viseme, blink, gaze and swap-pose compile passes over the `an.stage` compiler (`cutan.compile`), and the mouth and eye visuals of the stage runtime (`cutan/runtime/visuals.js`);
|
|
18
|
+
- the lip-sync providers `offline`, `rhubarb` and `whisper` (`cutan.audio`);
|
|
19
|
+
- locomotion, speech, blink and turn methods with their requirements and defaults, and the cut-out vocabulary;
|
|
20
|
+
- the `an character …` and `an impacts …` command namespaces;
|
|
21
|
+
- the style lint (`python -m cutan.verify.style`) and the style specs of the `cutan-style` skill.
|
|
22
|
+
|
|
23
|
+
The cut-out bench corpus (`misc/bench/`), `examples/` and the demo gallery (`misc/demos/`) live here too; `cutan.bench.run_bench()` runs the corpus through `an`'s bench runner.
|
|
24
|
+
|
|
25
|
+
## Names that never change
|
|
26
|
+
|
|
27
|
+
Moving the code renamed nothing that is stored. Scenes keep `renderer: cutout`, the genre keeps its slug `cutout_animation`, character descriptors keep their document kind and version, and asset ids keep their `character.` prefix. These names are in `cutan/__init__.py`. The old `an.characters`, `an.expression`, `an.impacts`, `an.audio.offline_lipsync`-style import paths still work in `an`, with a `MovedModuleWarning`.
|
|
28
|
+
|
|
29
|
+
## Where data lives
|
|
30
|
+
|
|
31
|
+
The genre's asset library and agent-made projects live under `~/.local/share/cutan` by default. Set `CUTAN_HOME` to move them. The core's own library (voices, sounds, fonts) stays under `an`'s root, and a project reads both as one search path.
|
|
32
|
+
|
|
33
|
+
## Working on cutan
|
|
34
|
+
|
|
35
|
+
Read `CLAUDE.md`. In short: `pip install -e . --no-deps` next to an editable `an`, then `pytest`. The tests that render need a headless browser (`playwright install chromium`) and `ffmpeg`.
|
cutan-0.0.2/conftest.py
ADDED
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
"""Repository-wide pytest setup: load the genres, as the real entry points do.
|
|
2
|
+
|
|
3
|
+
Genres are discovered explicitly (ADR 0001 decision 3): importing ``an``
|
|
4
|
+
registers none, and ``an.load(project)`` / the CLI call
|
|
5
|
+
:func:`an.genres.load`. Most tests and doctests build scenes with the cut-out
|
|
6
|
+
genre's kinds (``play``, ``expression``, ``character``, ``[emotion]``)
|
|
7
|
+
without going through either, so the session calls the same
|
|
8
|
+
:func:`an.genres.load` once, here — the in-distribution genres are found
|
|
9
|
+
whatever the installed metadata says. Tests of the unloaded core use
|
|
10
|
+
:func:`an.genres.without_genres`; the entry points themselves are held by
|
|
11
|
+
subprocess tests that run with no pre-registration
|
|
12
|
+
(``tests/test_open_document_model.py``).
|
|
13
|
+
|
|
14
|
+
At the repository root so it reaches both ``tests/`` and the doctests under
|
|
15
|
+
``an/`` (CI's ``--doctest-modules``).
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def pytest_configure(config): # noqa: D103 — a pytest hook
|
|
24
|
+
from an.genres import load
|
|
25
|
+
|
|
26
|
+
load()
|
|
27
|
+
_install_real_home_guard()
|
|
28
|
+
_redirect_account_home_for_the_session()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
#: What the session guard found: stack traces of writes that reached the real
|
|
32
|
+
#: account's data folder (review-269 B3).
|
|
33
|
+
_REAL_HOME_WRITES: list[str] = []
|
|
34
|
+
_REAL_DATA_SNAPSHOT: dict = {}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _real_data_dirs():
|
|
38
|
+
from an.library import registry
|
|
39
|
+
import sys
|
|
40
|
+
|
|
41
|
+
from an.library.root import POSIX_DATA_DEFAULT, WINDOWS_DATA_DEFAULT
|
|
42
|
+
|
|
43
|
+
defaults = (
|
|
44
|
+
WINDOWS_DATA_DEFAULT if sys.platform.startswith("win") else POSIX_DATA_DEFAULT
|
|
45
|
+
)
|
|
46
|
+
data = registry._account_home().joinpath(*defaults)
|
|
47
|
+
return [data / "an", data / "cutan"]
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _snapshot(dirs) -> dict:
|
|
51
|
+
out = {}
|
|
52
|
+
for d in dirs:
|
|
53
|
+
if not d.exists():
|
|
54
|
+
continue
|
|
55
|
+
for p in d.rglob("*"):
|
|
56
|
+
try:
|
|
57
|
+
st = p.lstat()
|
|
58
|
+
except OSError:
|
|
59
|
+
continue
|
|
60
|
+
out[str(p)] = (st.st_mtime_ns, st.st_size)
|
|
61
|
+
return out
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _install_real_home_guard() -> None:
|
|
65
|
+
"""Fail the run if this process writes into the real account's data folder.
|
|
66
|
+
|
|
67
|
+
Two layers, installed before any fixture runs:
|
|
68
|
+
|
|
69
|
+
- a tripwire, in this process: every library-store write and every
|
|
70
|
+
machine-registry path that lands under the real data folder is recorded
|
|
71
|
+
with its stack, and fails the session (``pytest_sessionfinish``);
|
|
72
|
+
- a snapshot of ``<data>/an`` and ``<data>/cutan`` before and after the
|
|
73
|
+
session. On CI (``CI`` set) any difference fails the run. On a developer
|
|
74
|
+
machine other sessions (an end-to-end render, an agent publishing) write
|
|
75
|
+
there concurrently, so a difference is reported, and fails only with
|
|
76
|
+
``AN_TEST_HOME_GUARD=strict``.
|
|
77
|
+
"""
|
|
78
|
+
import traceback
|
|
79
|
+
|
|
80
|
+
from an.library import registry, stores
|
|
81
|
+
|
|
82
|
+
data_dirs = _real_data_dirs()
|
|
83
|
+
real_prefixes = tuple(str(d) for d in data_dirs) # not their parent: on Windows
|
|
84
|
+
# the temp dir (home of the redirect) sits inside `AppData/Local`.
|
|
85
|
+
_REAL_DATA_SNAPSHOT["dirs"] = data_dirs
|
|
86
|
+
_REAL_DATA_SNAPSHOT["home"] = registry._account_home()
|
|
87
|
+
_REAL_DATA_SNAPSHOT["before"] = _snapshot(data_dirs)
|
|
88
|
+
|
|
89
|
+
def note(path) -> None:
|
|
90
|
+
if str(path).startswith(real_prefixes):
|
|
91
|
+
_REAL_HOME_WRITES.append(
|
|
92
|
+
f"{path}\n" + "".join(traceback.format_stack(limit=30))
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
temp, delete = stores.LocalFiles._temp, stores.LocalFiles.__delitem__
|
|
96
|
+
|
|
97
|
+
def guarded_temp(self, path, data):
|
|
98
|
+
note(path)
|
|
99
|
+
return temp(self, path, data)
|
|
100
|
+
|
|
101
|
+
def guarded_delete(self, key):
|
|
102
|
+
note(self._path(key))
|
|
103
|
+
return delete(self, key)
|
|
104
|
+
|
|
105
|
+
stores.LocalFiles._temp = guarded_temp
|
|
106
|
+
stores.LocalFiles.__delitem__ = guarded_delete
|
|
107
|
+
registry_dir = registry.machine_registry_dir
|
|
108
|
+
|
|
109
|
+
def guarded_registry_dir():
|
|
110
|
+
out = registry_dir()
|
|
111
|
+
note(out)
|
|
112
|
+
return out
|
|
113
|
+
|
|
114
|
+
registry.machine_registry_dir = guarded_registry_dir
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def pytest_collection_modifyitems(config, items): # noqa: D103 — a pytest hook
|
|
118
|
+
# Every test gets a fresh registry, so every publish is a "first use": the
|
|
119
|
+
# RegistryWarning would fire once per publishing test and bury real
|
|
120
|
+
# warnings (review-269 N2). `pytest.warns` still sees it.
|
|
121
|
+
mark = pytest.mark.filterwarnings("ignore::an.library.registry.RegistryWarning")
|
|
122
|
+
for item in items:
|
|
123
|
+
item.add_marker(mark)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def pytest_sessionfinish(session, exitstatus): # noqa: D103 — a pytest hook
|
|
127
|
+
import os
|
|
128
|
+
|
|
129
|
+
reporter = session.config.pluginmanager.get_plugin("terminalreporter")
|
|
130
|
+
|
|
131
|
+
def say(text: str) -> None:
|
|
132
|
+
if reporter is not None:
|
|
133
|
+
reporter.write_line(text)
|
|
134
|
+
|
|
135
|
+
child_log = _CHILD_STATE.get("path")
|
|
136
|
+
if child_log is not None and child_log.exists() and child_log.stat().st_size:
|
|
137
|
+
say(
|
|
138
|
+
"REAL-HOME GUARD: a CHILD python process reached the real data folder "
|
|
139
|
+
"despite the redirect (an#302):\n"
|
|
140
|
+
+ child_log.read_text(encoding="utf-8")[:4000]
|
|
141
|
+
)
|
|
142
|
+
session.exitstatus = 1
|
|
143
|
+
if _REAL_HOME_WRITES:
|
|
144
|
+
say(
|
|
145
|
+
f"REAL-HOME GUARD: {len(_REAL_HOME_WRITES)} write(s) or registry access(es) "
|
|
146
|
+
"reached the real data folder from this test process; first one:\n"
|
|
147
|
+
+ _REAL_HOME_WRITES[0]
|
|
148
|
+
)
|
|
149
|
+
session.exitstatus = 1
|
|
150
|
+
if "before" in _REAL_DATA_SNAPSHOT:
|
|
151
|
+
after = _snapshot(_REAL_DATA_SNAPSHOT["dirs"])
|
|
152
|
+
before = _REAL_DATA_SNAPSHOT["before"]
|
|
153
|
+
changed = sorted(
|
|
154
|
+
k for k in set(before) | set(after) if before.get(k) != after.get(k)
|
|
155
|
+
)
|
|
156
|
+
if changed:
|
|
157
|
+
strict = (
|
|
158
|
+
os.environ.get("CI") or os.environ.get("AN_TEST_HOME_GUARD") == "strict"
|
|
159
|
+
)
|
|
160
|
+
say(
|
|
161
|
+
f"REAL-HOME GUARD: {len(changed)} path(s) under the real data folder "
|
|
162
|
+
"changed during the session"
|
|
163
|
+
+ (
|
|
164
|
+
""
|
|
165
|
+
if strict
|
|
166
|
+
else " (another process? set AN_TEST_HOME_GUARD=strict to fail on it)"
|
|
167
|
+
)
|
|
168
|
+
+ ":\n "
|
|
169
|
+
+ "\n ".join(changed[:20])
|
|
170
|
+
)
|
|
171
|
+
if strict:
|
|
172
|
+
session.exitstatus = 1
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def _redirect_account_home_for_the_session() -> None:
|
|
176
|
+
"""Point the machine registry into a temp folder for the WHOLE session.
|
|
177
|
+
|
|
178
|
+
The per-test fixture below gives each test a fresh registry, but a
|
|
179
|
+
module- or session-scoped fixture runs before it: one that draws a
|
|
180
|
+
character (the factory records what it draws, an#269) or publishes would
|
|
181
|
+
otherwise write the developer's real ``~/.local/share/an/registry``. This
|
|
182
|
+
session-wide redirect is what those fixtures see; each test still gets
|
|
183
|
+
its own fresh one.
|
|
184
|
+
"""
|
|
185
|
+
import tempfile
|
|
186
|
+
from an.library import registry
|
|
187
|
+
|
|
188
|
+
home = Path(tempfile.mkdtemp(prefix="an-session-account-home-"))
|
|
189
|
+
registry._account_home = lambda: home
|
|
190
|
+
_redirect_children_too(home)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
#: Environment variable naming the redirected account home for child pythons.
|
|
194
|
+
CHILD_HOME_ENV: str = "AN_TEST_ACCOUNT_HOME"
|
|
195
|
+
#: Environment variable naming the file a child appends to if it reaches the real data folder.
|
|
196
|
+
CHILD_LOG_ENV: str = "AN_TEST_REAL_HOME_LOG"
|
|
197
|
+
#: The folder whose ``sitecustomize`` applies the redirect inside a child (an#302).
|
|
198
|
+
CHILD_GUARD_DIR = Path(__file__).parent / "tests" / "_child_guard"
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def _redirect_children_too(home) -> None:
|
|
202
|
+
"""Make every child python process the tests spawn honour the same redirect.
|
|
203
|
+
|
|
204
|
+
The monkeypatch above lives in THIS interpreter; a child (a CLI test, a
|
|
205
|
+
``[sys.executable, "-c", ...]`` probe) is a new one and, left alone, would
|
|
206
|
+
write the developer's real registry (an#302). The library reads no
|
|
207
|
+
environment variable for that location, so the redirect rides on
|
|
208
|
+
``tests/_child_guard/sitecustomize.py`` instead, put on each child's
|
|
209
|
+
``PYTHONPATH`` by wrapping ``subprocess.Popen`` — whatever ``env=`` the
|
|
210
|
+
test built, including one that replaces ``PYTHONPATH`` wholesale.
|
|
211
|
+
A child that reaches the real folder regardless logs itself to
|
|
212
|
+
``CHILD_LOG_ENV``, and :func:`pytest_sessionfinish` fails the run on it.
|
|
213
|
+
"""
|
|
214
|
+
import os
|
|
215
|
+
import subprocess
|
|
216
|
+
|
|
217
|
+
log = home / "child-real-home-writes.log"
|
|
218
|
+
os.environ[CHILD_HOME_ENV] = str(home)
|
|
219
|
+
os.environ[CHILD_LOG_ENV] = str(log)
|
|
220
|
+
_CHILD_STATE["path"] = log
|
|
221
|
+
_CHILD_STATE["home"] = home
|
|
222
|
+
init = subprocess.Popen.__init__
|
|
223
|
+
|
|
224
|
+
def popen_init(self, *args, **kwargs):
|
|
225
|
+
env = dict(os.environ if kwargs.get("env") is None else kwargs["env"])
|
|
226
|
+
# The CURRENT home: the per-test fixture swaps it, like the in-process one.
|
|
227
|
+
env[CHILD_HOME_ENV] = str(_CHILD_STATE["home"])
|
|
228
|
+
env.setdefault(CHILD_LOG_ENV, str(log))
|
|
229
|
+
existing = env.get("PYTHONPATH")
|
|
230
|
+
env["PYTHONPATH"] = os.pathsep.join(
|
|
231
|
+
[str(CHILD_GUARD_DIR)] + ([existing] if existing else [])
|
|
232
|
+
)
|
|
233
|
+
kwargs["env"] = env
|
|
234
|
+
return init(self, *args, **kwargs)
|
|
235
|
+
|
|
236
|
+
subprocess.Popen.__init__ = popen_init
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
#: Where the child-process tripwire writes (set by :func:`_redirect_children_too`).
|
|
240
|
+
_CHILD_STATE: dict = {}
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
import pytest
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
@pytest.fixture(autouse=True)
|
|
247
|
+
def _isolated_library_registry(tmp_path_factory, monkeypatch):
|
|
248
|
+
"""Point the machine's library registry and statement memory into a fresh temp folder.
|
|
249
|
+
|
|
250
|
+
The registry (:mod:`an.library.registry`, an#249) lives under the account's
|
|
251
|
+
home as the OS records it, deliberately ignoring every environment variable,
|
|
252
|
+
so the ``AN_HOME`` / ``XDG_DATA_HOME`` redirection the library tests use
|
|
253
|
+
cannot move it. Every test and doctest that publishes would otherwise
|
|
254
|
+
append to the developer's real registry — and one test's private bytes
|
|
255
|
+
would sit in the next test's rights floor. Fresh per test, for both
|
|
256
|
+
reasons. The account home is what is replaced, so the real path logic
|
|
257
|
+
(``machine_registry_path``) still runs, and a test can hold it to its
|
|
258
|
+
independence from the environment.
|
|
259
|
+
"""
|
|
260
|
+
from an.library import registry
|
|
261
|
+
|
|
262
|
+
home = tmp_path_factory.mktemp("account-home")
|
|
263
|
+
monkeypatch.setattr(registry, "_account_home", lambda: home)
|
|
264
|
+
# Children get the same fresh registry (an#302), restored afterwards.
|
|
265
|
+
monkeypatch.setitem(_CHILD_STATE, "home", home)
|