@oh-story/dsh 0.1.8 → 0.1.9
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 +11 -11
- package/lib/client.js +35 -14
- package/lib/client.js.map +2 -2
- package/lib/drama/manifest.json +165 -69
- package/lib/drama/skills/short-drama/SKILL.md +5 -1
- package/lib/drama/skills/short-drama/scripts/creator_markdown_check.py +126 -9
- package/lib/drama/skills/short-drama/scripts/project_tool.py +11 -0
- package/lib/drama/skills/short-drama-assets/references/character-and-look.md +18 -0
- package/lib/drama/skills/short-drama-assets/references/continuity-lock.md +10 -1
- package/lib/drama/skills/short-drama-edit/SKILL.md +148 -0
- package/lib/drama/skills/short-drama-edit/agents/openai.yaml +4 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/README.md +60 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/package.json +15 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/remotion.config.ts +6 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/Root.tsx +26 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/Subtitles.tsx +88 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/font.ts +46 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/index.ts +4 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/schema.ts +32 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/tsconfig.json +13 -0
- package/lib/drama/skills/short-drama-edit/assets//345/211/252/350/276/221/345/215/225.md +43 -0
- package/lib/drama/skills/short-drama-edit/references/cut-craft.md +81 -0
- package/lib/drama/skills/short-drama-edit/references/delivery-verify.md +58 -0
- package/lib/drama/skills/short-drama-edit/references/generated-footage.md +96 -0
- package/lib/drama/skills/short-drama-edit/references/sound-and-subtitles.md +113 -0
- package/lib/drama/skills/short-drama-edit/references/stage-contract.md +55 -0
- package/lib/drama/skills/short-drama-edit/scripts/edit_tool.py +1041 -0
- package/lib/drama/skills/short-drama-edit/scripts/selftest.py +456 -0
- package/lib/drama/skills/short-drama-image-prompts/assets/image-prompts.md +5 -0
- package/lib/drama/skills/short-drama-image-prompts/assets/lookdev-prompts.md +3 -0
- package/lib/drama/skills/short-drama-novel-analyze/references/aggregation-and-entities.md +2 -2
- package/lib/drama/skills/short-drama-novel-analyze/references/chapter-extraction.md +4 -1
- package/lib/drama/skills/short-drama-produce/SKILL.md +24 -3
- package/lib/drama/skills/short-drama-produce/references/adapter-contract.md +28 -0
- package/lib/drama/skills/short-drama-produce/references/providers/minimax-h3-video.md +24 -5
- package/lib/drama/skills/short-drama-produce/references/providers/seedance.md +7 -0
- package/lib/drama/skills/short-drama-produce/scripts/fixture_adapter.py +18 -1
- package/lib/drama/skills/short-drama-produce/scripts/production_tool.py +181 -2
- package/lib/drama/skills/short-drama-produce/scripts/provider_adapters.py +57 -2
- package/lib/drama/skills/short-drama-review/SKILL.md +3 -0
- package/lib/drama/skills/short-drama-review/references/production-quality-gates.md +20 -0
- package/lib/drama/skills/short-drama-review/references/rubric-visual-motion.md +36 -0
- package/lib/drama/skills/short-drama-storyboard/SKILL.md +7 -0
- package/lib/drama/skills/short-drama-storyboard/assets/keyframe-prompts.md +4 -0
- package/lib/drama/skills/short-drama-storyboard/references/shot-craft.md +39 -0
- package/lib/drama/skills/short-drama-storyboard/references/stage-contract.md +2 -0
- package/lib/drama/skills/short-drama-video-prompts/SKILL.md +7 -1
- package/lib/drama/skills/short-drama-video-prompts/references/minimax-h3.md +101 -6
- package/lib/drama/skills/short-drama-video-prompts/references/motion-recipe.md +7 -1
- package/lib/drama/skills/short-drama-video-prompts/references/performance-action-timing.md +35 -0
- package/lib/drama/skills/short-drama-video-prompts/references/production-prompt-grammar.md +3 -1
- package/lib/drama/skills/short-drama-video-prompts/references/seedance-2.0.md +13 -3
- package/lib/drama/skills/short-drama-video-prompts/references/seedance-2.5.md +35 -4
- package/lib/drama/skills/short-drama-video-prompts/references/stage-contract.md +1 -1
- package/lib/drama/skills/short-drama-write/SKILL.md +13 -2
- package/lib/drama/skills/short-drama-write/references/screenplay-format.md +6 -0
- package/lib/drama/skills/short-drama-write/references/stage-contract.md +1 -0
- package/lib/index.js +4 -2
- package/lib/index.js.map +2 -2
- package/lib/novel-to-game/VERSION +1 -1
- package/lib/novel-to-game/manifest.json +28 -28
- package/lib/novel-to-game/skills/game-art-direction/SKILL.md +6 -27
- package/lib/novel-to-game/skills/game-build/SKILL.md +12 -29
- package/lib/novel-to-game/skills/game-concept/SKILL.md +10 -26
- package/lib/novel-to-game/skills/game-qa/SKILL.md +5 -15
- package/lib/novel-to-game/skills/game-qa/references/test-design-method.md +1 -1
- package/lib/novel-to-game/skills/game-world-design/SKILL.md +19 -28
- package/lib/novel-to-game/skills/game-world-design/references/dialogue-design-method.md +1 -2
- package/lib/novel-to-game/skills/novel-game-analyze/SKILL.md +6 -9
- package/lib/novel-to-game/skills/novel-to-game/SKILL.md +6 -11
- package/lib/novel-to-game/skills/novel-to-game/references/intake-benchmark-reference.md +0 -4
- package/lib/novel-to-game/skills/novel-to-game/references/intake-method.md +1 -3
- package/lib/novel-to-game/skills/novel-to-game/references/pipeline-contract.md +3 -5
- package/lib/oh-story/manifest.json +292 -172
- package/lib/oh-story/roles/character-designer.md +1 -1
- package/lib/oh-story/roles/narrative-writer.md +56 -242
- package/lib/oh-story/roles/story-architect.md +1 -1
- package/lib/oh-story/skills/story/SKILL.md +1 -1
- package/lib/oh-story/skills/story/VERSION +1 -1
- package/lib/oh-story/skills/story/references/author-memory.md +10 -8
- package/lib/oh-story/skills/story-deslop/SKILL.md +20 -182
- package/lib/oh-story/skills/story-deslop/references/anti-ai-writing.md +19 -47
- package/lib/oh-story/skills/story-deslop/references/author-memory.md +10 -8
- package/lib/oh-story/skills/story-deslop/references/banned-words.md +3 -1
- package/lib/oh-story/skills/story-deslop/references/deslop-gates.md +154 -0
- package/lib/oh-story/skills/story-deslop/references/style-resolution.md +27 -0
- package/lib/oh-story/skills/story-deslop/scripts/check-ai-patterns.js +6 -1
- package/lib/oh-story/skills/story-deslop/scripts/check-degeneration.js +39 -20
- package/lib/oh-story/skills/story-deslop/scripts/normalize-punctuation.js +10 -5
- package/lib/oh-story/skills/story-deslop/scripts/style-whitelist.js +39 -0
- package/lib/oh-story/skills/story-import/SKILL.md +2 -2
- package/lib/oh-story/skills/story-import/references/format-and-structure.md +2 -2
- package/lib/oh-story/skills/story-import/references/tracking-initialization.md +31 -0
- package/lib/oh-story/skills/story-import/references/tracking-transaction.md +2 -29
- package/lib/oh-story/skills/story-import/scripts/check-outline-contract.js +176 -12
- package/lib/oh-story/skills/story-long-analyze/SKILL.md +1 -1
- package/lib/oh-story/skills/story-long-write/SKILL.md +20 -193
- package/lib/oh-story/skills/story-long-write/references/anti-ai-writing.md +19 -47
- package/lib/oh-story/skills/story-long-write/references/artifact-protocols.md +62 -7
- package/lib/oh-story/skills/story-long-write/references/author-memory.md +10 -8
- package/lib/oh-story/skills/story-long-write/references/banned-words.md +3 -1
- package/lib/oh-story/skills/story-long-write/references/benchmark-recall.md +20 -0
- package/lib/oh-story/skills/story-long-write/references/dialogue-mastery.md +6 -4
- package/lib/oh-story/skills/story-long-write/references/long-chapter-quality.md +2 -2
- package/lib/oh-story/skills/story-long-write/references/long-format.md +2 -0
- package/lib/oh-story/skills/story-long-write/references/outline-structure-theory.md +2 -0
- package/lib/oh-story/skills/story-long-write/references/project-files.md +108 -0
- package/lib/oh-story/skills/story-long-write/references/reference-index.md +87 -0
- package/lib/oh-story/skills/story-long-write/references/style-combat-face.md +1 -1
- package/lib/oh-story/skills/story-long-write/references/style-craft.md +9 -29
- package/lib/oh-story/skills/story-long-write/references/style-genre-modules.md +1 -1
- package/lib/oh-story/skills/story-long-write/references/style-resolution.md +27 -0
- package/lib/oh-story/skills/story-long-write/references/tracking-initialization.md +31 -0
- package/lib/oh-story/skills/story-long-write/references/tracking-transaction.md +2 -29
- package/lib/oh-story/skills/story-long-write/references/workflow-chapter.md +51 -52
- package/lib/oh-story/skills/story-long-write/references/workflow-daily.md +8 -7
- package/lib/oh-story/skills/story-long-write/references/workflow-setup.md +36 -15
- package/lib/oh-story/skills/story-long-write/references/writer-prompt-fallback.md +22 -0
- package/lib/oh-story/skills/story-long-write/references/writing-craft.md +69 -2
- package/lib/oh-story/skills/story-long-write/scripts/build_writer_prompt.py +410 -0
- package/lib/oh-story/skills/story-long-write/scripts/check-ai-patterns.js +6 -1
- package/lib/oh-story/skills/story-long-write/scripts/check-degeneration.js +39 -20
- package/lib/oh-story/skills/story-long-write/scripts/check-outline-contract.js +176 -12
- package/lib/oh-story/skills/story-long-write/scripts/check-outline-copy.js +90 -23
- package/lib/oh-story/skills/story-long-write/scripts/normalize-punctuation.js +10 -5
- package/lib/oh-story/skills/story-long-write/scripts/outline_view.py +283 -0
- package/lib/oh-story/skills/story-long-write/scripts/style-whitelist.js +39 -0
- package/lib/oh-story/skills/story-review/SKILL.md +13 -3
- package/lib/oh-story/skills/story-review/references/anti-ai-writing.md +19 -47
- package/lib/oh-story/skills/story-review/references/author-memory.md +10 -8
- package/lib/oh-story/skills/story-review/references/banned-words.md +3 -1
- package/lib/oh-story/skills/story-review/references/dialogue-mastery.md +6 -4
- package/lib/oh-story/skills/story-review/references/review-quality.md +2 -2
- package/lib/oh-story/skills/story-review/references/style-resolution.md +27 -0
- package/lib/oh-story/skills/story-review/references/tracking-initialization.md +31 -0
- package/lib/oh-story/skills/story-review/references/tracking-transaction.md +2 -29
- package/lib/oh-story/skills/story-review/scripts/check-ai-patterns.js +6 -1
- package/lib/oh-story/skills/story-review/scripts/check-degeneration.js +39 -20
- package/lib/oh-story/skills/story-review/scripts/normalize-punctuation.js +10 -5
- package/lib/oh-story/skills/story-review/scripts/style-whitelist.js +39 -0
- package/lib/oh-story/skills/story-setup/SKILL.md +17 -10
- package/lib/oh-story/skills/story-setup/references/agent-references/anti-ai-writing.md +19 -47
- package/lib/oh-story/skills/story-setup/references/agent-references/banned-words.md +3 -1
- package/lib/oh-story/skills/story-setup/references/agent-references/deslop-gates.md +154 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/dialogue-mastery.md +6 -4
- package/lib/oh-story/skills/story-setup/references/agent-references/format-and-structure.md +2 -2
- package/lib/oh-story/skills/story-setup/references/agent-references/short-genre-formulas.md +1 -1
- package/lib/oh-story/skills/story-setup/references/agent-references/style-combat-face.md +1 -1
- package/lib/oh-story/skills/story-setup/references/agent-references/style-genre-modules.md +1 -1
- package/lib/oh-story/skills/story-setup/references/agent-references/style-resolution.md +27 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/writing-craft.md +69 -2
- package/lib/oh-story/skills/story-setup/references/diagnostics.md +38 -0
- package/lib/oh-story/skills/story-short-analyze/references/analysis-writing-techniques.md +1 -1
- package/lib/oh-story/skills/story-short-analyze/references/source-story-quality.md +2 -2
- package/lib/oh-story/skills/story-short-write/SKILL.md +18 -186
- package/lib/oh-story/skills/story-short-write/references/author-memory.md +10 -8
- package/lib/oh-story/skills/story-short-write/references/banned-words.md +3 -1
- package/lib/oh-story/skills/story-short-write/references/benchmark-recall.md +60 -0
- package/lib/oh-story/skills/story-short-write/references/dialogue-mastery.md +6 -4
- package/lib/oh-story/skills/story-short-write/references/genre-styles//344/270/226/346/203/205/346/211/223/350/204/270.md +2 -2
- package/lib/oh-story/skills/story-short-write/references/genre-styles//345/217/214/347/224/267/344/270/273.md +1 -1
- package/lib/oh-story/skills/story-short-write/references/genre-styles//345/244/215/344/273/207/346/211/223/350/204/270.md +3 -3
- package/lib/oh-story/skills/story-short-write/references/genre-styles//345/256/205/346/226/227/345/256/253/346/226/227.md +3 -3
- package/lib/oh-story/skills/story-short-write/references/genre-styles//346/200/273/350/243/201/350/261/252/351/227/250.md +3 -3
- package/lib/oh-story/skills/story-short-write/references/genre-styles//346/202/254/347/226/221.md +5 -5
- package/lib/oh-story/skills/story-short-write/references/genre-styles//346/260/221/344/277/227/346/200/252/350/260/210.md +4 -4
- package/lib/oh-story/skills/story-short-write/references/genre-styles//347/224/234/345/256/240.md +5 -5
- package/lib/oh-story/skills/story-short-write/references/genre-styles//350/277/275/345/246/273/347/201/253/350/221/254/345/234/272.md +3 -3
- package/lib/oh-story/skills/story-short-write/references/genre-writing-formulas.md +1 -1
- package/lib/oh-story/skills/story-short-write/references/genre-writing-techniques.md +1 -1
- package/lib/oh-story/skills/story-short-write/references/reference-index.md +53 -0
- package/lib/oh-story/skills/story-short-write/references/short-craft.md +12 -14
- package/lib/oh-story/skills/story-short-write/references/short-deslop.md +10 -10
- package/lib/oh-story/skills/story-short-write/references/short-format.md +3 -1
- package/lib/oh-story/skills/story-short-write/references/short-prose-quality.md +2 -2
- package/lib/oh-story/skills/story-short-write/references/style-resolution.md +27 -0
- package/lib/oh-story/skills/story-short-write/references/workflow-design.md +77 -0
- package/lib/oh-story/skills/story-short-write/references/writing-workflow.md +5 -5
- package/lib/oh-story/skills/story-short-write/scripts/check-ai-patterns.js +6 -1
- package/lib/oh-story/skills/story-short-write/scripts/check-degeneration.js +39 -20
- package/lib/oh-story/skills/story-short-write/scripts/check-delivery-contract.js +34 -5
- package/lib/oh-story/skills/story-short-write/scripts/check-outline-copy.js +90 -23
- package/lib/oh-story/skills/story-short-write/scripts/normalize-punctuation.js +10 -5
- package/lib/oh-story/skills/story-short-write/scripts/style-whitelist.js +39 -0
- package/lib/types/client/drama-production-view.d.ts.map +1 -1
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/production-prompts.d.ts.map +1 -1
- package/lib/types/client/production-runtime.d.ts +17 -0
- package/lib/types/client/production-runtime.d.ts.map +1 -1
- package/lib/types/skill-provider.d.ts.map +1 -1
- package/lib/video-recap/CHANGELOG.md +31 -1
- package/lib/video-recap/manifest.json +162 -162
- package/lib/video-recap/skills/video-assemble/scripts/artifacts.py +4 -9
- package/lib/video-recap/skills/video-assemble/scripts/assemble.py +47 -60
- package/lib/video-recap/skills/video-assemble/scripts/assembly_contract.py +77 -115
- package/lib/video-recap/skills/video-assemble/scripts/assembly_settings.py +41 -40
- package/lib/video-recap/skills/video-assemble/scripts/audio_automation.py +22 -32
- package/lib/video-recap/skills/video-assemble/scripts/audio_mix.py +102 -192
- package/lib/video-recap/skills/video-assemble/scripts/jianying_builders.py +14 -35
- package/lib/video-recap/skills/video-assemble/scripts/jianying_model.py +14 -12
- package/lib/video-recap/skills/video-assemble/scripts/jianying_optional.py +8 -9
- package/lib/video-recap/skills/video-assemble/scripts/jianying_schema.py +3 -14
- package/lib/video-recap/skills/video-assemble/scripts/jianying_timeline_contract.py +23 -12
- package/lib/video-recap/skills/video-assemble/scripts/jianying_tracks.py +1 -4
- package/lib/video-recap/skills/video-assemble/scripts/jianying_writer.py +24 -25
- package/lib/video-recap/skills/video-assemble/scripts/lib.py +31 -85
- package/lib/video-recap/skills/video-assemble/scripts/media.py +100 -144
- package/lib/video-recap/skills/video-assemble/scripts/narration_audio.py +101 -195
- package/lib/video-recap/skills/video-assemble/scripts/render_preflight.py +10 -21
- package/lib/video-recap/skills/video-assemble/scripts/source_subtitles.py +90 -169
- package/lib/video-recap/skills/video-assemble/scripts/subtitle_core.py +69 -122
- package/lib/video-recap/skills/video-assemble/scripts/subtitle_render.py +10 -17
- package/lib/video-recap/skills/video-assemble/scripts/timeline.py +33 -48
- package/lib/video-recap/skills/video-assemble/scripts/timeline_emit.py +14 -20
- package/lib/video-recap/skills/video-assemble/scripts/visual_render.py +135 -213
- package/lib/video-recap/skills/video-cut/scripts/cut_cli.py +49 -57
- package/lib/video-recap/skills/video-cut/scripts/cut_contract.py +38 -74
- package/lib/video-recap/skills/video-cut/scripts/cut_render.py +46 -95
- package/lib/video-recap/skills/video-cut/scripts/lib.py +23 -25
- package/lib/video-recap/skills/video-cut/scripts/media_geometry.py +70 -173
- package/lib/video-recap/skills/video-cut/scripts/narration_mapping.py +28 -53
- package/lib/video-recap/skills/video-cut/scripts/sentence_boundaries.py +162 -322
- package/lib/video-recap/skills/video-recap/references/timeline-and-jianying.md +6 -7
- package/lib/video-recap/skills/video-recap/scripts/doctor.py +104 -124
- package/lib/video-recap/skills/video-recap/scripts/final_qc.py +255 -412
- package/lib/video-recap/skills/video-recap/scripts/lib.py +30 -52
- package/lib/video-recap/skills/video-recap/scripts/materials.py +97 -120
- package/lib/video-recap/skills/video-recap/scripts/mimo_qc_client.py +7 -12
- package/lib/video-recap/skills/video-recap/scripts/mimo_qc_evidence.py +46 -97
- package/lib/video-recap/skills/video-recap/scripts/mimo_qc_observations.py +18 -36
- package/lib/video-recap/skills/video-recap/scripts/mimo_qc_payload.py +29 -47
- package/lib/video-recap/skills/video-recap/scripts/mimo_qc_report.py +43 -76
- package/lib/video-recap/skills/video-recap/scripts/mimo_qc_runner.py +2 -1
- package/lib/video-recap/skills/video-recap/scripts/qc_contract.py +2 -1
- package/lib/video-recap/skills/video-recap/scripts/recap_cli.py +2 -2
- package/lib/video-recap/skills/video-recap/scripts/recap_inspect.py +129 -145
- package/lib/video-recap/skills/video-recap/scripts/recap_review.py +27 -20
- package/lib/video-recap/skills/video-recap/scripts/recap_runner.py +20 -31
- package/lib/video-recap/skills/video-recap/scripts/recap_runtime.py +31 -106
- package/lib/video-recap/skills/video-recap/scripts/recap_stage_qc.py +43 -113
- package/lib/video-recap/skills/video-recap/scripts/recap_timeline.py +196 -377
- package/lib/video-recap/skills/video-script/scripts/agent_brief.py +1 -1
- package/lib/video-recap/skills/video-script/scripts/agent_text.py +63 -172
- package/lib/video-recap/skills/video-script/scripts/brief_context.py +80 -135
- package/lib/video-recap/skills/video-script/scripts/brief_inputs.py +125 -167
- package/lib/video-recap/skills/video-script/scripts/brief_timeline.py +93 -208
- package/lib/video-recap/skills/video-script/scripts/deslop_qc.py +20 -46
- package/lib/video-recap/skills/video-script/scripts/lib.py +30 -26
- package/lib/video-recap/skills/video-script/scripts/narration_lint.py +145 -140
- package/lib/video-recap/skills/video-script/scripts/review_response.py +12 -42
- package/lib/video-recap/skills/video-script/scripts/review_runner.py +0 -6
- package/lib/video-recap/skills/video-script/scripts/speech_ownership.py +39 -100
- package/lib/video-recap/skills/video-script/scripts/timeline_fusion.py +33 -124
- package/lib/video-recap/skills/video-understanding/scripts/agent_brief.py +1 -1
- package/lib/video-recap/skills/video-understanding/scripts/agent_text.py +63 -172
- package/lib/video-recap/skills/video-understanding/scripts/asr.py +1 -2
- package/lib/video-recap/skills/video-understanding/scripts/brief_context.py +80 -135
- package/lib/video-recap/skills/video-understanding/scripts/brief_inputs.py +125 -167
- package/lib/video-recap/skills/video-understanding/scripts/brief_timeline.py +93 -208
- package/lib/video-recap/skills/video-understanding/scripts/consolidate.py +1 -1
- package/lib/video-recap/skills/video-understanding/scripts/deslop_qc.py +20 -46
- package/lib/video-recap/skills/video-understanding/scripts/lib.py +31 -26
- package/lib/video-recap/skills/video-understanding/scripts/narration_lint.py +145 -140
- package/lib/video-recap/skills/video-understanding/scripts/speech_ownership.py +39 -100
- package/lib/video-recap/skills/video-understanding/scripts/timeline_fusion.py +33 -124
- package/lib/video-recap/skills/video-understanding/scripts/vlm.py +1 -4
- package/lib/video-recap/skills/video-voiceover/scripts/dub.py +7 -13
- package/lib/video-recap/skills/video-voiceover/scripts/fish_audio.py +12 -30
- package/lib/video-recap/skills/video-voiceover/scripts/lib.py +98 -131
- package/lib/video-recap/skills/video-voiceover/scripts/tts_audio.py +17 -28
- package/lib/video-recap/skills/video-voiceover/scripts/voiceover.py +151 -240
- package/package.json +41 -41
|
@@ -29,6 +29,13 @@ profile above explicitly permits their values, then sent as the API's top-level
|
|
|
29
29
|
`generate_audio` is a boolean. The broad compiler envelope remains configurable rather than proving
|
|
30
30
|
that every Seedance release supports every value.
|
|
31
31
|
|
|
32
|
+
The compiler also forwards `duration: -1` without applying the positive-integer range. For ordinary
|
|
33
|
+
Seedance 2.0/2.5 generation this asks the model to choose within its native range; for 2.5 `edit` it
|
|
34
|
+
preserves the source video's duration instead. The adapter does not estimate spoken dialogue or
|
|
35
|
+
lengthen a short job. Fixed-duration delivery needs an explicit accepted duration; auto-duration
|
|
36
|
+
output needs its actual media length and speech checked before timing is accepted. The API response's
|
|
37
|
+
integer `duration` rounds down the frame-based duration, so it is not a precise media measurement.
|
|
38
|
+
|
|
32
39
|
`compile_seedance_payload` accepts explicit `reference_image`, `reference_video`, and
|
|
33
40
|
`reference_audio` roles with matching HTTPS, `asset://` or base64 data URI values. It emits the matching multimodal
|
|
34
41
|
content type and uses `@图片N`、`@视频N`、`@音频N` in its appended reference contract. The bundled
|
|
@@ -27,8 +27,22 @@ PNG = base64.b64decode(
|
|
|
27
27
|
def main() -> int:
|
|
28
28
|
parser = argparse.ArgumentParser()
|
|
29
29
|
parser.add_argument("--fail", action="store_true")
|
|
30
|
+
# A real video provider bills at submission and can be interrupted at any
|
|
31
|
+
# point afterwards. `--submit-then-fail` reproduces exactly that: record the
|
|
32
|
+
# handle the way a live adapter does, then die before returning anything.
|
|
33
|
+
parser.add_argument("--submit-then-fail", action="store_true")
|
|
30
34
|
args = parser.parse_args()
|
|
31
35
|
job = json.load(sys.stdin.buffer)
|
|
36
|
+
collecting = job.get("collect_provider_job_id")
|
|
37
|
+
if args.submit_then_fail and not collecting:
|
|
38
|
+
destination = job.get("handle_path")
|
|
39
|
+
if isinstance(destination, str) and destination:
|
|
40
|
+
path = Path(destination)
|
|
41
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
42
|
+
path.write_text(
|
|
43
|
+
json.dumps({"provider_job_id": "fixture-task-1"}), encoding="utf-8"
|
|
44
|
+
)
|
|
45
|
+
return 7
|
|
32
46
|
if args.fail:
|
|
33
47
|
return 7
|
|
34
48
|
directory_raw = job.get("output_root")
|
|
@@ -52,7 +66,10 @@ def main() -> int:
|
|
|
52
66
|
handle.setframerate(8000)
|
|
53
67
|
handle.writeframes(struct.pack("<h", 0) * 80)
|
|
54
68
|
outputs.append({"target": target, "source": str(path)})
|
|
55
|
-
|
|
69
|
+
response = {"outputs": outputs, "provider_job_id": "fixture-local"}
|
|
70
|
+
if isinstance(collecting, str) and collecting:
|
|
71
|
+
response["provider_job_id"] = collecting
|
|
72
|
+
json.dump(response, sys.stdout)
|
|
56
73
|
return 0
|
|
57
74
|
|
|
58
75
|
|
|
@@ -1547,6 +1547,37 @@ def _active_run(root: Path, job_id: str) -> dict[str, Any] | None:
|
|
|
1547
1547
|
return running[-1] if running else None
|
|
1548
1548
|
|
|
1549
1549
|
|
|
1550
|
+
def _read_provider_handle(root: Path, job_id: str, run_id: str) -> str | None:
|
|
1551
|
+
"""The provider job id an adapter recorded before it started polling.
|
|
1552
|
+
|
|
1553
|
+
A video task is billed the moment it is submitted, not when it is collected.
|
|
1554
|
+
If the adapter is then interrupted — killed, disconnected, the machine
|
|
1555
|
+
sleeps — the run record would otherwise say `running` with no way back to a
|
|
1556
|
+
task that is alive and already paid for, and the only move left is to submit
|
|
1557
|
+
(and pay) again. The adapter writes the id here as soon as it has one, into
|
|
1558
|
+
project metadata rather than the attempt's temporary directory, so it
|
|
1559
|
+
outlives the process that created it.
|
|
1560
|
+
"""
|
|
1561
|
+
|
|
1562
|
+
try:
|
|
1563
|
+
document = _metadata_read_json(
|
|
1564
|
+
root, ("handles", _job_key(job_id)), f"{run_id}.json",
|
|
1565
|
+
maximum=MAX_RUN_RECORD_BYTES,
|
|
1566
|
+
)
|
|
1567
|
+
except (FileNotFoundError, ValueError, json.JSONDecodeError):
|
|
1568
|
+
return None
|
|
1569
|
+
if not isinstance(document, Mapping):
|
|
1570
|
+
return None
|
|
1571
|
+
value = document.get("provider_job_id")
|
|
1572
|
+
if isinstance(value, str) and 0 < len(value) <= 200:
|
|
1573
|
+
return value
|
|
1574
|
+
return None
|
|
1575
|
+
|
|
1576
|
+
|
|
1577
|
+
def _handle_metadata_path(root: Path, job_id: str, run_id: str) -> Path:
|
|
1578
|
+
return root / PRODUCTION_ROOT / "handles" / _job_key(job_id) / f"{run_id}.json"
|
|
1579
|
+
|
|
1580
|
+
|
|
1550
1581
|
def _write_run(root: Path, job_id: str, run: Mapping[str, Any]) -> None:
|
|
1551
1582
|
_metadata_atomic_json(
|
|
1552
1583
|
root,
|
|
@@ -1620,6 +1651,9 @@ def run_job(root: Path, *, job_id: str, adapter_config: Path) -> dict[str, Any]:
|
|
|
1620
1651
|
"run_id": run_id,
|
|
1621
1652
|
"project_root": str(snapshot_root),
|
|
1622
1653
|
"output_root": str(output_root),
|
|
1654
|
+
# Where to record the provider job id the instant it exists, so
|
|
1655
|
+
# an interrupted attempt can be collected instead of re-billed.
|
|
1656
|
+
"handle_path": str(_handle_metadata_path(root, job_id, run_id)),
|
|
1623
1657
|
}
|
|
1624
1658
|
)
|
|
1625
1659
|
try:
|
|
@@ -1640,11 +1674,20 @@ def run_job(root: Path, *, job_id: str, adapter_config: Path) -> dict[str, Any]:
|
|
|
1640
1674
|
"sha256": digest,
|
|
1641
1675
|
}
|
|
1642
1676
|
)
|
|
1677
|
+
if _run_status(root, job_id, run_id) == "succeeded":
|
|
1678
|
+
# `collect` reached the same provider task first and its
|
|
1679
|
+
# outputs are already in the project. Leave that record
|
|
1680
|
+
# alone rather than replacing a collected attempt.
|
|
1681
|
+
raise RuntimeError(
|
|
1682
|
+
"这次尝试已经被 collect 取回并标为成功;本次运行不覆盖它"
|
|
1683
|
+
)
|
|
1643
1684
|
run["status"] = "succeeded"
|
|
1644
1685
|
run["finished_at"] = utc_now()
|
|
1645
1686
|
run["outputs"] = written
|
|
1646
|
-
provider_job_id = response.get("provider_job_id")
|
|
1647
|
-
|
|
1687
|
+
provider_job_id = response.get("provider_job_id") or _read_provider_handle(
|
|
1688
|
+
root, job_id, run_id
|
|
1689
|
+
)
|
|
1690
|
+
if isinstance(provider_job_id, str) and 0 < len(provider_job_id) <= 200:
|
|
1648
1691
|
run["provider_job_id"] = provider_job_id
|
|
1649
1692
|
_write_run(root, job_id, run)
|
|
1650
1693
|
except Exception as exc:
|
|
@@ -1653,6 +1696,12 @@ def run_job(root: Path, *, job_id: str, adapter_config: Path) -> dict[str, Any]:
|
|
|
1653
1696
|
run["finished_at"] = utc_now()
|
|
1654
1697
|
if isinstance(exc, AdapterError) and exc.public_error is not None:
|
|
1655
1698
|
run["error"] = exc.public_error
|
|
1699
|
+
# A timeout or a killed adapter does not cancel the provider's
|
|
1700
|
+
# task. Carry the handle onto the failed record so `audit` can
|
|
1701
|
+
# say "collect this" instead of the creator paying twice.
|
|
1702
|
+
orphan = _read_provider_handle(root, job_id, run_id)
|
|
1703
|
+
if orphan is not None:
|
|
1704
|
+
run["provider_job_id"] = orphan
|
|
1656
1705
|
_write_run(root, job_id, run)
|
|
1657
1706
|
raise
|
|
1658
1707
|
return {
|
|
@@ -1663,6 +1712,101 @@ def run_job(root: Path, *, job_id: str, adapter_config: Path) -> dict[str, Any]:
|
|
|
1663
1712
|
}
|
|
1664
1713
|
|
|
1665
1714
|
|
|
1715
|
+
def _run_status(root: Path, job_id: str, run_id: str) -> str | None:
|
|
1716
|
+
"""The status one attempt currently has on disk, re-read rather than cached."""
|
|
1717
|
+
|
|
1718
|
+
for run in _read_run_history(root, job_id):
|
|
1719
|
+
if str(run.get("run_id")) == run_id:
|
|
1720
|
+
status = run.get("status")
|
|
1721
|
+
return status if isinstance(status, str) else None
|
|
1722
|
+
return None
|
|
1723
|
+
|
|
1724
|
+
|
|
1725
|
+
def collect_job(root: Path, *, job_id: str, adapter_config: Path) -> dict[str, Any]:
|
|
1726
|
+
"""Collect an already-submitted task's result without submitting a new one.
|
|
1727
|
+
|
|
1728
|
+
This is deliberately outside the confirmation gate. The gate exists to stop
|
|
1729
|
+
an unintended charge; collecting costs nothing, because the charge already
|
|
1730
|
+
happened when the task was submitted. Requiring a fresh confirmation here
|
|
1731
|
+
would mean the cheapest way out of an interrupted attempt is to pay again,
|
|
1732
|
+
which is exactly the outcome the gate is meant to prevent.
|
|
1733
|
+
"""
|
|
1734
|
+
|
|
1735
|
+
root = find_project(root)
|
|
1736
|
+
with tempfile.TemporaryDirectory(prefix="short-drama-collect-") as directory:
|
|
1737
|
+
output_root = Path(directory) / "outputs"
|
|
1738
|
+
output_root.mkdir(parents=True)
|
|
1739
|
+
with _project_lock(root):
|
|
1740
|
+
job = _read_job(root, job_id)
|
|
1741
|
+
command, timeout = _load_adapter(adapter_config, str(job["adapter"]), root)
|
|
1742
|
+
target_run = None
|
|
1743
|
+
for run in reversed(_read_run_history(root, job_id)):
|
|
1744
|
+
if run.get("status") == "succeeded":
|
|
1745
|
+
continue
|
|
1746
|
+
handle = run.get("provider_job_id") or _read_provider_handle(
|
|
1747
|
+
root, job_id, str(run["run_id"])
|
|
1748
|
+
)
|
|
1749
|
+
if handle:
|
|
1750
|
+
target_run = (run, handle)
|
|
1751
|
+
break
|
|
1752
|
+
if target_run is None:
|
|
1753
|
+
raise RuntimeError(
|
|
1754
|
+
"no unfinished attempt of this job carries a provider job id; "
|
|
1755
|
+
"there is nothing to collect"
|
|
1756
|
+
)
|
|
1757
|
+
run, provider_job_id = target_run
|
|
1758
|
+
|
|
1759
|
+
payload = {key: job[key] for key in ALLOWED_JOB_KEYS if key in job}
|
|
1760
|
+
payload.update(
|
|
1761
|
+
{
|
|
1762
|
+
"run_id": str(run["run_id"]),
|
|
1763
|
+
"output_root": str(output_root),
|
|
1764
|
+
"collect_provider_job_id": provider_job_id,
|
|
1765
|
+
}
|
|
1766
|
+
)
|
|
1767
|
+
response = _run_adapter(command, timeout, payload, root)
|
|
1768
|
+
adapter_outputs = _validate_adapter_outputs(job, response, output_root)
|
|
1769
|
+
written: list[dict[str, Any]] = []
|
|
1770
|
+
with _project_lock(root):
|
|
1771
|
+
# A live `run_job` on the same provider task is indistinguishable
|
|
1772
|
+
# from a dead one — both sit at `running`. Re-read before writing,
|
|
1773
|
+
# or two writers race onto the same bytes and one record is lost.
|
|
1774
|
+
if _run_status(root, job_id, str(run["run_id"])) == "succeeded":
|
|
1775
|
+
raise RuntimeError(
|
|
1776
|
+
"这次尝试在取回期间已经自己完成了,产物已经落进项目;"
|
|
1777
|
+
"没有覆盖它——先看 status,确认无误就不需要再做别的"
|
|
1778
|
+
)
|
|
1779
|
+
for target_name, source in adapter_outputs:
|
|
1780
|
+
target = _project_file(root, target_name, create_parent=True)
|
|
1781
|
+
digest, size = _copy_output(
|
|
1782
|
+
source, target, overwrite=bool(job["overwrite"])
|
|
1783
|
+
)
|
|
1784
|
+
written.append(
|
|
1785
|
+
{
|
|
1786
|
+
"path": target_name,
|
|
1787
|
+
"media_type": MEDIA_TYPES[PurePosixPath(target_name).suffix.casefold()],
|
|
1788
|
+
"bytes": size,
|
|
1789
|
+
"sha256": digest,
|
|
1790
|
+
}
|
|
1791
|
+
)
|
|
1792
|
+
collected = dict(run)
|
|
1793
|
+
collected["status"] = "succeeded"
|
|
1794
|
+
collected["finished_at"] = utc_now()
|
|
1795
|
+
collected["outputs"] = written
|
|
1796
|
+
collected["provider_job_id"] = provider_job_id
|
|
1797
|
+
collected["collected"] = True
|
|
1798
|
+
collected.pop("error", None)
|
|
1799
|
+
_write_run(root, job_id, collected)
|
|
1800
|
+
return {
|
|
1801
|
+
"job_id": job_id,
|
|
1802
|
+
"run_id": str(run["run_id"]),
|
|
1803
|
+
"state": "succeeded",
|
|
1804
|
+
"collected": True,
|
|
1805
|
+
"provider_job_id": provider_job_id,
|
|
1806
|
+
"outputs": written,
|
|
1807
|
+
}
|
|
1808
|
+
|
|
1809
|
+
|
|
1666
1810
|
def job_status(root: Path, *, job_id: str) -> dict[str, Any]:
|
|
1667
1811
|
root = find_project(root)
|
|
1668
1812
|
job = _read_job(root, job_id)
|
|
@@ -1816,6 +1960,28 @@ def audit_project(root: Path) -> dict[str, Any]:
|
|
|
1816
1960
|
attempts_failed += sum(run["status"] == "failed" for run in completed)
|
|
1817
1961
|
attempts_running += len(running)
|
|
1818
1962
|
attempts_superseded += len(history) - len(current)
|
|
1963
|
+
for run in current:
|
|
1964
|
+
if run.get("status") == "succeeded":
|
|
1965
|
+
continue
|
|
1966
|
+
handle = run.get("provider_job_id") or _read_provider_handle(
|
|
1967
|
+
root, job_id, str(run["run_id"])
|
|
1968
|
+
)
|
|
1969
|
+
if not handle:
|
|
1970
|
+
continue
|
|
1971
|
+
problems.append(
|
|
1972
|
+
{
|
|
1973
|
+
"code": "orphaned_provider_job",
|
|
1974
|
+
"job_id": job_id,
|
|
1975
|
+
"run_id": run["run_id"],
|
|
1976
|
+
"provider_job_id": handle,
|
|
1977
|
+
"action": "collect_before_retry",
|
|
1978
|
+
# A live attempt and a dead one both sit at `running`, so
|
|
1979
|
+
# this finding cannot tell them apart. Collecting is still
|
|
1980
|
+
# the right first move — it is the only one that costs
|
|
1981
|
+
# nothing — and it refuses safely if the attempt finished.
|
|
1982
|
+
"note": "status 为 running 时它可能仍在正常轮询;collect 会先确认再写",
|
|
1983
|
+
}
|
|
1984
|
+
)
|
|
1819
1985
|
if running:
|
|
1820
1986
|
running_jobs += 1
|
|
1821
1987
|
for run in running:
|
|
@@ -1986,6 +2152,13 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
1986
2152
|
run.add_argument("project")
|
|
1987
2153
|
run.add_argument("--job-id", required=True)
|
|
1988
2154
|
run.add_argument("--adapter-config", required=True)
|
|
2155
|
+
collect = commands.add_parser(
|
|
2156
|
+
"collect",
|
|
2157
|
+
help="Fetch the result of an already-submitted provider task without paying again.",
|
|
2158
|
+
)
|
|
2159
|
+
collect.add_argument("project")
|
|
2160
|
+
collect.add_argument("--job-id", required=True)
|
|
2161
|
+
collect.add_argument("--adapter-config", required=True)
|
|
1989
2162
|
status = commands.add_parser("status", help="Show one media job state.")
|
|
1990
2163
|
status.add_argument("project")
|
|
1991
2164
|
status.add_argument("--job-id", required=True)
|
|
@@ -2011,6 +2184,12 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
2011
2184
|
job_id=args.job_id,
|
|
2012
2185
|
adapter_config=Path(args.adapter_config),
|
|
2013
2186
|
)
|
|
2187
|
+
elif args.command == "collect":
|
|
2188
|
+
result = collect_job(
|
|
2189
|
+
Path(args.project),
|
|
2190
|
+
job_id=args.job_id,
|
|
2191
|
+
adapter_config=Path(args.adapter_config),
|
|
2192
|
+
)
|
|
2014
2193
|
elif args.command == "status":
|
|
2015
2194
|
result = job_status(Path(args.project), job_id=args.job_id)
|
|
2016
2195
|
else:
|
|
@@ -1134,8 +1134,45 @@ def _download(
|
|
|
1134
1134
|
return path
|
|
1135
1135
|
|
|
1136
1136
|
|
|
1137
|
+
def _record_handle(job: Mapping[str, Any], provider_job_id: str) -> None:
|
|
1138
|
+
"""Write the provider task id where the caller can find it after a crash.
|
|
1139
|
+
|
|
1140
|
+
A video task is billed at submission. Everything after that — polling,
|
|
1141
|
+
downloading — can be interrupted, and without this the caller is left with a
|
|
1142
|
+
live, already-paid task it has no id for. Written before the first poll, and
|
|
1143
|
+
deliberately best-effort: failing to record the handle must not fail a task
|
|
1144
|
+
that was submitted successfully.
|
|
1145
|
+
"""
|
|
1146
|
+
|
|
1147
|
+
destination = job.get("handle_path")
|
|
1148
|
+
if not isinstance(destination, str) or not destination:
|
|
1149
|
+
return
|
|
1150
|
+
try:
|
|
1151
|
+
path = Path(destination)
|
|
1152
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
1153
|
+
temporary = path.with_name(f".{path.name}.{os.getpid()}.tmp")
|
|
1154
|
+
temporary.write_text(
|
|
1155
|
+
json.dumps({"provider_job_id": provider_job_id}, ensure_ascii=True),
|
|
1156
|
+
encoding="utf-8",
|
|
1157
|
+
)
|
|
1158
|
+
temporary.replace(path)
|
|
1159
|
+
except OSError:
|
|
1160
|
+
return
|
|
1161
|
+
|
|
1162
|
+
|
|
1163
|
+
def _collect_target(job: Mapping[str, Any]) -> str | None:
|
|
1164
|
+
value = job.get("collect_provider_job_id")
|
|
1165
|
+
if isinstance(value, str) and value:
|
|
1166
|
+
return value
|
|
1167
|
+
return None
|
|
1168
|
+
|
|
1169
|
+
|
|
1137
1170
|
def _run_seedance(job: Mapping[str, Any]) -> tuple[Path, str]:
|
|
1138
1171
|
token = _credential("ARK_API_KEY")
|
|
1172
|
+
base = _base_url("SEEDANCE_BASE_URL", SEEDANCE_BASE_URL)
|
|
1173
|
+
collecting = _collect_target(job)
|
|
1174
|
+
if collecting is not None:
|
|
1175
|
+
return _poll_seedance(job, base=base, token=token, task_id=collecting)
|
|
1139
1176
|
model = os.environ.get("SEEDANCE_MODEL", "")
|
|
1140
1177
|
references = _reference_paths(job)
|
|
1141
1178
|
reference_roles = (
|
|
@@ -1155,7 +1192,6 @@ def _run_seedance(job: Mapping[str, Any]) -> tuple[Path, str]:
|
|
|
1155
1192
|
allowed_ratios=allowed_ratios,
|
|
1156
1193
|
duration_range=duration_range,
|
|
1157
1194
|
)
|
|
1158
|
-
base = _base_url("SEEDANCE_BASE_URL", SEEDANCE_BASE_URL)
|
|
1159
1195
|
created, _ = _request_json(
|
|
1160
1196
|
f"{base}/contents/generations/tasks",
|
|
1161
1197
|
provider="seedance",
|
|
@@ -1167,6 +1203,14 @@ def _run_seedance(job: Mapping[str, Any]) -> tuple[Path, str]:
|
|
|
1167
1203
|
raise AdapterFailure(
|
|
1168
1204
|
"Seedance did not return a task id", code="missing_task_id"
|
|
1169
1205
|
)
|
|
1206
|
+
# Billed from here on. Record the id before the first poll.
|
|
1207
|
+
_record_handle(job, task_id)
|
|
1208
|
+
return _poll_seedance(job, base=base, token=token, task_id=task_id)
|
|
1209
|
+
|
|
1210
|
+
|
|
1211
|
+
def _poll_seedance(
|
|
1212
|
+
job: Mapping[str, Any], *, base: str, token: str, task_id: str
|
|
1213
|
+
) -> tuple[Path, str]:
|
|
1170
1214
|
try:
|
|
1171
1215
|
interval = float(os.environ.get("SEEDANCE_POLL_INTERVAL", "5"))
|
|
1172
1216
|
deadline = time.monotonic() + float(os.environ.get("SEEDANCE_TIMEOUT_SECONDS", "1800"))
|
|
@@ -1330,6 +1374,10 @@ def _run_minimax(job: Mapping[str, Any]) -> tuple[Path, str | None]:
|
|
|
1330
1374
|
|
|
1331
1375
|
def _run_minimax_video(job: Mapping[str, Any]) -> tuple[Path, str]:
|
|
1332
1376
|
token = _credential("MINIMAX_API_KEY")
|
|
1377
|
+
base = _base_url("MINIMAX_VIDEO_BASE_URL", MINIMAX_VIDEO_BASE_URL)
|
|
1378
|
+
collecting = _collect_target(job)
|
|
1379
|
+
if collecting is not None:
|
|
1380
|
+
return _poll_minimax_video(job, base=base, token=token, task_id=collecting)
|
|
1333
1381
|
model = os.environ.get("MINIMAX_VIDEO_MODEL", "")
|
|
1334
1382
|
references = _reference_paths(job)
|
|
1335
1383
|
reference_roles = (
|
|
@@ -1350,7 +1398,6 @@ def _run_minimax_video(job: Mapping[str, Any]) -> tuple[Path, str]:
|
|
|
1350
1398
|
allowed_resolutions=resolutions,
|
|
1351
1399
|
duration_range=duration_range,
|
|
1352
1400
|
)
|
|
1353
|
-
base = _base_url("MINIMAX_VIDEO_BASE_URL", MINIMAX_VIDEO_BASE_URL)
|
|
1354
1401
|
created, _ = _request_json(
|
|
1355
1402
|
f"{base}/video_generation",
|
|
1356
1403
|
provider="minimax-h3",
|
|
@@ -1363,6 +1410,14 @@ def _run_minimax_video(job: Mapping[str, Any]) -> tuple[Path, str]:
|
|
|
1363
1410
|
"MiniMax did not return a task id",
|
|
1364
1411
|
code=_provider_code(created) or "missing_task_id",
|
|
1365
1412
|
)
|
|
1413
|
+
# Billed from here on. Record the id before the first poll.
|
|
1414
|
+
_record_handle(job, task_id)
|
|
1415
|
+
return _poll_minimax_video(job, base=base, token=token, task_id=task_id)
|
|
1416
|
+
|
|
1417
|
+
|
|
1418
|
+
def _poll_minimax_video(
|
|
1419
|
+
job: Mapping[str, Any], *, base: str, token: str, task_id: str
|
|
1420
|
+
) -> tuple[Path, str]:
|
|
1366
1421
|
try:
|
|
1367
1422
|
interval = float(os.environ.get("MINIMAX_VIDEO_POLL_INTERVAL", "5"))
|
|
1368
1423
|
deadline = time.monotonic() + float(
|
|
@@ -71,6 +71,9 @@ license: MIT
|
|
|
71
71
|
- 剧本对白、动作、声音、画面文字是否都有画面/声音承载;
|
|
72
72
|
- 人物、地点、道具状态和镜头起止是否连续;
|
|
73
73
|
- 提示词是否从准确起点到准确终点;
|
|
74
|
+
- 目标模型画音同轨时,可复制正文的声音段是否写成正面闭合事件集:无对白镜不靠 `No dialogue`、
|
|
75
|
+
`无对白` 关闭语音通道,完整性声明也不枚举本镜没有的层(`VID-22`、`VID-23`,见
|
|
76
|
+
[视觉运动审查表](references/rubric-visual-motion.md));
|
|
74
77
|
- 私有输入、凭据、绝对路径或内部流程文字是否泄漏。
|
|
75
78
|
|
|
76
79
|
缺少必要输入时只停止依赖它的判断,其他问题可以继续汇总。
|
|
@@ -71,6 +71,26 @@
|
|
|
71
71
|
- 角色名/资产引用进入台词内文;
|
|
72
72
|
- 音效、环境底噪与对白方向没有来源或相互遮蔽。
|
|
73
73
|
|
|
74
|
+
目标模型画音同轨时(`VID-23`),这一节多四条只在同轨条件下成立的核对。四条里只有配乐那条
|
|
75
|
+
有实测后果,其余三条是「正文要如实、完整地交代本镜声音」,按这个理由报,不要替它们编一套
|
|
76
|
+
生成机制:
|
|
77
|
+
|
|
78
|
+
- **配乐层在正文里整个缺席**(实测有后果):写了空形式(H3 为 `non_diegetic_music: N/A`)的
|
|
79
|
+
14 次生成没有一次自带配乐,完全不提配乐的 3 次里 2 次返回了自造配乐。这一条按 major 起报。
|
|
80
|
+
**空形式本身就是合规写法**:`non_diegetic_music: N/A` 是「本镜没有非画内配乐」的正面陈述,
|
|
81
|
+
不是待填占位符。只有整行不存在时才报,永远不要要求 owner 删掉已经写好的空形式;
|
|
82
|
+
- 用排除句代替声轨描述:正文出现 `No dialogue`、`无对白` 而没有正面写出这条声轨由什么组成、
|
|
83
|
+
覆盖多长——问题在于正文没交代本镜该有什么声音,不在于"否定会招来它";
|
|
84
|
+
- 完整性声明枚举了本镜没有的层:无对白镜写出
|
|
85
|
+
`The audio timeline is complete after the listed ambience, effects and timed dialogue.`
|
|
86
|
+
是一句与本镜事实不符的话;
|
|
87
|
+
- 无对白镜从没说清声轨**是什么**:既没有覆盖全时长的底声构成,也没给在场人物一个可见的
|
|
88
|
+
不说话状态(嘴唇闭合)。
|
|
89
|
+
|
|
90
|
+
中间两条同时出现时按一个 finding 报,证据引这两句话本身:它们互相矛盾,本身就是声音契约
|
|
91
|
+
没写清。修订要求写成正面声轨,不能是"再加一句不要对白"。本套件实测未观察到这组写法导致
|
|
92
|
+
模型补出台词,报 finding 时不要这样断言。
|
|
93
|
+
|
|
74
94
|
### 5. 负面补丁淹没动作
|
|
75
95
|
|
|
76
96
|
为防止模型缺陷叠加大量禁令,真正的起点、动作、表演与终点被挤成几句。
|
|
@@ -86,6 +86,42 @@
|
|
|
86
86
|
- Does each media reference state its exact purpose, what may be copied, and what
|
|
87
87
|
must not be copied, rather than treating all visible pixels as authority?
|
|
88
88
|
|
|
89
|
+
For a copyable body, ask these separately — they are the two rules a rewrite regresses on
|
|
90
|
+
most often, because a body that reads "complete" can still carry both defects. Findings here
|
|
91
|
+
are about whether the body states this shot's sound and text truthfully and completely; only
|
|
92
|
+
the music-layer item below has a measured generation consequence behind it:
|
|
93
|
+
|
|
94
|
+
- Does the body carry only what will be filmed (`VID-22`)? Negative action intent must
|
|
95
|
+
already be rewritten as a visible positive state. The one permitted negation is the
|
|
96
|
+
no-subtitle/caption/dialogue-overlay declaration; a second exclusion clause is a defect,
|
|
97
|
+
not extra safety. (The often-repeated reason — that a noun inside a negation gets
|
|
98
|
+
rendered — did not reproduce in this suite's probes, so cite the body's own clarity,
|
|
99
|
+
not that mechanism, when you raise this.)
|
|
100
|
+
- When the target model generates audio in the same pass (`VID-23`), is the sound written
|
|
101
|
+
as a closed positive event set — what the track consists of, and across what span —
|
|
102
|
+
rather than a list of what must not be heard? Check three failures by name:
|
|
103
|
+
- an exclusion clause (`No dialogue`, `无对白`, `no music`) standing in for a positive
|
|
104
|
+
soundscape, so the body never says what this shot sounds like;
|
|
105
|
+
- a completeness declaration that enumerates a layer this shot does not have. "The audio
|
|
106
|
+
timeline is complete after the listed ambience, effects and timed dialogue" on a shot
|
|
107
|
+
with no lines states something untrue of this shot. Read the declaration against the
|
|
108
|
+
shot's actual layers, not against a template;
|
|
109
|
+
- a shot that leaves the non-diegetic music layer unstated. This is the one sound defect
|
|
110
|
+
with measured consequences: bodies that omitted the layer came back with an invented
|
|
111
|
+
score. **The dialect's empty form satisfies this** — for H3 that is the literal line
|
|
112
|
+
`non_diegetic_music: N/A`, which is a correct statement of "no non-diegetic music",
|
|
113
|
+
not a placeholder. Raise this only when the layer is missing from the body altogether;
|
|
114
|
+
never ask an owner to delete a present empty form;
|
|
115
|
+
- a shot with no lines whose body never says what the track *is* for the full duration,
|
|
116
|
+
and never gives the on-screen performers a visible non-speaking state.
|
|
117
|
+
Report a contradictory pair — an exclusion clause plus a declaration that enumerates the
|
|
118
|
+
excluded layer — as one finding against the body's sound contract, and quote both
|
|
119
|
+
sentences. Raise it as an internally inconsistent sound contract, which it is; do not
|
|
120
|
+
assert that it makes the model invent lines, which this suite has probed and not seen.
|
|
121
|
+
|
|
122
|
+
These two apply to a body the review itself is about to ask an owner to rewrite. A revision
|
|
123
|
+
request that removes a defect by adding `no ...` has moved the defect, not fixed it.
|
|
124
|
+
|
|
89
125
|
For a pickup or alternate, ask these separately:
|
|
90
126
|
|
|
91
127
|
- Does the pickup/alternate name its master by visible `MOTION-...` heading?
|
|
@@ -49,6 +49,8 @@ license: MIT
|
|
|
49
49
|
先用已接受集长换成明确时间边界再分镜;例如 60 秒作品的“后半段首次出现”不得早于 30 秒。
|
|
50
50
|
3. 每镜写清唯一职责、来源、时长、景别/机位、起点、唯一主要动作、终点和声音;用
|
|
51
51
|
`起点 → 唯一动作 → 终点` 直接说明人物、双手与持物怎样完成本镜状态转换。三条依据留到第 6 步。
|
|
52
|
+
有对白、VO 或 OS 时,先按原文和人物说话方式估算完整发声时间,再安排不能重叠的动作、停顿与
|
|
53
|
+
末尾反应;把估时依据简写在本镜「声音」里。具体做法见 [对白估时](references/shot-craft.md#对白估时)。
|
|
52
54
|
4. 锁定人物朝向、位置、视线、持物、出入口、屏幕方向和必要状态;上游已有会限制动作空间的持续关系时,把关系投影进本镜起点与终点。
|
|
53
55
|
5. 冻结关键帧只投影镜头起点;删除终点才出现的文字、动作、姿态或道具状态。
|
|
54
56
|
6. 关键帧成稿后,先从成稿正文读出这一格里需要保持身份、造型或地理的人物、地点和道具,
|
|
@@ -69,6 +71,11 @@ license: MIT
|
|
|
69
71
|
|
|
70
72
|
- 每镜有唯一职责、明确来源、可见起止状态和合理时长。「来源」以《剧本.md》里真实存在的场景 ID 开头,
|
|
71
73
|
ID 之后可以跟必要短引文,跨场次的镜头用 `、` 连写多条;`creator_markdown_check.py` 会核对每个 ID 都解析得到。
|
|
74
|
+
- **引文是认领,不是背景。** 「来源」里引了哪一句,就等于说这一镜负责拍它。引一个本镜画面里
|
|
75
|
+
根本没有的人做的动作——把「另一个人低头去翻自己的稿子」写进一个只拍某一个人的特写——
|
|
76
|
+
那个动作就从所有人的清单上消失了:没有别的镜头认领它,也没有任何一步报它缺失,
|
|
77
|
+
于是它根本不会被拍出来,而这个缺口只有等人看完成片、问「他的反应呢」才浮出来。
|
|
78
|
+
确实看不见就在「视觉依据」末尾写 `;画外:<类别>「<名称>」`,机械核对认这个写法。
|
|
72
79
|
- 剧本里的每个场景都要有镜头承载。确实决定不拍某一场时,在《分镜.md》正文开头写一行
|
|
73
80
|
`- 未拍场次:<场景 ID>(理由:……)`,多项用 `;` 连接。SHT-01 一直允许带理由的省略,
|
|
74
81
|
但省略和漏拍在成稿文档里长得一模一样,所以理由要写下来,而不是留在对话里。
|
|
@@ -4,6 +4,10 @@
|
|
|
4
4
|
每次导出都记下配方版本;文本被手改时,先预览恢复、
|
|
5
5
|
采用或合并的结果。
|
|
6
6
|
|
|
7
|
+
**这份模板属于结构化管线**(`keyframes.jsonl` 一路)。creator-first 项目的《分镜.md》骨架
|
|
8
|
+
以 `SKILL.md` 为准:`## SHOT-... · 中文名`,关键帧正文标题是 `### 冻结关键帧提示词`。
|
|
9
|
+
不要把这里的加粗字段名、`KEY-...` 或 `### 可复制通用提示词` 搬进 creator-first 文档。
|
|
10
|
+
|
|
7
11
|
```markdown
|
|
8
12
|
# EP<编号> · 冻结关键帧提示词
|
|
9
13
|
|
|
@@ -65,6 +65,27 @@
|
|
|
65
65
|
镜头可以负责建立空间、让观众站到某个人物一边、展示证据、暂缓信息、转移权力、
|
|
66
66
|
表现反应,或让后果无法回避。“中景展示人物说话”不算足够的镜头目的。
|
|
67
67
|
|
|
68
|
+
### 承受者也要有一镜
|
|
69
|
+
|
|
70
|
+
一次打击、否定、揭穿或翻盘,画面上有两个人:**说出它的人,和挨了它的人**。
|
|
71
|
+
只拍前者,观众收到的是一句评价;拍了后者,观众才收到一次事件。
|
|
72
|
+
|
|
73
|
+
这不是「多给一个反应镜头会更好」的锦上添花。剧情上被推翻的那个人如果全程只有后脑、
|
|
74
|
+
只在群像里占几十个像素,观众其实不知道该看谁输——那句最狠的台词就只是一个人在说话,
|
|
75
|
+
爽点不成立。**分镜阶段就要决定谁承受这一下、哪一镜给他**,不要留给剪辑去补,
|
|
76
|
+
因为剪辑只能在已经生成的素材里选,选不出一个没拍过的反应。
|
|
77
|
+
|
|
78
|
+
具体到写法:
|
|
79
|
+
|
|
80
|
+
- 台词落点是针对某个在场的人时,检查分镜里有没有一镜的画面主体是**那个人**;
|
|
81
|
+
- 没有就补一镜,通常很短(一到两秒足够:停手、抬眼、想开口又闭嘴);
|
|
82
|
+
- 承受者不在场时,那一下的后果要落到别的可见东西上(屏幕、门、被推开的文件),
|
|
83
|
+
否则这次打击在画面上没有发生过;
|
|
84
|
+
- 群像镜头**不算**承受者反应:一张在四个人里的脸承担不了「他输了」这件事。
|
|
85
|
+
|
|
86
|
+
**这条只在剧情确实存在承受者时生效。** 不为每句台词配反应镜头,也不把它变成正反打配额——
|
|
87
|
+
安静的场次、独角戏、纯信息交代不适用。
|
|
88
|
+
|
|
68
89
|
### 观众可见性 (`SHT-12`)
|
|
69
90
|
|
|
70
91
|
构图同时决定观众此刻能知道什么。每个事实单独写一条观众可见性判断:绑定准确场景 ID,说明
|
|
@@ -156,6 +177,24 @@
|
|
|
156
177
|
在变化处切镜:新信息、新目光、新决定、新空间关系、新威胁、新证据或节奏变化。
|
|
157
178
|
不要只为换角度而重复同一个动作。给反应留下足够时间,让观众读懂后果。
|
|
158
179
|
|
|
180
|
+
### 对白估时
|
|
181
|
+
|
|
182
|
+
**`craft_default`**:先排完整声音事件,再确定镜长。不要先选一个短镜长,再要求人物加快语速把台词塞进去。
|
|
183
|
+
|
|
184
|
+
- 有同一句、同语言、同表演方式的录音或配音时,使用实际发声区间;文件首尾静默与句内停顿分开核对,
|
|
185
|
+
不把整个文件长度当作纯说话时间。没有音频时按原文、人物语速、情绪和换气估一个区间,明确标为
|
|
186
|
+
「文本估计」,不要声称已实测。项目已校准的语速可以辅助估算;数字、缩写按实际读法考虑,不能只数可见字符。
|
|
187
|
+
- 顺序说话的多句或多人对白累加,句间停顿与听者反应按实际顺序加入。边说边抬眼等确能并行的动作
|
|
188
|
+
与对白共用时间;必须等话说完才发生的动作另占时间。不要重复计算已包含在录音或估计里的停顿。
|
|
189
|
+
- 在本镜「声音」中简写依据,例如「对白按克制语气文本估计 8–10 秒(含句内停顿);开口前 0.5 秒,
|
|
190
|
+
说完后的反应 1 秒;抬眼与对白并行」。此例按估计上沿需要 11.5 秒;若目标是 H3,向上取到 12 秒,
|
|
191
|
+
余下 0.5 秒安排在已有终点停留。数字只是此例的预算,不是统一余量或官方语速。
|
|
192
|
+
- 取容得下时间线的合法时长;超过模型上限时按语义和可见状态闭合点拆镜,并检查整集总时长。
|
|
193
|
+
已接受镜长或集长固定且放不下时,说明冲突并提出延长、拆镜或剧本修订;不擅自删词或把正常对白改成快读。
|
|
194
|
+
|
|
195
|
+
文本估计是规划依据,实际生成后还需听完整性、语速和尾音。没有录音、配音或生成结果时,不把估计写成
|
|
196
|
+
秒级准确的声音控制承诺;纯动作镜无需补对白预算。
|
|
197
|
+
|
|
159
198
|
## 视觉方向选择 (`SHT-07`)
|
|
160
199
|
|
|
161
200
|
焦段语言、节奏、固定机位、手持或形式化调度属于 `taste_option`,读取创作者已接受的
|
|
@@ -37,6 +37,8 @@
|
|
|
37
37
|
| SHT-23 | structural_invariant | An omitted reference choice is not consent to text-to-video. After reference discovery, each shot either binds all verified real images as `REF-...`, declares creator-supplied pictures as `PLAN-...` slots that locate an existing `IMG-...` board or `SHOT-...` keyframe, preserves partial bindings while naming the still-missing start/identity/geography/prop images, or records the creator's explicit text-to-video choice. A prompt entry, plausible filename, or text description never counts as a real image, and a `PLAN-...` slot never claims one: it states that the picture is supplied outside the project at generation time, which is why it cannot be sent to production. |
|
|
38
38
|
| SHT-24 | reviewed_invariant | A visual basis describes this frame, not the episode's cast list. Each declared 控制 scope stays within what this frame can carry, and a binding is not carried forward merely because the subject remains in the scene. |
|
|
39
39
|
| SHT-25 | structural_invariant | Every real reference image a shot binds declares one 用途 from the closed set 身份/造型状态/地理/构图/尺度/效果/起始帧/结束帧/风格, so the shot can say which start frame and which identity/geography/prop pictures a downstream job sends. At most one 起始帧 and one 结束帧 per shot, and 结束帧 requires 起始帧. |
|
|
40
|
+
| SHT-26 | structural_invariant | A shot's 来源 is its claim on the screenplay: whatever it quotes, this shot is the one that films it. Every 视觉设定 entry the quote calls by name is therefore either in this shot's 视觉依据 or declared 画外 there. Quoting an action performed by someone the frame never shows takes that action off every list — no other shot claims it and nothing reports it missing, so it simply never gets filmed, and the gap only surfaces when a person watches the finished film. The check matches entry names rather than 画面代称, because a 来源 quote comes from 剧本.md; an entry that opted out with `画面代称:无` stays out of this check too. |
|
|
41
|
+
| SHT-27 | reviewed_invariant | When a beat lands on a present character — a reversal, a rebuttal, an exposure, a decision that overrules someone — some shot takes that person as its subject. Filming only the speaker delivers a remark; filming the one it lands on delivers an event, and an audience that cannot see who lost does not experience the turn. A group shot does not count: one face among four cannot carry it. Editing cannot supply this later, because editing chooses among shots that exist. Inactive where no one is on the receiving end — quiet scenes, solo work, and plain exposition are not owed a reaction shot, and this is not a shot-reverse-shot quota. |
|
|
40
42
|
|
|
41
43
|
### `CON`
|
|
42
44
|
|
|
@@ -74,6 +74,8 @@ license: MIT
|
|
|
74
74
|
多槽参考与首/尾帧在同一请求内互斥的模型,起始帧也随整组走多槽参考,不拆成两套输入。
|
|
75
75
|
5. 核对本镜时长落在目标模型档案的原生区间内;不在区间内就回到分镜修订,不能写完提示词后留给
|
|
76
76
|
adapter 报错,也不能在正文里暗自改变时长。
|
|
77
|
+
有对白、VO 或 OS 时,同时核对分镜「声音」中的估时依据与实际可用发声窗口;缺少依据就按
|
|
78
|
+
[对白预算](references/performance-action-timing.md#对白预算) 补做判断。秒段加总正确不代表台词说得完。
|
|
77
79
|
6. 写不能动的边框:图生视频以参考帧和视觉设定锁定;文生视频把必要静态事实写入「静态视觉锚点」和可复制正文。
|
|
78
80
|
7. 原样读取分镜的「起点 → 唯一动作 → 终点」,确认人物、双手和持物的状态转换都发生在镜内。
|
|
79
81
|
8. 写“静态锚点 → 起点 → 唯一动作(触发、接触、落点)→ 次级反应 → 运镜 → 声音 → 终点”,
|
|
@@ -85,7 +87,10 @@ license: MIT
|
|
|
85
87
|
且逐字对白在单镜可复制正文中只出现一次。视觉动作段只按时间和说话人引用该声音事件,不再抄一遍
|
|
86
88
|
台词。事件外回到已声明的环境声或静默。Brief、剧本或声音方向已经明确禁止外语前导、语气词、
|
|
87
89
|
额外人声或重复时,把这些**已确认的发声边界**在完整时间线末尾合并写一次;不靠“只包含”让执行端
|
|
88
|
-
|
|
90
|
+
猜,也不自行扩充新的禁用类别。完整时间线声明是声音段落终点,其后不再追加第二组声音排除句,
|
|
91
|
+
且只列本镜实际有的声音层。本镜没有对白时,这条声明里不出现 dialogue,正文也不写 `no dialogue`、
|
|
92
|
+
`无对白` 这类排除句,改为正面写出底声的构成与覆盖区间,并把「不说话」写成在场人物的可见状态:
|
|
93
|
+
正面句同时交代了这一镜该有什么声音。配乐层则必须显式关掉,那一条有实测支撑。
|
|
89
94
|
13. 成稿时逐句把 `no/not/without + 动作或状态变化` 改写为当前持续状态或允许动作;直接改正文,不调用工具或生成检查产物。再检查不跨下一镜、不要求互斥动作、不让人物或道具瞬移变形。
|
|
90
95
|
14. 用户要整集就完成整集,镜头组只是内部批次。
|
|
91
96
|
15. 同一连续时空中相邻正式生成段、且目标档案支持续接时,默认选择“从上一段生成结果续接”;
|
|
@@ -108,6 +113,7 @@ license: MIT
|
|
|
108
113
|
- 声音只写该镜需要的对白、画外音、环境或转场职责,不擅自补歌词。
|
|
109
114
|
- 非画内字幕、caption 和对白文字叠加默认不生成;正文直接声明全镜任何时刻都不生成这些内容,不用抽象工作流术语代替执行指令。只有剧本或创作者明确要求才开启。画内精确文字是独立允许集合,不因关闭字幕而消失,也不让未批准文字混入。
|
|
110
115
|
- 同轨对白/VO/OS 同时写逐字文本、口语语言和发声边界;完整时间线闭合后,不再枚举不存在的语言、语气或声源,也不追加 `no/not/without + 声音类别`。
|
|
116
|
+
- 同轨模型下的无对白镜同样要写满声音段:正面写出底声的构成与覆盖区间,把「不说话」写成在场人物的可见状态,完整性声明只列本镜实际有的层,不枚举 dialogue。非画内配乐层显式写空(H3 为 `non_diegetic_music: N/A`)——不写配乐层实测会被模型补上,语音层未观察到同样现象。
|
|
111
117
|
- 正文里被引号或 `<d>` 包起来的中文必须逐字来自《剧本.md》,画内标牌文字可来自《视觉设定.md》;
|
|
112
118
|
分镜自己写下的画内文字与声音同样算数。`creator_markdown_check.py` 会核对四字以上的引文(VID-25)。
|
|
113
119
|
台词不在剧本里就回到写作技能改剧本,不在提示词里改。
|