@lemoncat7/dsh-partner 2.5.0 → 2.7.1
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 +4 -0
- package/docs/THIRD_PARTY_LICENSES.md +13 -0
- package/docs/attachment-storage.md +18 -0
- package/docs/director-skills.md +24 -0
- package/docs/long-task-continuation.md +1 -1
- package/docs/releases/2.6.0.md +11 -0
- package/docs/releases/2.7.1.md +10 -0
- package/lib/agent-runtime.d.ts +3 -1
- package/lib/agent-runtime.d.ts.map +1 -1
- package/lib/agent-runtime.js +35 -11
- package/lib/agent-runtime.js.map +1 -1
- package/lib/api/features/attachments-api.d.ts.map +1 -1
- package/lib/api/features/attachments-api.js +31 -1
- package/lib/api/features/attachments-api.js.map +1 -1
- package/lib/api.d.ts +1 -1
- package/lib/api.d.ts.map +1 -1
- package/lib/api.js +1 -1
- package/lib/api.js.map +1 -1
- package/lib/attachments/grouped-storage.d.ts +5 -0
- package/lib/attachments/grouped-storage.d.ts.map +1 -0
- package/lib/attachments/grouped-storage.js +47 -0
- package/lib/attachments/grouped-storage.js.map +1 -0
- package/lib/attachments/service.d.ts +17 -0
- package/lib/attachments/service.d.ts.map +1 -1
- package/lib/attachments/service.js +100 -8
- package/lib/attachments/service.js.map +1 -1
- package/lib/attachments/shared-storage.d.ts +7 -0
- package/lib/attachments/shared-storage.d.ts.map +1 -0
- package/lib/attachments/shared-storage.js +44 -0
- package/lib/attachments/shared-storage.js.map +1 -0
- package/lib/attachments/storage-admin.d.ts +6 -0
- package/lib/attachments/storage-admin.d.ts.map +1 -0
- package/lib/attachments/storage-admin.js +43 -0
- package/lib/attachments/storage-admin.js.map +1 -0
- package/lib/attachments/storage-types.d.ts +33 -0
- package/lib/attachments/storage-types.d.ts.map +1 -0
- package/lib/attachments/storage-types.js +2 -0
- package/lib/attachments/storage-types.js.map +1 -0
- package/lib/attachments/tool.d.ts.map +1 -1
- package/lib/attachments/tool.js +16 -5
- package/lib/attachments/tool.js.map +1 -1
- package/lib/channels/delivery-policy.d.ts.map +1 -1
- package/lib/channels/delivery-policy.js +6 -4
- package/lib/channels/delivery-policy.js.map +1 -1
- package/lib/channels/direct/message-format.js +294 -157
- package/lib/channels/direct/message-format.js.map +4 -4
- package/lib/channels/manager.d.ts +6 -3
- package/lib/channels/manager.d.ts.map +1 -1
- package/lib/channels/manager.js +64 -44
- package/lib/channels/manager.js.map +1 -1
- package/lib/channels/weixin/api.d.ts.map +1 -1
- package/lib/channels/weixin/api.js +7 -2
- package/lib/channels/weixin/api.js.map +1 -1
- package/lib/channels/weixin/errors.d.ts +14 -0
- package/lib/channels/weixin/errors.d.ts.map +1 -0
- package/lib/channels/weixin/errors.js +29 -0
- package/lib/channels/weixin/errors.js.map +1 -0
- package/lib/channels/weixin/poll.d.ts +14 -0
- package/lib/channels/weixin/poll.d.ts.map +1 -0
- package/lib/channels/weixin/poll.js +50 -0
- package/lib/channels/weixin/poll.js.map +1 -0
- package/lib/client-controller.d.ts.map +1 -1
- package/lib/client-controller.js +3 -3
- package/lib/client-controller.js.map +1 -1
- package/lib/client.d.ts.map +1 -1
- package/lib/client.js +983 -575
- package/lib/client.js.map +4 -4
- package/lib/collaboration/composition.d.ts.map +1 -1
- package/lib/collaboration/composition.js +16 -6
- package/lib/collaboration/composition.js.map +1 -1
- package/lib/collaboration/domain.d.ts +6 -0
- package/lib/collaboration/domain.d.ts.map +1 -1
- package/lib/collaboration/domain.js.map +1 -1
- package/lib/collaboration/service.d.ts.map +1 -1
- package/lib/collaboration/service.js +33 -3
- package/lib/collaboration/service.js.map +1 -1
- package/lib/concern-notification.d.ts.map +1 -1
- package/lib/concern-notification.js +2 -1
- package/lib/concern-notification.js.map +1 -1
- package/lib/concern-tool.js +1 -1
- package/lib/concern-tool.js.map +1 -1
- package/lib/execution/reply-progress.d.ts.map +1 -1
- package/lib/execution/reply-progress.js +4 -2
- package/lib/execution/reply-progress.js.map +1 -1
- package/lib/execution/service.d.ts +1 -1
- package/lib/execution/service.d.ts.map +1 -1
- package/lib/execution/service.js +4 -7
- package/lib/execution/service.js.map +1 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +3 -1
- package/lib/index.js.map +1 -1
- package/lib/memory-reflection.js +2 -2
- package/lib/memory-reflection.js.map +1 -1
- package/lib/message-source.d.ts +25 -0
- package/lib/message-source.d.ts.map +1 -0
- package/lib/message-source.js +8 -0
- package/lib/message-source.js.map +1 -0
- package/lib/observation-loop.js +4 -4
- package/lib/observation-loop.js.map +1 -1
- package/lib/pendant/reader.js +1 -1
- package/lib/pendant/reader.js.map +1 -1
- package/lib/pendant/renderer.d.ts.map +1 -1
- package/lib/pendant/renderer.js +18 -2
- package/lib/pendant/renderer.js.map +1 -1
- package/lib/pendant/settings-panel.js +1 -1
- package/lib/pendant/settings-panel.js.map +1 -1
- package/lib/pendant/startup.d.ts +4 -0
- package/lib/pendant/startup.d.ts.map +1 -0
- package/lib/pendant/startup.js +64 -0
- package/lib/pendant/startup.js.map +1 -0
- package/lib/pendant/widget.d.ts.map +1 -1
- package/lib/pendant/widget.js +2 -1
- package/lib/pendant/widget.js.map +1 -1
- package/lib/pendant-renderer.js +169 -169
- package/lib/persona/service.js +1 -1
- package/lib/persona/service.js.map +1 -1
- package/lib/requirements/attachments.d.ts +7 -0
- package/lib/requirements/attachments.d.ts.map +1 -0
- package/lib/requirements/attachments.js +26 -0
- package/lib/requirements/attachments.js.map +1 -0
- package/lib/requirements/domain.d.ts +1 -1
- package/lib/requirements/domain.d.ts.map +1 -1
- package/lib/requirements/service.d.ts.map +1 -1
- package/lib/requirements/service.js +4 -1
- package/lib/requirements/service.js.map +1 -1
- package/lib/scheduler/channel-origin.d.ts +7 -0
- package/lib/scheduler/channel-origin.d.ts.map +1 -0
- package/lib/scheduler/channel-origin.js +27 -0
- package/lib/scheduler/channel-origin.js.map +1 -0
- package/lib/scheduler/continuations.d.ts +2 -2
- package/lib/scheduler/continuations.d.ts.map +1 -1
- package/lib/scheduler/continuations.js +28 -8
- package/lib/scheduler/continuations.js.map +1 -1
- package/lib/scheduler/domain.d.ts +13 -0
- package/lib/scheduler/domain.d.ts.map +1 -1
- package/lib/scheduler/final-reply.d.ts +13 -0
- package/lib/scheduler/final-reply.d.ts.map +1 -0
- package/lib/scheduler/final-reply.js +21 -0
- package/lib/scheduler/final-reply.js.map +1 -0
- package/lib/skills/builtin-resources.d.ts +6 -0
- package/lib/skills/builtin-resources.d.ts.map +1 -0
- package/lib/skills/builtin-resources.js +47 -0
- package/lib/skills/builtin-resources.js.map +1 -0
- package/lib/skills/builtin.d.ts +2 -5
- package/lib/skills/builtin.d.ts.map +1 -1
- package/lib/skills/builtin.js +6 -0
- package/lib/skills/builtin.js.map +1 -1
- package/lib/skills/director-builtins.d.ts +8 -0
- package/lib/skills/director-builtins.d.ts.map +1 -0
- package/lib/skills/director-builtins.js +65 -0
- package/lib/skills/director-builtins.js.map +1 -0
- package/lib/skills/drama-builtin.d.ts +3 -0
- package/lib/skills/drama-builtin.d.ts.map +1 -0
- package/lib/skills/drama-builtin.js +49 -0
- package/lib/skills/drama-builtin.js.map +1 -0
- package/lib/skills/long-task-continuation.d.ts +2 -2
- package/lib/skills/long-task-continuation.d.ts.map +1 -1
- package/lib/skills/long-task-continuation.js +2 -2
- package/lib/skills/service.d.ts.map +1 -1
- package/lib/skills/service.js +8 -3
- package/lib/skills/service.js.map +1 -1
- package/lib/skills/viora-canvas-builtin.d.ts +3 -0
- package/lib/skills/viora-canvas-builtin.d.ts.map +1 -0
- package/lib/skills/viora-canvas-builtin.js +19 -0
- package/lib/skills/viora-canvas-builtin.js.map +1 -0
- package/lib/storage/migrate-layout.d.ts.map +1 -1
- package/lib/storage/migrate-layout.js +13 -4
- package/lib/storage/migrate-layout.js.map +1 -1
- package/lib/tasks/context.js +1 -0
- package/lib/tasks/context.js.map +1 -1
- package/lib/tasks/continuation.d.ts.map +1 -1
- package/lib/tasks/continuation.js +3 -0
- package/lib/tasks/continuation.js.map +1 -1
- package/lib/tasks/domain.d.ts +2 -0
- package/lib/tasks/domain.d.ts.map +1 -1
- package/lib/tasks/service.d.ts +2 -1
- package/lib/tasks/service.d.ts.map +1 -1
- package/lib/tasks/service.js +15 -1
- package/lib/tasks/service.js.map +1 -1
- package/lib/ui/attachment-storage.d.ts +2 -0
- package/lib/ui/attachment-storage.d.ts.map +1 -0
- package/lib/ui/attachment-storage.js +59 -0
- package/lib/ui/attachment-storage.js.map +1 -0
- package/lib/ui/capability-editor.js +1 -1
- package/lib/ui/capability-editor.js.map +1 -1
- package/lib/ui/companion-access-panel.js +1 -1
- package/lib/ui/companion-access-panel.js.map +1 -1
- package/lib/ui/companion-create.js +1 -1
- package/lib/ui/companion-create.js.map +1 -1
- package/lib/ui/companion-picker.js +1 -1
- package/lib/ui/companion-picker.js.map +1 -1
- package/lib/ui/identity-editor.js +1 -1
- package/lib/ui/identity-editor.js.map +1 -1
- package/lib/ui/mcp-capabilities.js +1 -1
- package/lib/ui/mcp-capabilities.js.map +1 -1
- package/lib/ui/mcp-panel.js +1 -1
- package/lib/ui/mcp-panel.js.map +1 -1
- package/lib/ui/memory/concern-board.js +2 -2
- package/lib/ui/memory/concern-board.js.map +1 -1
- package/lib/ui/memory/concern-deletion.d.ts +1 -1
- package/lib/ui/memory/concern-deletion.d.ts.map +1 -1
- package/lib/ui/memory/concern-deletion.js +9 -4
- package/lib/ui/memory/concern-deletion.js.map +1 -1
- package/lib/ui/memory/concern-panel.js +1 -1
- package/lib/ui/memory/concern-panel.js.map +1 -1
- package/lib/ui/memory/memory-library.js +1 -1
- package/lib/ui/memory/memory-library.js.map +1 -1
- package/lib/ui/memory/memory-panel.js +1 -1
- package/lib/ui/memory/memory-panel.js.map +1 -1
- package/lib/ui/partner-components.js +1 -1
- package/lib/ui/partner-components.js.map +1 -1
- package/lib/ui/requirement-board.js +1 -1
- package/lib/ui/requirement-board.js.map +1 -1
- package/lib/ui/requirement-dialog.js +1 -1
- package/lib/ui/requirement-dialog.js.map +1 -1
- package/lib/ui/requirement-tasks-panel.js +1 -1
- package/lib/ui/requirement-tasks-panel.js.map +1 -1
- package/lib/ui/schedule-panel.js +1 -1
- package/lib/ui/schedule-panel.js.map +1 -1
- package/lib/ui/skills-panel.js +1 -1
- package/lib/ui/skills-panel.js.map +1 -1
- package/lib/ui/storage-settings.d.ts.map +1 -1
- package/lib/ui/storage-settings.js +2 -1
- package/lib/ui/storage-settings.js.map +1 -1
- package/lib/ui/task-board-stage.js +1 -1
- package/lib/ui/task-board-stage.js.map +1 -1
- package/lib/ui/workspace-components.js +1 -1
- package/lib/ui/workspace-components.js.map +1 -1
- package/lib/ui/workspace-navigation.d.ts +16 -16
- package/lib/ui/workspace-navigation.js +1 -1
- package/lib/ui/workspace-navigation.js.map +1 -1
- package/package.json +53 -41
- package/resources/skills/cinematic-director/manifest.json +205 -0
- package/resources/skills/cinematic-director/upstream/CHANGELOG.md +213 -0
- package/resources/skills/cinematic-director/upstream/CONTRIBUTING.md +316 -0
- package/resources/skills/cinematic-director/upstream/LICENSE +21 -0
- package/resources/skills/cinematic-director/upstream/README.md +507 -0
- package/resources/skills/cinematic-director/upstream/SKILL.md +420 -0
- package/resources/skills/cinematic-director/upstream/assets/beat-sheet-template.md +97 -0
- package/resources/skills/cinematic-director/upstream/assets/director-book-template.md +254 -0
- package/resources/skills/cinematic-director/upstream/assets/edit-timeline-template.md +147 -0
- package/resources/skills/cinematic-director/upstream/assets/keyframe-prompt-template.md +334 -0
- package/resources/skills/cinematic-director/upstream/assets/qc-checklist.md +153 -0
- package/resources/skills/cinematic-director/upstream/assets/shot-plan-template.md +95 -0
- package/resources/skills/cinematic-director/upstream/assets/sound-plan-template.md +181 -0
- package/resources/skills/cinematic-director/upstream/assets/video-prompt-template.md +305 -0
- package/resources/skills/cinematic-director/upstream/evals/run.md +69 -0
- package/resources/skills/cinematic-director/upstream/references/ai-video-tool-adapters.md +527 -0
- package/resources/skills/cinematic-director/upstream/references/blocking-and-staging.md +424 -0
- package/resources/skills/cinematic-director/upstream/references/cinematic-language.md +595 -0
- package/resources/skills/cinematic-director/upstream/references/continuity-bible.md +410 -0
- package/resources/skills/cinematic-director/upstream/references/director_styles/01_spielberg.md +192 -0
- package/resources/skills/cinematic-director/upstream/references/director_styles/02_hitchcock.md +193 -0
- package/resources/skills/cinematic-director/upstream/references/director_styles/03_kubrick.md +196 -0
- package/resources/skills/cinematic-director/upstream/references/director_styles/04_kurosawa.md +196 -0
- package/resources/skills/cinematic-director/upstream/references/director_styles/05_scorsese.md +195 -0
- package/resources/skills/cinematic-director/upstream/references/director_styles/06_fellini.md +197 -0
- package/resources/skills/cinematic-director/upstream/references/director_styles/07_bergman.md +197 -0
- package/resources/skills/cinematic-director/upstream/references/director_styles/08_tarkovsky.md +184 -0
- package/resources/skills/cinematic-director/upstream/references/director_styles/09_wong_kar_wai.md +189 -0
- package/resources/skills/cinematic-director/upstream/references/director_styles/10_nolan.md +185 -0
- package/resources/skills/cinematic-director/upstream/references/director_styles/11_villeneuve.md +188 -0
- package/resources/skills/cinematic-director/upstream/references/director_styles/12_fincher.md +185 -0
- package/resources/skills/cinematic-director/upstream/references/director_styles/13_refn.md +186 -0
- package/resources/skills/cinematic-director/upstream/references/director_styles/14_bi_gan.md +188 -0
- package/resources/skills/cinematic-director/upstream/references/director_styles/15_zhang_yimou.md +192 -0
- package/resources/skills/cinematic-director/upstream/references/director_styles/16_hou_hsiao_hsien.md +192 -0
- package/resources/skills/cinematic-director/upstream/references/director_styles/17_park_chan_wook.md +192 -0
- package/resources/skills/cinematic-director/upstream/references/director_styles/18_malick.md +191 -0
- package/resources/skills/cinematic-director/upstream/references/director_styles/19_michael_mann.md +192 -0
- package/resources/skills/cinematic-director/upstream/references/director_styles/20_coen_brothers.md +194 -0
- package/resources/skills/cinematic-director/upstream/references/director_styles/README.md +89 -0
- package/resources/skills/cinematic-director/upstream/references/director_styles/example_comparisons.md +140 -0
- package/resources/skills/cinematic-director/upstream/references/editing-and-assembly.md +263 -0
- package/resources/skills/cinematic-director/upstream/references/failure-modes.md +594 -0
- package/resources/skills/cinematic-director/upstream/references/genre-playbooks.md +407 -0
- package/resources/skills/cinematic-director/upstream/references/image-model-adapters.md +290 -0
- package/resources/skills/cinematic-director/upstream/references/lighting-and-color.md +349 -0
- package/resources/skills/cinematic-director/upstream/references/product-and-macro.md +207 -0
- package/resources/skills/cinematic-director/upstream/references/production-workflow.md +238 -0
- package/resources/skills/cinematic-director/upstream/references/prompt-lexicon.md +602 -0
- package/resources/skills/cinematic-director/upstream/references/sound-and-dialogue.md +364 -0
- package/resources/skills/director-skills/manifest.json +541 -0
- package/resources/skills/director-skills/upstream/CHANGELOG.md +32 -0
- package/resources/skills/director-skills/upstream/LICENSE +21 -0
- package/resources/skills/director-skills/upstream/MANIFEST.md +366 -0
- package/resources/skills/director-skills/upstream/README.md +251 -0
- package/resources/skills/director-skills/upstream/RESEARCH_PROVENANCE.md +37 -0
- package/resources/skills/director-skills/upstream/SKILL.md +114 -0
- package/resources/skills/director-skills/upstream/skills/character-location-prop-bible/SKILL.md +81 -0
- package/resources/skills/director-skills/upstream/skills/character-location-prop-bible/checklists/continuity_checklist.md +8 -0
- package/resources/skills/director-skills/upstream/skills/character-location-prop-bible/examples/character_continuity_update.md +9 -0
- package/resources/skills/director-skills/upstream/skills/character-location-prop-bible/examples/location_continuity_update.md +9 -0
- package/resources/skills/director-skills/upstream/skills/character-location-prop-bible/references/continuity_system.md +21 -0
- package/resources/skills/director-skills/upstream/skills/character-location-prop-bible/references/golden_image_workflow.md +29 -0
- package/resources/skills/director-skills/upstream/skills/character-location-prop-bible/references/identity_anchors.md +14 -0
- package/resources/skills/director-skills/upstream/skills/character-location-prop-bible/references/visual_bible_system.md +15 -0
- package/resources/skills/director-skills/upstream/skills/character-location-prop-bible/templates/character_profile.md +15 -0
- package/resources/skills/director-skills/upstream/skills/character-location-prop-bible/templates/continuity_checklist.md +11 -0
- package/resources/skills/director-skills/upstream/skills/character-location-prop-bible/templates/costume_profile.md +12 -0
- package/resources/skills/director-skills/upstream/skills/character-location-prop-bible/templates/location_profile.md +14 -0
- package/resources/skills/director-skills/upstream/skills/character-location-prop-bible/templates/prop_profile.md +13 -0
- package/resources/skills/director-skills/upstream/skills/character-location-prop-bible/templates/visual_style_bible.md +15 -0
- package/resources/skills/director-skills/upstream/skills/character-location-prop-bible/tests/continuity_tests.md +6 -0
- package/resources/skills/director-skills/upstream/skills/cinematic-image-prompting/SKILL.md +80 -0
- package/resources/skills/director-skills/upstream/skills/cinematic-image-prompting/checklists/image_prompt_quality_checklist.md +10 -0
- package/resources/skills/director-skills/upstream/skills/cinematic-image-prompting/examples/shot_list_to_image_prompts.md +4 -0
- package/resources/skills/director-skills/upstream/skills/cinematic-image-prompting/references/image_prompt_components.md +22 -0
- package/resources/skills/director-skills/upstream/skills/cinematic-image-prompting/references/lighting_and_photorealism.md +19 -0
- package/resources/skills/director-skills/upstream/skills/cinematic-image-prompting/references/negative_prompt_patterns.md +18 -0
- package/resources/skills/director-skills/upstream/skills/cinematic-image-prompting/references/prompt_anatomy.md +18 -0
- package/resources/skills/director-skills/upstream/skills/cinematic-image-prompting/templates/character_reference_sheet_prompt.md +5 -0
- package/resources/skills/director-skills/upstream/skills/cinematic-image-prompting/templates/cinematic_still_prompt.md +3 -0
- package/resources/skills/director-skills/upstream/skills/cinematic-image-prompting/templates/keyframe_prompt.md +6 -0
- package/resources/skills/director-skills/upstream/skills/cinematic-image-prompting/templates/location_reference_prompt.md +3 -0
- package/resources/skills/director-skills/upstream/skills/cinematic-image-prompting/templates/prop_prompt.md +3 -0
- package/resources/skills/director-skills/upstream/skills/cinematic-image-prompting/tests/image_prompt_completeness_tests.md +5 -0
- package/resources/skills/director-skills/upstream/skills/cinematic-video-prompting/SKILL.md +84 -0
- package/resources/skills/director-skills/upstream/skills/cinematic-video-prompting/checklists/video_prompt_quality_checklist.md +9 -0
- package/resources/skills/director-skills/upstream/skills/cinematic-video-prompting/examples/image_prompt_to_video_prompt.md +3 -0
- package/resources/skills/director-skills/upstream/skills/cinematic-video-prompting/references/ai_film_production_pipeline.md +21 -0
- package/resources/skills/director-skills/upstream/skills/cinematic-video-prompting/references/audio_motion_timing.md +16 -0
- package/resources/skills/director-skills/upstream/skills/cinematic-video-prompting/references/camera_lens_movement.md +26 -0
- package/resources/skills/director-skills/upstream/skills/cinematic-video-prompting/references/negative_prompt_patterns.md +18 -0
- package/resources/skills/director-skills/upstream/skills/cinematic-video-prompting/references/prompt_anatomy.md +18 -0
- package/resources/skills/director-skills/upstream/skills/cinematic-video-prompting/references/video_prompt_components.md +25 -0
- package/resources/skills/director-skills/upstream/skills/cinematic-video-prompting/templates/image_to_video_prompt.md +5 -0
- package/resources/skills/director-skills/upstream/skills/cinematic-video-prompting/templates/text_to_video_prompt.md +5 -0
- package/resources/skills/director-skills/upstream/skills/cinematic-video-prompting/templates/video_beat_prompt.md +9 -0
- package/resources/skills/director-skills/upstream/skills/cinematic-video-prompting/tests/video_prompt_completeness_tests.md +5 -0
- package/resources/skills/director-skills/upstream/skills/model-adaptation/SKILL.md +82 -0
- package/resources/skills/director-skills/upstream/skills/model-adaptation/checklists/model_export_checklist.md +9 -0
- package/resources/skills/director-skills/upstream/skills/model-adaptation/examples/failed_model_transfer_diagnosis.md +5 -0
- package/resources/skills/director-skills/upstream/skills/model-adaptation/examples/image_prompt_translation.md +10 -0
- package/resources/skills/director-skills/upstream/skills/model-adaptation/examples/same_scene_across_models.md +9 -0
- package/resources/skills/director-skills/upstream/skills/model-adaptation/examples/universal_prompt_to_model_specific_exports.md +14 -0
- package/resources/skills/director-skills/upstream/skills/model-adaptation/examples/video_prompt_translation.md +9 -0
- package/resources/skills/director-skills/upstream/skills/model-adaptation/references/failure_modes_by_model.md +13 -0
- package/resources/skills/director-skills/upstream/skills/model-adaptation/references/image_model_profiles.md +226 -0
- package/resources/skills/director-skills/upstream/skills/model-adaptation/references/model_capability_matrix.md +24 -0
- package/resources/skills/director-skills/upstream/skills/model-adaptation/references/model_update_protocol.md +10 -0
- package/resources/skills/director-skills/upstream/skills/model-adaptation/references/parameter_compatibility.md +20 -0
- package/resources/skills/director-skills/upstream/skills/model-adaptation/references/prompt_translation_rules.md +18 -0
- package/resources/skills/director-skills/upstream/skills/model-adaptation/references/video_model_profiles.md +251 -0
- package/resources/skills/director-skills/upstream/skills/model-adaptation/templates/model_comparison_table.md +4 -0
- package/resources/skills/director-skills/upstream/skills/model-adaptation/templates/model_specific_prompt_export.md +21 -0
- package/resources/skills/director-skills/upstream/skills/model-adaptation/templates/multi_model_export_package.md +18 -0
- package/resources/skills/director-skills/upstream/skills/model-adaptation/templates/prompt_translation_report.md +13 -0
- package/resources/skills/director-skills/upstream/skills/model-adaptation/templates/universal_prompt_brief.md +25 -0
- package/resources/skills/director-skills/upstream/skills/model-adaptation/tests/model_routing_tests.md +8 -0
- package/resources/skills/director-skills/upstream/skills/model-adaptation/tests/prompt_translation_tests.md +7 -0
- package/resources/skills/director-skills/upstream/skills/model-adaptation/tests/research_provenance_tests.md +6 -0
- package/resources/skills/director-skills/upstream/skills/model-adaptation/tests/unknown_capability_tests.md +7 -0
- package/resources/skills/director-skills/upstream/skills/prompt-iteration-and-diagnostics/SKILL.md +82 -0
- package/resources/skills/director-skills/upstream/skills/prompt-iteration-and-diagnostics/checklists/diagnostic_checklist.md +8 -0
- package/resources/skills/director-skills/upstream/skills/prompt-iteration-and-diagnostics/checklists/release_quality_checklist.md +9 -0
- package/resources/skills/director-skills/upstream/skills/prompt-iteration-and-diagnostics/examples/failed_output_to_revised_prompt.md +10 -0
- package/resources/skills/director-skills/upstream/skills/prompt-iteration-and-diagnostics/examples/final_prompt_package_export.md +10 -0
- package/resources/skills/director-skills/upstream/skills/prompt-iteration-and-diagnostics/references/failure_modes.md +13 -0
- package/resources/skills/director-skills/upstream/skills/prompt-iteration-and-diagnostics/references/iteration_diagnostics.md +17 -0
- package/resources/skills/director-skills/upstream/skills/prompt-iteration-and-diagnostics/references/revision_loops.md +17 -0
- package/resources/skills/director-skills/upstream/skills/prompt-iteration-and-diagnostics/references/story_voice_release_qc.md +30 -0
- package/resources/skills/director-skills/upstream/skills/prompt-iteration-and-diagnostics/templates/failed_output_intake.md +13 -0
- package/resources/skills/director-skills/upstream/skills/prompt-iteration-and-diagnostics/templates/final_production_package.md +11 -0
- package/resources/skills/director-skills/upstream/skills/prompt-iteration-and-diagnostics/templates/prompt_revision_report.md +17 -0
- package/resources/skills/director-skills/upstream/skills/prompt-iteration-and-diagnostics/tests/prompt_quality_checklist_tests.md +6 -0
- package/resources/skills/director-skills/upstream/skills/screenplay-and-scene-writing/SKILL.md +81 -0
- package/resources/skills/director-skills/upstream/skills/screenplay-and-scene-writing/checklists/scene_quality_checklist.md +8 -0
- package/resources/skills/director-skills/upstream/skills/screenplay-and-scene-writing/examples/scene_to_screenplay_excerpt.md +18 -0
- package/resources/skills/director-skills/upstream/skills/screenplay-and-scene-writing/references/dialogue_and_subtext.md +15 -0
- package/resources/skills/director-skills/upstream/skills/screenplay-and-scene-writing/references/fountain_workflow.md +29 -0
- package/resources/skills/director-skills/upstream/skills/screenplay-and-scene-writing/references/scene_construction.md +20 -0
- package/resources/skills/director-skills/upstream/skills/screenplay-and-scene-writing/references/screenplay_format.md +18 -0
- package/resources/skills/director-skills/upstream/skills/screenplay-and-scene-writing/templates/beat_sheet.md +6 -0
- package/resources/skills/director-skills/upstream/skills/screenplay-and-scene-writing/templates/dialogue_pass.md +9 -0
- package/resources/skills/director-skills/upstream/skills/screenplay-and-scene-writing/templates/fountain_scene.md +25 -0
- package/resources/skills/director-skills/upstream/skills/screenplay-and-scene-writing/templates/screenplay_scene.md +19 -0
- package/resources/skills/director-skills/upstream/skills/screenplay-and-scene-writing/tests/screenplay_scene_tests.md +6 -0
- package/resources/skills/director-skills/upstream/skills/short-film-development/SKILL.md +83 -0
- package/resources/skills/director-skills/upstream/skills/short-film-development/checklists/concept_quality_checklist.md +8 -0
- package/resources/skills/director-skills/upstream/skills/short-film-development/examples/idea_to_logline.md +13 -0
- package/resources/skills/director-skills/upstream/skills/short-film-development/examples/logline_to_outline.md +10 -0
- package/resources/skills/director-skills/upstream/skills/short-film-development/references/ai_storytelling_workflow.md +46 -0
- package/resources/skills/director-skills/upstream/skills/short-film-development/references/continuity_handoff.md +8 -0
- package/resources/skills/director-skills/upstream/skills/short-film-development/references/screenplay_structure.md +19 -0
- package/resources/skills/director-skills/upstream/skills/short-film-development/references/story_development_workflow.md +23 -0
- package/resources/skills/director-skills/upstream/skills/short-film-development/templates/beat_sheet.md +7 -0
- package/resources/skills/director-skills/upstream/skills/short-film-development/templates/idea_intake.md +16 -0
- package/resources/skills/director-skills/upstream/skills/short-film-development/templates/logline.md +10 -0
- package/resources/skills/director-skills/upstream/skills/short-film-development/templates/short_film_treatment.md +23 -0
- package/resources/skills/director-skills/upstream/skills/short-film-development/templates/short_story_plan.md +22 -0
- package/resources/skills/director-skills/upstream/skills/short-film-development/templates/story_premise.md +19 -0
- package/resources/skills/director-skills/upstream/skills/short-film-development/templates/story_revision_audit.md +22 -0
- package/resources/skills/director-skills/upstream/skills/short-film-development/tests/output_format_tests.md +3 -0
- package/resources/skills/director-skills/upstream/skills/short-film-development/tests/routing_tests.md +7 -0
- package/resources/skills/director-skills/upstream/skills/shotlist-and-visual-breakdown/SKILL.md +77 -0
- package/resources/skills/director-skills/upstream/skills/shotlist-and-visual-breakdown/checklists/shotlist_quality_checklist.md +8 -0
- package/resources/skills/director-skills/upstream/skills/shotlist-and-visual-breakdown/examples/screenplay_scene_to_shot_list.md +8 -0
- package/resources/skills/director-skills/upstream/skills/shotlist-and-visual-breakdown/references/camera_plan.md +11 -0
- package/resources/skills/director-skills/upstream/skills/shotlist-and-visual-breakdown/references/production_breakdown.md +11 -0
- package/resources/skills/director-skills/upstream/skills/shotlist-and-visual-breakdown/references/shotlist_schema.md +21 -0
- package/resources/skills/director-skills/upstream/skills/shotlist-and-visual-breakdown/templates/asset_list.md +7 -0
- package/resources/skills/director-skills/upstream/skills/shotlist-and-visual-breakdown/templates/camera_plan.md +10 -0
- package/resources/skills/director-skills/upstream/skills/shotlist-and-visual-breakdown/templates/scene_breakdown.md +16 -0
- package/resources/skills/director-skills/upstream/skills/shotlist-and-visual-breakdown/templates/shot_list.md +5 -0
- package/resources/skills/director-skills/upstream/skills/shotlist-and-visual-breakdown/tests/screenplay_to_shotlist_tests.md +5 -0
- package/resources/skills/director-skills/upstream/skills/style-cinematography-director/SKILL.md +77 -0
- package/resources/skills/director-skills/upstream/skills/style-cinematography-director/checklists/style_quality_checklist.md +8 -0
- package/resources/skills/director-skills/upstream/skills/style-cinematography-director/examples/genre_to_visual_direction.md +15 -0
- package/resources/skills/director-skills/upstream/skills/style-cinematography-director/references/camera_lens_movement.md +26 -0
- package/resources/skills/director-skills/upstream/skills/style-cinematography-director/references/cinematic_language.md +32 -0
- package/resources/skills/director-skills/upstream/skills/style-cinematography-director/references/genre_style_tone.md +17 -0
- package/resources/skills/director-skills/upstream/skills/style-cinematography-director/references/lighting_and_photorealism.md +19 -0
- package/resources/skills/director-skills/upstream/skills/style-cinematography-director/templates/visual_style_bible.md +33 -0
- package/resources/skills/director-skills/upstream/skills/style-cinematography-director/tests/style_translation_tests.md +5 -0
- package/resources/skills/director-skills/upstream/tests/master_router_tests.md +80 -0
- package/resources/skills/drama-production/manifest.json +933 -0
- package/resources/skills/drama-production/upstream/LICENSE +21 -0
- package/resources/skills/drama-production/upstream/skills/short-drama/SKILL.md +153 -0
- package/resources/skills/drama-production/upstream/skills/short-drama/agents/openai.yaml +4 -0
- package/resources/skills/drama-production/upstream/skills/short-drama/assets/creator-decision.example.jsonl +4 -0
- package/resources/skills/drama-production/upstream/skills/short-drama/assets/dashboard/app.js +1263 -0
- package/resources/skills/drama-production/upstream/skills/short-drama/assets/dashboard/index.html +114 -0
- package/resources/skills/drama-production/upstream/skills/short-drama/assets/dashboard/styles.css +297 -0
- package/resources/skills/drama-production/upstream/skills/short-drama/assets/production-observation.example.jsonl +2 -0
- package/resources/skills/drama-production/upstream/skills/short-drama/assets/project-template/short-drama.json +36 -0
- package/resources/skills/drama-production/upstream/skills/short-drama/assets/reference-observation.example.jsonl +2 -0
- package/resources/skills/drama-production/upstream/skills/short-drama/references/audience-reveal.md +21 -0
- package/resources/skills/drama-production/upstream/skills/short-drama/references/contract-and-ownership.md +45 -0
- package/resources/skills/drama-production/upstream/skills/short-drama/references/creator-documents.md +230 -0
- package/resources/skills/drama-production/upstream/skills/short-drama/references/creator-workflow.md +98 -0
- package/resources/skills/drama-production/upstream/skills/short-drama/references/form-cards/Q/347/211/210/350/241/250/350/276/276.md +73 -0
- package/resources/skills/drama-production/upstream/skills/short-drama/references/form-cards//344/272/214/347/273/264/345/212/250/346/200/201/346/274/253.md +74 -0
- package/resources/skills/drama-production/upstream/skills/short-drama/references/form-cards//345/233/275/346/274/253/344/272/214/346/254/241/345/205/203.md +79 -0
- package/resources/skills/drama-production/upstream/skills/short-drama/references/form-cards//345/256/236/346/213/215.md +75 -0
- package/resources/skills/drama-production/upstream/skills/short-drama/references/form-cards//346/260/264/345/242/250/347/254/224/350/247/246.md +85 -0
- package/resources/skills/drama-production/upstream/skills/short-drama/references/form-cards//351/243/216/346/240/274/345/214/226/344/270/211/347/273/264.md +72 -0
- package/resources/skills/drama-production/upstream/skills/short-drama/references/knowhow-index.md +85 -0
- package/resources/skills/drama-production/upstream/skills/short-drama/references/look-development.md +51 -0
- package/resources/skills/drama-production/upstream/skills/short-drama/references/pickup-and-alternate.md +24 -0
- package/resources/skills/drama-production/upstream/skills/short-drama/references/production-form-profiles.md +126 -0
- package/resources/skills/drama-production/upstream/skills/short-drama/references/reference-roles.md +53 -0
- package/resources/skills/drama-production/upstream/skills/short-drama/references/runtime-preflight.md +24 -0
- package/resources/skills/drama-production/upstream/skills/short-drama/scripts/creator_markdown_check.py +1413 -0
- package/resources/skills/drama-production/upstream/skills/short-drama/scripts/dashboard_server.py +1798 -0
- package/resources/skills/drama-production/upstream/skills/short-drama/scripts/project_tool.py +2454 -0
- package/resources/skills/drama-production/upstream/skills/short-drama/scripts/selftest.py +287 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-assets/SKILL.md +76 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-assets/agents/openai.yaml +4 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-assets/assets/character-look.example.jsonl +3 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-assets/assets/continuity.example.jsonl +4 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-assets/assets/decisions.example.jsonl +5 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-assets/assets/location-view.example.jsonl +3 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-assets/assets/occurrences.example.jsonl +5 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-assets/assets/prop-state.example.jsonl +3 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-assets/examples/minimal/characters.jsonl +2 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-assets/examples/minimal/looks.jsonl +2 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-assets/references/asset-review-checklist.md +82 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-assets/references/character-and-look.md +190 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-assets/references/continuity-delta.md +120 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-assets/references/continuity-lock.md +122 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-assets/references/identity-vs-variant.md +140 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-assets/references/location-and-view.md +84 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-assets/references/occurrence-extraction.md +108 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-assets/references/prop-and-state.md +126 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-assets/references/stage-contract.md +43 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-assets/references/voice-direction.md +179 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-assets/scripts/asset_check.py +267 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-assets/scripts/selftest.py +89 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-develop/SKILL.md +157 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-develop/agents/openai.yaml +4 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-develop/assets/adaptation-map.example.jsonl +1 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-develop/assets/creative-brief.md +120 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-develop/assets/episode-map.jsonl +1 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-develop/assets/story-engine.md +181 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-develop/references/adaptation-craft.md +219 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-develop/references/creative-reference-intake.md +57 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-develop/references/director-brief-craft.md +113 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-develop/references/episode-design.md +509 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-develop/references/genre-and-hook-playbook.md +240 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-develop/references/genre-cards//344/272/262/345/255/220/351/232/220/347/247/230.md +112 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-develop/references/genre-cards//344/273/231/344/276/240/344/277/256/347/234/237.md +51 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-develop/references/genre-cards//345/212/250/344/275/234/344/273/273/345/212/241.md +51 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-develop/references/genre-cards//345/217/244/350/243/205/346/235/203/350/260/213.md +51 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-develop/references/genre-cards//345/244/215/344/273/207/346/211/223/350/204/270.md +51 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-develop/references/genre-cards//345/256/266/345/272/255/345/205/263/347/263/273.md +51 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-develop/references/genre-cards//346/202/254/347/226/221/350/247/204/345/210/231.md +51 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-develop/references/genre-cards//347/224/237/346/264/273/346/265/201.md +51 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-develop/references/genre-cards//347/240/264/351/225/234/351/207/215/345/234/206.md +109 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-develop/references/genre-cards//350/201/214/345/234/272/345/226/234/345/211/247.md +51 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-develop/references/genre-cards//350/261/252/351/227/250/345/251/232/346/201/213.md +51 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-develop/references/genre-cards//350/272/253/344/273/275/351/224/231/344/275/215.md +51 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-develop/references/genre-cards.md +92 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-develop/references/mechanism-loop.md +282 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-develop/references/multi-episode-intake.md +128 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-develop/references/premise-devices.md +211 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-develop/references/reveal-reversal-payoff.md +99 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-develop/references/serial-character-and-memory.md +293 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-develop/references/stage-contract.md +88 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-develop/references/story-craft.md +359 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-develop/scripts/episode_intake.py +699 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-develop/scripts/selftest.py +47 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-edit/SKILL.md +146 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-edit/agents/openai.yaml +4 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-edit/assets/remotion/README.md +60 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-edit/assets/remotion/package.json +15 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-edit/assets/remotion/remotion.config.ts +6 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-edit/assets/remotion/src/Root.tsx +26 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-edit/assets/remotion/src/Subtitles.tsx +88 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-edit/assets/remotion/src/font.ts +46 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-edit/assets/remotion/src/index.ts +4 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-edit/assets/remotion/src/schema.ts +32 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-edit/assets/remotion/tsconfig.json +13 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-edit/assets//345/211/252/350/276/221/345/215/225.md +43 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-edit/references/cut-craft.md +38 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-edit/references/delivery-verify.md +29 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-edit/references/generated-footage.md +54 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-edit/references/sound-and-subtitles.md +60 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-edit/references/stage-contract.md +51 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-edit/scripts/edit_tool.py +1156 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-edit/scripts/selftest.py +520 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-image-prompts/SKILL.md +73 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-image-prompts/agents/openai.yaml +4 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-image-prompts/assets/image-prompt-spec.jsonl.md +187 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-image-prompts/assets/image-prompts.md +42 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-image-prompts/assets/lookdev-frame-spec.jsonl.md +128 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-image-prompts/assets/lookdev-prompts.md +34 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-image-prompts/examples/minimal-image-prompt-specs.jsonl +2 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-image-prompts/references/character-and-look.md +153 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-image-prompts/references/common-recipe.md +241 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-image-prompts/references/edit-and-revision.md +110 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-image-prompts/references/location-plate.md +151 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-image-prompts/references/look-and-state-variant.md +97 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-image-prompts/references/lookdev-frame.md +34 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-image-prompts/references/production-sheet-recipes.md +177 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-image-prompts/references/prop-plate.md +137 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-image-prompts/references/review-and-fixtures.md +92 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-image-prompts/references/stage-contract.md +32 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-image-prompts/scripts/image_prompt_check.py +443 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-image-prompts/scripts/selftest.py +95 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-novel-analyze/SKILL.md +245 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-novel-analyze/agents/openai.yaml +4 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-novel-analyze/assets/episode-candidate.example.jsonl +3 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-novel-analyze/references/adaptation-triage.md +116 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-novel-analyze/references/adaptation-value.md +124 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-novel-analyze/references/aggregation-and-entities.md +155 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-novel-analyze/references/chapter-extraction.md +144 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-novel-analyze/references/stage-contract.md +67 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-novel-analyze/scripts/novel_index.py +651 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-novel-analyze/scripts/selftest.py +51 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-produce/SKILL.md +183 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-produce/agents/openai.yaml +4 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-produce/references/adapter-contract.md +219 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-produce/references/providers/gpt-image-2.md +24 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-produce/references/providers/minimax-h3-video.md +86 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-produce/references/providers/minimax-music.md +25 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-produce/references/providers/minimax-speech.md +41 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-produce/references/providers/seedance.md +64 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-produce/scripts/fixture_adapter.py +77 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-produce/scripts/production_tool.py +2205 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-produce/scripts/provider_adapters.py +1747 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-produce/scripts/selftest.py +135 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-review/SKILL.md +118 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-review/agents/openai.yaml +4 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-review/assets/finding-template.jsonl +2 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-review/assets/supersession-decision.example.json +42 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-review/assets/verdict-template.json +32 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-review/examples/minimal-findings.jsonl +2 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-review/examples/minimal-verdict.json +34 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-review/references/anti-template-repair.md +109 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-review/references/production-quality-gates.md +276 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-review/references/project-calibration.md +83 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-review/references/review-method.md +81 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-review/references/rubric-assets-prompts.md +111 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-review/references/rubric-source-analysis.md +108 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-review/references/rubric-story-script.md +264 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-review/references/rubric-visual-motion.md +184 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-review/references/stage-contract.md +28 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-review/scripts/review_check.py +325 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-review/scripts/selftest.py +87 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-storyboard/SKILL.md +175 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-storyboard/agents/openai.yaml +4 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-storyboard/assets/coverage-audition.example.jsonl +2 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-storyboard/assets/coverage-template.json +49 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-storyboard/assets/keyframe-prompts.md +36 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-storyboard/assets/keyframe-template.jsonl +2 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-storyboard/assets/revision-lineage.fragment.json +12 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-storyboard/assets/scene-visual-plan.example.jsonl +2 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-storyboard/assets/shot-template.jsonl +2 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-storyboard/references/blocking-playbooks.md +216 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-storyboard/references/comic-keyframe-lexicon.md +156 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-storyboard/references/coverage-audition.md +34 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-storyboard/references/keyframe-craft.md +174 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-storyboard/references/lighting-craft.md +39 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-storyboard/references/production-shot-grammar.md +279 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-storyboard/references/review-and-fixtures.md +36 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-storyboard/references/scene-visual-plan.md +30 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-storyboard/references/screenplay-to-keyframe-example.md +155 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-storyboard/references/shot-craft.md +266 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-storyboard/references/shot-revision-identity.md +25 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-storyboard/references/stage-contract.md +57 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-storyboard/scripts/selftest.py +191 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-storyboard/scripts/storyboard_check.py +824 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-video-prompts/SKILL.md +154 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-video-prompts/agents/openai.yaml +4 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-video-prompts/assets/coverage-scope.fragment.json +21 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-video-prompts/assets/delivery-container.jsonl.md +101 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-video-prompts/assets/motion-spec.jsonl.md +217 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-video-prompts/assets/motion-terminal.example.jsonl +2 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-video-prompts/assets/music-spec.jsonl.md +31 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-video-prompts/assets/performance.fragment.json +38 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-video-prompts/assets/video-prompts.md +67 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-video-prompts/examples/minimal-music-specs.jsonl +2 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-video-prompts/references/camera-audio-continuity.md +259 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-video-prompts/references/delivery-profile.md +273 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-video-prompts/references/generability.md +93 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-video-prompts/references/minimax-h3.md +253 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-video-prompts/references/motion-recipe.md +315 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-video-prompts/references/performance-action-timing.md +209 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-video-prompts/references/production-prompt-grammar.md +304 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-video-prompts/references/review-and-fixtures.md +139 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-video-prompts/references/seedance-2.0.md +50 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-video-prompts/references/seedance-2.5.md +122 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-video-prompts/references/stage-contract.md +60 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-video-prompts/references/target-model-profile.md +116 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-video-prompts/scripts/container_check.py +329 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-video-prompts/scripts/motion_timing_check.py +450 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-video-prompts/scripts/music_spec_check.py +327 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-video-prompts/scripts/selftest.py +82 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-write/SKILL.md +99 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-write/agents/openai.yaml +4 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-write/assets/beats.jsonl +2 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-write/assets/episode-card-standalone.json +120 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-write/assets/episode-card.json +55 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-write/assets/screenplay.md +36 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-write/assets/voice-record-sheet.jsonl.md +89 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-write/references/dialogue-craft.md +526 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-write/references/production-format-dialect.md +273 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-write/references/scene-handoff-capsule.md +25 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-write/references/scene-sound-dramaturgy.md +43 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-write/references/screenplay-format.md +266 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-write/references/script-craft.md +745 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-write/references/stage-contract.md +36 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-write/references/substitutable-realization.md +107 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-write/scripts/duration_estimate.py +309 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-write/scripts/screenplay_index.py +1091 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-write/scripts/selftest.py +229 -0
- package/resources/skills/drama-production/upstream/skills/short-drama-write/scripts/voice_sheet_check.py +416 -0
- package/resources/skills/viora-canvas-production/manifest.json +23 -0
- package/resources/skills/viora-canvas-production/references/canvas-contract.md +50 -0
- package/resources/skills/viora-canvas-production/references/creative-production.md +50 -0
- package/resources/skills/viora-canvas-production/references/generation-delivery.md +36 -0
- package/resources/skills/viora-canvas-production/references/production-ledger.md +25 -0
|
@@ -0,0 +1,1413 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Validate the executable cross-document contract of one creator-first episode."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
import json
|
|
8
|
+
import re
|
|
9
|
+
import sys
|
|
10
|
+
from pathlib import Path, PurePosixPath
|
|
11
|
+
from collections.abc import Iterable
|
|
12
|
+
from typing import NamedTuple, Optional
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
MINIMUM_PYTHON = (3, 9)
|
|
16
|
+
if sys.version_info < MINIMUM_PYTHON:
|
|
17
|
+
raise SystemExit(
|
|
18
|
+
"creator_markdown_check.py requires Python {}.{}, running {}.{}".format(
|
|
19
|
+
*MINIMUM_PYTHON, sys.version_info.major, sys.version_info.minor
|
|
20
|
+
)
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
REQUIRED_DOCUMENTS = (
|
|
25
|
+
"剧本.md",
|
|
26
|
+
"视觉设定.md",
|
|
27
|
+
"图片提示词.md",
|
|
28
|
+
"分镜.md",
|
|
29
|
+
"视频提示词.md",
|
|
30
|
+
)
|
|
31
|
+
SECTION_RE = re.compile(r"^## ((?:SHOT|MOTION)-[A-Z0-9-]+)\b", re.MULTILINE)
|
|
32
|
+
IMG_RE = re.compile(r"\b(IMG-[A-Z0-9-]+)《([^》]+)》(控制:([^)]+))")
|
|
33
|
+
# 用途 is optional in the pattern on purpose: a declaration written before this
|
|
34
|
+
# field existed still parses, so the creator gets "REF 缺少用途" instead of the
|
|
35
|
+
# generic syntax error that gives no hint about what to add.
|
|
36
|
+
REF_RE = re.compile(
|
|
37
|
+
r"(REF-[A-Z0-9-]+)(顺序:([1-9]\d*))· "
|
|
38
|
+
r"([^;\n]+?\.(?:png|jpe?g|webp))《([^》\n]+)》"
|
|
39
|
+
r"((?:用途:([^;)\n]+);)?控制:([^;)]+);不得控制:([^)]+))",
|
|
40
|
+
re.IGNORECASE,
|
|
41
|
+
)
|
|
42
|
+
# The same slot grammar, but the locator is a document entry instead of a file.
|
|
43
|
+
# A creator who generates in an external tool never has the file inside the
|
|
44
|
+
# project, and the copyable body only ever needed 顺序 and 用途 -- the path is a
|
|
45
|
+
# production input, not a prompt input. PLAN says "this picture does not exist
|
|
46
|
+
# here; attach it yourself in this order", which is a different claim from
|
|
47
|
+
# 「已有图片」 and from 「待补」.
|
|
48
|
+
PLAN_RE = re.compile(
|
|
49
|
+
r"(PLAN-[A-Z0-9-]+)(顺序:([1-9]\d*))· "
|
|
50
|
+
r"((?:IMG|SHOT)-[A-Z0-9-]+)《([^》\n]+)》"
|
|
51
|
+
r"((?:用途:([^;)\n]+);)?控制:([^;)]+);不得控制:([^)]+))",
|
|
52
|
+
re.IGNORECASE,
|
|
53
|
+
)
|
|
54
|
+
# One picture answers one question. 「同一张图承担两个作用时分开说明」 in
|
|
55
|
+
# references/reference-roles.md is what makes a slot translatable into a
|
|
56
|
+
# provider role, so the vocabulary is closed rather than free prose.
|
|
57
|
+
REF_PURPOSES = (
|
|
58
|
+
"身份",
|
|
59
|
+
"造型状态",
|
|
60
|
+
"地理",
|
|
61
|
+
"构图",
|
|
62
|
+
"尺度",
|
|
63
|
+
"效果",
|
|
64
|
+
"起始帧",
|
|
65
|
+
"结束帧",
|
|
66
|
+
"风格",
|
|
67
|
+
)
|
|
68
|
+
# `EP001-SC001` is the documented shape, but a project that scopes ids by season
|
|
69
|
+
# writes `S01-EP001-SC001`. Both are one stable scene id, so the pattern takes
|
|
70
|
+
# any hyphenated prefix rather than exactly one segment.
|
|
71
|
+
SLOT_HEAD_RE = re.compile(
|
|
72
|
+
r"(?:REF|PLAN)-[A-Za-z0-9][A-Za-z0-9-]*(顺序:", re.IGNORECASE
|
|
73
|
+
)
|
|
74
|
+
SCENE_ID_PATTERN = r"(?:[A-Za-z0-9]+-)+SC[0-9]+"
|
|
75
|
+
SCENE_HEADING_RE = re.compile(
|
|
76
|
+
r"^## (" + SCENE_ID_PATTERN + r")\b", re.MULTILINE
|
|
77
|
+
)
|
|
78
|
+
SCENE_ID_RE = re.compile(SCENE_ID_PATTERN)
|
|
79
|
+
# A scene the storyboard decided not to film. SHT-01 has always allowed the
|
|
80
|
+
# omission and always required a reason; without a place to write it the
|
|
81
|
+
# omission and an oversight look identical in the finished document.
|
|
82
|
+
UNFILMED_LINE_RE = re.compile(
|
|
83
|
+
r"^[ \t\u3000]*[-*+][ \t\u3000]*未拍场次[::](.*)$", re.MULTILINE
|
|
84
|
+
)
|
|
85
|
+
UNFILMED_ENTRY_RE = re.compile(
|
|
86
|
+
r"(" + SCENE_ID_PATTERN + r")(理由:([^)\n]+))"
|
|
87
|
+
)
|
|
88
|
+
# Dialogue reaches the model as a quoted run, either inside the H3 `<d>` tag or
|
|
89
|
+
# inside quotation marks. Runs shorter than four Han characters are in-frame
|
|
90
|
+
# labels and interjections far more often than they are lines, so the check
|
|
91
|
+
# stays off them rather than inventing an escape hatch for signage.
|
|
92
|
+
SPOKEN_SPAN_RE = re.compile(r"<d>([^<]*)</d>|[\"“]([^\"”]*)[\"”]|「([^」]*)」")
|
|
93
|
+
SPOKEN_RUN_RE = re.compile(r"[\u3400-\u9fff]{4,}")
|
|
94
|
+
EXPLICIT_TEXT_TO_VIDEO = "无(创作者已明确选择文生视频)"
|
|
95
|
+
PENDING_REFERENCE_SUFFIX_RE = re.compile(r";待补参考图:[^;。\n]+。?$")
|
|
96
|
+
VISUAL_CATEGORIES = ("人物", "造型", "地点", "道具")
|
|
97
|
+
# A malformed entry always tries to name something; a heading that is only the
|
|
98
|
+
# category word is a section divider ("## 人物"), and rejecting it left the
|
|
99
|
+
# document with no way to group entries at all.
|
|
100
|
+
VISUAL_SETTING_LINE_RE = re.compile(
|
|
101
|
+
r"^#{2,4}[ \t ]*(?:"
|
|
102
|
+
+ "|".join(VISUAL_CATEGORIES)
|
|
103
|
+
+ r")(?![ \t ]*$)[^\n]*$",
|
|
104
|
+
re.MULTILINE,
|
|
105
|
+
)
|
|
106
|
+
VISUAL_SETTING_HEADING_RE = re.compile(
|
|
107
|
+
r"^## (" + "|".join(VISUAL_CATEGORIES) + r") · (.+?)[ \t ]*$", re.MULTILINE
|
|
108
|
+
)
|
|
109
|
+
# Any other `## <word> · <name>` heading, so a 视觉依据 entry that fails to
|
|
110
|
+
# resolve can say "the entry is there, the section word is not one of the four"
|
|
111
|
+
# instead of blaming the storyboard for a `视觉设定.md` problem.
|
|
112
|
+
OTHER_SETTING_HEADING_RE = re.compile(r"^## ([^\n·]+?) · (.+?)[ \t ]*$", re.MULTILINE)
|
|
113
|
+
# 画面代称 is the one field the coverage check depends on, so — like a
|
|
114
|
+
# continuity lock — anything that looks like it has to parse rather than
|
|
115
|
+
# silently drop out.
|
|
116
|
+
# Anchored at the start of the line so a sentence that merely mentions the
|
|
117
|
+
# field is prose, not a malformed declaration.
|
|
118
|
+
SCREEN_NAME_LINE_RE = re.compile(
|
|
119
|
+
r"^[ \t ]*[-*+]?[ \t ]*画面代称[ \t ]*[::][^\n]*$", re.MULTILINE
|
|
120
|
+
)
|
|
121
|
+
SCREEN_NAME_RE = re.compile(
|
|
122
|
+
r"^[ \t ]*[-*+][ \t ]*画面代称[::](.+)$", re.MULTILINE
|
|
123
|
+
)
|
|
124
|
+
VISUAL_BASIS_PREFIX = "《视觉设定.md》·"
|
|
125
|
+
VISUAL_BASIS_ENTRY_RE = re.compile(
|
|
126
|
+
r"(" + "|".join(VISUAL_CATEGORIES) + r")「([^」\n]+)」(控制:([^)\n]+))"
|
|
127
|
+
)
|
|
128
|
+
# A subject a keyframe names but does not show — an owner's abandoned bag, a
|
|
129
|
+
# name on a screen, a homonym of an entry name. SHT-22 excludes these from
|
|
130
|
+
# coverage, so the field needs a way to say so instead of forcing the creator
|
|
131
|
+
# to declare an absent subject as present.
|
|
132
|
+
OFFSCREEN_PREFIX = ";画外:"
|
|
133
|
+
OFFSCREEN_ENTRY_RE = re.compile(
|
|
134
|
+
r"(" + "|".join(VISUAL_CATEGORIES) + r")「([^」\n]+)」"
|
|
135
|
+
)
|
|
136
|
+
# A declared lock must never become a no-op. Anything that *looks* like a lock
|
|
137
|
+
# line -- any list marker, any leading whitespace -- is captured here and then
|
|
138
|
+
# has to parse, so a creator who indents the bullet under 识别锚点 gets an error
|
|
139
|
+
# instead of silent non-enforcement.
|
|
140
|
+
LOCK_LINE_RE = re.compile(r"^[ \t\u3000]*[-*+][ \t\u3000]*连续性锁[::].*$", re.MULTILINE)
|
|
141
|
+
LOCK_RE = re.compile(
|
|
142
|
+
r"^[ \t\u3000]*[-*+][ \t\u3000]*连续性锁:(LOCK-[A-Z0-9-]+)《([^》\n]+)》"
|
|
143
|
+
r"(镜头:([^;)\n]+)"
|
|
144
|
+
r"(?:;图片提示词项:([^;)\n]+))?)"
|
|
145
|
+
r"· 锁面:(.+)$"
|
|
146
|
+
)
|
|
147
|
+
# The surface has to name what is in the picture. A match glued to a negation
|
|
148
|
+
# ("no pale blue sweater") describes what must be absent, so it cannot be the
|
|
149
|
+
# evidence that the fact is present.
|
|
150
|
+
# Chinese runs without spaces, so the CJK markers cannot require a preceding
|
|
151
|
+
# boundary the way the English ones do. A bare 无 is deliberately not a marker:
|
|
152
|
+
# 无袖毛衣 describes the garment rather than excluding it.
|
|
153
|
+
NEGATION_RE = re.compile(
|
|
154
|
+
r"(?:"
|
|
155
|
+
r"(?:^|[\s,;:(\[/—-])(?:no|not|non|never|without|avoid|excludes?|excluding|"
|
|
156
|
+
r"free\s+of|--?no)(?:\s+(?:a|an|the|any|some))?[\s-]*"
|
|
157
|
+
r"|(?:不要|不得|不能|不应|不含|不出现|没有|未|避免|禁止|排除|去掉|移除)"
|
|
158
|
+
r"(?:出现|包含|存在|带|有)?[\s]*"
|
|
159
|
+
r")$",
|
|
160
|
+
re.IGNORECASE,
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def _sections(document: str, kind: str) -> dict[str, str]:
|
|
165
|
+
matches = [
|
|
166
|
+
match
|
|
167
|
+
for match in SECTION_RE.finditer(document)
|
|
168
|
+
if match.group(1).startswith(f"{kind}-")
|
|
169
|
+
]
|
|
170
|
+
return {
|
|
171
|
+
match.group(1): document[
|
|
172
|
+
match.start() : matches[index + 1].start()
|
|
173
|
+
if index + 1 < len(matches)
|
|
174
|
+
else None
|
|
175
|
+
]
|
|
176
|
+
for index, match in enumerate(matches)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def _fields(section: str, *, owner: str, errors: list[str]) -> dict[str, str]:
|
|
181
|
+
pairs = re.findall(r"^- ([^:\n]+):(.+)$", section, re.MULTILINE)
|
|
182
|
+
fields: dict[str, str] = {}
|
|
183
|
+
for key, value in pairs:
|
|
184
|
+
# `- **参考**:…` is ordinary Markdown and reads as the same field to a
|
|
185
|
+
# human. Taking the emphasis literally turned it into a different key,
|
|
186
|
+
# so the author got "缺少参考字段" while looking straight at 参考.
|
|
187
|
+
key = re.sub(r"^(?:\*\*|__|\*|_)(.+?)(?:\*\*|__|\*|_)$", r"\1", key.strip())
|
|
188
|
+
if key in fields:
|
|
189
|
+
errors.append(f"{owner}: 字段重复: {key}")
|
|
190
|
+
fields[key] = value
|
|
191
|
+
return fields
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def _plain(value: str) -> str:
|
|
195
|
+
return value.strip().rstrip("。")
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def _contains_ref_token(value: str) -> bool:
|
|
199
|
+
return "ref-" in value.casefold()
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def _contains_plan_token(value: str) -> bool:
|
|
203
|
+
return "plan-" in value.casefold()
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def _contains_slot_token(value: str) -> bool:
|
|
207
|
+
return _contains_ref_token(value) or _contains_plan_token(value)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def _is_none(value: str) -> bool:
|
|
211
|
+
# `[^)]+` would stop at the first closing bracket, so a gap list that names
|
|
212
|
+
# an entry in brackets -- 「江晨人物图(IMG-...)」 -- fell through to the REF
|
|
213
|
+
# syntax error, whose obvious repair is deleting the gap record. That is the
|
|
214
|
+
# silent downgrade this contract exists to prevent, so the note takes any
|
|
215
|
+
# content as long as it is bracketed.
|
|
216
|
+
return not _contains_slot_token(value) and bool(
|
|
217
|
+
re.fullmatch(r"无(?:(.+))?", _plain(value), re.DOTALL)
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def _is_explicit_text_to_video(value: str) -> bool:
|
|
222
|
+
return _plain(value) == EXPLICIT_TEXT_TO_VIDEO
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def _has_pending_references(value: str) -> bool:
|
|
226
|
+
plain = _plain(value)
|
|
227
|
+
return plain.startswith("无(待补参考图:") or bool(
|
|
228
|
+
PENDING_REFERENCE_SUFFIX_RE.search(value.strip())
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def _is_no_external_reference(value: str) -> bool:
|
|
233
|
+
return not _contains_slot_token(value) and bool(
|
|
234
|
+
re.fullmatch(r"无(?:外部参考)?(?:;[^\n]*)?。?", value.strip())
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def _copyable_prompt(
|
|
239
|
+
section: str, heading: str = r"可复制(?:通用)?提示词"
|
|
240
|
+
) -> Optional[str]:
|
|
241
|
+
markers = list(re.finditer(rf"^### {heading}\s*$", section, re.MULTILINE))
|
|
242
|
+
if len(markers) != 1:
|
|
243
|
+
return None
|
|
244
|
+
body = section[markers[0].end() :]
|
|
245
|
+
following = re.search(r"^###\s+|^##\s+", body, re.MULTILINE)
|
|
246
|
+
if following is not None:
|
|
247
|
+
body = body[: following.start()]
|
|
248
|
+
lines = [line for line in body.splitlines() if line.strip()]
|
|
249
|
+
if not lines or any(not line.startswith(">") for line in lines):
|
|
250
|
+
return None
|
|
251
|
+
prompt = "\n".join(line[1:].lstrip() for line in lines).strip()
|
|
252
|
+
return prompt or None
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def _copyable_prompt_cause(
|
|
256
|
+
section: str, heading: str = r"可复制(?:通用)?提示词"
|
|
257
|
+
) -> str:
|
|
258
|
+
"""Why `_copyable_prompt` returned None, as a suffix for the error.
|
|
259
|
+
|
|
260
|
+
"缺少唯一且非空的可复制提示词" is true of four different documents and tells
|
|
261
|
+
the author nothing about which one they wrote. The expensive case is a
|
|
262
|
+
single stray line -- a separator, an HTML comment, a note -- inside an
|
|
263
|
+
otherwise complete block: the prompt is visibly right there, so the message
|
|
264
|
+
reads as a checker bug and the real line goes unlooked at.
|
|
265
|
+
"""
|
|
266
|
+
|
|
267
|
+
# Name the heading the author actually has to write, not this one's default:
|
|
268
|
+
# the keyframe caller passes a different one, and pointing at the wrong
|
|
269
|
+
# heading is worse than saying nothing.
|
|
270
|
+
label = "### " + re.sub(r"\(\?:([^)]*)\)\?", "", heading).replace("\\", "")
|
|
271
|
+
markers = list(re.finditer(rf"^### {heading}\s*$", section, re.MULTILINE))
|
|
272
|
+
if not markers:
|
|
273
|
+
return f":没有 `{label}` 小节标题"
|
|
274
|
+
if len(markers) > 1:
|
|
275
|
+
return f":`{label}` 出现了 {len(markers)} 次,只能有一个"
|
|
276
|
+
body = section[markers[0].end() :]
|
|
277
|
+
following = re.search(r"^###\s+|^##\s+", body, re.MULTILINE)
|
|
278
|
+
if following is not None:
|
|
279
|
+
body = body[: following.start()]
|
|
280
|
+
lines = [line for line in body.splitlines() if line.strip()]
|
|
281
|
+
if not lines:
|
|
282
|
+
return f":`{label}` 下面是空的"
|
|
283
|
+
intruders = [line for line in lines if not line.startswith(">")]
|
|
284
|
+
if intruders:
|
|
285
|
+
return (
|
|
286
|
+
":小节内有不以 `>` 开头的行,整块因此不算引用块——"
|
|
287
|
+
f"把它移到小节外,或删掉:{_excerpt(intruders[0])}"
|
|
288
|
+
)
|
|
289
|
+
return ":引用块里只有空白"
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def _portable_path(value: str) -> bool:
|
|
293
|
+
if not value or "\\" in value or re.match(r"^[A-Za-z]:", value):
|
|
294
|
+
return False
|
|
295
|
+
parts = value.split("/")
|
|
296
|
+
return not PurePosixPath(value).is_absolute() and not any(
|
|
297
|
+
part in {"", ".", ".."} for part in parts
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def _inside(path: Path, root: Path) -> bool:
|
|
302
|
+
resolved = path.resolve()
|
|
303
|
+
return resolved == root or root in resolved.parents
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
class EntryIndex(NamedTuple):
|
|
307
|
+
"""The document entries a `PLAN-...` locator is allowed to name."""
|
|
308
|
+
|
|
309
|
+
images: dict[str, str]
|
|
310
|
+
shots: frozenset
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def _slot_matches(value: str) -> list:
|
|
314
|
+
"""Every `REF-...` and `PLAN-...` slot in `value`, in written order.
|
|
315
|
+
|
|
316
|
+
The two patterns cannot match the same span: a REF locator has to be an
|
|
317
|
+
image path and a PLAN locator has to be an `IMG-`/`SHOT-` id.
|
|
318
|
+
"""
|
|
319
|
+
found = []
|
|
320
|
+
for kind, pattern in (("REF", REF_RE), ("PLAN", PLAN_RE)):
|
|
321
|
+
for match in pattern.finditer(value):
|
|
322
|
+
found.append((match.start(), match.end(), kind, match.groups()))
|
|
323
|
+
found.sort(key=lambda item: item[0])
|
|
324
|
+
return found
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
def _references(
|
|
328
|
+
value: str,
|
|
329
|
+
owner: str,
|
|
330
|
+
project_root: Path,
|
|
331
|
+
errors: list[str],
|
|
332
|
+
entries: Optional[EntryIndex] = None,
|
|
333
|
+
) -> frozenset:
|
|
334
|
+
"""Validate one 输入参考图/参考 declaration; return the slot kinds it uses."""
|
|
335
|
+
if _is_none(value):
|
|
336
|
+
return frozenset()
|
|
337
|
+
reference_value = PENDING_REFERENCE_SUFFIX_RE.sub("", value.strip())
|
|
338
|
+
matches = _slot_matches(reference_value)
|
|
339
|
+
cursor = 0
|
|
340
|
+
separators_are_valid = True
|
|
341
|
+
for index, (start, end, _kind, _groups) in enumerate(matches):
|
|
342
|
+
if reference_value[cursor:start] != ("" if index == 0 else ";"):
|
|
343
|
+
separators_are_valid = False
|
|
344
|
+
cursor = end
|
|
345
|
+
trailing = reference_value[cursor:]
|
|
346
|
+
# Counted as slot heads on the same text the slots were matched in. A bare
|
|
347
|
+
# token count would read the `ref-` in a real path like
|
|
348
|
+
# `输入/参考图/ref-江晨.png` as a second slot and reject a correct declaration.
|
|
349
|
+
declared = len(SLOT_HEAD_RE.findall(reference_value))
|
|
350
|
+
kinds = frozenset(item[2] for item in matches)
|
|
351
|
+
if (
|
|
352
|
+
len(matches) != declared
|
|
353
|
+
or not matches
|
|
354
|
+
or not separators_are_valid
|
|
355
|
+
or trailing not in {"", "。"}
|
|
356
|
+
):
|
|
357
|
+
# A gap list joined with ; reads as a fourth REF slot and would otherwise
|
|
358
|
+
# be reported as broken REF syntax, whose obvious repair is deleting the
|
|
359
|
+
# gap record -- the silent downgrade this contract exists to prevent.
|
|
360
|
+
if "待补参考图" in value and not _has_pending_references(value):
|
|
361
|
+
errors.append(
|
|
362
|
+
f"{owner}: 待补参考图必须写在最后一个 REF 槽位之后,"
|
|
363
|
+
"缺口之间只用、分隔"
|
|
364
|
+
)
|
|
365
|
+
elif _contains_plan_token(value) and not _contains_ref_token(value):
|
|
366
|
+
errors.append(f"{owner}: 输入参考图必须使用完整 PLAN 语法")
|
|
367
|
+
else:
|
|
368
|
+
errors.append(f"{owner}: 输入参考图必须使用完整 REF 语法")
|
|
369
|
+
return kinds
|
|
370
|
+
groups = [item[3] for item in matches]
|
|
371
|
+
slot_kinds = [item[2] for item in matches]
|
|
372
|
+
slots = [group[0] for group in groups]
|
|
373
|
+
orders = [int(group[1]) for group in groups]
|
|
374
|
+
locators = [group[2] for group in groups]
|
|
375
|
+
purposes = [(group[4] or "").strip() for group in groups]
|
|
376
|
+
# A declaration made only of PLAN slots is not a REF declaration, so its
|
|
377
|
+
# diagnostics must not send the creator looking for a REF line.
|
|
378
|
+
noun = "PLAN" if kinds == frozenset({"PLAN"}) else "REF"
|
|
379
|
+
if len(slots) != len(set(slots)):
|
|
380
|
+
errors.append(f"{owner}: {noun} 槽位重复")
|
|
381
|
+
if len(orders) != len(set(orders)) or sorted(orders) != list(
|
|
382
|
+
range(1, len(orders) + 1)
|
|
383
|
+
):
|
|
384
|
+
errors.append(f"{owner}: {noun} 顺序必须唯一且从 1 连续编号")
|
|
385
|
+
locator_purposes = list(zip(locators, purposes))
|
|
386
|
+
if len(locator_purposes) != len(set(locator_purposes)):
|
|
387
|
+
errors.append(f"{owner}: {noun} 路径与用途完全重复")
|
|
388
|
+
for slot, purpose in zip(slots, purposes):
|
|
389
|
+
if not purpose:
|
|
390
|
+
errors.append(
|
|
391
|
+
f"{owner}: {noun} 缺少用途: {slot};用途只能是"
|
|
392
|
+
f"{'、'.join(REF_PURPOSES)}其中一个"
|
|
393
|
+
)
|
|
394
|
+
elif purpose not in REF_PURPOSES:
|
|
395
|
+
errors.append(
|
|
396
|
+
f"{owner}: {noun} 用途不在允许集合内: {slot}({purpose});"
|
|
397
|
+
f"只能是{'、'.join(REF_PURPOSES)}"
|
|
398
|
+
)
|
|
399
|
+
for role in ("起始帧", "结束帧"):
|
|
400
|
+
if purposes.count(role) > 1:
|
|
401
|
+
errors.append(f"{owner}: 同一条目只能有一张{role}参考图")
|
|
402
|
+
if "结束帧" in purposes and "起始帧" not in purposes:
|
|
403
|
+
errors.append(f"{owner}: 绑定结束帧参考图时必须同时绑定起始帧")
|
|
404
|
+
for index, (kind, group) in enumerate(zip(slot_kinds, groups)):
|
|
405
|
+
slot, raw_locator, label = group[0], group[2], group[3]
|
|
406
|
+
may_control, must_not_control = group[5], group[6]
|
|
407
|
+
if kind == "REF":
|
|
408
|
+
if not _portable_path(raw_locator):
|
|
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}")
|
|
418
|
+
else:
|
|
419
|
+
_plan_locator(
|
|
420
|
+
raw_locator, label, owner, slot, purposes[index], entries, errors
|
|
421
|
+
)
|
|
422
|
+
if not re.search(r"[\u4e00-\u9fff]", label):
|
|
423
|
+
errors.append(f"{owner}: {kind} 缺少中文名称: {slot}")
|
|
424
|
+
if not may_control.strip() or not must_not_control.strip():
|
|
425
|
+
errors.append(f"{owner}: {kind} 必须同时声明控制与不得控制: {slot}")
|
|
426
|
+
allowed = {item.strip() for item in re.split(r"[、,,]", may_control)}
|
|
427
|
+
prohibited = {item.strip() for item in re.split(r"[、,,]", must_not_control)}
|
|
428
|
+
if "" in allowed or "" in prohibited or allowed & prohibited:
|
|
429
|
+
errors.append(f"{owner}: {kind} 控制与不得控制范围冲突: {slot}")
|
|
430
|
+
return kinds
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
def _plan_locator(
|
|
434
|
+
locator: str,
|
|
435
|
+
label: str,
|
|
436
|
+
owner: str,
|
|
437
|
+
slot: str,
|
|
438
|
+
purpose: str,
|
|
439
|
+
entries: Optional[EntryIndex],
|
|
440
|
+
errors: list[str],
|
|
441
|
+
) -> None:
|
|
442
|
+
"""A planned picture still has to name an entry that exists today.
|
|
443
|
+
|
|
444
|
+
The picture itself is the creator's to supply, but what it depicts is
|
|
445
|
+
already decided by an `IMG-...` board or a `SHOT-...` frozen keyframe. A
|
|
446
|
+
locator that resolves to neither is a reference nobody can act on.
|
|
447
|
+
"""
|
|
448
|
+
if entries is None:
|
|
449
|
+
return
|
|
450
|
+
if locator.startswith("IMG-"):
|
|
451
|
+
if locator not in entries.images:
|
|
452
|
+
errors.append(f"{owner}: PLAN 指向不存在的 IMG 条目: {locator}")
|
|
453
|
+
elif label != entries.images[locator]:
|
|
454
|
+
errors.append(f"{owner}: PLAN 中文名称与 IMG 标题不一致: {slot}")
|
|
455
|
+
elif locator.startswith("SHOT-"):
|
|
456
|
+
if locator not in entries.shots:
|
|
457
|
+
errors.append(f"{owner}: PLAN 指向不存在的 SHOT 条目: {locator}")
|
|
458
|
+
elif purpose == "起始帧":
|
|
459
|
+
# The picture that opens this shot is this shot's own frozen
|
|
460
|
+
# keyframe. Another shot's keyframe opens a different moment.
|
|
461
|
+
own = re.sub(r"^(?:SHOT|MOTION)-", "SHOT-", owner)
|
|
462
|
+
if own.startswith("SHOT-") and locator != own:
|
|
463
|
+
errors.append(
|
|
464
|
+
f"{owner}: 起始帧 PLAN 必须指向本镜的冻结关键帧: {locator}"
|
|
465
|
+
)
|
|
466
|
+
else:
|
|
467
|
+
errors.append(
|
|
468
|
+
f"{owner}: PLAN 定位符只能是 IMG-... 或 SHOT-...: {locator}"
|
|
469
|
+
)
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
def _excerpt(value: str, limit: int = 60) -> str:
|
|
473
|
+
"""A short, single-line quote of an offending line for a diagnostic."""
|
|
474
|
+
collapsed = re.sub(r"\s+", " ", value).strip()
|
|
475
|
+
return collapsed if len(collapsed) <= limit else collapsed[: limit - 1] + "…"
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
def _unique(values: "Iterable[str]") -> list[str]:
|
|
479
|
+
seen: set[str] = set()
|
|
480
|
+
result: list[str] = []
|
|
481
|
+
for value in values:
|
|
482
|
+
if value not in seen:
|
|
483
|
+
seen.add(value)
|
|
484
|
+
result.append(value)
|
|
485
|
+
return result
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
def _normalized(value: str) -> str:
|
|
489
|
+
"""Fold case and collapse whitespace so a hard-wrapped prompt still matches.
|
|
490
|
+
|
|
491
|
+
A copyable prompt is one rendered paragraph; the line breaks the repo's
|
|
492
|
+
Markdown style puts in it are not part of the text a creator wrote.
|
|
493
|
+
"""
|
|
494
|
+
return re.sub(r"\s+", " ", value).strip().casefold()
|
|
495
|
+
|
|
496
|
+
|
|
497
|
+
def _wordish(character: str) -> bool:
|
|
498
|
+
return bool(character) and character.isascii() and (
|
|
499
|
+
character.isalnum() or character == "-"
|
|
500
|
+
)
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+
def _carries_surface(prompt: str, surface: str) -> bool:
|
|
504
|
+
"""Is this lock surface actually asserted by this prompt?
|
|
505
|
+
|
|
506
|
+
Plain containment is not enough. `chipped white enamel mug` must not be
|
|
507
|
+
satisfied by `unchipped ... mug`, and `no pale blue sweater` in a negative
|
|
508
|
+
tail asserts the opposite of the fact the lock exists to hold.
|
|
509
|
+
"""
|
|
510
|
+
haystack = _normalized(prompt)
|
|
511
|
+
needle = _normalized(surface)
|
|
512
|
+
if not needle:
|
|
513
|
+
return False
|
|
514
|
+
start = haystack.find(needle)
|
|
515
|
+
while start != -1:
|
|
516
|
+
end = start + len(needle)
|
|
517
|
+
before = haystack[start - 1] if start else ""
|
|
518
|
+
after = haystack[end] if end < len(haystack) else ""
|
|
519
|
+
# Only ASCII words have boundaries to glue across. Chinese is written
|
|
520
|
+
# without spaces, so treating an adjacent CJK character as "glued" would
|
|
521
|
+
# make a Chinese surface impossible to satisfy.
|
|
522
|
+
glued = (
|
|
523
|
+
(_wordish(needle[:1]) and _wordish(before))
|
|
524
|
+
or (_wordish(needle[-1:]) and _wordish(after))
|
|
525
|
+
)
|
|
526
|
+
if not glued and NEGATION_RE.search(haystack[:start]) is None:
|
|
527
|
+
return True
|
|
528
|
+
start = haystack.find(needle, start + 1)
|
|
529
|
+
return False
|
|
530
|
+
|
|
531
|
+
|
|
532
|
+
class VisualEntry(NamedTuple):
|
|
533
|
+
"""One `视觉设定.md` entry plus every name a prompt body may call it by."""
|
|
534
|
+
|
|
535
|
+
category: str
|
|
536
|
+
name: str
|
|
537
|
+
designators: list[str]
|
|
538
|
+
|
|
539
|
+
@property
|
|
540
|
+
def key(self) -> tuple[str, str]:
|
|
541
|
+
return (self.category, self.name)
|
|
542
|
+
|
|
543
|
+
|
|
544
|
+
def _visual_entries(document: str, errors: list[str]) -> list[VisualEntry]:
|
|
545
|
+
"""Parse the 人物/造型/地点/道具 entries of one `视觉设定.md`.
|
|
546
|
+
|
|
547
|
+
A heading that *looks* like an entry but does not parse would silently drop
|
|
548
|
+
out of the coverage check and then reappear as "视觉依据 names an entry that
|
|
549
|
+
does not exist", so it is rejected here where the cause is visible.
|
|
550
|
+
"""
|
|
551
|
+
for line in VISUAL_SETTING_LINE_RE.findall(document):
|
|
552
|
+
if VISUAL_SETTING_HEADING_RE.match(line) is None:
|
|
553
|
+
errors.append(
|
|
554
|
+
"视觉设定.md: 条目标题必须写成 `## <人物|造型|地点|道具> · <名称>`: "
|
|
555
|
+
+ _excerpt(line)
|
|
556
|
+
)
|
|
557
|
+
for line in SCREEN_NAME_LINE_RE.findall(document):
|
|
558
|
+
if SCREEN_NAME_RE.match(line) is None:
|
|
559
|
+
errors.append(
|
|
560
|
+
"视觉设定.md: 画面代称必须写成 `- 画面代称:<正文里的拼写>`: "
|
|
561
|
+
+ _excerpt(line)
|
|
562
|
+
)
|
|
563
|
+
headings = list(VISUAL_SETTING_HEADING_RE.finditer(document))
|
|
564
|
+
entries: list[VisualEntry] = []
|
|
565
|
+
seen: set[tuple[str, str]] = set()
|
|
566
|
+
for index, heading in enumerate(headings):
|
|
567
|
+
end = (
|
|
568
|
+
headings[index + 1].start()
|
|
569
|
+
if index + 1 < len(headings)
|
|
570
|
+
else len(document)
|
|
571
|
+
)
|
|
572
|
+
category, name = heading.group(1), heading.group(2).strip()
|
|
573
|
+
if not name:
|
|
574
|
+
errors.append(f"视觉设定.md: {category}条目缺少名称")
|
|
575
|
+
continue
|
|
576
|
+
if (category, name) in seen:
|
|
577
|
+
errors.append(f"视觉设定.md: 条目重复: {category}「{name}」")
|
|
578
|
+
continue
|
|
579
|
+
seen.add((category, name))
|
|
580
|
+
declared = SCREEN_NAME_RE.findall(document[heading.end() : end])
|
|
581
|
+
if not declared:
|
|
582
|
+
# No declaration: the entry's own name is the designator, which is
|
|
583
|
+
# what a prompt body written in the project's own language calls it.
|
|
584
|
+
designators = [name]
|
|
585
|
+
elif all(_is_none(_plain(raw)) for raw in declared):
|
|
586
|
+
# `画面代称:无` is the deliberate opt-out for a name too common to
|
|
587
|
+
# match reliably in prose (道具「手机」 against 手机店). It removes the
|
|
588
|
+
# derived name too, otherwise the opt-out would do nothing.
|
|
589
|
+
designators = []
|
|
590
|
+
else:
|
|
591
|
+
designators = []
|
|
592
|
+
for raw in declared:
|
|
593
|
+
value = _plain(raw)
|
|
594
|
+
if _is_none(value):
|
|
595
|
+
continue
|
|
596
|
+
for item in re.split(r"[、,,]", value):
|
|
597
|
+
item = item.strip()
|
|
598
|
+
if item and item not in designators:
|
|
599
|
+
designators.append(item)
|
|
600
|
+
# Only a *declared* designator earns this diagnostic: the creator chose a
|
|
601
|
+
# spelling the matcher cannot honour and deserves to know. A one-character
|
|
602
|
+
# entry name they never declared is simply not name-matched, the same as
|
|
603
|
+
# any entry in a project whose prompt body is written in another language.
|
|
604
|
+
if declared:
|
|
605
|
+
for designator in designators:
|
|
606
|
+
if len(designator.strip()) < 2:
|
|
607
|
+
errors.append(
|
|
608
|
+
f"视觉设定.md: {category}「{name}」的画面代称「{designator}」"
|
|
609
|
+
"过短,无法在正文中可靠识别;请写成至少两个字符,或写「画面代称:无」"
|
|
610
|
+
)
|
|
611
|
+
entries.append(VisualEntry(category, name, designators))
|
|
612
|
+
return entries
|
|
613
|
+
|
|
614
|
+
|
|
615
|
+
def _named_entries(
|
|
616
|
+
prompt: str,
|
|
617
|
+
entries: list[VisualEntry],
|
|
618
|
+
*,
|
|
619
|
+
fold_case: bool = False,
|
|
620
|
+
by_entry_name: bool = False,
|
|
621
|
+
) -> set[tuple[str, str]]:
|
|
622
|
+
"""Which declared entries does this frozen keyframe actually call by name?
|
|
623
|
+
|
|
624
|
+
Longer designators win: a keyframe that shows 「江晨手机」 names the prop, and
|
|
625
|
+
the 人物「江晨」 substring inside it is not a second, unrelated claim.
|
|
626
|
+
"""
|
|
627
|
+
haystack = re.sub(r"\s+", " ", prompt).strip()
|
|
628
|
+
if fold_case:
|
|
629
|
+
haystack = haystack.casefold()
|
|
630
|
+
owners: dict[str, list[VisualEntry]] = {}
|
|
631
|
+
for entry in entries:
|
|
632
|
+
# 来源 quotes 剧本.md, which is written in the project's own language and
|
|
633
|
+
# calls people by their entry names — not by the 画面代称 a prompt body
|
|
634
|
+
# uses. Matching a Chinese screenplay quote against an English designator
|
|
635
|
+
# would never fire, so that text is matched by entry name instead. An
|
|
636
|
+
# entry that opted out with `画面代称:无` opted out of being found by
|
|
637
|
+
# name at all, and stays out of both.
|
|
638
|
+
needles = (
|
|
639
|
+
[entry.name] if by_entry_name and entry.designators else entry.designators
|
|
640
|
+
)
|
|
641
|
+
for designator in needles:
|
|
642
|
+
needle = re.sub(r"\s+", " ", designator).strip()
|
|
643
|
+
if fold_case:
|
|
644
|
+
needle = needle.casefold()
|
|
645
|
+
# A one-character designator matches far too much prose to be
|
|
646
|
+
# evidence; _visual_entries already reports it.
|
|
647
|
+
if len(needle) < 2:
|
|
648
|
+
continue
|
|
649
|
+
owners.setdefault(needle, []).append(entry)
|
|
650
|
+
named: set[tuple[str, str]] = set()
|
|
651
|
+
# Spans of every occurrence of a longer designator, whether or not it was
|
|
652
|
+
# credited. 「戒指盒」 occupies its characters even when the sentence is
|
|
653
|
+
# 「没有戒指」, so 道具「戒指」 must not be read out of it.
|
|
654
|
+
taken: list[tuple[int, int]] = []
|
|
655
|
+
for needle in sorted(owners, key=len, reverse=True):
|
|
656
|
+
occurrences: list[tuple[int, int]] = []
|
|
657
|
+
start = haystack.find(needle)
|
|
658
|
+
while start != -1:
|
|
659
|
+
end = start + len(needle)
|
|
660
|
+
occurrences.append((start, end))
|
|
661
|
+
start = haystack.find(needle, start + 1)
|
|
662
|
+
for start, end in occurrences:
|
|
663
|
+
before = haystack[start - 1] if start else ""
|
|
664
|
+
after = haystack[end] if end < len(haystack) else ""
|
|
665
|
+
glued = (
|
|
666
|
+
(_wordish(needle[:1]) and _wordish(before))
|
|
667
|
+
or (_wordish(needle[-1:]) and _wordish(after))
|
|
668
|
+
)
|
|
669
|
+
# Strictly longer, so two entries sharing one designator are both
|
|
670
|
+
# credited instead of the first in document order winning.
|
|
671
|
+
inside_longer = any(
|
|
672
|
+
taken_start <= start
|
|
673
|
+
and end <= taken_end
|
|
674
|
+
and taken_end - taken_start > end - start
|
|
675
|
+
for taken_start, taken_end in taken
|
|
676
|
+
)
|
|
677
|
+
if (
|
|
678
|
+
not glued
|
|
679
|
+
and not inside_longer
|
|
680
|
+
and NEGATION_RE.search(haystack[:start]) is None
|
|
681
|
+
):
|
|
682
|
+
named.update(entry.key for entry in owners[needle])
|
|
683
|
+
taken.extend(occurrences)
|
|
684
|
+
return named
|
|
685
|
+
|
|
686
|
+
|
|
687
|
+
class VisualBasis(NamedTuple):
|
|
688
|
+
"""One shot's parsed 视觉依据: what it declares, and what it excludes."""
|
|
689
|
+
|
|
690
|
+
parsed: bool
|
|
691
|
+
declared: set[tuple[str, str]]
|
|
692
|
+
offscreen: set[tuple[str, str]]
|
|
693
|
+
|
|
694
|
+
@property
|
|
695
|
+
def accounted(self) -> set[tuple[str, str]]:
|
|
696
|
+
return self.declared | self.offscreen
|
|
697
|
+
|
|
698
|
+
|
|
699
|
+
def _resolve_entry(
|
|
700
|
+
key: tuple[str, str],
|
|
701
|
+
known: set[tuple[str, str]],
|
|
702
|
+
other_headings: dict[str, str],
|
|
703
|
+
owner: str,
|
|
704
|
+
errors: list[str],
|
|
705
|
+
) -> None:
|
|
706
|
+
if key in known:
|
|
707
|
+
return
|
|
708
|
+
category, name = key
|
|
709
|
+
section = other_headings.get(name)
|
|
710
|
+
if section is not None:
|
|
711
|
+
errors.append(
|
|
712
|
+
f"{owner}: 《视觉设定.md》里有「{name}」,但它的分节词是「{section}」;"
|
|
713
|
+
"条目标题必须写成 `## <人物|造型|地点|道具> · <名称>`"
|
|
714
|
+
)
|
|
715
|
+
else:
|
|
716
|
+
errors.append(
|
|
717
|
+
f"{owner}: 视觉依据指向不存在的《视觉设定.md》条目: {category}「{name}」"
|
|
718
|
+
)
|
|
719
|
+
|
|
720
|
+
|
|
721
|
+
def _check_named_coverage(
|
|
722
|
+
prompt: str,
|
|
723
|
+
owner: str,
|
|
724
|
+
where: str,
|
|
725
|
+
basis: "VisualBasis",
|
|
726
|
+
entries: list[VisualEntry],
|
|
727
|
+
errors: list[str],
|
|
728
|
+
*,
|
|
729
|
+
by_entry_name: bool = False,
|
|
730
|
+
) -> None:
|
|
731
|
+
"""Every entry this text calls by name is either in frame or declared 画外."""
|
|
732
|
+
# Matching is case-sensitive so an ordinary English word never impersonates a
|
|
733
|
+
# character called May or Will. A body that writes the name in another case
|
|
734
|
+
# would otherwise fall out of the check silently, so it is reported here.
|
|
735
|
+
named = _named_entries(prompt, entries, by_entry_name=by_entry_name)
|
|
736
|
+
folded = _named_entries(
|
|
737
|
+
prompt, entries, fold_case=True, by_entry_name=by_entry_name
|
|
738
|
+
)
|
|
739
|
+
for category, name in sorted(folded - named):
|
|
740
|
+
errors.append(
|
|
741
|
+
f"{owner}: {where}里的名字与画面代称大小写不一致: {category}「{name}」;"
|
|
742
|
+
"同一个名字全集只用一个拼写"
|
|
743
|
+
)
|
|
744
|
+
for category, name in sorted(named):
|
|
745
|
+
if (category, name) not in basis.accounted:
|
|
746
|
+
errors.append(
|
|
747
|
+
f"{owner}: {where}写到{category}「{name}」,视觉依据没有覆盖;"
|
|
748
|
+
"本镜确实看不见时在视觉依据末尾加「;画外:"
|
|
749
|
+
f"{category}「{name}」」,正文里这个名字不可靠时在《视觉设定.md》"
|
|
750
|
+
"写「画面代称:无」"
|
|
751
|
+
)
|
|
752
|
+
|
|
753
|
+
|
|
754
|
+
def _visual_basis(
|
|
755
|
+
value: str,
|
|
756
|
+
owner: str,
|
|
757
|
+
entries: list[VisualEntry],
|
|
758
|
+
other_headings: dict[str, str],
|
|
759
|
+
errors: list[str],
|
|
760
|
+
) -> VisualBasis:
|
|
761
|
+
"""Parse one shot's 视觉依据 and resolve it against `视觉设定.md`."""
|
|
762
|
+
empty = VisualBasis(True, set(), set())
|
|
763
|
+
if _is_none(value):
|
|
764
|
+
return empty
|
|
765
|
+
plain = _plain(value)
|
|
766
|
+
offscreen_raw = ""
|
|
767
|
+
if OFFSCREEN_PREFIX in plain:
|
|
768
|
+
plain, _, offscreen_raw = plain.partition(OFFSCREEN_PREFIX)
|
|
769
|
+
prefixed = plain.startswith(VISUAL_BASIS_PREFIX)
|
|
770
|
+
body = plain[len(VISUAL_BASIS_PREFIX) :] if prefixed else ""
|
|
771
|
+
matches = list(VISUAL_BASIS_ENTRY_RE.finditer(body)) if prefixed else []
|
|
772
|
+
cursor = 0
|
|
773
|
+
separators_are_valid = True
|
|
774
|
+
for index, match in enumerate(matches):
|
|
775
|
+
# Repeating 《视觉设定.md》· before each entry says the same thing and
|
|
776
|
+
# reads naturally; rejecting it would cost a round trip over nothing.
|
|
777
|
+
separator = body[cursor : match.start()]
|
|
778
|
+
allowed = (
|
|
779
|
+
{""}
|
|
780
|
+
if index == 0
|
|
781
|
+
else {";", ";" + VISUAL_BASIS_PREFIX}
|
|
782
|
+
)
|
|
783
|
+
if separator not in allowed:
|
|
784
|
+
separators_are_valid = False
|
|
785
|
+
cursor = match.end()
|
|
786
|
+
if not prefixed or not matches or not separators_are_valid or body[cursor:]:
|
|
787
|
+
errors.append(
|
|
788
|
+
f"{owner}: 视觉依据必须使用完整语法:"
|
|
789
|
+
"《视觉设定.md》·<人物|造型|地点|道具>「<名称>」(控制:<范围>),多项用;连接"
|
|
790
|
+
)
|
|
791
|
+
# Coverage is not reported on top of a parse failure: every entry would
|
|
792
|
+
# be listed as uncovered and bury the one error that matters.
|
|
793
|
+
return VisualBasis(False, set(), set())
|
|
794
|
+
known = {entry.key for entry in entries}
|
|
795
|
+
declared: set[tuple[str, str]] = set()
|
|
796
|
+
for match in matches:
|
|
797
|
+
key = (match.group(1), match.group(2).strip())
|
|
798
|
+
if key in declared:
|
|
799
|
+
errors.append(f"{owner}: 视觉依据条目重复: {key[0]}「{key[1]}」")
|
|
800
|
+
declared.add(key)
|
|
801
|
+
_resolve_entry(key, known, other_headings, owner, errors)
|
|
802
|
+
if not match.group(3).strip():
|
|
803
|
+
errors.append(f"{owner}: 视觉依据缺少控制范围: {key[0]}「{key[1]}」")
|
|
804
|
+
offscreen: set[tuple[str, str]] = set()
|
|
805
|
+
if offscreen_raw:
|
|
806
|
+
offscreen_raw = offscreen_raw.replace(VISUAL_BASIS_PREFIX, "")
|
|
807
|
+
remainder = OFFSCREEN_ENTRY_RE.sub("", offscreen_raw).strip(";、 ")
|
|
808
|
+
offscreen_matches = list(OFFSCREEN_ENTRY_RE.finditer(offscreen_raw))
|
|
809
|
+
if not offscreen_matches or remainder:
|
|
810
|
+
errors.append(
|
|
811
|
+
f"{owner}: 画外清单必须写成 `;画外:<人物|造型|地点|道具>「<名称>」`,多项用;连接"
|
|
812
|
+
)
|
|
813
|
+
return VisualBasis(False, set(), set())
|
|
814
|
+
for match in offscreen_matches:
|
|
815
|
+
key = (match.group(1), match.group(2).strip())
|
|
816
|
+
if key in declared:
|
|
817
|
+
errors.append(
|
|
818
|
+
f"{owner}: {key[0]}「{key[1]}」同时写进视觉依据和画外清单"
|
|
819
|
+
)
|
|
820
|
+
offscreen.add(key)
|
|
821
|
+
_resolve_entry(key, known, other_headings, owner, errors)
|
|
822
|
+
return VisualBasis(True, declared, offscreen)
|
|
823
|
+
|
|
824
|
+
|
|
825
|
+
class ContinuityLock(NamedTuple):
|
|
826
|
+
"""One declared cross-shot lock: the exact surface and where it applies."""
|
|
827
|
+
|
|
828
|
+
lock_id: str
|
|
829
|
+
surface: str
|
|
830
|
+
shots: list[str]
|
|
831
|
+
images: list[str]
|
|
832
|
+
|
|
833
|
+
|
|
834
|
+
def _continuity_locks(document: str, errors: list[str]) -> list[ContinuityLock]:
|
|
835
|
+
"""Parse the declared continuity locks of one 视觉设定.md."""
|
|
836
|
+
locks: list[ContinuityLock] = []
|
|
837
|
+
seen: set[str] = set()
|
|
838
|
+
for line in LOCK_LINE_RE.findall(document):
|
|
839
|
+
match = LOCK_RE.match(line)
|
|
840
|
+
if match is None:
|
|
841
|
+
errors.append(
|
|
842
|
+
"视觉设定.md: 连续性锁必须使用完整语法: " + _excerpt(line)
|
|
843
|
+
)
|
|
844
|
+
continue
|
|
845
|
+
lock_id, label, scope, image_scope, surface = match.groups()
|
|
846
|
+
if lock_id in seen:
|
|
847
|
+
errors.append(f"{lock_id}: 连续性锁 ID 重复")
|
|
848
|
+
continue
|
|
849
|
+
seen.add(lock_id)
|
|
850
|
+
if not re.search(r"[\u3400-\u9fff]", label):
|
|
851
|
+
errors.append(f"{lock_id}: 连续性锁缺少中文名称")
|
|
852
|
+
surface = _plain(surface)
|
|
853
|
+
if not surface:
|
|
854
|
+
errors.append(f"{lock_id}: 连续性锁缺少锁面")
|
|
855
|
+
continue
|
|
856
|
+
shots = _unique(
|
|
857
|
+
item.strip() for item in re.split(r"[、,,]", _plain(scope)) if item.strip()
|
|
858
|
+
)
|
|
859
|
+
if not shots:
|
|
860
|
+
errors.append(f"{lock_id}: 连续性锁缺少镜头范围")
|
|
861
|
+
continue
|
|
862
|
+
if "全集" in shots and len(shots) != 1:
|
|
863
|
+
errors.append(f"{lock_id}: 连续性锁的镜头范围不能把全集与具体镜头混写")
|
|
864
|
+
continue
|
|
865
|
+
images: list[str] = []
|
|
866
|
+
if image_scope is not None and not _is_none(image_scope):
|
|
867
|
+
images = _unique(
|
|
868
|
+
item.strip()
|
|
869
|
+
for item in re.split(r"[、,,]", _plain(image_scope))
|
|
870
|
+
if item.strip()
|
|
871
|
+
)
|
|
872
|
+
if any(not item.startswith("IMG-") for item in images):
|
|
873
|
+
errors.append(f"{lock_id}: 连续性锁的图片提示词项必须使用 IMG- ID")
|
|
874
|
+
continue
|
|
875
|
+
locks.append(ContinuityLock(lock_id, surface, shots, images))
|
|
876
|
+
return locks
|
|
877
|
+
|
|
878
|
+
|
|
879
|
+
def _prompt_language(project_root: Path) -> str:
|
|
880
|
+
"""The language a copyable prompt body is written in.
|
|
881
|
+
|
|
882
|
+
Mirrors the skills' own routing: the project's declared prompt language,
|
|
883
|
+
falling back to `en` when there is no `short-drama.json` -- which is what
|
|
884
|
+
the storyboard skill tells the keyframe author to assume.
|
|
885
|
+
"""
|
|
886
|
+
configuration = project_root / "short-drama.json"
|
|
887
|
+
if not configuration.is_file():
|
|
888
|
+
return "en"
|
|
889
|
+
try:
|
|
890
|
+
project = json.loads(configuration.read_text(encoding="utf-8"))
|
|
891
|
+
except (OSError, ValueError):
|
|
892
|
+
return "en"
|
|
893
|
+
if not isinstance(project, dict):
|
|
894
|
+
return "en"
|
|
895
|
+
authority = project.get("creator_authority")
|
|
896
|
+
profile = (
|
|
897
|
+
authority.get("production_profile") if isinstance(authority, dict) else None
|
|
898
|
+
)
|
|
899
|
+
if isinstance(profile, dict) and profile.get("status") == "accepted":
|
|
900
|
+
choices = profile.get("choices")
|
|
901
|
+
if isinstance(choices, dict):
|
|
902
|
+
declared = choices.get("video_prompt_language")
|
|
903
|
+
if isinstance(declared, str) and declared:
|
|
904
|
+
return declared
|
|
905
|
+
formats = project.get("format")
|
|
906
|
+
declared = formats.get("prompt_language") if isinstance(formats, dict) else None
|
|
907
|
+
return declared if isinstance(declared, str) and declared else "en"
|
|
908
|
+
|
|
909
|
+
|
|
910
|
+
def _check_language_designators(
|
|
911
|
+
project_root: Path, *, entries: list[VisualEntry], errors: list[str]
|
|
912
|
+
) -> None:
|
|
913
|
+
"""Every character must be nameable in the prompt body's language.
|
|
914
|
+
|
|
915
|
+
Without this, omitting `画面代称` is a silent opt-out of the coverage check
|
|
916
|
+
for exactly the projects that need it -- the keyframe body defaults to `en`
|
|
917
|
+
while `视觉设定.md` is Chinese, which is the shape issue #94 reported.
|
|
918
|
+
|
|
919
|
+
This deliberately does not wait until some shot's 视觉依据 references the
|
|
920
|
+
entry. Gating on that made the two halves of the contract land in different
|
|
921
|
+
rounds: a document missing 视觉依据 got one error, and only after fixing it
|
|
922
|
+
did the missing 画面代称 appear.
|
|
923
|
+
"""
|
|
924
|
+
if _prompt_language(project_root).casefold().startswith("zh"):
|
|
925
|
+
return
|
|
926
|
+
for entry in entries:
|
|
927
|
+
if entry.category != "人物":
|
|
928
|
+
continue
|
|
929
|
+
if entry.designators == [entry.name]:
|
|
930
|
+
errors.append(
|
|
931
|
+
f"视觉设定.md: 人物「{entry.name}」缺少画面代称;"
|
|
932
|
+
"提示词正文不是中文时,写「画面代称:<正文里的拼写>」,"
|
|
933
|
+
"正文从不点名时写「画面代称:无」"
|
|
934
|
+
)
|
|
935
|
+
|
|
936
|
+
|
|
937
|
+
def _declared_seconds(value: str) -> Optional[float]:
|
|
938
|
+
"""The number of seconds a 时长 field declares, however it is spelled.
|
|
939
|
+
|
|
940
|
+
`4s`, `4 秒` and `4秒` are the same duration. Returning None for anything
|
|
941
|
+
else keeps an unparseable value out of the comparison instead of turning a
|
|
942
|
+
formatting difference into a false duration conflict.
|
|
943
|
+
"""
|
|
944
|
+
match = re.search(r"(\d+(?:\.\d+)?)", value or "")
|
|
945
|
+
return float(match.group(1)) if match else None
|
|
946
|
+
|
|
947
|
+
|
|
948
|
+
def _check_continuity_locks(
|
|
949
|
+
locks: list[ContinuityLock],
|
|
950
|
+
*,
|
|
951
|
+
shots: dict[str, str],
|
|
952
|
+
motion_by_shot: dict[str, tuple[str, str, Optional[str]]],
|
|
953
|
+
image_prompts: dict[str, Optional[str]],
|
|
954
|
+
errors: list[str],
|
|
955
|
+
) -> None:
|
|
956
|
+
"""Require every declared lock surface to be present where it was scoped."""
|
|
957
|
+
for lock in locks:
|
|
958
|
+
lock_id = lock.lock_id
|
|
959
|
+
surface = lock.surface
|
|
960
|
+
targets = sorted(shots) if lock.shots == ["全集"] else lock.shots
|
|
961
|
+
for shot_id in targets:
|
|
962
|
+
if shot_id not in shots:
|
|
963
|
+
errors.append(f"{lock_id}: 连续性锁指向不存在的镜头: {shot_id}")
|
|
964
|
+
continue
|
|
965
|
+
keyframe = _copyable_prompt(shots[shot_id], heading=r"冻结关键帧提示词")
|
|
966
|
+
if keyframe is None:
|
|
967
|
+
errors.append(f"{lock_id}: {shot_id} 缺少可读的冻结关键帧提示词")
|
|
968
|
+
elif not _carries_surface(keyframe, surface):
|
|
969
|
+
errors.append(f"{lock_id}: {shot_id} 冻结关键帧提示词缺少锁面")
|
|
970
|
+
motion = motion_by_shot.get(shot_id)
|
|
971
|
+
if motion is None:
|
|
972
|
+
continue
|
|
973
|
+
motion_id, _, copyable_prompt = motion
|
|
974
|
+
if copyable_prompt is not None and not _carries_surface(
|
|
975
|
+
copyable_prompt, surface
|
|
976
|
+
):
|
|
977
|
+
errors.append(f"{lock_id}: {motion_id} 可复制提示词缺少锁面")
|
|
978
|
+
for image_id in lock.images:
|
|
979
|
+
if image_id not in image_prompts:
|
|
980
|
+
errors.append(f"{lock_id}: 连续性锁指向不存在的 IMG 条目: {image_id}")
|
|
981
|
+
continue
|
|
982
|
+
image_prompt = image_prompts[image_id]
|
|
983
|
+
if image_prompt is not None and not _carries_surface(image_prompt, surface):
|
|
984
|
+
errors.append(f"{lock_id}: {image_id} 可复制提示词缺少锁面")
|
|
985
|
+
|
|
986
|
+
|
|
987
|
+
def _storyboard_head(storyboard: str) -> str:
|
|
988
|
+
"""The part of 分镜.md that belongs to the episode rather than to a shot."""
|
|
989
|
+
match = re.search(r"^## SHOT-", storyboard, re.MULTILINE)
|
|
990
|
+
return storyboard if match is None else storyboard[: match.start()]
|
|
991
|
+
|
|
992
|
+
|
|
993
|
+
def _shot_sources(
|
|
994
|
+
value: str, owner: str, scenes: "frozenset", errors: list[str]
|
|
995
|
+
) -> list:
|
|
996
|
+
"""Resolve one shot's 来源 against the scene ids that exist in 剧本.md."""
|
|
997
|
+
plain = _plain(value)
|
|
998
|
+
if not plain:
|
|
999
|
+
errors.append(f"{owner}: 缺少来源字段")
|
|
1000
|
+
return []
|
|
1001
|
+
# 《镜头手艺》 asks for the scene id plus a short quote of the source line,
|
|
1002
|
+
# and a quote contains commas of its own. So the ids are read out of the
|
|
1003
|
+
# field rather than by cutting it into pieces on punctuation.
|
|
1004
|
+
if SCENE_ID_RE.match(plain) is None:
|
|
1005
|
+
errors.append(
|
|
1006
|
+
f"{owner}: 来源必须以《剧本.md》的场景 ID 开头: {_excerpt(plain, 24)}"
|
|
1007
|
+
)
|
|
1008
|
+
return []
|
|
1009
|
+
resolved = []
|
|
1010
|
+
for scene_id in _unique(SCENE_ID_RE.findall(plain)):
|
|
1011
|
+
if scene_id not in scenes:
|
|
1012
|
+
errors.append(f"{owner}: 来源场景不在《剧本.md》中: {scene_id}")
|
|
1013
|
+
else:
|
|
1014
|
+
resolved.append(scene_id)
|
|
1015
|
+
return resolved
|
|
1016
|
+
|
|
1017
|
+
|
|
1018
|
+
def _unfilmed_scenes(
|
|
1019
|
+
storyboard: str, scenes: "frozenset", errors: list[str]
|
|
1020
|
+
) -> dict:
|
|
1021
|
+
"""Scenes 分镜.md declares it deliberately does not film, with reasons."""
|
|
1022
|
+
head = _storyboard_head(storyboard)
|
|
1023
|
+
declared: dict = {}
|
|
1024
|
+
# The line belongs to the episode, not to a shot. Placed inside a SHOT
|
|
1025
|
+
# section it would be read as that shot's field and quietly do nothing,
|
|
1026
|
+
# which looks exactly like forgetting to write it.
|
|
1027
|
+
if len(UNFILMED_LINE_RE.findall(storyboard)) != len(
|
|
1028
|
+
UNFILMED_LINE_RE.findall(head)
|
|
1029
|
+
):
|
|
1030
|
+
errors.append("分镜.md: 未拍场次要写在第一个 SHOT 之前的正文开头")
|
|
1031
|
+
for value in UNFILMED_LINE_RE.findall(head):
|
|
1032
|
+
value = value.strip()
|
|
1033
|
+
entries = UNFILMED_ENTRY_RE.findall(value)
|
|
1034
|
+
remainder = UNFILMED_ENTRY_RE.sub("", value).strip(";;。 ")
|
|
1035
|
+
if not entries or remainder:
|
|
1036
|
+
errors.append(
|
|
1037
|
+
"分镜.md: 未拍场次必须写成 <场景 ID>(理由:……),多项用;分隔"
|
|
1038
|
+
)
|
|
1039
|
+
continue
|
|
1040
|
+
for scene_id, reason in entries:
|
|
1041
|
+
if not reason.strip():
|
|
1042
|
+
errors.append(f"分镜.md: 未拍场次缺少理由: {scene_id}")
|
|
1043
|
+
elif scene_id not in scenes:
|
|
1044
|
+
errors.append(f"分镜.md: 未拍场次不在《剧本.md》中: {scene_id}")
|
|
1045
|
+
else:
|
|
1046
|
+
declared[scene_id] = reason.strip()
|
|
1047
|
+
return declared
|
|
1048
|
+
|
|
1049
|
+
|
|
1050
|
+
def _spoken_key(value: str) -> str:
|
|
1051
|
+
"""Drop punctuation and spacing so quoting choices do not decide identity."""
|
|
1052
|
+
return re.sub(r"[^\u3400-\u9fff0-9A-Za-z]", "", value)
|
|
1053
|
+
|
|
1054
|
+
|
|
1055
|
+
def _quoted_speech(prompt: str) -> list:
|
|
1056
|
+
# The body is one prompt; a quote that happens to wrap across lines is the
|
|
1057
|
+
# same quote. Chinese does not separate words with spaces, so rejoining the
|
|
1058
|
+
# lines reconstructs the run exactly.
|
|
1059
|
+
joined = prompt.replace("\n", "")
|
|
1060
|
+
runs: list = []
|
|
1061
|
+
for match in SPOKEN_SPAN_RE.finditer(joined):
|
|
1062
|
+
span = next((group for group in match.groups() if group), "")
|
|
1063
|
+
# An unpaired quotation mark can pair with one much later and swallow
|
|
1064
|
+
# the narration between them. That span is not a quotation, so it is
|
|
1065
|
+
# not evidence of an invented line either.
|
|
1066
|
+
if len(span) > 200:
|
|
1067
|
+
continue
|
|
1068
|
+
runs.extend(SPOKEN_RUN_RE.findall(span))
|
|
1069
|
+
return runs
|
|
1070
|
+
|
|
1071
|
+
|
|
1072
|
+
def _check_spoken_lines(
|
|
1073
|
+
prompt: str, owner: str, sources: "tuple", errors: list[str]
|
|
1074
|
+
) -> None:
|
|
1075
|
+
"""Quoted Chinese in a copyable body has to come from an upstream document.
|
|
1076
|
+
|
|
1077
|
+
The video stage already owes the screenplay verbatim dialogue. Checking it
|
|
1078
|
+
here is what stops a prompt from writing the line the shot wishes the
|
|
1079
|
+
character had said. 视觉设定.md and 分镜.md count as sources too: a location
|
|
1080
|
+
or prop entry carries the exact text on a sign, and a shot's own fields
|
|
1081
|
+
carry the on-screen text and sound this shot was designed around. What no
|
|
1082
|
+
upstream document contains is a line the prompt made up.
|
|
1083
|
+
"""
|
|
1084
|
+
for run in _quoted_speech(prompt):
|
|
1085
|
+
key = _spoken_key(run)
|
|
1086
|
+
if key and not any(key in source for source in sources):
|
|
1087
|
+
errors.append(
|
|
1088
|
+
f"{owner}: 可复制提示词的引文在《剧本.md》《视觉设定.md》《分镜.md》里都找不到: "
|
|
1089
|
+
f"{_excerpt(run, 24)}"
|
|
1090
|
+
)
|
|
1091
|
+
|
|
1092
|
+
|
|
1093
|
+
def validate_episode(episode: Path, project_root: Optional[Path] = None) -> list[str]:
|
|
1094
|
+
"""Return all deterministic contract errors for ``episode``."""
|
|
1095
|
+
episode = episode.resolve()
|
|
1096
|
+
project_root = (project_root or episode.parent.parent).resolve()
|
|
1097
|
+
errors: list[str] = []
|
|
1098
|
+
missing = [name for name in REQUIRED_DOCUMENTS if not (episode / name).is_file()]
|
|
1099
|
+
if missing:
|
|
1100
|
+
return [f"缺少创作文档: {', '.join(missing)}"]
|
|
1101
|
+
|
|
1102
|
+
images = (episode / "图片提示词.md").read_text(encoding="utf-8")
|
|
1103
|
+
storyboard = (episode / "分镜.md").read_text(encoding="utf-8")
|
|
1104
|
+
video = (episode / "视频提示词.md").read_text(encoding="utf-8")
|
|
1105
|
+
visual = (episode / "视觉设定.md").read_text(encoding="utf-8")
|
|
1106
|
+
screenplay = (episode / "剧本.md").read_text(encoding="utf-8")
|
|
1107
|
+
scene_headings = SCENE_HEADING_RE.findall(screenplay)
|
|
1108
|
+
scenes = frozenset(scene_headings)
|
|
1109
|
+
if not scenes:
|
|
1110
|
+
errors.append(
|
|
1111
|
+
"剧本.md: 没有可解析的场景标题;场景标题写成 ## <场景 ID> 内|外 · 地点 · 时间"
|
|
1112
|
+
)
|
|
1113
|
+
if len(scene_headings) != len(scenes):
|
|
1114
|
+
errors.append("剧本.md: 场景 ID 重复")
|
|
1115
|
+
spoken_sources = (
|
|
1116
|
+
_spoken_key(screenplay),
|
|
1117
|
+
_spoken_key(visual),
|
|
1118
|
+
_spoken_key(storyboard),
|
|
1119
|
+
)
|
|
1120
|
+
unfilmed = _unfilmed_scenes(storyboard, scenes, errors)
|
|
1121
|
+
claimed_scenes: set = set()
|
|
1122
|
+
locks = _continuity_locks(visual, errors)
|
|
1123
|
+
visual_entries = _visual_entries(visual, errors)
|
|
1124
|
+
other_headings = {
|
|
1125
|
+
match.group(2).strip(): match.group(1).strip()
|
|
1126
|
+
for match in OTHER_SETTING_HEADING_RE.finditer(visual)
|
|
1127
|
+
if match.group(1).strip() not in VISUAL_CATEGORIES
|
|
1128
|
+
}
|
|
1129
|
+
shots = _sections(storyboard, "SHOT")
|
|
1130
|
+
image_pairs = re.findall(r"^## (IMG-[A-Z0-9-]+) · (.+)$", images, re.MULTILINE)
|
|
1131
|
+
image_headings = dict(image_pairs)
|
|
1132
|
+
# A planned picture names the board or keyframe that already decides what it
|
|
1133
|
+
# depicts, so both indexes have to exist before any declaration is read.
|
|
1134
|
+
entries = EntryIndex(images=image_headings, shots=frozenset(shots))
|
|
1135
|
+
all_image_headings = re.findall(r"^## (IMG-[A-Z0-9-]+)\b", images, re.MULTILINE)
|
|
1136
|
+
if len(image_pairs) != len(all_image_headings):
|
|
1137
|
+
errors.append("图片提示词.md: IMG 标题必须包含中文名称")
|
|
1138
|
+
if len(image_pairs) != len(image_headings):
|
|
1139
|
+
errors.append("图片提示词.md: IMG 标题 ID 重复")
|
|
1140
|
+
for image_id, label in image_pairs:
|
|
1141
|
+
if not re.search(r"[\u3400-\u9fff]", label):
|
|
1142
|
+
errors.append(f"{image_id}: IMG 标题缺少中文名称")
|
|
1143
|
+
image_matches = list(re.finditer(r"^## (IMG-[A-Z0-9-]+)\b", images, re.MULTILINE))
|
|
1144
|
+
image_prompts: dict[str, Optional[str]] = {}
|
|
1145
|
+
for index, match in enumerate(image_matches):
|
|
1146
|
+
body = images[
|
|
1147
|
+
match.start() : image_matches[index + 1].start()
|
|
1148
|
+
if index + 1 < len(image_matches)
|
|
1149
|
+
else None
|
|
1150
|
+
]
|
|
1151
|
+
reference_value = _fields(body, owner=match.group(1), errors=errors).get(
|
|
1152
|
+
"参考", ""
|
|
1153
|
+
)
|
|
1154
|
+
if not reference_value:
|
|
1155
|
+
errors.append(f"{match.group(1)}: 缺少参考字段")
|
|
1156
|
+
elif _is_no_external_reference(reference_value):
|
|
1157
|
+
pass
|
|
1158
|
+
elif "REF-" in reference_value:
|
|
1159
|
+
_references(
|
|
1160
|
+
reference_value, match.group(1), project_root, errors, entries
|
|
1161
|
+
)
|
|
1162
|
+
else:
|
|
1163
|
+
errors.append(
|
|
1164
|
+
f"{match.group(1)}: 参考必须声明无外部参考或使用完整 REF 语法"
|
|
1165
|
+
)
|
|
1166
|
+
image_prompt = _copyable_prompt(body)
|
|
1167
|
+
image_prompts[match.group(1)] = image_prompt
|
|
1168
|
+
if image_prompt is None:
|
|
1169
|
+
errors.append(
|
|
1170
|
+
f"{match.group(1)}: 缺少唯一且非空的可复制提示词"
|
|
1171
|
+
+ _copyable_prompt_cause(body)
|
|
1172
|
+
)
|
|
1173
|
+
|
|
1174
|
+
motions = _sections(video, "MOTION")
|
|
1175
|
+
shot_ids = re.findall(r"^## (SHOT-[A-Z0-9-]+)\b", storyboard, re.MULTILINE)
|
|
1176
|
+
motion_ids = re.findall(r"^## (MOTION-[A-Z0-9-]+)\b", video, re.MULTILINE)
|
|
1177
|
+
if len(shot_ids) != len(set(shot_ids)):
|
|
1178
|
+
errors.append("分镜.md: SHOT 标题 ID 重复")
|
|
1179
|
+
if len(motion_ids) != len(set(motion_ids)):
|
|
1180
|
+
errors.append("视频提示词.md: MOTION 标题 ID 重复")
|
|
1181
|
+
named_shots = re.findall(
|
|
1182
|
+
r"^## (SHOT-[A-Z0-9-]+) · ([^\n]+)$", storyboard, re.MULTILINE
|
|
1183
|
+
)
|
|
1184
|
+
named_motions = re.findall(
|
|
1185
|
+
r"^## (MOTION-[A-Z0-9-]+) · ([^\n]+)$", video, re.MULTILINE
|
|
1186
|
+
)
|
|
1187
|
+
if len(named_shots) != len(shot_ids):
|
|
1188
|
+
errors.append("分镜.md: SHOT 标题必须包含中文名称")
|
|
1189
|
+
if len(named_motions) != len(motion_ids):
|
|
1190
|
+
errors.append("视频提示词.md: MOTION 标题必须包含中文名称")
|
|
1191
|
+
for shot_id, label in named_shots:
|
|
1192
|
+
if not re.search(r"[\u3400-\u9fff]", label):
|
|
1193
|
+
errors.append(f"{shot_id}: SHOT 标题缺少中文名称")
|
|
1194
|
+
for motion_id, label in named_motions:
|
|
1195
|
+
if not re.search(r"[\u3400-\u9fff]", label):
|
|
1196
|
+
errors.append(f"{motion_id}: MOTION 标题缺少中文名称")
|
|
1197
|
+
if not shots:
|
|
1198
|
+
errors.append("分镜.md: 没有 SHOT 条目")
|
|
1199
|
+
if not motions:
|
|
1200
|
+
errors.append("视频提示词.md: 没有 MOTION 条目")
|
|
1201
|
+
|
|
1202
|
+
motion_by_shot: dict[str, tuple[str, str, Optional[str]]] = {}
|
|
1203
|
+
motion_duration: dict[str, str] = {}
|
|
1204
|
+
for motion_id, body in motions.items():
|
|
1205
|
+
fields = _fields(body, owner=motion_id, errors=errors)
|
|
1206
|
+
shot_id = _plain(fields.get("分镜", ""))
|
|
1207
|
+
motion_duration[shot_id] = _plain(fields.get("时长", ""))
|
|
1208
|
+
copyable_prompt = _copyable_prompt(body)
|
|
1209
|
+
if not shot_id:
|
|
1210
|
+
errors.append(f"{motion_id}: 缺少分镜字段")
|
|
1211
|
+
elif shot_id in motion_by_shot:
|
|
1212
|
+
errors.append(f"{motion_id}: 分镜 {shot_id} 被多个 MOTION 引用")
|
|
1213
|
+
else:
|
|
1214
|
+
motion_by_shot[shot_id] = (motion_id, body, copyable_prompt)
|
|
1215
|
+
if motion_id.removeprefix("MOTION-") != shot_id.removeprefix("SHOT-"):
|
|
1216
|
+
errors.append(f"{motion_id}: ID 必须与分镜 {shot_id} 一一对应")
|
|
1217
|
+
if copyable_prompt is None:
|
|
1218
|
+
errors.append(
|
|
1219
|
+
f"{motion_id}: 缺少唯一且非空的可复制提示词"
|
|
1220
|
+
+ _copyable_prompt_cause(body)
|
|
1221
|
+
)
|
|
1222
|
+
|
|
1223
|
+
if set(motion_by_shot) != set(shots):
|
|
1224
|
+
errors.append("分镜.md/视频提示词.md: SHOT 与 MOTION 未一一对应")
|
|
1225
|
+
|
|
1226
|
+
for shot_id, shot_body in shots.items():
|
|
1227
|
+
fields = _fields(shot_body, owner=shot_id, errors=errors)
|
|
1228
|
+
source_value = fields.get("来源", "")
|
|
1229
|
+
claimed_scenes.update(
|
|
1230
|
+
_shot_sources(source_value, shot_id, scenes, errors)
|
|
1231
|
+
)
|
|
1232
|
+
shot_seconds = _declared_seconds(_plain(fields.get("时长", "")))
|
|
1233
|
+
motion_seconds = _declared_seconds(motion_duration.get(shot_id, ""))
|
|
1234
|
+
if (
|
|
1235
|
+
shot_seconds is not None
|
|
1236
|
+
and motion_seconds is not None
|
|
1237
|
+
and shot_seconds != motion_seconds
|
|
1238
|
+
):
|
|
1239
|
+
# 时长 is the value actually sent to the execution end and the term
|
|
1240
|
+
# VID-04/VID-13 arithmetic is built on. A downstream copy that drifts
|
|
1241
|
+
# from its accepted upstream is invisible in every other check.
|
|
1242
|
+
errors.append(
|
|
1243
|
+
f"{shot_id}: 分镜时长 {shot_seconds:g} 秒与视频提示词 "
|
|
1244
|
+
f"{motion_seconds:g} 秒不一致;视频提示词只能原样照抄已接受的镜头时长"
|
|
1245
|
+
)
|
|
1246
|
+
image_value = fields.get("图片提示词项", "")
|
|
1247
|
+
if not image_value:
|
|
1248
|
+
errors.append(f"{shot_id}: 缺少图片提示词项字段")
|
|
1249
|
+
image_refs = IMG_RE.findall(image_value)
|
|
1250
|
+
image_remainder = IMG_RE.sub("", image_value).strip(";。 ")
|
|
1251
|
+
if not _is_none(image_value) and (
|
|
1252
|
+
len(image_refs) != len(re.findall(r"\bIMG-[A-Z0-9-]+\b", image_value))
|
|
1253
|
+
or image_remainder
|
|
1254
|
+
):
|
|
1255
|
+
errors.append(f"{shot_id}: 图片提示词项语法不完整")
|
|
1256
|
+
for image_id, label, _ in image_refs:
|
|
1257
|
+
if image_id not in image_headings:
|
|
1258
|
+
errors.append(f"{shot_id}: IMG 标题不存在: {image_id}")
|
|
1259
|
+
elif label != image_headings[image_id]:
|
|
1260
|
+
errors.append(f"{shot_id}: IMG 中文名称与标题不一致: {image_id}")
|
|
1261
|
+
|
|
1262
|
+
shot_input = fields.get("输入参考图", "")
|
|
1263
|
+
if not shot_input:
|
|
1264
|
+
errors.append(f"{shot_id}: 缺少输入参考图字段")
|
|
1265
|
+
_references(shot_input, shot_id, project_root, errors, entries)
|
|
1266
|
+
|
|
1267
|
+
basis_value = fields.get("视觉依据", "")
|
|
1268
|
+
if not basis_value:
|
|
1269
|
+
errors.append(f"{shot_id}: 缺少视觉依据字段")
|
|
1270
|
+
basis = VisualBasis(False, set(), set())
|
|
1271
|
+
else:
|
|
1272
|
+
basis = _visual_basis(
|
|
1273
|
+
basis_value, shot_id, visual_entries, other_headings, errors
|
|
1274
|
+
)
|
|
1275
|
+
# The keyframe is the only place the frame's contents exist as text, so a
|
|
1276
|
+
# shot without one would make the coverage check below vacuous.
|
|
1277
|
+
keyframe = _copyable_prompt(shot_body, heading=r"冻结关键帧提示词")
|
|
1278
|
+
if keyframe is None:
|
|
1279
|
+
errors.append(
|
|
1280
|
+
f"{shot_id}: 缺少唯一且非空的冻结关键帧提示词"
|
|
1281
|
+
+ _copyable_prompt_cause(shot_body, heading=r"冻结关键帧提示词")
|
|
1282
|
+
)
|
|
1283
|
+
elif basis.parsed:
|
|
1284
|
+
_check_named_coverage(
|
|
1285
|
+
keyframe, shot_id, "冻结关键帧提示词", basis, visual_entries, errors
|
|
1286
|
+
)
|
|
1287
|
+
# The closing keyframe is optional — many shots end where they began —
|
|
1288
|
+
# but a heading that is present and malformed is a defect, not a shot
|
|
1289
|
+
# that quietly has no closing frame.
|
|
1290
|
+
if re.search(r"^### 收尾关键帧提示词\s*$", shot_body, re.MULTILINE):
|
|
1291
|
+
closing = _copyable_prompt(shot_body, heading=r"收尾关键帧提示词")
|
|
1292
|
+
if closing is None:
|
|
1293
|
+
errors.append(
|
|
1294
|
+
f"{shot_id}: 收尾关键帧提示词不是唯一且非空的可复制正文"
|
|
1295
|
+
+ _copyable_prompt_cause(shot_body, heading=r"收尾关键帧提示词")
|
|
1296
|
+
)
|
|
1297
|
+
elif basis.parsed:
|
|
1298
|
+
_check_named_coverage(
|
|
1299
|
+
closing, shot_id, "收尾关键帧提示词", basis, visual_entries, errors
|
|
1300
|
+
)
|
|
1301
|
+
if basis.parsed and source_value:
|
|
1302
|
+
# A shot's 来源 is its claim on the screenplay: whatever it quotes,
|
|
1303
|
+
# this shot is the one that films it. Quoting an action performed by
|
|
1304
|
+
# someone the frame never shows takes that action off everyone's
|
|
1305
|
+
# list — no other shot claims it, and nothing reports it missing, so
|
|
1306
|
+
# it simply never gets filmed. Same rule as the keyframe: in frame,
|
|
1307
|
+
# or declared 画外.
|
|
1308
|
+
_check_named_coverage(
|
|
1309
|
+
source_value,
|
|
1310
|
+
shot_id,
|
|
1311
|
+
"来源引文",
|
|
1312
|
+
basis,
|
|
1313
|
+
visual_entries,
|
|
1314
|
+
errors,
|
|
1315
|
+
by_entry_name=True,
|
|
1316
|
+
)
|
|
1317
|
+
|
|
1318
|
+
motion = motion_by_shot.get(shot_id)
|
|
1319
|
+
if not motion:
|
|
1320
|
+
continue
|
|
1321
|
+
motion_id, motion_body, copyable_prompt = motion
|
|
1322
|
+
motion_fields = _fields(motion_body, owner=motion_id, errors=errors)
|
|
1323
|
+
if copyable_prompt is not None:
|
|
1324
|
+
_check_spoken_lines(
|
|
1325
|
+
copyable_prompt, motion_id, spoken_sources, errors
|
|
1326
|
+
)
|
|
1327
|
+
motion_input = motion_fields.get("输入参考图", "")
|
|
1328
|
+
_references(motion_input, motion_id, project_root, errors, entries)
|
|
1329
|
+
if _plain(motion_input) != _plain(shot_input):
|
|
1330
|
+
errors.append(f"{motion_id}: 输入参考图与 {shot_id} 不一致")
|
|
1331
|
+
|
|
1332
|
+
if _has_pending_references(shot_input):
|
|
1333
|
+
errors.append(f"{motion_id}: 仍有待补参考图,不能生成最终视频提示词")
|
|
1334
|
+
|
|
1335
|
+
# A `PLAN-...` slot is not a file, but the prompt is still written for
|
|
1336
|
+
# image-conditioned generation: the creator attaches those pictures at
|
|
1337
|
+
# generation time. What decides 生成方式 is whether the shot sends
|
|
1338
|
+
# pictures at all, not whether this suite can read them.
|
|
1339
|
+
sends_pictures = not _is_none(shot_input)
|
|
1340
|
+
expected_mode = "图生视频" if sends_pictures else "文生视频"
|
|
1341
|
+
if _plain(motion_fields.get("生成方式", "")) != expected_mode:
|
|
1342
|
+
errors.append(f"{motion_id}: 生成方式应为{expected_mode}")
|
|
1343
|
+
if not sends_pictures:
|
|
1344
|
+
if not _is_explicit_text_to_video(shot_input):
|
|
1345
|
+
errors.append(
|
|
1346
|
+
f"{motion_id}: 无真实输入参考图时不能静默降级为文生视频;"
|
|
1347
|
+
"请先绑定已有图片、列出待补图片,或记录创作者已明确选择文生视频"
|
|
1348
|
+
)
|
|
1349
|
+
anchor = _plain(motion_fields.get("静态视觉锚点", ""))
|
|
1350
|
+
if not anchor or anchor == "无":
|
|
1351
|
+
errors.append(f"{motion_id}: 文生视频缺少静态视觉锚点")
|
|
1352
|
+
if (
|
|
1353
|
+
copyable_prompt is not None
|
|
1354
|
+
and anchor
|
|
1355
|
+
and anchor != "无"
|
|
1356
|
+
and anchor not in copyable_prompt
|
|
1357
|
+
):
|
|
1358
|
+
errors.append(f"{motion_id}: 可复制提示词没有包含静态视觉锚点")
|
|
1359
|
+
# In text-to-video the anchor carries the appearance the keyframe
|
|
1360
|
+
# would otherwise have carried, so it is the same claim about the
|
|
1361
|
+
# same frame and answers to the same visual basis. The rest of the
|
|
1362
|
+
# motion body is not checked: it may legitimately name an offscreen
|
|
1363
|
+
# speaker, which SHT-22 excludes.
|
|
1364
|
+
if anchor and anchor != "无" and basis.parsed:
|
|
1365
|
+
_check_named_coverage(
|
|
1366
|
+
anchor, motion_id, "静态视觉锚点", basis, visual_entries, errors
|
|
1367
|
+
)
|
|
1368
|
+
|
|
1369
|
+
for scene_id in scene_headings:
|
|
1370
|
+
if scene_id in claimed_scenes or scene_id in unfilmed:
|
|
1371
|
+
continue
|
|
1372
|
+
errors.append(
|
|
1373
|
+
f"分镜.md: 没有镜头承载场景 {scene_id};"
|
|
1374
|
+
"拍它就写进某一镜的来源,不拍就写进未拍场次并给出理由"
|
|
1375
|
+
)
|
|
1376
|
+
for scene_id in sorted(set(unfilmed) & claimed_scenes):
|
|
1377
|
+
errors.append(f"分镜.md: 场景 {scene_id} 既被镜头承载又记为未拍")
|
|
1378
|
+
|
|
1379
|
+
_check_language_designators(
|
|
1380
|
+
project_root, entries=visual_entries, errors=errors
|
|
1381
|
+
)
|
|
1382
|
+
_check_continuity_locks(
|
|
1383
|
+
locks,
|
|
1384
|
+
shots=shots,
|
|
1385
|
+
motion_by_shot=motion_by_shot,
|
|
1386
|
+
image_prompts=image_prompts,
|
|
1387
|
+
errors=errors,
|
|
1388
|
+
)
|
|
1389
|
+
return errors
|
|
1390
|
+
|
|
1391
|
+
|
|
1392
|
+
def main() -> int:
|
|
1393
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
1394
|
+
parser.add_argument("episode", type=Path, help="包含五份 Markdown 的剧集目录")
|
|
1395
|
+
parser.add_argument("--project-root", type=Path, help="用于解析 REF 项目相对路径")
|
|
1396
|
+
args = parser.parse_args()
|
|
1397
|
+
# 诊断与剧集路径都是中文。stdout 重定向时 Windows 用 ANSI 代码页,默认的
|
|
1398
|
+
# strict 处理器会在打印这一步抛错;stderr 早就是 backslashreplace,这里让
|
|
1399
|
+
# stdout 用同一个处理器:能编码就照常显示中文,不能编码才退成转义。
|
|
1400
|
+
reconfigure = getattr(sys.stdout, "reconfigure", None)
|
|
1401
|
+
if reconfigure is not None:
|
|
1402
|
+
reconfigure(errors="backslashreplace")
|
|
1403
|
+
errors = validate_episode(args.episode, args.project_root)
|
|
1404
|
+
if errors:
|
|
1405
|
+
for error in errors:
|
|
1406
|
+
print(f"ERROR: {error}")
|
|
1407
|
+
return 1
|
|
1408
|
+
print(f"OK: {args.episode}")
|
|
1409
|
+
return 0
|
|
1410
|
+
|
|
1411
|
+
|
|
1412
|
+
if __name__ == "__main__":
|
|
1413
|
+
raise SystemExit(main())
|