@oh-story/dsh 0.1.11 → 0.1.13
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.
- package/README.md +17 -11
- package/lib/client.js +85 -35
- package/lib/client.js.map +3 -3
- package/lib/drama/manifest.json +41 -36
- package/lib/drama/skills/short-drama/scripts/creator_markdown_check.py +340 -13
- package/lib/drama/skills/short-drama-assets/references/voice-direction.md +4 -2
- package/lib/drama/skills/short-drama-edit/SKILL.md +32 -12
- package/lib/drama/skills/short-drama-edit/references/still-cuts.md +90 -0
- package/lib/drama/skills/short-drama-edit/scripts/edit_tool.py +1035 -145
- package/lib/drama/skills/short-drama-edit/scripts/selftest.py +48 -0
- package/lib/drama/skills/short-drama-produce/SKILL.md +6 -1
- package/lib/drama/skills/short-drama-produce/references/adapter-contract.md +12 -0
- package/lib/drama/skills/short-drama-produce/scripts/production_tool.py +138 -9
- package/lib/drama/skills/short-drama-produce/scripts/provider_adapters.py +6 -0
- package/lib/drama/skills/short-drama-storyboard/SKILL.md +2 -0
- package/lib/drama/skills/short-drama-storyboard/references/production-shot-grammar.md +3 -1
- package/lib/drama/skills/short-drama-video-prompts/SKILL.md +7 -0
- package/lib/drama/skills/short-drama-video-prompts/references/performance-action-timing.md +14 -0
- package/lib/drama/skills/short-drama-video-prompts/references/seedance-2.5.md +19 -6
- package/lib/drama/skills/short-drama-video-prompts/references/stage-contract.md +1 -0
- package/lib/drama/skills/short-drama-video-prompts/references/target-model-profile.md +5 -0
- package/lib/drama/skills/short-drama-video-prompts/references/wan-3.0.md +4 -1
- package/lib/index.js +16 -7
- package/lib/index.js.map +2 -2
- package/lib/novel-to-game/README.md +98 -44
- package/lib/novel-to-game/README_ZH.md +100 -46
- package/lib/novel-to-game/VERSION +1 -1
- package/lib/novel-to-game/examples/jin-ping-mei/build/BUILD_BRIEF.md +27 -202
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/RUN.md +23 -115
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/chamber.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/court_night.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/court_rain.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/garden.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/hall.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/kitchen.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/street.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/street_dawn.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/temple.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/yue/moon.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/meng/angry.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/meng/blush.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/meng/neutral.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/meng/sad.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/meng/smile.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/meng/surprised.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pan/angry.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pan/blush.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pan/neutral.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pan/sad.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pan/smile.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pan/surprised.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pinger/angry.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pinger/blush.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pinger/neutral.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pinger/sad.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pinger/smile.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pinger/surprised.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/xuee/angry.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/xuee/blush.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/xuee/neutral.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/xuee/sad.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/xuee/smile.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/xuee/surprised.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/yue/angry.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/yue/blush.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/yue/neutral.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/yue/sad.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/yue/smile.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/yue/surprised.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/css/style.css +326 -2503
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/index.html +71 -4
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/adv.js +221 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/audio.js +113 -257
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/main.js +511 -3026
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/menus.js +277 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/petals.js +67 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/stage.js +180 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/store.js +70 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/common.js +198 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/common2.js +204 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/common3.js +198 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/dates.js +186 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/index.js +122 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/meng.js +190 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/pan.js +212 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/pinger.js +198 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/xuee.js +199 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/yue.js +214 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/package.json +2 -2
- package/lib/novel-to-game/examples/jin-ping-mei/build/art/generated-art.json +182 -45
- package/lib/novel-to-game/examples/jin-ping-mei/qa/README.md +7 -17
- package/lib/novel-to-game/examples/jin-ping-mei/qa/verification.json +9 -9
- package/lib/novel-to-game/manifest.json +303 -78
- package/lib/novel-to-game/skills/game-art-direction/references/art-direction-method.md +7 -2
- package/lib/novel-to-game/skills/game-concept/references/concept-method.md +2 -0
- package/lib/novel-to-game/skills/game-world-design/references/narrative-design-method.md +2 -0
- package/lib/novel-to-game/skills/game-world-design/references/world-design-method.md +9 -0
- package/lib/types/client/drama-production-view.d.ts +11 -0
- package/lib/types/client/drama-production-view.d.ts.map +1 -1
- package/lib/types/client/file-activity.d.ts +1 -1
- package/lib/types/client/file-activity.d.ts.map +1 -1
- package/lib/types/client/production-prompts.d.ts +7 -1
- package/lib/types/client/production-prompts.d.ts.map +1 -1
- package/lib/types/client/production-runtime.d.ts +7 -2
- package/lib/types/client/production-runtime.d.ts.map +1 -1
- package/lib/types/drama-adapters.d.ts.map +1 -1
- package/lib/types/skill-provider.d.ts.map +1 -1
- package/lib/types/video-project.d.ts.map +1 -1
- package/lib/video-recap/CHANGELOG.md +108 -1
- package/lib/video-recap/README.en.md +18 -14
- package/lib/video-recap/README.md +18 -14
- package/lib/video-recap/manifest.json +297 -321
- package/lib/video-recap/skills/video-assemble/SKILL.md +18 -16
- package/lib/video-recap/skills/video-assemble/references/explicit-audio-mix.md +3 -1
- package/lib/video-recap/skills/video-assemble/references/jianying/SOURCE.md +5 -5
- package/lib/video-recap/skills/video-assemble/references/packaging.md +4 -4
- package/lib/video-recap/skills/video-assemble/references/subtitle-track.md +10 -10
- package/lib/video-recap/skills/video-assemble/scripts/adoption/audio_mix_binding.py +13 -2
- package/lib/video-recap/skills/video-assemble/scripts/adoption/av_clock.py +109 -0
- package/lib/video-recap/skills/video-assemble/scripts/adoption/strict_publish.py +4 -1
- package/lib/video-recap/skills/video-assemble/scripts/artifacts.py +2 -9
- package/lib/video-recap/skills/video-assemble/scripts/assemble.py +188 -51
- package/lib/video-recap/skills/video-assemble/scripts/assemble_constants.py +0 -1
- package/lib/video-recap/skills/video-assemble/scripts/assembly_contract.py +32 -75
- package/lib/video-recap/skills/video-assemble/scripts/assembly_settings.py +8 -15
- package/lib/video-recap/skills/video-assemble/scripts/audio_mix.py +202 -161
- package/lib/video-recap/skills/video-assemble/scripts/codec_peak.py +112 -0
- package/lib/video-recap/skills/video-assemble/scripts/export_jianying.py +10 -101
- package/lib/video-recap/skills/video-assemble/scripts/jianying/builders.py +44 -475
- package/lib/video-recap/skills/video-assemble/scripts/jianying/model.py +1 -22
- package/lib/video-recap/skills/video-assemble/scripts/jianying/templates.py +0 -3
- package/lib/video-recap/skills/video-assemble/scripts/jianying/timeline_contract.py +36 -103
- package/lib/video-recap/skills/video-assemble/scripts/jianying/tracks.py +0 -8
- package/lib/video-recap/skills/video-assemble/scripts/jianying/writer.py +28 -203
- package/lib/video-recap/skills/video-assemble/scripts/lib.py +20 -34
- package/lib/video-recap/skills/video-assemble/scripts/loudness.py +332 -0
- package/lib/video-recap/skills/video-assemble/scripts/media.py +158 -38
- package/lib/video-recap/skills/video-assemble/scripts/narration_audio.py +30 -16
- package/lib/video-recap/skills/video-assemble/scripts/render_preflight.py +49 -8
- package/lib/video-recap/skills/video-assemble/scripts/source_subtitles.py +17 -6
- package/lib/video-recap/skills/video-assemble/scripts/subtitles/core.py +24 -2
- package/lib/video-recap/skills/video-assemble/scripts/subtitles/track.py +2 -5
- package/lib/video-recap/skills/video-assemble/scripts/subtitles/track_binding.py +2 -9
- package/lib/video-recap/skills/video-assemble/scripts/timeline.py +12 -34
- package/lib/video-recap/skills/video-assemble/scripts/timeline_emit.py +3 -4
- package/lib/video-recap/skills/video-assemble/scripts/visual_render.py +28 -2
- package/lib/video-recap/skills/video-cut/SKILL.md +37 -11
- package/lib/video-recap/skills/video-cut/references/shot-review.md +1 -1
- package/lib/video-recap/skills/video-cut/scripts/cut.py +2 -34
- package/lib/video-recap/skills/video-cut/scripts/cut_cli.py +96 -124
- package/lib/video-recap/skills/video-cut/scripts/cut_contract.py +97 -182
- package/lib/video-recap/skills/video-cut/scripts/cut_render.py +177 -166
- package/lib/video-recap/skills/video-cut/scripts/frame_grid.py +249 -0
- package/lib/video-recap/skills/video-cut/scripts/lib.py +0 -3
- package/lib/video-recap/skills/video-cut/scripts/media_geometry.py +173 -7
- package/lib/video-recap/skills/video-cut/scripts/sentence_boundaries.py +234 -151
- package/lib/video-recap/skills/video-cut/scripts/sentence_gate.py +189 -0
- package/lib/video-recap/skills/video-cut/scripts/shot_review.py +2 -6
- package/lib/video-recap/skills/video-recap/SKILL.md +24 -27
- package/lib/video-recap/skills/video-recap/references/audio-routing.md +8 -6
- package/lib/video-recap/skills/video-recap/references/config-playbook.md +8 -11
- package/lib/video-recap/skills/video-recap/references/data-schema.md +131 -74
- package/lib/video-recap/skills/video-recap/references/resource-library.md +17 -1
- package/lib/video-recap/skills/video-recap/scripts/doctor.py +51 -268
- package/lib/video-recap/skills/video-recap/scripts/final_qc.py +89 -254
- package/lib/video-recap/skills/video-recap/scripts/lib.py +81 -70
- package/lib/video-recap/skills/video-recap/scripts/library.py +40 -4
- package/lib/video-recap/skills/video-recap/scripts/materials.py +13 -80
- package/lib/video-recap/skills/video-recap/scripts/recap_cli.py +49 -17
- package/lib/video-recap/skills/video-recap/scripts/recap_inspect.py +1 -3
- package/lib/video-recap/skills/video-recap/scripts/recap_runner.py +86 -116
- package/lib/video-recap/skills/video-recap/scripts/recap_runtime.py +87 -23
- package/lib/video-recap/skills/video-recap/scripts/recap_source.py +0 -39
- package/lib/video-recap/skills/video-recap/scripts/recap_stage_qc.py +37 -135
- package/lib/video-recap/skills/video-recap/scripts/recap_timeline.py +118 -127
- package/lib/video-recap/skills/video-recap/scripts/resources/__init__.py +4 -0
- package/lib/video-recap/skills/video-recap/scripts/{resource_lock.py → resources/lock.py} +1 -1
- package/lib/video-recap/skills/video-recap/scripts/{project_binding.py → resources/project_binding.py} +78 -17
- package/lib/video-recap/skills/video-reference/SKILL.md +108 -0
- package/lib/video-recap/skills/video-reference/references/reference-schema.md +139 -0
- package/lib/video-recap/skills/video-reference/scripts/lib.py +85 -0
- package/lib/video-recap/skills/video-reference/scripts/reference.py +96 -0
- package/lib/video-recap/skills/video-reference/scripts/reference_check.py +553 -0
- package/lib/video-recap/skills/video-reference/scripts/reference_frames.py +122 -0
- package/lib/video-recap/skills/video-reference/scripts/reference_measure.py +253 -0
- package/lib/video-recap/skills/video-reference/scripts/reference_profile.py +271 -0
- package/lib/video-recap/skills/video-script/SKILL.md +9 -7
- package/lib/video-recap/skills/video-script/scripts/agent_text.py +18 -267
- package/lib/video-recap/skills/video-script/scripts/deslop_qc.py +12 -30
- package/lib/video-recap/skills/video-script/scripts/entry_suggestion.py +125 -0
- package/lib/video-recap/skills/video-script/scripts/evidence_bundle.py +2 -26
- package/lib/video-recap/skills/video-script/scripts/lib.py +5 -15
- package/lib/video-recap/skills/video-script/scripts/lint_summary.py +184 -0
- package/lib/video-recap/skills/video-script/scripts/narration_lint.py +120 -100
- package/lib/video-recap/skills/video-script/scripts/review.py +3 -34
- package/lib/video-recap/skills/video-script/scripts/review_grounding.py +20 -46
- package/lib/video-recap/skills/video-script/scripts/review_response.py +120 -258
- package/lib/video-recap/skills/video-script/scripts/review_runner.py +2 -10
- package/lib/video-recap/skills/video-script/scripts/speech_ownership.py +89 -5
- package/lib/video-recap/skills/video-script/scripts/validate.py +26 -34
- package/lib/video-recap/skills/video-understanding/SKILL.md +29 -4
- package/lib/video-recap/skills/video-understanding/references/data-schema.md +5 -22
- package/lib/video-recap/skills/video-understanding/scripts/agent_text.py +24 -121
- package/lib/video-recap/skills/video-understanding/scripts/asr.py +11 -5
- package/lib/video-recap/skills/video-understanding/scripts/asr_timing_evidence.py +15 -24
- package/lib/video-recap/skills/video-understanding/scripts/briefing/builder.py +59 -25
- package/lib/video-recap/skills/video-understanding/scripts/briefing/context.py +39 -3
- package/lib/video-recap/skills/video-understanding/scripts/briefing/inputs.py +15 -27
- package/lib/video-recap/skills/video-understanding/scripts/briefing/timeline.py +136 -37
- package/lib/video-recap/skills/video-understanding/scripts/consolidate.py +121 -37
- package/lib/video-recap/skills/video-understanding/scripts/detect.py +118 -16
- package/lib/video-recap/skills/video-understanding/scripts/index_normalize.py +359 -0
- package/lib/video-recap/skills/video-understanding/scripts/lib.py +62 -21
- package/lib/video-recap/skills/video-understanding/scripts/storyboard.py +181 -51
- package/lib/video-recap/skills/video-understanding/scripts/timeline_fusion.py +0 -26
- package/lib/video-recap/skills/video-understanding/scripts/understanding_brief.py +37 -14
- package/lib/video-recap/skills/video-understanding/scripts/understanding_cache.py +40 -32
- package/lib/video-recap/skills/video-understanding/scripts/understanding_runner.py +100 -127
- package/lib/video-recap/skills/video-understanding/scripts/understanding_storyboard.py +182 -30
- package/lib/video-recap/skills/video-understanding/scripts/vlm.py +30 -27
- package/lib/video-recap/skills/video-voiceover/SKILL.md +19 -12
- package/lib/video-recap/skills/video-voiceover/references/index-tts.md +3 -3
- package/lib/video-recap/skills/video-voiceover/scripts/approved_text_policy.py +3 -1
- package/lib/video-recap/skills/video-voiceover/scripts/dub.py +60 -135
- package/lib/video-recap/skills/video-voiceover/scripts/lib.py +9 -34
- package/lib/video-recap/skills/video-voiceover/scripts/providers/fish_audio.py +6 -1
- package/lib/video-recap/skills/video-voiceover/scripts/tts_audio.py +64 -1
- package/lib/video-recap/skills/video-voiceover/scripts/tts_cache.py +99 -0
- package/lib/video-recap/skills/video-voiceover/scripts/voiceover.py +177 -200
- package/package.json +45 -45
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/inner_court_accord_five.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/public_day15.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/household/li_jiaoer.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/css/new-guofeng.css +0 -1494
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/assets.js +0 -147
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/data.js +0 -7470
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/engine.js +0 -13066
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/text.js +0 -32
- package/lib/novel-to-game/examples/jin-ping-mei/qa/playable-model.json +0 -99
- package/lib/novel-to-game/examples/jin-ping-mei/qa/verify_playable_model.mjs +0 -152
- package/lib/video-recap/skills/video-assemble/references/foreground-compose.md +0 -118
- package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_combination_segment.json +0 -84
- package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_combination_video_material.json +0 -98
- package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_draft.json +0 -204
- package/lib/video-recap/skills/video-assemble/references/pair-media.md +0 -74
- package/lib/video-recap/skills/video-assemble/scripts/compose_foreground.py +0 -293
- package/lib/video-recap/skills/video-assemble/scripts/pair_media.py +0 -216
- package/lib/video-recap/skills/video-recap/references/shift-left-qc-schema.md +0 -23
- package/lib/video-recap/skills/video-recap/scripts/mimo_qc.py +0 -33
- package/lib/video-recap/skills/video-recap/scripts/qc/__init__.py +0 -1
- package/lib/video-recap/skills/video-recap/scripts/qc/mimo_client.py +0 -13
- package/lib/video-recap/skills/video-recap/scripts/qc/mimo_contract.py +0 -9
- package/lib/video-recap/skills/video-recap/scripts/qc/mimo_evidence.py +0 -287
- package/lib/video-recap/skills/video-recap/scripts/qc/mimo_observations.py +0 -175
- package/lib/video-recap/skills/video-recap/scripts/qc/mimo_payload.py +0 -206
- package/lib/video-recap/skills/video-recap/scripts/qc/mimo_report.py +0 -318
- package/lib/video-recap/skills/video-recap/scripts/qc/mimo_runner.py +0 -130
- package/lib/video-recap/skills/video-recap/scripts/qc_contract.py +0 -320
- package/lib/video-recap/skills/video-script/scripts/timeline_fusion.py +0 -108
- package/lib/video-recap/skills/video-understanding/scripts/brief.py +0 -9
- package/lib/video-recap/skills/video-understanding/scripts/deslop_qc.py +0 -211
- package/lib/video-recap/skills/video-understanding/scripts/narration_lint.py +0 -707
- package/lib/video-recap/skills/video-understanding/scripts/speech_ownership.py +0 -143
- /package/lib/video-recap/skills/video-cut/scripts/{narration_mapping.py → cut_qc.py} +0 -0
package/lib/drama/manifest.json
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"upstream": {
|
|
4
4
|
"repository": "https://github.com/zenstory-ai/drama-skills.git",
|
|
5
|
-
"commit": "
|
|
5
|
+
"commit": "4e48ccbf0f77da757d7cacc6937b1cc59c124845"
|
|
6
6
|
},
|
|
7
|
-
"generatedAt": "2026-
|
|
7
|
+
"generatedAt": "2026-10-03T14:31:32.480Z",
|
|
8
8
|
"skills": [
|
|
9
9
|
"short-drama",
|
|
10
10
|
"short-drama-assets",
|
|
@@ -126,8 +126,8 @@
|
|
|
126
126
|
},
|
|
127
127
|
{
|
|
128
128
|
"path": "skills/short-drama-assets/references/voice-direction.md",
|
|
129
|
-
"sha256": "
|
|
130
|
-
"bytes":
|
|
129
|
+
"sha256": "e2dd623693632f29c56e65bb5f40a4450a9c2808dbf7680dbc91bd043c02c4b2",
|
|
130
|
+
"bytes": 10004
|
|
131
131
|
},
|
|
132
132
|
{
|
|
133
133
|
"path": "skills/short-drama-assets/scripts/asset_check.py",
|
|
@@ -321,8 +321,8 @@
|
|
|
321
321
|
},
|
|
322
322
|
{
|
|
323
323
|
"path": "skills/short-drama-edit/SKILL.md",
|
|
324
|
-
"sha256": "
|
|
325
|
-
"bytes":
|
|
324
|
+
"sha256": "3a8c8e52f6d648cff82aedc783ef7aee65594ecb089cfc8da62c44b1f3227bb0",
|
|
325
|
+
"bytes": 15232
|
|
326
326
|
},
|
|
327
327
|
{
|
|
328
328
|
"path": "skills/short-drama-edit/agents/openai.yaml",
|
|
@@ -449,15 +449,20 @@
|
|
|
449
449
|
"sha256": "d6edfc8a528f6079894109ec0c650fb411b7ee04f3b4b5462c71564db9304219",
|
|
450
450
|
"bytes": 5839
|
|
451
451
|
},
|
|
452
|
+
{
|
|
453
|
+
"path": "skills/short-drama-edit/references/still-cuts.md",
|
|
454
|
+
"sha256": "7afa3517e1c257b457a2bf83b3f8c2b93d3b5e84dcccb856c482c90de292d9a3",
|
|
455
|
+
"bytes": 5860
|
|
456
|
+
},
|
|
452
457
|
{
|
|
453
458
|
"path": "skills/short-drama-edit/scripts/edit_tool.py",
|
|
454
|
-
"sha256": "
|
|
455
|
-
"bytes":
|
|
459
|
+
"sha256": "b9b7d5b7f47c5e2cddc3b42fd8a77973b25b7111540a5a80cccd22825ad048ef",
|
|
460
|
+
"bytes": 130747
|
|
456
461
|
},
|
|
457
462
|
{
|
|
458
463
|
"path": "skills/short-drama-edit/scripts/selftest.py",
|
|
459
|
-
"sha256": "
|
|
460
|
-
"bytes":
|
|
464
|
+
"sha256": "308760a10f0031cace5a47bdf3f1107a51bdca1e0cce4cee71466ad887ae2825",
|
|
465
|
+
"bytes": 26399
|
|
461
466
|
},
|
|
462
467
|
{
|
|
463
468
|
"path": "skills/short-drama-image-prompts/SKILL.md",
|
|
@@ -606,8 +611,8 @@
|
|
|
606
611
|
},
|
|
607
612
|
{
|
|
608
613
|
"path": "skills/short-drama-produce/SKILL.md",
|
|
609
|
-
"sha256": "
|
|
610
|
-
"bytes":
|
|
614
|
+
"sha256": "25ba8c4294806abe34c9c62477d8dc8c9e1e86ab210ce54c354959904f9ff760",
|
|
615
|
+
"bytes": 15124
|
|
611
616
|
},
|
|
612
617
|
{
|
|
613
618
|
"path": "skills/short-drama-produce/agents/openai.yaml",
|
|
@@ -616,8 +621,8 @@
|
|
|
616
621
|
},
|
|
617
622
|
{
|
|
618
623
|
"path": "skills/short-drama-produce/references/adapter-contract.md",
|
|
619
|
-
"sha256": "
|
|
620
|
-
"bytes":
|
|
624
|
+
"sha256": "b0b43959521f88f2ffc18a8d17507b2033d37dddc4ed1876804cac4a334b2699",
|
|
625
|
+
"bytes": 12009
|
|
621
626
|
},
|
|
622
627
|
{
|
|
623
628
|
"path": "skills/short-drama-produce/references/providers/gpt-image-2.md",
|
|
@@ -651,13 +656,13 @@
|
|
|
651
656
|
},
|
|
652
657
|
{
|
|
653
658
|
"path": "skills/short-drama-produce/scripts/production_tool.py",
|
|
654
|
-
"sha256": "
|
|
655
|
-
"bytes":
|
|
659
|
+
"sha256": "1f9e8e1e4f460a68736bb142280563df8ccaa06d1d97bac6be5e9f83aa27e3d6",
|
|
660
|
+
"bytes": 93209
|
|
656
661
|
},
|
|
657
662
|
{
|
|
658
663
|
"path": "skills/short-drama-produce/scripts/provider_adapters.py",
|
|
659
|
-
"sha256": "
|
|
660
|
-
"bytes":
|
|
664
|
+
"sha256": "9555f41da83e3666324dafedaf271a0205ed924bd956424c037e71419d7c067b",
|
|
665
|
+
"bytes": 70718
|
|
661
666
|
},
|
|
662
667
|
{
|
|
663
668
|
"path": "skills/short-drama-produce/scripts/selftest.py",
|
|
@@ -756,8 +761,8 @@
|
|
|
756
761
|
},
|
|
757
762
|
{
|
|
758
763
|
"path": "skills/short-drama-storyboard/SKILL.md",
|
|
759
|
-
"sha256": "
|
|
760
|
-
"bytes":
|
|
764
|
+
"sha256": "a603d2254e91d6dc323f69fd137620fdfdc36b1ff738f3ef292ba9c3f4a8b4f5",
|
|
765
|
+
"bytes": 19365
|
|
761
766
|
},
|
|
762
767
|
{
|
|
763
768
|
"path": "skills/short-drama-storyboard/agents/openai.yaml",
|
|
@@ -826,8 +831,8 @@
|
|
|
826
831
|
},
|
|
827
832
|
{
|
|
828
833
|
"path": "skills/short-drama-storyboard/references/production-shot-grammar.md",
|
|
829
|
-
"sha256": "
|
|
830
|
-
"bytes":
|
|
834
|
+
"sha256": "dc3f1d1ee10d9783a493a1f02b0029ee145bcc6d42457eed97f3a3e195327bfe",
|
|
835
|
+
"bytes": 21788
|
|
831
836
|
},
|
|
832
837
|
{
|
|
833
838
|
"path": "skills/short-drama-storyboard/references/review-and-fixtures.md",
|
|
@@ -871,8 +876,8 @@
|
|
|
871
876
|
},
|
|
872
877
|
{
|
|
873
878
|
"path": "skills/short-drama-video-prompts/SKILL.md",
|
|
874
|
-
"sha256": "
|
|
875
|
-
"bytes":
|
|
879
|
+
"sha256": "a542f15744384e86d63cf4e07431fdcbbe741df3c8b3fa3984885b31185dbdf7",
|
|
880
|
+
"bytes": 16930
|
|
876
881
|
},
|
|
877
882
|
{
|
|
878
883
|
"path": "skills/short-drama-video-prompts/agents/openai.yaml",
|
|
@@ -946,8 +951,8 @@
|
|
|
946
951
|
},
|
|
947
952
|
{
|
|
948
953
|
"path": "skills/short-drama-video-prompts/references/performance-action-timing.md",
|
|
949
|
-
"sha256": "
|
|
950
|
-
"bytes":
|
|
954
|
+
"sha256": "24cbb164e5a7983c0146ff65907ed6c0ff15d12df830e03c9472fcc474dae8a9",
|
|
955
|
+
"bytes": 13916
|
|
951
956
|
},
|
|
952
957
|
{
|
|
953
958
|
"path": "skills/short-drama-video-prompts/references/production-prompt-grammar.md",
|
|
@@ -966,23 +971,23 @@
|
|
|
966
971
|
},
|
|
967
972
|
{
|
|
968
973
|
"path": "skills/short-drama-video-prompts/references/seedance-2.5.md",
|
|
969
|
-
"sha256": "
|
|
970
|
-
"bytes":
|
|
974
|
+
"sha256": "32f1b81fe4636fc020011aec0f29255a236b5363126b1fc0af3c451f23b27d88",
|
|
975
|
+
"bytes": 9922
|
|
971
976
|
},
|
|
972
977
|
{
|
|
973
978
|
"path": "skills/short-drama-video-prompts/references/stage-contract.md",
|
|
974
|
-
"sha256": "
|
|
975
|
-
"bytes":
|
|
979
|
+
"sha256": "5d299f955e97cb9b5779a257372c3d8cc3b66a60bd181895dfd526dc12a428b6",
|
|
980
|
+
"bytes": 12263
|
|
976
981
|
},
|
|
977
982
|
{
|
|
978
983
|
"path": "skills/short-drama-video-prompts/references/target-model-profile.md",
|
|
979
|
-
"sha256": "
|
|
980
|
-
"bytes":
|
|
984
|
+
"sha256": "a78fcf795de91f248652acf55cfbceb4aedfe930da60a0a273d40f620fa11c58",
|
|
985
|
+
"bytes": 10582
|
|
981
986
|
},
|
|
982
987
|
{
|
|
983
988
|
"path": "skills/short-drama-video-prompts/references/wan-3.0.md",
|
|
984
|
-
"sha256": "
|
|
985
|
-
"bytes":
|
|
989
|
+
"sha256": "a21375b61496b0506e220bd3a52a3138142852d11ef793550f0e1d0f24271339",
|
|
990
|
+
"bytes": 7120
|
|
986
991
|
},
|
|
987
992
|
{
|
|
988
993
|
"path": "skills/short-drama-video-prompts/scripts/container_check.py",
|
|
@@ -1211,8 +1216,8 @@
|
|
|
1211
1216
|
},
|
|
1212
1217
|
{
|
|
1213
1218
|
"path": "skills/short-drama/scripts/creator_markdown_check.py",
|
|
1214
|
-
"sha256": "
|
|
1215
|
-
"bytes":
|
|
1219
|
+
"sha256": "c09f9f510f0c8f18be8cae1bd00efcb35cc132c6a04606b53d686fee7fa4c632",
|
|
1220
|
+
"bytes": 76402
|
|
1216
1221
|
},
|
|
1217
1222
|
{
|
|
1218
1223
|
"path": "skills/short-drama/scripts/project_tool.py",
|
|
@@ -65,6 +65,58 @@ REF_PURPOSES = (
|
|
|
65
65
|
"结束帧",
|
|
66
66
|
"风格",
|
|
67
67
|
)
|
|
68
|
+
# A character's voice travels on its own line, `- 参考音频:`, never inside
|
|
69
|
+
# 输入参考图: the picture slots number `@图片N`/`<Picture N>`/`图N` and the audio
|
|
70
|
+
# slots number `@音频N`/`<Audio N>`/`音频N`, each from 1, so mixing them in one
|
|
71
|
+
# field would renumber every picture after the first voice.
|
|
72
|
+
AUDIO_SUFFIXES = ("wav", "mp3", "m4a", "aac", "flac")
|
|
73
|
+
AUDIO_REF_RE = re.compile(
|
|
74
|
+
r"(REF-[A-Z0-9-]+)(顺序:([1-9]\d*))· "
|
|
75
|
+
r"([^;\n]+?\.(?:" + "|".join(AUDIO_SUFFIXES) + r"))《([^》\n]+)》"
|
|
76
|
+
r"(用途:([^;)\n]+);角色:([^;)\n]+);控制:([^;)]+);不得控制:([^)]+))",
|
|
77
|
+
re.IGNORECASE,
|
|
78
|
+
)
|
|
79
|
+
AUDIO_PURPOSE = "音色"
|
|
80
|
+
AUDIO_SYNTAX = (
|
|
81
|
+
"REF-<槽位>(顺序:<n>)· <音频路径>《<中文名>》"
|
|
82
|
+
"(用途:音色;角色:<人物名>;控制:<范围>;不得控制:<范围>)"
|
|
83
|
+
)
|
|
84
|
+
# An audio file or 用途:音色 in a picture field is a voice binding written in
|
|
85
|
+
# the wrong place; the generic "broken REF syntax" would not say where it goes.
|
|
86
|
+
AUDIO_IN_IMAGE_FIELD_RE = re.compile(
|
|
87
|
+
r"\.(?:" + "|".join(AUDIO_SUFFIXES) + r"|opus)《|用途:音色", re.IGNORECASE
|
|
88
|
+
)
|
|
89
|
+
# Where 《视觉设定.md》 records a character's accepted voice reference: one
|
|
90
|
+
# line under that 人物 entry, path first.
|
|
91
|
+
VOICE_RECORD_RE = re.compile(
|
|
92
|
+
r"^[ \t ]*[-*+][ \t ]*声音参考[::](.*)$", re.MULTILINE
|
|
93
|
+
)
|
|
94
|
+
# Same filename rule as the binding's locator (`[^;\n]+?`), so any path a
|
|
95
|
+
# 参考音频 slot may name can also be the recorded one -- spaces included.
|
|
96
|
+
VOICE_RECORD_PATH_RE = re.compile(
|
|
97
|
+
r"([^(;\n]+?\.(?:" + "|".join(AUDIO_SUFFIXES) + r"))(?=(|$)", re.IGNORECASE
|
|
98
|
+
)
|
|
99
|
+
# The screenplay's dialogue grammar, mirrored from the write skill's
|
|
100
|
+
# screenplay_index.py (DIALOGUE_RE, and TAG_RE + VOICE_TAG_BODY_RE for [VO] and
|
|
101
|
+
# [OS]). Each is matched against a whole paragraph, so a line that wraps onto
|
|
102
|
+
# the next physical line is still one line of dialogue.
|
|
103
|
+
SCREENPLAY_DIALOGUE_RE = re.compile(
|
|
104
|
+
r"^(?P<speaker>[^\s:():\[\]#]{1,40})"
|
|
105
|
+
r"(?:([^()\r\n]+))?:(?P<text>\S[\s\S]*)$"
|
|
106
|
+
)
|
|
107
|
+
SCREENPLAY_VOICE_TAG_RE = re.compile(
|
|
108
|
+
r"^\[(?:VO|OS)\]\s*(?P<speaker>[^\s:():\[\]#]{1,40}):(?P<text>\S[\s\S]*)$"
|
|
109
|
+
)
|
|
110
|
+
# A later line of a paragraph that starts another block (the index's
|
|
111
|
+
# ASCII_DIALOGUE_RE, ANY_TAG_RE and MALFORMED_TAG_RE): the paragraph is missing
|
|
112
|
+
# a separator and the index emits no dialogue for it.
|
|
113
|
+
SCREENPLAY_BLOCK_START_RE = re.compile(
|
|
114
|
+
r"^[^\s:():\[\]#]{1,40}(?:([^()\r\n]+))?:\s*\S"
|
|
115
|
+
r"|^\[[^\]\r\n]+\]"
|
|
116
|
+
r"|^\[(?:VO|OS|SFX|画面文字|连续性|转场)(?:\s|:|:)"
|
|
117
|
+
)
|
|
118
|
+
# Where the clause that introduces a quote begins.
|
|
119
|
+
CLAUSE_BREAK_RE = re.compile(r"[。!?;.!?;]")
|
|
68
120
|
# `EP001-SC001` is the documented shape, but a project that scopes ids by season
|
|
69
121
|
# writes `S01-EP001-SC001`. Both are one stable scene id, so the pattern takes
|
|
70
122
|
# any hyphenated prefix rather than exactly one segment.
|
|
@@ -334,6 +386,12 @@ def _references(
|
|
|
334
386
|
"""Validate one 输入参考图/参考 declaration; return the slot kinds it uses."""
|
|
335
387
|
if _is_none(value):
|
|
336
388
|
return frozenset()
|
|
389
|
+
if AUDIO_IN_IMAGE_FIELD_RE.search(value):
|
|
390
|
+
errors.append(
|
|
391
|
+
f"{owner}: 输入参考图只收图片;角色音色参考写在《视频提示词.md》"
|
|
392
|
+
"该 MOTION 的「- 参考音频:」行"
|
|
393
|
+
)
|
|
394
|
+
return frozenset()
|
|
337
395
|
reference_value = PENDING_REFERENCE_SUFFIX_RE.sub("", value.strip())
|
|
338
396
|
matches = _slot_matches(reference_value)
|
|
339
397
|
cursor = 0
|
|
@@ -405,16 +463,7 @@ def _references(
|
|
|
405
463
|
slot, raw_locator, label = group[0], group[2], group[3]
|
|
406
464
|
may_control, must_not_control = group[5], group[6]
|
|
407
465
|
if kind == "REF":
|
|
408
|
-
|
|
409
|
-
errors.append(
|
|
410
|
-
f"{owner}: REF 路径不是安全的项目相对路径: {raw_locator}"
|
|
411
|
-
)
|
|
412
|
-
else:
|
|
413
|
-
reference_path = project_root / raw_locator
|
|
414
|
-
if not _inside(reference_path, project_root):
|
|
415
|
-
errors.append(f"{owner}: REF 路径越出项目根目录: {raw_locator}")
|
|
416
|
-
elif not reference_path.is_file():
|
|
417
|
-
errors.append(f"{owner}: REF 文件不存在: {raw_locator}")
|
|
466
|
+
_check_project_file(raw_locator, owner, project_root, errors)
|
|
418
467
|
else:
|
|
419
468
|
_plan_locator(
|
|
420
469
|
raw_locator, label, owner, slot, purposes[index], entries, errors
|
|
@@ -430,6 +479,253 @@ def _references(
|
|
|
430
479
|
return kinds
|
|
431
480
|
|
|
432
481
|
|
|
482
|
+
def _check_project_file(
|
|
483
|
+
locator: str, owner: str, project_root: Path, errors: list[str]
|
|
484
|
+
) -> None:
|
|
485
|
+
"""A REF locator names a readable file inside the project, or it is a defect."""
|
|
486
|
+
if not _portable_path(locator):
|
|
487
|
+
errors.append(f"{owner}: REF 路径不是安全的项目相对路径: {locator}")
|
|
488
|
+
return
|
|
489
|
+
reference_path = project_root / locator
|
|
490
|
+
if not _inside(reference_path, project_root):
|
|
491
|
+
errors.append(f"{owner}: REF 路径越出项目根目录: {locator}")
|
|
492
|
+
elif not reference_path.is_file():
|
|
493
|
+
errors.append(f"{owner}: REF 文件不存在: {locator}")
|
|
494
|
+
|
|
495
|
+
|
|
496
|
+
def _han_key(value: str) -> str:
|
|
497
|
+
"""Only the Han characters, so quoting and language tags do not decide identity."""
|
|
498
|
+
return "".join(re.findall(r"[\u3400-\u9fff]+", value))
|
|
499
|
+
|
|
500
|
+
|
|
501
|
+
class SpokenLine(NamedTuple):
|
|
502
|
+
"""One dialogue paragraph of 剧本.md: its scene, speaker and Han text."""
|
|
503
|
+
|
|
504
|
+
scene: Optional[str]
|
|
505
|
+
speaker: str
|
|
506
|
+
key: str
|
|
507
|
+
|
|
508
|
+
|
|
509
|
+
def _screenplay_dialogue(screenplay: str) -> list[SpokenLine]:
|
|
510
|
+
"""Every dialogue paragraph, read the way screenplay_index.py reads it.
|
|
511
|
+
|
|
512
|
+
Mirrors `_parse_screenplay` block by block: a comment runs through the line
|
|
513
|
+
that closes it, and an unclosed one ends the parse; a heading sets or clears
|
|
514
|
+
the scene (scene ids use this checker's own SCENE_HEADING_RE, the one
|
|
515
|
+
来源 resolves against); a paragraph runs until a blank line, a heading or a comment and
|
|
516
|
+
counts only inside a scene; a later line that starts another block means a
|
|
517
|
+
missing separator, so the paragraph is not dialogue. The one difference is
|
|
518
|
+
the speaker list: the index takes it from the project, and here a speaker
|
|
519
|
+
is only ever looked up by 人物 entry name.
|
|
520
|
+
"""
|
|
521
|
+
found: list[SpokenLine] = []
|
|
522
|
+
scene: Optional[str] = None
|
|
523
|
+
lines = screenplay.splitlines()
|
|
524
|
+
index = 0
|
|
525
|
+
while index < len(lines):
|
|
526
|
+
stripped = lines[index].strip()
|
|
527
|
+
if not stripped:
|
|
528
|
+
index += 1
|
|
529
|
+
continue
|
|
530
|
+
if stripped.startswith("<!--"):
|
|
531
|
+
end = index
|
|
532
|
+
while end < len(lines) and "-->" not in lines[end]:
|
|
533
|
+
end += 1
|
|
534
|
+
if end >= len(lines):
|
|
535
|
+
break
|
|
536
|
+
index = end + 1
|
|
537
|
+
continue
|
|
538
|
+
if stripped.startswith("#"):
|
|
539
|
+
heading = SCENE_HEADING_RE.match(stripped)
|
|
540
|
+
scene = heading.group(1) if heading else None
|
|
541
|
+
index += 1
|
|
542
|
+
continue
|
|
543
|
+
end = index
|
|
544
|
+
while end + 1 < len(lines):
|
|
545
|
+
following = lines[end + 1].strip()
|
|
546
|
+
if not following or following.startswith(("#", "<!--")):
|
|
547
|
+
break
|
|
548
|
+
end += 1
|
|
549
|
+
paragraph = [line.strip() for line in lines[index : end + 1]]
|
|
550
|
+
index = end + 1
|
|
551
|
+
if scene is None or any(
|
|
552
|
+
SCREENPLAY_DIALOGUE_RE.fullmatch(line) or SCREENPLAY_BLOCK_START_RE.match(line)
|
|
553
|
+
for line in paragraph[1:]
|
|
554
|
+
):
|
|
555
|
+
continue
|
|
556
|
+
text = "\n".join(paragraph)
|
|
557
|
+
match = SCREENPLAY_VOICE_TAG_RE.fullmatch(text) or SCREENPLAY_DIALOGUE_RE.fullmatch(
|
|
558
|
+
text
|
|
559
|
+
)
|
|
560
|
+
if match is not None and _han_key(match.group("text")):
|
|
561
|
+
found.append(
|
|
562
|
+
SpokenLine(scene, match.group("speaker"), _han_key(match.group("text")))
|
|
563
|
+
)
|
|
564
|
+
return found
|
|
565
|
+
|
|
566
|
+
|
|
567
|
+
def _quote_speakers(
|
|
568
|
+
prompt: Optional[str], lines: list[SpokenLine], names: dict[str, list[str]]
|
|
569
|
+
) -> list[tuple[str, frozenset]]:
|
|
570
|
+
"""Each quote in the copyable body with the speakers it can belong to.
|
|
571
|
+
|
|
572
|
+
Dialogue reaches the body quoted and verbatim (VID-25), so the screenplay
|
|
573
|
+
says whose line it is. A short quote such as 「是。」 has to match a whole
|
|
574
|
+
line, or any one-character quote would count as anybody's. When two
|
|
575
|
+
speakers share the line, the clause that introduces the quote decides it
|
|
576
|
+
only if it names exactly one of them; otherwise the quote stays ambiguous.
|
|
577
|
+
"""
|
|
578
|
+
if prompt is None:
|
|
579
|
+
return []
|
|
580
|
+
joined = prompt.replace("\n", "")
|
|
581
|
+
result: list[tuple[str, frozenset]] = []
|
|
582
|
+
previous_end = 0
|
|
583
|
+
for match in SPOKEN_SPAN_RE.finditer(joined):
|
|
584
|
+
span = next((group for group in match.groups() if group), "")
|
|
585
|
+
key = _han_key(span) if len(span) <= 200 else ""
|
|
586
|
+
candidates = frozenset(
|
|
587
|
+
line.speaker
|
|
588
|
+
for line in lines
|
|
589
|
+
if key and (key == line.key or (len(key) >= 4 and key in line.key))
|
|
590
|
+
)
|
|
591
|
+
if len(candidates) > 1:
|
|
592
|
+
lead = joined[previous_end : match.start()]
|
|
593
|
+
breaks = list(CLAUSE_BREAK_RE.finditer(lead))
|
|
594
|
+
clause = lead[breaks[-1].end() :] if breaks else lead
|
|
595
|
+
named = frozenset(
|
|
596
|
+
speaker
|
|
597
|
+
for speaker in candidates
|
|
598
|
+
if any(name in clause for name in names.get(speaker, [speaker]))
|
|
599
|
+
)
|
|
600
|
+
if len(named) == 1:
|
|
601
|
+
candidates = named
|
|
602
|
+
if candidates:
|
|
603
|
+
result.append((span, candidates))
|
|
604
|
+
previous_end = match.end()
|
|
605
|
+
return result
|
|
606
|
+
|
|
607
|
+
|
|
608
|
+
def _voice_records(visual: str, errors: list[str]) -> dict[str, str]:
|
|
609
|
+
"""Each 人物 entry's `- 声音参考:` value, keyed by the entry name."""
|
|
610
|
+
headings = list(re.finditer(r"^## [^\n]*$", visual, re.MULTILINE))
|
|
611
|
+
records: dict[str, str] = {}
|
|
612
|
+
for index, heading in enumerate(headings):
|
|
613
|
+
entry = VISUAL_SETTING_HEADING_RE.match(heading.group(0))
|
|
614
|
+
if entry is None or entry.group(1) != "人物":
|
|
615
|
+
continue
|
|
616
|
+
end = headings[index + 1].start() if index + 1 < len(headings) else None
|
|
617
|
+
declared = VOICE_RECORD_RE.findall(visual[heading.end() : end])
|
|
618
|
+
if len(declared) > 1:
|
|
619
|
+
errors.append(f"视觉设定.md: 人物「{entry.group(2).strip()}」的声音参考重复,只能登记一条")
|
|
620
|
+
elif declared:
|
|
621
|
+
records[entry.group(2).strip()] = _plain(declared[0])
|
|
622
|
+
return records
|
|
623
|
+
|
|
624
|
+
|
|
625
|
+
class VoiceContext(NamedTuple):
|
|
626
|
+
"""What a MOTION's 参考音频 line is checked against."""
|
|
627
|
+
|
|
628
|
+
characters: frozenset
|
|
629
|
+
records: dict[str, str]
|
|
630
|
+
dialogue: list[SpokenLine]
|
|
631
|
+
# Every name a clause may use for a 人物 entry: its name and 画面代称.
|
|
632
|
+
names: dict[str, list[str]]
|
|
633
|
+
|
|
634
|
+
|
|
635
|
+
def _audio_references(
|
|
636
|
+
value: str,
|
|
637
|
+
owner: str,
|
|
638
|
+
project_root: Path,
|
|
639
|
+
errors: list[str],
|
|
640
|
+
*,
|
|
641
|
+
image_slots: list[str],
|
|
642
|
+
prompt: Optional[str],
|
|
643
|
+
voices: VoiceContext,
|
|
644
|
+
scenes: list[str],
|
|
645
|
+
) -> None:
|
|
646
|
+
"""Validate one MOTION's 参考音频 line against the character it names."""
|
|
647
|
+
if _is_none(value):
|
|
648
|
+
return
|
|
649
|
+
# A line said in another scene is not evidence for this shot.
|
|
650
|
+
in_scope = [line for line in voices.dialogue if not scenes or line.scene in scenes]
|
|
651
|
+
quotes = _quote_speakers(prompt, in_scope, voices.names)
|
|
652
|
+
plain = value.strip()
|
|
653
|
+
matches = list(AUDIO_REF_RE.finditer(plain))
|
|
654
|
+
cursor = 0
|
|
655
|
+
for index, match in enumerate(matches):
|
|
656
|
+
if plain[cursor : match.start()] != ("" if index == 0 else ";"):
|
|
657
|
+
matches = []
|
|
658
|
+
break
|
|
659
|
+
cursor = match.end()
|
|
660
|
+
if not matches or plain[cursor:] not in {"", "。"}:
|
|
661
|
+
errors.append(f"{owner}: 参考音频必须写成 {AUDIO_SYNTAX},多条用;连接")
|
|
662
|
+
return
|
|
663
|
+
slots = [match.group(1) for match in matches]
|
|
664
|
+
orders = sorted(int(match.group(2)) for match in matches)
|
|
665
|
+
if len(set(slots)) != len(slots) or set(slots) & set(image_slots):
|
|
666
|
+
errors.append(f"{owner}: 参考音频槽位重复,或与输入参考图的槽位重名")
|
|
667
|
+
if orders != list(range(1, len(orders) + 1)):
|
|
668
|
+
errors.append(f"{owner}: 参考音频顺序必须唯一且从 1 连续编号,与图片分开数")
|
|
669
|
+
speakers = [match.group(6).strip() for match in matches]
|
|
670
|
+
for name in sorted({name for name in speakers if speakers.count(name) > 1}):
|
|
671
|
+
errors.append(f"{owner}: 人物「{name}」绑定了不止一条参考音频")
|
|
672
|
+
for match in matches:
|
|
673
|
+
slot, _, locator, label, purpose, _, may, must = match.groups()
|
|
674
|
+
name = match.group(6).strip()
|
|
675
|
+
_check_project_file(locator, owner, project_root, errors)
|
|
676
|
+
if purpose.strip() != AUDIO_PURPOSE:
|
|
677
|
+
errors.append(
|
|
678
|
+
f"{owner}: 参考音频用途只能是{AUDIO_PURPOSE}: {slot}({purpose.strip()})"
|
|
679
|
+
)
|
|
680
|
+
if not re.search(r"[\u4e00-\u9fff]", label):
|
|
681
|
+
errors.append(f"{owner}: REF 缺少中文名称: {slot}")
|
|
682
|
+
allowed = {item.strip() for item in re.split(r"[、,,]", may)}
|
|
683
|
+
prohibited = {item.strip() for item in re.split(r"[、,,]", must)}
|
|
684
|
+
if "" in allowed or "" in prohibited or allowed & prohibited:
|
|
685
|
+
errors.append(f"{owner}: REF 控制与不得控制范围冲突: {slot}")
|
|
686
|
+
if not {"台词", "语气", "情绪"} <= prohibited:
|
|
687
|
+
errors.append(f"{owner}: 参考音频不得控制至少包含台词、语气、情绪: {slot}")
|
|
688
|
+
if name not in voices.characters:
|
|
689
|
+
errors.append(
|
|
690
|
+
f"{owner}: 参考音频的角色不是《视觉设定.md》里的人物条目: {name}"
|
|
691
|
+
)
|
|
692
|
+
continue
|
|
693
|
+
shared = [span for span, speakers in quotes if name in speakers]
|
|
694
|
+
if any(speakers == {name} for _, speakers in quotes):
|
|
695
|
+
pass
|
|
696
|
+
elif shared:
|
|
697
|
+
others = sorted(
|
|
698
|
+
{s for _, speakers in quotes if name in speakers for s in speakers} - {name}
|
|
699
|
+
)
|
|
700
|
+
errors.append(
|
|
701
|
+
f"{owner}: 引文「{_excerpt(shared[0], 24)}」在《剧本.md》里也是"
|
|
702
|
+
f"{'、'.join(others)}的台词,看不出是不是人物「{name}」在说;"
|
|
703
|
+
"在引文前的分句里点名说话人,或引一句只属于他的台词"
|
|
704
|
+
)
|
|
705
|
+
else:
|
|
706
|
+
errors.append(
|
|
707
|
+
f"{owner}: 人物「{name}」在本镜可复制提示词里没有说出《剧本.md》记在其名下的台词;"
|
|
708
|
+
"不说话的人物不绑定参考音频"
|
|
709
|
+
)
|
|
710
|
+
record = voices.records.get(name)
|
|
711
|
+
recorded = VOICE_RECORD_PATH_RE.match(record.strip()) if record else None
|
|
712
|
+
if record is None:
|
|
713
|
+
errors.append(
|
|
714
|
+
f"{owner}: 《视觉设定.md》人物「{name}」没有声音参考行;"
|
|
715
|
+
"先在该人物条目下写「- 声音参考:<项目相对路径>(…)」"
|
|
716
|
+
)
|
|
717
|
+
elif recorded is None:
|
|
718
|
+
errors.append(
|
|
719
|
+
f"{owner}: 《视觉设定.md》人物「{name}」的声音参考还没有音频路径: "
|
|
720
|
+
f"{_excerpt(record, 24)}"
|
|
721
|
+
)
|
|
722
|
+
elif recorded.group(1) != locator:
|
|
723
|
+
errors.append(
|
|
724
|
+
f"{owner}: 参考音频 {slot} 的路径 {locator} 与《视觉设定.md》"
|
|
725
|
+
f"人物「{name}」登记的声音参考 {recorded.group(1)} 不一致"
|
|
726
|
+
)
|
|
727
|
+
|
|
728
|
+
|
|
433
729
|
def _plan_locator(
|
|
434
730
|
locator: str,
|
|
435
731
|
label: str,
|
|
@@ -1121,6 +1417,18 @@ def validate_episode(episode: Path, project_root: Optional[Path] = None) -> list
|
|
|
1121
1417
|
claimed_scenes: set = set()
|
|
1122
1418
|
locks = _continuity_locks(visual, errors)
|
|
1123
1419
|
visual_entries = _visual_entries(visual, errors)
|
|
1420
|
+
voices = VoiceContext(
|
|
1421
|
+
characters=frozenset(
|
|
1422
|
+
entry.name for entry in visual_entries if entry.category == "人物"
|
|
1423
|
+
),
|
|
1424
|
+
records=_voice_records(visual, errors),
|
|
1425
|
+
dialogue=_screenplay_dialogue(screenplay),
|
|
1426
|
+
names={
|
|
1427
|
+
entry.name: _unique([entry.name, *entry.designators])
|
|
1428
|
+
for entry in visual_entries
|
|
1429
|
+
if entry.category == "人物"
|
|
1430
|
+
},
|
|
1431
|
+
)
|
|
1124
1432
|
other_headings = {
|
|
1125
1433
|
match.group(2).strip(): match.group(1).strip()
|
|
1126
1434
|
for match in OTHER_SETTING_HEADING_RE.finditer(visual)
|
|
@@ -1226,9 +1534,8 @@ def validate_episode(episode: Path, project_root: Optional[Path] = None) -> list
|
|
|
1226
1534
|
for shot_id, shot_body in shots.items():
|
|
1227
1535
|
fields = _fields(shot_body, owner=shot_id, errors=errors)
|
|
1228
1536
|
source_value = fields.get("来源", "")
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
)
|
|
1537
|
+
shot_scenes = _shot_sources(source_value, shot_id, scenes, errors)
|
|
1538
|
+
claimed_scenes.update(shot_scenes)
|
|
1232
1539
|
shot_seconds = _declared_seconds(_plain(fields.get("时长", "")))
|
|
1233
1540
|
motion_seconds = _declared_seconds(motion_duration.get(shot_id, ""))
|
|
1234
1541
|
if (
|
|
@@ -1262,6 +1569,10 @@ def validate_episode(episode: Path, project_root: Optional[Path] = None) -> list
|
|
|
1262
1569
|
shot_input = fields.get("输入参考图", "")
|
|
1263
1570
|
if not shot_input:
|
|
1264
1571
|
errors.append(f"{shot_id}: 缺少输入参考图字段")
|
|
1572
|
+
if "参考音频" in fields:
|
|
1573
|
+
errors.append(
|
|
1574
|
+
f"{shot_id}: 参考音频写在《视频提示词.md》的 MOTION 条目里,分镜不登记"
|
|
1575
|
+
)
|
|
1265
1576
|
_references(shot_input, shot_id, project_root, errors, entries)
|
|
1266
1577
|
|
|
1267
1578
|
basis_value = fields.get("视觉依据", "")
|
|
@@ -1328,6 +1639,22 @@ def validate_episode(episode: Path, project_root: Optional[Path] = None) -> list
|
|
|
1328
1639
|
_references(motion_input, motion_id, project_root, errors, entries)
|
|
1329
1640
|
if _plain(motion_input) != _plain(shot_input):
|
|
1330
1641
|
errors.append(f"{motion_id}: 输入参考图与 {shot_id} 不一致")
|
|
1642
|
+
if "参考音频" in motion_fields:
|
|
1643
|
+
_audio_references(
|
|
1644
|
+
motion_fields["参考音频"],
|
|
1645
|
+
motion_id,
|
|
1646
|
+
project_root,
|
|
1647
|
+
errors,
|
|
1648
|
+
image_slots=[
|
|
1649
|
+
item[3][0]
|
|
1650
|
+
for item in _slot_matches(
|
|
1651
|
+
PENDING_REFERENCE_SUFFIX_RE.sub("", motion_input.strip())
|
|
1652
|
+
)
|
|
1653
|
+
],
|
|
1654
|
+
prompt=copyable_prompt,
|
|
1655
|
+
voices=voices,
|
|
1656
|
+
scenes=shot_scenes,
|
|
1657
|
+
)
|
|
1331
1658
|
|
|
1332
1659
|
if _has_pending_references(shot_input):
|
|
1333
1660
|
errors.append(f"{motion_id}: 仍有待补参考图,不能生成最终视频提示词")
|
|
@@ -165,7 +165,8 @@ CHAR-A 与 CHAR-B 最易混:两条参考同为中低音区、语速偏慢。
|
|
|
165
165
|
|
|
166
166
|
需要跨集保持声音身份时,在对应人物条目下增加“声音方向”,只保留:
|
|
167
167
|
|
|
168
|
-
1.
|
|
168
|
+
1. **参考**:一行 `- 声音参考:<项目相对路径>(用途:…;控制:…;不得控制:…;状态:<admission_status>)`,
|
|
169
|
+
路径打头;没有参考时写 `- 声音参考:待选型`;
|
|
169
170
|
2. **选型判据**:三到五条可听判据,每条带反例;
|
|
170
171
|
3. **对比角色**:最容易混淆的是谁,以及靠什么可听差异区分;
|
|
171
172
|
4. **专名发音**:唯一确认读法;
|
|
@@ -174,6 +175,7 @@ CHAR-A 与 CHAR-B 最易混:两条参考同为中低音区、语速偏慢。
|
|
|
174
175
|
临时情绪、呼吸、重音、音量和空间混响仍由剧本/镜头/视频提示词拥有。声音方向是生产输入,不是
|
|
175
176
|
合成结果保证;实际 TTS 仍由 `$short-drama-produce` 在精确预览并显式确认后执行。
|
|
176
177
|
|
|
177
|
-
|
|
178
|
+
参考音频落地之后,它在下游有两个去处:作为视频模型的音色参考绑定(《视频提示词.md》该镜的
|
|
179
|
+
`参考音频` 行照抄这条路径,由 `$short-drama-video-prompts` 写),
|
|
178
180
|
以及作为配音本的实际人声。配音使用同一已选音色逐句合成;参考音频用于选型或模型绑定,不能直接充当不同台词的成品音轨。
|
|
179
181
|
是否保持角色声音,仍需在生成后回听确认。
|