@oh-story/dsh 0.1.10 → 0.1.12
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 +7 -3
- package/lib/client.js.map +2 -2
- package/lib/drama/manifest.json +212 -142
- package/lib/drama/skills/short-drama/SKILL.md +14 -2
- package/lib/drama/skills/short-drama/assets/project-template/short-drama.json +3 -0
- package/lib/drama/skills/short-drama/references/creator-documents.md +3 -0
- package/lib/drama/skills/short-drama/references/knowhow-index.md +15 -0
- package/lib/drama/skills/short-drama/scripts/project_tool.py +131 -0
- package/lib/drama/skills/short-drama-assets/SKILL.md +2 -1
- package/lib/drama/skills/short-drama-assets/references/asset-review-checklist.md +2 -0
- package/lib/drama/skills/short-drama-assets/references/era-anchors.md +37 -0
- package/lib/drama/skills/short-drama-assets/references/stage-contract.md +2 -0
- package/lib/drama/skills/short-drama-develop/SKILL.md +10 -4
- package/lib/drama/skills/short-drama-develop/assets/adaptation-map.example.jsonl +1 -1
- package/lib/drama/skills/short-drama-develop/assets/creative-brief.md +8 -0
- package/lib/drama/skills/short-drama-develop/references/adaptation-craft.md +80 -13
- package/lib/drama/skills/short-drama-develop/references/episode-design.md +95 -12
- package/lib/drama/skills/short-drama-develop/references/genre-and-hook-playbook.md +9 -5
- package/lib/drama/skills/short-drama-develop/references/genre-cards//345/244/215/344/273/207/346/211/223/350/204/270.md +1 -1
- package/lib/drama/skills/short-drama-develop/references/genre-cards//347/263/273/347/273/237/346/265/201.md +51 -0
- package/lib/drama/skills/short-drama-develop/references/genre-cards//351/200/206/350/242/255.md +51 -0
- package/lib/drama/skills/short-drama-develop/references/genre-cards//351/207/215/347/224/237/347/251/277/350/266/212.md +51 -0
- package/lib/drama/skills/short-drama-develop/references/genre-cards.md +10 -7
- package/lib/drama/skills/short-drama-develop/references/premise-devices.md +15 -9
- package/lib/drama/skills/short-drama-develop/references/serial-character-and-memory.md +3 -2
- package/lib/drama/skills/short-drama-develop/references/stage-contract.md +14 -7
- package/lib/drama/skills/short-drama-develop/references/story-craft.md +7 -3
- package/lib/drama/skills/short-drama-edit/SKILL.md +43 -20
- package/lib/drama/skills/short-drama-edit/assets/remotion/README.md +49 -13
- package/lib/drama/skills/short-drama-edit/assets/remotion/package.json +4 -2
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/Overlay.tsx +31 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/Root.tsx +5 -5
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/Subtitles.tsx +68 -69
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/font.ts +39 -29
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/rules.mjs +188 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/schema.ts +26 -13
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/Card.tsx +72 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/CornerChip.tsx +39 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/ScreenText.tsx +36 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/SystemPanel.tsx +44 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/TaskPanel.tsx +100 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/chrome.tsx +197 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/tokens.ts +62 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/tsconfig.json +1 -0
- package/lib/drama/skills/short-drama-edit/assets//345/211/252/350/276/221/345/215/225.md +4 -2
- package/lib/drama/skills/short-drama-edit/references/cut-craft.md +3 -1
- package/lib/drama/skills/short-drama-edit/references/delivery-verify.md +6 -2
- package/lib/drama/skills/short-drama-edit/references/generated-footage.md +12 -6
- package/lib/drama/skills/short-drama-edit/references/sound-and-subtitles.md +13 -8
- package/lib/drama/skills/short-drama-edit/references/stage-contract.md +2 -0
- package/lib/drama/skills/short-drama-edit/scripts/edit_tool.py +1153 -121
- package/lib/drama/skills/short-drama-edit/scripts/selftest.py +49 -9
- package/lib/drama/skills/short-drama-novel-analyze/SKILL.md +3 -3
- package/lib/drama/skills/short-drama-novel-analyze/assets/episode-candidate.example.jsonl +2 -2
- package/lib/drama/skills/short-drama-novel-analyze/references/adaptation-triage.md +8 -4
- package/lib/drama/skills/short-drama-novel-analyze/references/adaptation-value.md +10 -3
- package/lib/drama/skills/short-drama-novel-analyze/references/aggregation-and-entities.md +25 -1
- package/lib/drama/skills/short-drama-novel-analyze/references/stage-contract.md +1 -0
- package/lib/drama/skills/short-drama-review/SKILL.md +2 -2
- package/lib/drama/skills/short-drama-review/references/review-method.md +6 -1
- package/lib/drama/skills/short-drama-review/references/rubric-assets-prompts.md +11 -0
- package/lib/drama/skills/short-drama-review/references/rubric-story-script.md +77 -3
- package/lib/drama/skills/short-drama-review/references/rubric-visual-motion.md +15 -0
- package/lib/drama/skills/short-drama-review/references/stage-contract.md +3 -0
- package/lib/drama/skills/short-drama-storyboard/SKILL.md +13 -5
- package/lib/drama/skills/short-drama-storyboard/references/blocking-playbooks.md +1 -1
- package/lib/drama/skills/short-drama-storyboard/references/production-shot-grammar.md +2 -2
- package/lib/drama/skills/short-drama-storyboard/references/shot-craft.md +82 -14
- package/lib/drama/skills/short-drama-storyboard/references/stage-contract.md +4 -2
- package/lib/drama/skills/short-drama-video-prompts/SKILL.md +5 -3
- package/lib/drama/skills/short-drama-video-prompts/references/camera-audio-continuity.md +12 -0
- package/lib/drama/skills/short-drama-video-prompts/references/delivery-profile.md +4 -5
- package/lib/drama/skills/short-drama-video-prompts/references/generability.md +4 -2
- package/lib/drama/skills/short-drama-video-prompts/references/minimax-h3.md +40 -1
- package/lib/drama/skills/short-drama-video-prompts/references/motion-recipe.md +20 -14
- package/lib/drama/skills/short-drama-video-prompts/references/performance-action-timing.md +1 -1
- package/lib/drama/skills/short-drama-video-prompts/references/production-prompt-grammar.md +8 -7
- package/lib/drama/skills/short-drama-video-prompts/references/review-and-fixtures.md +1 -1
- package/lib/drama/skills/short-drama-video-prompts/references/stage-contract.md +1 -0
- package/lib/drama/skills/short-drama-video-prompts/references/target-model-profile.md +8 -1
- package/lib/drama/skills/short-drama-video-prompts/references/wan-3.0.md +103 -0
- package/lib/drama/skills/short-drama-video-prompts/scripts/motion_timing_check.py +8 -7
- package/lib/drama/skills/short-drama-write/SKILL.md +8 -3
- package/lib/drama/skills/short-drama-write/references/dialogue-craft.md +1 -1
- package/lib/drama/skills/short-drama-write/references/script-craft.md +56 -3
- package/lib/drama/skills/short-drama-write/references/stage-contract.md +4 -0
- package/lib/drama/skills/short-drama-write/scripts/duration_estimate.py +48 -2
- package/lib/index.js +71 -21
- package/lib/index.js.map +3 -3
- package/lib/oh-story/manifest.json +545 -310
- package/lib/oh-story/roles/chapter-extractor.md +11 -11
- package/lib/oh-story/roles/character-designer.md +1 -2
- package/lib/oh-story/roles/consistency-checker.md +11 -9
- package/lib/oh-story/roles/narrative-writer.md +24 -25
- package/lib/oh-story/roles/story-architect.md +9 -61
- package/lib/oh-story/roles/story-explorer.md +20 -125
- package/lib/oh-story/roles/story-researcher.md +0 -1
- package/lib/oh-story/skills/story/SKILL.md +6 -6
- package/lib/oh-story/skills/story/VERSION +1 -1
- package/lib/oh-story/skills/story/references/author-memory-maintenance.md +75 -0
- package/lib/oh-story/skills/story/references/author-memory.md +40 -118
- package/lib/oh-story/skills/story/scripts/author_memory_commit.py +3 -3
- package/lib/oh-story/skills/story-deslop/SKILL.md +24 -25
- package/lib/oh-story/skills/story-deslop/references/agent-calls.md +5 -0
- package/lib/oh-story/skills/story-deslop/references/anti-ai-writing.md +1 -1
- package/lib/oh-story/skills/story-deslop/references/author-memory-maintenance.md +75 -0
- package/lib/oh-story/skills/story-deslop/references/author-memory.md +40 -118
- package/lib/oh-story/skills/story-deslop/references/banned-words.md +2 -2
- package/lib/oh-story/skills/story-deslop/references/deslop-gates.md +21 -4
- package/lib/oh-story/skills/story-deslop/references/style-resolution.md +4 -4
- package/lib/oh-story/skills/story-deslop/scripts/author_memory_commit.py +3 -3
- package/lib/oh-story/skills/story-deslop/scripts/check-ai-patterns.js +155 -8
- package/lib/oh-story/skills/story-import/SKILL.md +63 -579
- package/lib/oh-story/skills/story-import/references/deep-analysis.md +47 -0
- package/lib/oh-story/skills/story-import/references/format-and-structure.md +1 -1
- package/lib/oh-story/skills/story-import/references/import-report.md +64 -0
- package/lib/oh-story/skills/story-import/references/import-special-cases.md +34 -0
- package/lib/oh-story/skills/story-import/references/import-tracking.md +95 -0
- package/lib/oh-story/skills/story-import/references/length-routing.md +1 -1
- package/lib/oh-story/skills/story-import/references/outline-reverse-rules.md +79 -0
- package/lib/oh-story/skills/story-import/references/outline-reverse.md +24 -0
- package/lib/oh-story/skills/story-import/references/structure-mapping-long.md +95 -91
- package/lib/oh-story/skills/story-import/references/structure-mapping-short.md +24 -12
- package/lib/oh-story/skills/story-import/references/tracking-initialization.md +1 -1
- package/lib/oh-story/skills/story-import/references/tracking-transaction.md +11 -9
- package/lib/oh-story/skills/story-import/scripts/build_outline_brief.py +332 -0
- package/lib/oh-story/skills/story-import/scripts/check-outline-contract.js +101 -14
- package/lib/oh-story/skills/story-import/scripts/tracking_commit.py +278 -23
- package/lib/oh-story/skills/story-import/scripts/wordcount_core.py +179 -23
- package/lib/oh-story/skills/story-long-analyze/SKILL.md +28 -50
- package/lib/oh-story/skills/story-long-analyze/references/author-facing.md +12 -5
- package/lib/oh-story/skills/story-long-analyze/references/deconstruction-notes.md +1 -1
- package/lib/oh-story/skills/story-long-analyze/references/final-checks.md +19 -0
- package/lib/oh-story/skills/story-long-analyze/references/index-rebuild.md +11 -0
- package/lib/oh-story/skills/story-long-analyze/references/pipeline-ops.md +21 -55
- package/lib/oh-story/skills/story-long-analyze/references/stage1-golden-chapters.md +128 -0
- package/lib/oh-story/skills/story-long-analyze/references/stage2-extraction.md +229 -0
- package/lib/oh-story/skills/story-long-analyze/references/stage3-plot-rhythm.md +480 -0
- package/lib/oh-story/skills/story-long-analyze/references/stage4-characters-settings.md +195 -0
- package/lib/oh-story/skills/story-long-analyze/references/stage5-report.md +33 -0
- package/lib/oh-story/skills/story-long-analyze/references/style-profile-generator.md +17 -0
- package/lib/oh-story/skills/story-long-analyze/references/style-profile-protocol.md +1 -1
- package/lib/oh-story/skills/story-long-analyze/references/synthesis-inputs.md +39 -0
- package/lib/oh-story/skills/story-long-analyze/scripts/manage_analysis_run.py +47 -8
- package/lib/oh-story/skills/story-long-scan/SKILL.md +41 -193
- package/lib/oh-story/skills/story-long-scan/references/platform-ciweimao.md +19 -0
- package/lib/oh-story/skills/story-long-scan/references/platform-fanqie.md +45 -0
- package/lib/oh-story/skills/story-long-scan/references/platform-jjwxc.md +28 -0
- package/lib/oh-story/skills/story-long-scan/references/platform-qidian.md +35 -0
- package/lib/oh-story/skills/story-long-scan/references/platform-qimao.md +27 -0
- package/lib/oh-story/skills/story-long-scan/references/topic-decision.md +6 -2
- package/lib/oh-story/skills/story-long-write/SKILL.md +32 -73
- package/lib/oh-story/skills/story-long-write/references/agent-calls.md +47 -0
- package/lib/oh-story/skills/story-long-write/references/anti-ai-writing.md +1 -1
- package/lib/oh-story/skills/story-long-write/references/artifact-protocols.md +18 -201
- package/lib/oh-story/skills/story-long-write/references/author-memory-maintenance.md +75 -0
- package/lib/oh-story/skills/story-long-write/references/author-memory.md +40 -118
- package/lib/oh-story/skills/story-long-write/references/banned-words.md +2 -2
- package/lib/oh-story/skills/story-long-write/references/benchmark-recall.md +6 -10
- package/lib/oh-story/skills/story-long-write/references/character-relations.md +49 -1
- package/lib/oh-story/skills/story-long-write/references/deslop-gates.md +171 -0
- package/lib/oh-story/skills/story-long-write/references/emotion-on-page.md +116 -0
- package/lib/oh-story/skills/story-long-write/references/emotional-arc-design.md +1 -114
- package/lib/oh-story/skills/story-long-write/references/genre-prose-cards.md +1 -1
- package/lib/oh-story/skills/story-long-write/references/long-chapter-quality.md +3 -2
- package/lib/oh-story/skills/story-long-write/references/long-format.md +0 -8
- package/lib/oh-story/skills/story-long-write/references/long-genre-catalog.md +1 -1
- package/lib/oh-story/skills/story-long-write/references/long-genre-mechanics.md +3 -3
- package/lib/oh-story/skills/story-long-write/references/opening-design.md +2 -2
- package/lib/oh-story/skills/story-long-write/references/outline-conflict.md +4 -4
- package/lib/oh-story/skills/story-long-write/references/outline-methods.md +7 -7
- package/lib/oh-story/skills/story-long-write/references/outline-rhythm.md +3 -3
- package/lib/oh-story/skills/story-long-write/references/outline-structure-theory.md +14 -69
- package/lib/oh-story/skills/story-long-write/references/plot-frameworks.md +1 -1
- package/lib/oh-story/skills/story-long-write/references/plot-special-topics.md +2 -2
- package/lib/oh-story/skills/story-long-write/references/project-files.md +10 -6
- package/lib/oh-story/skills/story-long-write/references/reader-contract-and-progression.md +1 -1
- package/lib/oh-story/skills/story-long-write/references/reference-index.md +4 -4
- package/lib/oh-story/skills/story-long-write/references/stop-and-fix.md +9 -0
- package/lib/oh-story/skills/story-long-write/references/style-combat-face.md +1 -58
- 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 +4 -4
- package/lib/oh-story/skills/story-long-write/references/tracking-initialization.md +1 -1
- package/lib/oh-story/skills/story-long-write/references/tracking-transaction.md +11 -9
- package/lib/oh-story/skills/story-long-write/references/workflow-chapter.md +33 -44
- package/lib/oh-story/skills/story-long-write/references/workflow-daily.md +18 -51
- package/lib/oh-story/skills/story-long-write/references/workflow-outline.md +157 -0
- package/lib/oh-story/skills/story-long-write/references/workflow-revision.md +53 -67
- package/lib/oh-story/skills/story-long-write/references/workflow-setup.md +112 -251
- package/lib/oh-story/skills/story-long-write/references/workflow-volume.md +142 -0
- package/lib/oh-story/skills/story-long-write/references/writer-prompt-fallback.md +5 -3
- package/lib/oh-story/skills/story-long-write/references/writing-craft.md +18 -56
- package/lib/oh-story/skills/story-long-write/scripts/author_memory_commit.py +3 -3
- package/lib/oh-story/skills/story-long-write/scripts/build_architect_brief.py +260 -0
- package/lib/oh-story/skills/story-long-write/scripts/build_writer_prompt.py +96 -16
- package/lib/oh-story/skills/story-long-write/scripts/check-ai-patterns.js +155 -8
- package/lib/oh-story/skills/story-long-write/scripts/check-outline-contract.js +101 -14
- package/lib/oh-story/skills/story-long-write/scripts/check-outline-copy.js +7 -2
- package/lib/oh-story/skills/story-long-write/scripts/outline_view.py +2 -1
- package/lib/oh-story/skills/story-long-write/scripts/storyctl.py +250 -50
- package/lib/oh-story/skills/story-long-write/scripts/tracking_commit.py +278 -23
- package/lib/oh-story/skills/story-long-write/scripts/wordcount_core.py +179 -23
- package/lib/oh-story/skills/story-review/SKILL.md +19 -273
- package/lib/oh-story/skills/story-review/references/agent-prompts.md +184 -0
- package/lib/oh-story/skills/story-review/references/anti-ai-writing.md +1 -1
- package/lib/oh-story/skills/story-review/references/author-memory-maintenance.md +75 -0
- package/lib/oh-story/skills/story-review/references/author-memory.md +40 -118
- package/lib/oh-story/skills/story-review/references/banned-words.md +2 -2
- package/lib/oh-story/skills/story-review/references/batch-review.md +19 -0
- package/lib/oh-story/skills/story-review/references/character-relations.md +49 -1
- package/lib/oh-story/skills/story-review/references/review-quality.md +2 -1
- package/lib/oh-story/skills/story-review/references/review-tracking.md +10 -0
- package/lib/oh-story/skills/story-review/references/solo.md +52 -0
- package/lib/oh-story/skills/story-review/references/style-resolution.md +4 -4
- package/lib/oh-story/skills/story-review/references/tracking-initialization.md +1 -1
- package/lib/oh-story/skills/story-review/references/tracking-transaction.md +11 -9
- package/lib/oh-story/skills/story-review/scripts/author_memory_commit.py +3 -3
- package/lib/oh-story/skills/story-review/scripts/check-ai-patterns.js +155 -8
- package/lib/oh-story/skills/story-review/scripts/tracking_commit.py +278 -23
- package/lib/oh-story/skills/story-review/scripts/wordcount_core.py +179 -23
- package/lib/oh-story/skills/story-setup/SKILL.md +69 -458
- package/lib/oh-story/skills/story-setup/references/agent-references/agent-quality.md +4 -4
- package/lib/oh-story/skills/story-setup/references/agent-references/anti-ai-writing.md +1 -1
- package/lib/oh-story/skills/story-setup/references/agent-references/banned-words.md +2 -2
- package/lib/oh-story/skills/story-setup/references/agent-references/benchmark-style-load.md +107 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/character-relations.md +49 -1
- package/lib/oh-story/skills/story-setup/references/agent-references/deslop-gates.md +21 -4
- package/lib/oh-story/skills/story-setup/references/agent-references/emotion-on-page.md +116 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/emotional-arc-design.md +1 -114
- package/lib/oh-story/skills/story-setup/references/agent-references/format-and-structure.md +1 -1
- package/lib/oh-story/skills/story-setup/references/agent-references/genre-prose-cards.md +1 -1
- package/lib/oh-story/skills/story-setup/references/agent-references/long-genre-catalog.md +1 -1
- package/lib/oh-story/skills/story-setup/references/agent-references/long-genre-mechanics.md +3 -3
- package/lib/oh-story/skills/story-setup/references/agent-references/long-quality.md +7 -5
- package/lib/oh-story/skills/story-setup/references/agent-references/opening-design.md +2 -2
- package/lib/oh-story/skills/story-setup/references/agent-references/outline-conflict.md +4 -4
- package/lib/oh-story/skills/story-setup/references/agent-references/outline-methods.md +7 -7
- package/lib/oh-story/skills/story-setup/references/agent-references/outline-rhythm.md +3 -3
- package/lib/oh-story/skills/story-setup/references/agent-references/short-emotional-methods.md +1 -1
- package/lib/oh-story/skills/story-setup/references/agent-references/short-genre-formulas.md +0 -15
- package/lib/oh-story/skills/story-setup/references/agent-references/short-reversal.md +1 -1
- package/lib/oh-story/skills/story-setup/references/agent-references/style-combat-face.md +1 -58
- 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 +4 -4
- package/lib/oh-story/skills/story-setup/references/agent-references/writing-craft.md +18 -56
- package/lib/oh-story/skills/story-setup/references/diagnostics.md +3 -3
- package/lib/oh-story/skills/story-short-analyze/SKILL.md +37 -110
- package/lib/oh-story/skills/story-short-analyze/references/analysis-character-relations.md +1 -1
- package/lib/oh-story/skills/story-short-analyze/references/analysis-method.md +42 -0
- package/lib/oh-story/skills/story-short-analyze/references/optional-modules.md +97 -0
- package/lib/oh-story/skills/story-short-analyze/references/output-contract.md +9 -47
- package/lib/oh-story/skills/story-short-analyze/references/quality-checklist.md +80 -0
- package/lib/oh-story/skills/story-short-analyze/references/source-story-quality.md +4 -4
- package/lib/oh-story/skills/story-short-analyze/references/stage2-3-structure-emotion.md +308 -0
- package/lib/oh-story/skills/story-short-analyze/references/{material-decomposition.md → stage4-6-reversal-summary.md} +303 -374
- package/lib/oh-story/skills/story-short-scan/SKILL.md +30 -64
- package/lib/oh-story/skills/story-short-scan/references/manual-rank-input.md +36 -0
- package/lib/oh-story/skills/story-short-scan/references/platform-dianzhong.md +24 -0
- package/lib/oh-story/skills/story-short-scan/references/platform-heiyan.md +21 -0
- package/lib/oh-story/skills/story-short-scan/references/topic-match.md +38 -0
- package/lib/oh-story/skills/story-short-write/SKILL.md +23 -34
- package/lib/oh-story/skills/story-short-write/references/agent-calls.md +13 -0
- package/lib/oh-story/skills/story-short-write/references/author-memory-maintenance.md +75 -0
- package/lib/oh-story/skills/story-short-write/references/author-memory.md +40 -118
- package/lib/oh-story/skills/story-short-write/references/banned-words.md +2 -2
- package/lib/oh-story/skills/story-short-write/references/benchmark-recall.md +1 -21
- package/lib/oh-story/skills/story-short-write/references/emotional-methods.md +1 -1
- package/lib/oh-story/skills/story-short-write/references/genre-writing-formulas.md +0 -15
- package/lib/oh-story/skills/story-short-write/references/output-contract.md +9 -47
- package/lib/oh-story/skills/story-short-write/references/reference-index.md +17 -49
- package/lib/oh-story/skills/story-short-write/references/scene-craft.md +40 -0
- package/lib/oh-story/skills/story-short-write/references/short-craft.md +7 -43
- package/lib/oh-story/skills/story-short-write/references/short-format.md +3 -3
- package/lib/oh-story/skills/story-short-write/references/short-prose-quality.md +2 -1
- package/lib/oh-story/skills/story-short-write/references/short-reversal.md +1 -1
- package/lib/oh-story/skills/story-short-write/references/style-resolution.md +4 -4
- package/lib/oh-story/skills/story-short-write/references/submission-craft.md +1 -1
- package/lib/oh-story/skills/story-short-write/references/villain-and-reveal.md +5 -10
- package/lib/oh-story/skills/story-short-write/references/workflow-design.md +19 -12
- package/lib/oh-story/skills/story-short-write/references/workflow-draft.md +22 -32
- package/lib/oh-story/skills/story-short-write/references/workflow-revision.md +84 -17
- package/lib/oh-story/skills/story-short-write/references/writing-workflow.md +7 -119
- package/lib/oh-story/skills/story-short-write/scripts/author_memory_commit.py +3 -3
- package/lib/oh-story/skills/story-short-write/scripts/check-ai-patterns.js +155 -8
- package/lib/oh-story/skills/story-short-write/scripts/check-outline-copy.js +7 -2
- package/lib/types/client/video-studio.d.ts.map +1 -1
- package/lib/types/native-hooks.d.ts +2 -2
- package/lib/types/native-hooks.d.ts.map +1 -1
- package/lib/types/role-tool.d.ts.map +1 -1
- package/lib/types/skill-provider.d.ts.map +1 -1
- package/lib/types/video-project.d.ts.map +1 -1
- package/lib/types/workspace-route.d.ts.map +1 -1
- package/lib/video-recap/CHANGELOG.md +60 -1
- package/lib/video-recap/README.en.md +348 -137
- package/lib/video-recap/README.md +340 -136
- package/lib/video-recap/manifest.json +406 -251
- package/lib/video-recap/skills/video-assemble/SKILL.md +30 -15
- package/lib/video-recap/skills/video-assemble/references/audio-modes.md +63 -0
- package/lib/video-recap/skills/video-assemble/references/explicit-audio-mix.md +75 -0
- package/lib/video-recap/skills/video-assemble/references/foreground-compose.md +118 -0
- package/lib/video-recap/skills/video-assemble/references/narration-adoption.md +103 -0
- package/lib/video-recap/skills/video-assemble/references/packaging.md +45 -0
- package/lib/video-recap/skills/video-assemble/references/pair-media.md +74 -0
- package/lib/video-recap/skills/video-assemble/references/source-score.md +147 -0
- package/lib/video-recap/skills/video-assemble/references/subtitle-track.md +202 -0
- package/lib/video-recap/skills/video-assemble/scripts/adoption/__init__.py +1 -0
- package/lib/video-recap/skills/video-assemble/scripts/adoption/audio_mix_binding.py +279 -0
- package/lib/video-recap/skills/video-assemble/scripts/adoption/frozen_audio.py +187 -0
- package/lib/video-recap/skills/video-assemble/scripts/adoption/narration_binding.py +323 -0
- package/lib/video-recap/skills/video-assemble/scripts/adoption/strict_inputs.py +109 -0
- package/lib/video-recap/skills/video-assemble/scripts/adoption/strict_publish.py +130 -0
- package/lib/video-recap/skills/video-assemble/scripts/artifacts.py +10 -46
- package/lib/video-recap/skills/video-assemble/scripts/assemble.py +378 -119
- package/lib/video-recap/skills/video-assemble/scripts/assemble_constants.py +29 -12
- package/lib/video-recap/skills/video-assemble/scripts/assembly_contract.py +95 -44
- package/lib/video-recap/skills/video-assemble/scripts/assembly_settings.py +75 -32
- package/lib/video-recap/skills/video-assemble/scripts/audio_mix.py +11 -11
- package/lib/video-recap/skills/video-assemble/scripts/compose_foreground.py +293 -0
- package/lib/video-recap/skills/video-assemble/scripts/export_jianying.py +71 -78
- package/lib/video-recap/skills/video-assemble/scripts/jianying/__init__.py +1 -0
- package/lib/video-recap/skills/video-assemble/scripts/{jianying_builders.py → jianying/builders.py} +39 -67
- package/lib/video-recap/skills/video-assemble/scripts/{jianying_model.py → jianying/model.py} +3 -11
- package/lib/video-recap/skills/video-assemble/scripts/{jianying_optional.py → jianying/optional.py} +1 -1
- package/lib/video-recap/skills/video-assemble/scripts/jianying/schema.py +53 -0
- package/lib/video-recap/skills/video-assemble/scripts/{jianying_templates.py → jianying/templates.py} +1 -1
- package/lib/video-recap/skills/video-assemble/scripts/{jianying_timeline_contract.py → jianying/timeline_contract.py} +34 -13
- package/lib/video-recap/skills/video-assemble/scripts/{jianying_writer.py → jianying/writer.py} +7 -17
- package/lib/video-recap/skills/video-assemble/scripts/lib.py +37 -4
- package/lib/video-recap/skills/video-assemble/scripts/media.py +12 -21
- package/lib/video-recap/skills/video-assemble/scripts/narration_audio.py +64 -34
- package/lib/video-recap/skills/video-assemble/scripts/packaging.py +122 -0
- package/lib/video-recap/skills/video-assemble/scripts/pair_media.py +216 -0
- package/lib/video-recap/skills/video-assemble/scripts/render_preflight.py +1 -3
- package/lib/video-recap/skills/video-assemble/scripts/source_score.py +469 -0
- package/lib/video-recap/skills/video-assemble/scripts/source_subtitles.py +25 -9
- package/lib/video-recap/skills/video-assemble/scripts/subtitles/__init__.py +1 -0
- package/lib/video-recap/skills/video-assemble/scripts/{subtitle_core.py → subtitles/core.py} +13 -6
- package/lib/video-recap/skills/video-assemble/scripts/{subtitle_render.py → subtitles/render.py} +5 -4
- package/lib/video-recap/skills/video-assemble/scripts/subtitles/track.py +324 -0
- package/lib/video-recap/skills/video-assemble/scripts/subtitles/track_binding.py +200 -0
- package/lib/video-recap/skills/video-assemble/scripts/timeline.py +30 -46
- package/lib/video-recap/skills/video-assemble/scripts/timeline_emit.py +91 -7
- package/lib/video-recap/skills/video-assemble/scripts/visual_render.py +22 -8
- package/lib/video-recap/skills/video-cut/SKILL.md +34 -16
- package/lib/video-recap/skills/video-cut/references/shot-review.md +50 -0
- package/lib/video-recap/skills/video-cut/scripts/cut.py +2 -28
- package/lib/video-recap/skills/video-cut/scripts/cut_cli.py +58 -62
- package/lib/video-recap/skills/video-cut/scripts/cut_contract.py +21 -106
- package/lib/video-recap/skills/video-cut/scripts/cut_render.py +14 -9
- package/lib/video-recap/skills/video-cut/scripts/lib.py +39 -0
- package/lib/video-recap/skills/video-cut/scripts/narration_mapping.py +2 -155
- package/lib/video-recap/skills/video-cut/scripts/narrative_selection.py +244 -0
- package/lib/video-recap/skills/video-cut/scripts/shot_review.py +357 -0
- package/lib/video-recap/skills/video-recap/SKILL.md +86 -42
- package/lib/video-recap/skills/video-recap/references/audio-routing.md +126 -0
- package/lib/video-recap/skills/video-recap/references/config-playbook.md +12 -8
- package/lib/video-recap/skills/video-recap/references/data-schema.md +44 -33
- package/lib/video-recap/skills/video-recap/references/resource-library.md +151 -0
- package/lib/video-recap/skills/video-recap/references/shift-left-qc-schema.md +1 -1
- package/lib/video-recap/skills/video-recap/scripts/doctor.py +123 -30
- package/lib/video-recap/skills/video-recap/scripts/final_qc.py +22 -75
- package/lib/video-recap/skills/video-recap/scripts/lib.py +3 -3
- package/lib/video-recap/skills/video-recap/scripts/library.py +509 -0
- package/lib/video-recap/skills/video-recap/scripts/materials.py +63 -91
- package/lib/video-recap/skills/video-recap/scripts/mimo_qc.py +8 -70
- package/lib/video-recap/skills/video-recap/scripts/project_binding.py +286 -0
- package/lib/video-recap/skills/video-recap/scripts/qc/__init__.py +1 -0
- package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_client.py → qc/mimo_client.py} +1 -1
- package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_evidence.py → qc/mimo_evidence.py} +33 -66
- package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_observations.py → qc/mimo_observations.py} +3 -11
- package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_payload.py → qc/mimo_payload.py} +4 -7
- package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_report.py → qc/mimo_report.py} +17 -28
- package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_runner.py → qc/mimo_runner.py} +2 -2
- package/lib/video-recap/skills/video-recap/scripts/qc_contract.py +0 -19
- package/lib/video-recap/skills/video-recap/scripts/recap_cli.py +119 -50
- package/lib/video-recap/skills/video-recap/scripts/recap_inspect.py +32 -114
- package/lib/video-recap/skills/video-recap/scripts/recap_review.py +9 -23
- package/lib/video-recap/skills/video-recap/scripts/recap_runner.py +387 -334
- package/lib/video-recap/skills/video-recap/scripts/recap_runtime.py +23 -23
- package/lib/video-recap/skills/video-recap/scripts/recap_source.py +278 -0
- package/lib/video-recap/skills/video-recap/scripts/recap_stage_qc.py +25 -12
- package/lib/video-recap/skills/video-recap/scripts/recap_timeline.py +59 -52
- package/lib/video-recap/skills/video-recap/scripts/resource_lock.py +162 -0
- package/lib/video-recap/skills/video-script/SKILL.md +16 -13
- package/lib/video-recap/skills/video-script/references/creative-editing-playbook.md +9 -5
- package/lib/video-recap/skills/video-script/references/promotional-copy.md +68 -0
- package/lib/video-recap/skills/video-script/references/research-guide.md +1 -1
- package/lib/video-recap/skills/video-script/scripts/evidence_bundle.py +24 -76
- package/lib/video-recap/skills/video-script/scripts/lib.py +10 -87
- package/lib/video-recap/skills/video-script/scripts/narration_lint.py +63 -39
- package/lib/video-recap/skills/video-script/scripts/review.py +0 -2
- package/lib/video-recap/skills/video-script/scripts/review_grounding.py +35 -59
- package/lib/video-recap/skills/video-script/scripts/review_response.py +66 -128
- package/lib/video-recap/skills/video-script/scripts/review_runner.py +12 -20
- package/lib/video-recap/skills/video-script/scripts/speech_ownership.py +4 -4
- package/lib/video-recap/skills/video-script/scripts/validate.py +53 -42
- package/lib/video-recap/skills/video-understanding/SKILL.md +6 -4
- package/lib/video-recap/skills/video-understanding/references/data-schema.md +50 -156
- package/lib/video-recap/skills/video-understanding/references/research-guide.md +4 -4
- package/lib/video-recap/skills/video-understanding/scripts/asr.py +85 -65
- package/lib/video-recap/skills/video-understanding/scripts/asr_timing_evidence.py +270 -0
- package/lib/video-recap/skills/video-understanding/scripts/brief.py +2 -5
- package/lib/video-recap/skills/video-understanding/scripts/briefing/__init__.py +1 -0
- package/lib/video-recap/skills/video-understanding/scripts/{agent_brief.py → briefing/builder.py} +38 -35
- package/lib/video-recap/skills/{video-script/scripts/brief_context.py → video-understanding/scripts/briefing/context.py} +4 -35
- package/lib/video-recap/skills/{video-script/scripts/brief_inputs.py → video-understanding/scripts/briefing/inputs.py} +9 -31
- package/lib/video-recap/skills/video-understanding/scripts/{brief_timeline.py → briefing/timeline.py} +5 -6
- package/lib/video-recap/skills/video-understanding/scripts/consolidate.py +65 -116
- package/lib/video-recap/skills/video-understanding/scripts/detect.py +31 -40
- package/lib/video-recap/skills/video-understanding/scripts/lib.py +30 -86
- package/lib/video-recap/skills/video-understanding/scripts/narration_lint.py +63 -39
- package/lib/video-recap/skills/video-understanding/scripts/speech_ownership.py +4 -4
- package/lib/video-recap/skills/video-understanding/scripts/storyboard.py +17 -79
- package/lib/video-recap/skills/video-understanding/scripts/understanding_brief.py +14 -40
- package/lib/video-recap/skills/video-understanding/scripts/understanding_cache.py +64 -46
- package/lib/video-recap/skills/video-understanding/scripts/understanding_runner.py +31 -11
- package/lib/video-recap/skills/video-understanding/scripts/understanding_storyboard.py +19 -14
- package/lib/video-recap/skills/video-understanding/scripts/vlm.py +80 -108
- package/lib/video-recap/skills/video-voiceover/SKILL.md +29 -19
- package/lib/video-recap/skills/video-voiceover/references/index-tts.md +31 -0
- package/lib/video-recap/skills/video-voiceover/scripts/approved_text_policy.py +123 -0
- package/lib/video-recap/skills/video-voiceover/scripts/dub.py +51 -59
- package/lib/video-recap/skills/video-voiceover/scripts/lib.py +8 -44
- package/lib/video-recap/skills/video-voiceover/scripts/providers/__init__.py +1 -0
- package/lib/video-recap/skills/video-voiceover/scripts/providers/index_tts.py +184 -0
- package/lib/video-recap/skills/video-voiceover/scripts/voiceover.py +212 -134
- package/package.json +41 -41
- package/lib/oh-story/skills/story-long-analyze/references/material-decomposition.md +0 -538
- package/lib/oh-story/skills/story-long-analyze/references/output-templates.md +0 -603
- package/lib/oh-story/skills/story-long-scan/references/scan-output-format.md +0 -207
- package/lib/oh-story/skills/story-short-analyze/references/output-templates.md +0 -561
- package/lib/video-recap/skills/video-assemble/scripts/jianying_schema.py +0 -98
- package/lib/video-recap/skills/video-recap/references/creative-editing-playbook.md +0 -225
- package/lib/video-recap/skills/video-recap/references/research-guide.md +0 -74
- package/lib/video-recap/skills/video-recap/references/timeline-and-jianying.md +0 -250
- package/lib/video-recap/skills/video-script/scripts/agent_brief.py +0 -399
- package/lib/video-recap/skills/video-script/scripts/brief_timeline.py +0 -327
- package/lib/video-recap/skills/video-script/scripts/narration.py +0 -12
- package/lib/video-recap/skills/video-understanding/scripts/brief_context.py +0 -348
- package/lib/video-recap/skills/video-understanding/scripts/brief_inputs.py +0 -290
- /package/lib/video-recap/skills/video-assemble/scripts/{jianying_tracks.py → jianying/tracks.py} +0 -0
- /package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_contract.py → qc/mimo_contract.py} +0 -0
- /package/lib/video-recap/skills/video-voiceover/scripts/{fish_audio.py → providers/fish_audio.py} +0 -0
|
@@ -1,327 +0,0 @@
|
|
|
1
|
-
"""Remap cut evidence and format output-timeline brief directives."""
|
|
2
|
-
|
|
3
|
-
import json
|
|
4
|
-
import math
|
|
5
|
-
import re
|
|
6
|
-
from pathlib import Path
|
|
7
|
-
|
|
8
|
-
from lib import CONFIG, stable_hash
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
def _parse_target_seconds(value):
|
|
12
|
-
"""Parse a cut-mode target duration ("30m" / "600" / "1h5m" / "00:30:00") to seconds.
|
|
13
|
-
|
|
14
|
-
None or blank means the knob is unset (None). Any other value that does not parse to
|
|
15
|
-
a positive duration is a typo in user input and raises ValueError.
|
|
16
|
-
"""
|
|
17
|
-
if value is None:
|
|
18
|
-
return None
|
|
19
|
-
if isinstance(value, (int, float)):
|
|
20
|
-
seconds = float(value)
|
|
21
|
-
else:
|
|
22
|
-
text = str(value).strip().lower()
|
|
23
|
-
if not text:
|
|
24
|
-
return None
|
|
25
|
-
if ":" in text:
|
|
26
|
-
parts = [float(p) for p in text.split(":")]
|
|
27
|
-
if len(parts) == 2:
|
|
28
|
-
seconds = parts[0] * 60 + parts[1]
|
|
29
|
-
elif len(parts) == 3:
|
|
30
|
-
seconds = parts[0] * 3600 + parts[1] * 60 + parts[2]
|
|
31
|
-
else:
|
|
32
|
-
raise ValueError(f"unparseable target duration: {value!r}")
|
|
33
|
-
if any(p < 0 for p in parts):
|
|
34
|
-
raise ValueError(f"unparseable target duration: {value!r}")
|
|
35
|
-
else:
|
|
36
|
-
factors = {"ms": 0.001, "s": 1, "m": 60, "h": 3600}
|
|
37
|
-
seconds = 0.0
|
|
38
|
-
pos = 0
|
|
39
|
-
for m in re.finditer(r"([0-9]+(?:\.[0-9]+)?)(ms|s|m|h)?", text):
|
|
40
|
-
if m.start() != pos:
|
|
41
|
-
break
|
|
42
|
-
pos = m.end()
|
|
43
|
-
seconds += float(m.group(1)) * factors[m.group(2) or "s"]
|
|
44
|
-
if pos == 0 or pos != len(text):
|
|
45
|
-
raise ValueError(f"unparseable target duration: {value!r}")
|
|
46
|
-
if not seconds > 0:
|
|
47
|
-
raise ValueError(f"target duration must be positive: {value!r}")
|
|
48
|
-
return seconds
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
def _format_research_directive(work_dir, substrate):
|
|
52
|
-
"""A loud, actionable research-first directive — but ONLY when the substrate is too thin
|
|
53
|
-
for real commentary (no dialogue/story spine) and no background_research.json exists yet.
|
|
54
|
-
|
|
55
|
-
The narration's quality ceiling is how much story context the agent has: with only frame
|
|
56
|
-
descriptions it can only narrate pixels, so research the title FIRST (see
|
|
57
|
-
references/research-guide.md). Fires only for thin/empty substrate — NOT merely because a
|
|
58
|
-
title was given — so a dialogue-rich titled run is never nagged.
|
|
59
|
-
"""
|
|
60
|
-
if (Path(work_dir) / "background_research.json").exists():
|
|
61
|
-
return [] # already researched; _format_background_research surfaces it
|
|
62
|
-
if substrate["level"] not in ("thin", "empty"):
|
|
63
|
-
return [] # rich enough to write from dialogue/spine; do not nag
|
|
64
|
-
context = CONFIG["context_info"].strip()
|
|
65
|
-
return [
|
|
66
|
-
"## ⚑ Research the story FIRST (do this before writing narration)",
|
|
67
|
-
"",
|
|
68
|
-
"Reason: the understanding substrate is thin — no dialogue/story spine, only frame",
|
|
69
|
-
"descriptions, so without research the narration can only describe pixels.",
|
|
70
|
-
"1. Pull the title/keywords from `--context`, the filename, or the user's description"
|
|
71
|
-
+ (f" (context: {context})." if context else "."),
|
|
72
|
-
"2. Use any available web-search/browser tool to look up synopsis, characters, and",
|
|
73
|
-
" relationships (see `references/research-guide.md`).",
|
|
74
|
-
"3. Write `work_dir/background_research.json`, then re-read this brief and write narration",
|
|
75
|
-
" that names people and reads the picture through the plot.",
|
|
76
|
-
"4. If no tool/network or nothing found: skip — keep beats sparse and strictly grounded in",
|
|
77
|
-
" the visible ASR/frame evidence rather than inventing drama.",
|
|
78
|
-
"",
|
|
79
|
-
]
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
def _load_cut_output_spans_for_brief(work_dir, *, required=False):
|
|
83
|
-
"""Load fresh source→output spans for cut pass 2 brief evidence.
|
|
84
|
-
|
|
85
|
-
Pass 2 narration is authored against edited_source.mp4's OUTPUT timeline, so
|
|
86
|
-
ASR chunks and timeline_fusion must use the same OUTPUT clock. Before pass 2
|
|
87
|
-
(no edited_source.mp4 yet), callers may fall back to source-time evidence; once
|
|
88
|
-
pass 2 exists, missing/stale validated spans are a hard contract failure.
|
|
89
|
-
"""
|
|
90
|
-
|
|
91
|
-
def fail(reason):
|
|
92
|
-
if required:
|
|
93
|
-
raise SystemExit(
|
|
94
|
-
"cut pass2 brief requires fresh clip_plan_validated.json with explicit "
|
|
95
|
-
f"source/output spans ({reason})"
|
|
96
|
-
)
|
|
97
|
-
return None
|
|
98
|
-
|
|
99
|
-
work_dir = Path(work_dir)
|
|
100
|
-
if not (work_dir / "edited_source.mp4").exists():
|
|
101
|
-
return None
|
|
102
|
-
validated_path = work_dir / "clip_plan_validated.json"
|
|
103
|
-
if not validated_path.exists():
|
|
104
|
-
return fail("missing clip_plan_validated.json")
|
|
105
|
-
plan = json.loads(validated_path.read_text(encoding="utf-8"))
|
|
106
|
-
raw_path = work_dir / "clip_plan.json"
|
|
107
|
-
if raw_path.exists() and plan["raw_plan_fingerprint"] != stable_hash(
|
|
108
|
-
json.loads(raw_path.read_text(encoding="utf-8"))
|
|
109
|
-
):
|
|
110
|
-
return fail("stale clip_plan_validated.json")
|
|
111
|
-
spans = []
|
|
112
|
-
for clip in plan["clips"]:
|
|
113
|
-
span = {
|
|
114
|
-
key: clip[key]
|
|
115
|
-
for key in ("source_start", "source_end", "output_start", "output_end")
|
|
116
|
-
}
|
|
117
|
-
if not all(math.isfinite(value) for value in span.values()):
|
|
118
|
-
return fail("non-finite clip span")
|
|
119
|
-
if span["source_end"] <= span["source_start"] or span["output_end"] <= span["output_start"]:
|
|
120
|
-
return fail("non-positive clip span")
|
|
121
|
-
spans.append(span)
|
|
122
|
-
return spans or fail("no clips")
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
def _source_output_overlaps_for_brief(start, end, spans):
|
|
126
|
-
overlaps = []
|
|
127
|
-
for span in spans:
|
|
128
|
-
source_start = max(start, span["source_start"])
|
|
129
|
-
source_end = min(end, span["source_end"])
|
|
130
|
-
if source_end <= source_start:
|
|
131
|
-
continue
|
|
132
|
-
output_start = span["output_start"] + (source_start - span["source_start"])
|
|
133
|
-
output_end = span["output_start"] + (source_end - span["source_start"])
|
|
134
|
-
overlaps.append(
|
|
135
|
-
{
|
|
136
|
-
"source_start": source_start,
|
|
137
|
-
"source_end": source_end,
|
|
138
|
-
"output_start": output_start,
|
|
139
|
-
"output_end": output_end,
|
|
140
|
-
}
|
|
141
|
-
)
|
|
142
|
-
return overlaps
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
def _remap_frame_facts_for_brief(frame_facts, overlap):
|
|
146
|
-
out = {}
|
|
147
|
-
for raw_ts, vals in frame_facts.items():
|
|
148
|
-
ts = float(raw_ts)
|
|
149
|
-
if not (overlap["source_start"] <= ts <= overlap["source_end"]):
|
|
150
|
-
continue
|
|
151
|
-
out_ts = overlap["output_start"] + (ts - overlap["source_start"])
|
|
152
|
-
out[f"{out_ts:.3f}"] = vals
|
|
153
|
-
return out
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
def _remap_scenes_to_output_for_brief(scenes, spans):
|
|
157
|
-
out = []
|
|
158
|
-
for scene in scenes:
|
|
159
|
-
overlaps = _source_output_overlaps_for_brief(scene["start"], scene["end"], spans)
|
|
160
|
-
for part_idx, overlap in enumerate(overlaps):
|
|
161
|
-
item = dict(scene)
|
|
162
|
-
item["start"] = round(overlap["output_start"], 3)
|
|
163
|
-
item["end"] = round(overlap["output_end"], 3)
|
|
164
|
-
item["frame_facts"] = _remap_frame_facts_for_brief(
|
|
165
|
-
scene.get("frame_facts", {}), overlap
|
|
166
|
-
)
|
|
167
|
-
if len(overlaps) > 1:
|
|
168
|
-
item["scene_id"] = f"{scene['scene_id']}.{part_idx}"
|
|
169
|
-
out.append(item)
|
|
170
|
-
out.sort(key=lambda x: (x["start"], x["end"]))
|
|
171
|
-
return out
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
def _remap_segments_to_output_for_brief(segments, spans):
|
|
175
|
-
out = []
|
|
176
|
-
for seg in segments:
|
|
177
|
-
for overlap in _source_output_overlaps_for_brief(seg["start"], seg["end"], spans):
|
|
178
|
-
item = dict(seg)
|
|
179
|
-
item["start"] = round(overlap["output_start"], 3)
|
|
180
|
-
item["end"] = round(overlap["output_end"], 3)
|
|
181
|
-
if "duration" in item:
|
|
182
|
-
item["duration"] = round(item["end"] - item["start"], 3)
|
|
183
|
-
out.append(item)
|
|
184
|
-
out.sort(key=lambda x: (x["start"], x["end"]))
|
|
185
|
-
return out
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
def _remap_brief_evidence_to_output_timeline(
|
|
189
|
-
work_dir, scenes_analysis, asr_result, silence_periods, *, required=False
|
|
190
|
-
):
|
|
191
|
-
spans = _load_cut_output_spans_for_brief(work_dir, required=required)
|
|
192
|
-
if not spans:
|
|
193
|
-
return scenes_analysis, asr_result, silence_periods
|
|
194
|
-
return (
|
|
195
|
-
_remap_scenes_to_output_for_brief(scenes_analysis, spans),
|
|
196
|
-
_remap_segments_to_output_for_brief(asr_result, spans),
|
|
197
|
-
_remap_segments_to_output_for_brief(silence_periods, spans),
|
|
198
|
-
)
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
def _read_json(path, default):
|
|
202
|
-
"""JSON artifact, or `default` when the optional file was never written."""
|
|
203
|
-
return json.loads(path.read_text(encoding="utf-8")) if path.exists() else default
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
def _sentence_entry_anchors_for_brief(work_dir, edit_mode):
|
|
207
|
-
"""Load sentence anchors and remap them to cut OUTPUT time when needed."""
|
|
208
|
-
work_dir = Path(work_dir)
|
|
209
|
-
anchors = [
|
|
210
|
-
dict(item)
|
|
211
|
-
for item in _read_json(
|
|
212
|
-
work_dir / "speech_boundary_anchors.json", {"sentence_anchors": []}
|
|
213
|
-
)["sentence_anchors"]
|
|
214
|
-
]
|
|
215
|
-
if edit_mode != "cut" or not (work_dir / "edited_source.mp4").exists():
|
|
216
|
-
return anchors
|
|
217
|
-
|
|
218
|
-
spans = _load_cut_output_spans_for_brief(work_dir, required=True)
|
|
219
|
-
remapped = []
|
|
220
|
-
for anchor in anchors:
|
|
221
|
-
source_time = anchor["time"]
|
|
222
|
-
for span in spans:
|
|
223
|
-
if span["source_start"] - 0.05 <= source_time <= span["source_end"] + 0.05:
|
|
224
|
-
item = dict(anchor)
|
|
225
|
-
item["source_time"] = round(source_time, 3)
|
|
226
|
-
item["time"] = round(
|
|
227
|
-
span["output_start"] + source_time - span["source_start"], 3
|
|
228
|
-
)
|
|
229
|
-
# Preserve the measured safe pause in OUTPUT time too, so cut-mode lint
|
|
230
|
-
# compares one clock.
|
|
231
|
-
source_pause_start = max(
|
|
232
|
-
span["source_start"], min(anchor.get("pause_start", source_time - 0.12), source_time)
|
|
233
|
-
)
|
|
234
|
-
item["source_pause_start"] = round(source_pause_start, 3)
|
|
235
|
-
item["pause_start"] = round(
|
|
236
|
-
span["output_start"] + source_pause_start - span["source_start"], 3
|
|
237
|
-
)
|
|
238
|
-
remapped.append(item)
|
|
239
|
-
|
|
240
|
-
speech_rows = [
|
|
241
|
-
row for row in _read_json(work_dir / "asr_result.json", []) if row["text"]
|
|
242
|
-
]
|
|
243
|
-
quiet_rows = [
|
|
244
|
-
row
|
|
245
|
-
for row in _read_json(work_dir / "silence_periods.json", [])
|
|
246
|
-
if not row["has_speech"]
|
|
247
|
-
]
|
|
248
|
-
out_payload = {
|
|
249
|
-
"schema_version": 2,
|
|
250
|
-
"artifact": "speech_boundary_anchors_output.json",
|
|
251
|
-
"timeline": "cut_output",
|
|
252
|
-
"source_artifact": "speech_boundary_anchors.json",
|
|
253
|
-
"clip_plan_fingerprint": stable_hash(
|
|
254
|
-
json.loads((work_dir / "clip_plan_validated.json").read_text(encoding="utf-8"))
|
|
255
|
-
),
|
|
256
|
-
"sentence_anchors": sorted(remapped, key=lambda item: item["time"]),
|
|
257
|
-
"speech_spans": _remap_segments_to_output_for_brief(speech_rows, spans),
|
|
258
|
-
"quiet_windows": _remap_segments_to_output_for_brief(quiet_rows, spans),
|
|
259
|
-
}
|
|
260
|
-
(work_dir / "speech_boundary_anchors_output.json").write_text(
|
|
261
|
-
json.dumps(out_payload, ensure_ascii=False, indent=2), encoding="utf-8"
|
|
262
|
-
)
|
|
263
|
-
return out_payload["sentence_anchors"]
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
def _format_sentence_entry_anchors_for_brief(work_dir, edit_mode):
|
|
267
|
-
anchors = [
|
|
268
|
-
anchor
|
|
269
|
-
for anchor in _sentence_entry_anchors_for_brief(work_dir, edit_mode)
|
|
270
|
-
if anchor["confidence"] in {"high", "medium"}
|
|
271
|
-
]
|
|
272
|
-
if not anchors:
|
|
273
|
-
return []
|
|
274
|
-
lines = [
|
|
275
|
-
"## 原声句末安全切入点",
|
|
276
|
-
"",
|
|
277
|
-
"这些时间是 ASR 句末标点与短声学停顿对齐后的旁白安全入口。旁白在原声已开始后切入时,"
|
|
278
|
-
"必须从其中一个点开始;否则会在 TTS 前被 `interrupts_source_sentence` 硬阻断。",
|
|
279
|
-
"- 调整方式:优先把 `start` 移到建议锚点;放不下时缩短文本、移动整块或删除该旁白,不能让脚本静默挪音频。",
|
|
280
|
-
'- 原声句子完整性是硬约束:切入块写 `"source_entry_policy": "sentence_boundary"`;'
|
|
281
|
-
"不存在 `intentional_interrupt` 绕过方式。没有后续可靠句末锚点时,移动、缩短或删除旁白块。",
|
|
282
|
-
]
|
|
283
|
-
for anchor in anchors:
|
|
284
|
-
source_suffix = (
|
|
285
|
-
f" (SOURCE {anchor['source_time']:.2f}s)" if "source_time" in anchor else ""
|
|
286
|
-
)
|
|
287
|
-
text_tail = str(anchor.get("text_tail", "")).strip()
|
|
288
|
-
lines.append(
|
|
289
|
-
f"- {anchor['time']:.2f}s [{anchor['confidence']}]{source_suffix} {text_tail}".rstrip()
|
|
290
|
-
)
|
|
291
|
-
lines.append("")
|
|
292
|
-
return lines
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
def _format_output_clip_list(work_dir):
|
|
296
|
-
"""List the kept clips on the OUTPUT timeline (cut-first/narrate-second pass 2), so the
|
|
297
|
-
agent narrates against the real rendered cut instead of the source timeline."""
|
|
298
|
-
path = Path(work_dir) / "clip_plan_validated.json"
|
|
299
|
-
if not path.exists():
|
|
300
|
-
return []
|
|
301
|
-
plan = json.loads(path.read_text(encoding="utf-8"))
|
|
302
|
-
out = ["## Kept clips on the OUTPUT timeline", ""]
|
|
303
|
-
for c in plan["clips"]:
|
|
304
|
-
reason = c.get("reason", "")
|
|
305
|
-
out.append(
|
|
306
|
-
f"- OUTPUT {c['output_start']:.1f}–{c['output_end']:.1f}s ← SOURCE[{c.get('source_id', '0')}] "
|
|
307
|
-
f"{c['source_start']:.1f}–{c['source_end']:.1f}s (clip_id={c.get('clip_id', '?')})"
|
|
308
|
-
+ (f" — {reason}" if reason else "")
|
|
309
|
-
)
|
|
310
|
-
out.append("")
|
|
311
|
-
return out if len(out) > 2 else []
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
def _write_deslop_qc_requirements(work_dir):
|
|
315
|
-
"""Write the stable deslop QC contract consumed by deslop_qc.py.
|
|
316
|
-
|
|
317
|
-
style_card_required defaults to False (advisory): a missing style_card.json is a
|
|
318
|
-
warning, not a render-blocking error. A future opt-in run can set it True to make
|
|
319
|
-
style_card.json a hard requirement — deslop_qc.py reads that field.
|
|
320
|
-
"""
|
|
321
|
-
payload = {
|
|
322
|
-
"schema_version": 1,
|
|
323
|
-
"style_card_required": False,
|
|
324
|
-
}
|
|
325
|
-
path = Path(work_dir) / "deslop_qc_requirements.json"
|
|
326
|
-
path.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")
|
|
327
|
-
return path
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
"""Public narration-brief API for this self-contained skill."""
|
|
2
|
-
|
|
3
|
-
from agent_brief import build_agent_brief
|
|
4
|
-
from brief_context import assess_understanding_substrate
|
|
5
|
-
from narration_lint import lint_narration, validate_narration_or_raise
|
|
6
|
-
|
|
7
|
-
__all__ = [
|
|
8
|
-
"assess_understanding_substrate",
|
|
9
|
-
"build_agent_brief",
|
|
10
|
-
"lint_narration",
|
|
11
|
-
"validate_narration_or_raise",
|
|
12
|
-
]
|
|
@@ -1,348 +0,0 @@
|
|
|
1
|
-
"""Load and format research, consolidation, and substrate context."""
|
|
2
|
-
|
|
3
|
-
import hashlib
|
|
4
|
-
import json
|
|
5
|
-
import re
|
|
6
|
-
from pathlib import Path
|
|
7
|
-
|
|
8
|
-
from lib import CONFIG
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
def _load_background_research(work_dir):
|
|
12
|
-
"""Load the agent-authored background_research.json ({} when absent)."""
|
|
13
|
-
path = Path(work_dir) / "background_research.json"
|
|
14
|
-
if not path.exists():
|
|
15
|
-
return {}
|
|
16
|
-
return json.loads(path.read_text(encoding="utf-8"))
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
def _clip_text(text, limit):
|
|
20
|
-
"""Collapse whitespace and clip. Research fields are optional, so None clips to ""."""
|
|
21
|
-
return re.sub(r"\s+", " ", str(text or "")).strip()[:limit]
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
def _format_background_research(research, limit=1800):
|
|
25
|
-
"""Render background_research.json into a bounded Story-context brief section."""
|
|
26
|
-
if not research:
|
|
27
|
-
return []
|
|
28
|
-
lines = [
|
|
29
|
-
"## Story context (from background_research.json)",
|
|
30
|
-
"",
|
|
31
|
-
"Research is context_only: use it for aliases/names/world terms and weak background only; do NOT reveal future plot or upgrade research-only relationships/causes into current on-screen facts without visual/ASR support.",
|
|
32
|
-
"",
|
|
33
|
-
]
|
|
34
|
-
for key, label in (
|
|
35
|
-
("synopsis", "Synopsis"),
|
|
36
|
-
("worldbuilding", "Worldbuilding"),
|
|
37
|
-
("episode_context", "Episode context"),
|
|
38
|
-
):
|
|
39
|
-
value = _clip_text(research.get(key), 500)
|
|
40
|
-
if value:
|
|
41
|
-
lines.append(f"- {label}: {value}")
|
|
42
|
-
|
|
43
|
-
characters = research.get("characters", {})
|
|
44
|
-
if characters:
|
|
45
|
-
lines.append("- Characters:")
|
|
46
|
-
for name, desc in list(characters.items())[:12]:
|
|
47
|
-
lines.append(f" - {_clip_text(name, 60)}: {_clip_text(desc, 160)}")
|
|
48
|
-
|
|
49
|
-
details = research.get("character_details", {})
|
|
50
|
-
if details:
|
|
51
|
-
lines.append("- Character details:")
|
|
52
|
-
for name, info in list(details.items())[:8]:
|
|
53
|
-
bits = []
|
|
54
|
-
aliases = [_clip_text(alias, 40) for alias in info.get("aliases", [])[:4]]
|
|
55
|
-
if aliases:
|
|
56
|
-
bits.append("别名 " + "/".join(aliases))
|
|
57
|
-
role = _clip_text(info.get("role"), 80)
|
|
58
|
-
if role:
|
|
59
|
-
bits.append(role)
|
|
60
|
-
rels = [_clip_text(rel, 80) for rel in info.get("relationships", [])[:4]]
|
|
61
|
-
if rels:
|
|
62
|
-
bits.append(";".join(rels))
|
|
63
|
-
if bits:
|
|
64
|
-
lines.append(f" - {_clip_text(name, 60)}: {'; '.join(bits)}")
|
|
65
|
-
|
|
66
|
-
arcs = research.get("plot_arcs", [])
|
|
67
|
-
if arcs:
|
|
68
|
-
lines.append("- Plot arcs:")
|
|
69
|
-
for arc in arcs[:8]:
|
|
70
|
-
name = _clip_text(arc.get("name"), 80)
|
|
71
|
-
desc = _clip_text(arc.get("description"), 180)
|
|
72
|
-
status = _clip_text(arc.get("status"), 40)
|
|
73
|
-
if name or desc:
|
|
74
|
-
tail = f" [{status}]" if status else ""
|
|
75
|
-
lines.append(f" - {name}: {desc}{tail}".rstrip())
|
|
76
|
-
|
|
77
|
-
notes = research.get("cultural_notes", [])
|
|
78
|
-
if notes:
|
|
79
|
-
lines.append("- Cultural notes:")
|
|
80
|
-
for note in notes[:6]:
|
|
81
|
-
item = _clip_text(note.get("item"), 80)
|
|
82
|
-
expl = _clip_text(note.get("explanation"), 160)
|
|
83
|
-
if item and expl:
|
|
84
|
-
lines.append(f" - {item}: {expl}")
|
|
85
|
-
elif item or expl:
|
|
86
|
-
lines.append(f" - {item or expl}")
|
|
87
|
-
|
|
88
|
-
lines.extend(
|
|
89
|
-
[
|
|
90
|
-
"",
|
|
91
|
-
'Use these names, relationships, and stakes in the narration instead of generic labels like "男子"/"白发女子".',
|
|
92
|
-
"",
|
|
93
|
-
]
|
|
94
|
-
)
|
|
95
|
-
text = "\n".join(lines)
|
|
96
|
-
if len(text) <= limit:
|
|
97
|
-
return lines
|
|
98
|
-
clipped = text[:limit].rsplit("\n", 1)[0].rstrip()
|
|
99
|
-
return [
|
|
100
|
-
*clipped.splitlines(),
|
|
101
|
-
"",
|
|
102
|
-
"[Story context clipped to keep ASR/visual evidence in context]",
|
|
103
|
-
"",
|
|
104
|
-
]
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
def assess_understanding_substrate(
|
|
108
|
-
scenes_analysis, asr_result, *, has_story_context=False
|
|
109
|
-
):
|
|
110
|
-
"""Measure how much real signal the writing agent has to work with.
|
|
111
|
-
|
|
112
|
-
Recap quality collapses to generic 看图说话 when the agent has no story spine and
|
|
113
|
-
only literal frame descriptions to paraphrase. A spine is substantial dialogue
|
|
114
|
-
(ASR) OR researched/given story context — frame-fact VOLUME alone (a visually busy
|
|
115
|
-
but storyless clip, e.g. an anime whose dialogue ASR could not read) is NOT a spine,
|
|
116
|
-
so it must not grade as "rich"; otherwise the sparse-substrate warning, the research
|
|
117
|
-
directive, and the density relief never fire for exactly that cold-narration case.
|
|
118
|
-
"""
|
|
119
|
-
asr_chars = sum(len(seg["text"]) for seg in asr_result)
|
|
120
|
-
scenes_with_facts = sum(1 for s in scenes_analysis if s.get("frame_facts"))
|
|
121
|
-
desc_lens = [len(s["description"]) for s in scenes_analysis]
|
|
122
|
-
avg_desc = sum(desc_lens) // len(desc_lens) if desc_lens else 0
|
|
123
|
-
|
|
124
|
-
has_asr = asr_chars >= 20
|
|
125
|
-
has_facts = scenes_with_facts > 0
|
|
126
|
-
has_story_spine = asr_chars >= 200 or bool(has_story_context)
|
|
127
|
-
if not has_asr and not has_facts and avg_desc < 25:
|
|
128
|
-
level = "empty"
|
|
129
|
-
elif (has_asr or has_facts) and has_story_spine:
|
|
130
|
-
level = "rich"
|
|
131
|
-
else:
|
|
132
|
-
level = "thin"
|
|
133
|
-
return {
|
|
134
|
-
"level": level,
|
|
135
|
-
"asr_chars": asr_chars,
|
|
136
|
-
"scene_count": len(scenes_analysis),
|
|
137
|
-
"scenes_with_frame_facts": scenes_with_facts,
|
|
138
|
-
"avg_description_len": avg_desc,
|
|
139
|
-
"has_story_context": bool(has_story_context),
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
def _format_substrate_warning(assessment):
|
|
144
|
-
"""Render a loud brief banner when the understanding substrate is weak."""
|
|
145
|
-
if assessment["level"] == "rich":
|
|
146
|
-
return []
|
|
147
|
-
if assessment["level"] == "empty":
|
|
148
|
-
head = "⚠️ UNDERSTANDING SUBSTRATE IS EMPTY — narration will be generic guesswork unless you fix this first."
|
|
149
|
-
else:
|
|
150
|
-
head = '⚠️ Understanding substrate is THIN — narration risks generic "看图说话" without more grounding.'
|
|
151
|
-
return [
|
|
152
|
-
head,
|
|
153
|
-
f" ASR chars: {assessment['asr_chars']} | scenes: {assessment['scene_count']} | "
|
|
154
|
-
f"scenes with frame_facts: {assessment['scenes_with_frame_facts']} | avg description: {assessment['avg_description_len']} chars",
|
|
155
|
-
" Before writing: do background research (write background_research.json with names/relationships/plot),",
|
|
156
|
-
" lean on any --context provided, and use ASR dialogue + frame_facts as the factual spine.",
|
|
157
|
-
" Do NOT invent plot. If you truly have nothing, keep beats sparse and factual rather than fabricating drama.",
|
|
158
|
-
"",
|
|
159
|
-
]
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
def _write_json_artifact(work_dir, name, payload):
|
|
163
|
-
path = Path(work_dir) / name
|
|
164
|
-
path.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")
|
|
165
|
-
return path
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
def _format_asr_chunks_for_brief(chunks, max_chunks=24):
|
|
169
|
-
if not chunks:
|
|
170
|
-
return []
|
|
171
|
-
lines = [
|
|
172
|
-
"## ASR writing chunks (semantic windows)",
|
|
173
|
-
"",
|
|
174
|
-
"Use these chunks as the dialogue spine instead of swallowing the full transcript at once.",
|
|
175
|
-
"Full data: `asr_writing_chunks.json`.",
|
|
176
|
-
"",
|
|
177
|
-
]
|
|
178
|
-
for chunk in chunks[:max_chunks]:
|
|
179
|
-
scene_ids = ",".join(str(sid) for sid in chunk["scene_ids"]) or "n/a"
|
|
180
|
-
text = chunk["text"]
|
|
181
|
-
if len(text) > 900:
|
|
182
|
-
text = text[:897] + "..."
|
|
183
|
-
lines.extend(
|
|
184
|
-
[
|
|
185
|
-
f"### ASR chunk {chunk['chunk_id'] + 1}: {chunk['start']:.1f}-{chunk['end']:.1f}s | scenes {scene_ids} | {chunk['char_count']} units",
|
|
186
|
-
text or "(empty transcript chunk)",
|
|
187
|
-
"",
|
|
188
|
-
]
|
|
189
|
-
)
|
|
190
|
-
if len(chunks) > max_chunks:
|
|
191
|
-
lines.append(
|
|
192
|
-
f"... {len(chunks) - max_chunks} more chunks in `asr_writing_chunks.json`."
|
|
193
|
-
)
|
|
194
|
-
lines.append("")
|
|
195
|
-
return lines
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
def _format_timeline_fusion_for_brief(fusion, max_items=40):
|
|
199
|
-
if not fusion:
|
|
200
|
-
return []
|
|
201
|
-
lines = [
|
|
202
|
-
"## Timeline fusion (VLM + ASR + quiet slots)",
|
|
203
|
-
"",
|
|
204
|
-
"This is the pre-aligned multimodal view. Use `narration_slots` when available; otherwise write ducked-bed beats around dialogue.",
|
|
205
|
-
"Full data: `timeline_fusion.json`.",
|
|
206
|
-
"",
|
|
207
|
-
]
|
|
208
|
-
for item in fusion[:max_items]:
|
|
209
|
-
start, end = item["time_range"]
|
|
210
|
-
slot_text = (
|
|
211
|
-
", ".join(
|
|
212
|
-
f"{slot['start']:.1f}-{slot['end']:.1f}s/{slot['char_budget']}字"
|
|
213
|
-
for slot in item["narration_slots"][:4]
|
|
214
|
-
)
|
|
215
|
-
or "none"
|
|
216
|
-
)
|
|
217
|
-
dialogue_text = (
|
|
218
|
-
"; ".join(
|
|
219
|
-
f"{seg['start']:.1f}-{seg['end']:.1f}s {seg['text'][:80]}"
|
|
220
|
-
for seg in item["dialogue_segments"][:3]
|
|
221
|
-
if seg["text"]
|
|
222
|
-
)
|
|
223
|
-
or "none"
|
|
224
|
-
)
|
|
225
|
-
lines.extend(
|
|
226
|
-
[
|
|
227
|
-
f"### Fusion scene {item['scene_id']}: {start:.1f}-{end:.1f}s ({item['recommended_mode']})",
|
|
228
|
-
f"- Visual: {item['visual_description']}",
|
|
229
|
-
f"- Dialogue overlap: {item['dialogue_overlap_seconds']:.1f}s | {dialogue_text}",
|
|
230
|
-
f"- Narration slots: {slot_text}",
|
|
231
|
-
"",
|
|
232
|
-
]
|
|
233
|
-
)
|
|
234
|
-
if len(fusion) > max_items:
|
|
235
|
-
lines.append(
|
|
236
|
-
f"... {len(fusion) - max_items} more fused scenes in `timeline_fusion.json`."
|
|
237
|
-
)
|
|
238
|
-
lines.append("")
|
|
239
|
-
return lines
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
def _consolidation_model():
|
|
243
|
-
return CONFIG["vlm_model"]
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
def _clean_asr_prompt_fingerprint():
|
|
247
|
-
# Keep this literal in sync with consolidate.CLEAN_PROMPT without importing it;
|
|
248
|
-
# brief.py and narration.py must remain byte-identical cross-skill copies.
|
|
249
|
-
prompt = """你在清洗中文视频的 ASR 逐段转写。对【每一段】做:补标点、修明显同音/错别字、(能判断时)在句首轻标说话人,让长段连读文本变成清晰可读的句子。
|
|
250
|
-
铁律:
|
|
251
|
-
- 不要合并或拆分段落,输出段数必须与输入完全一致,顺序一致。
|
|
252
|
-
- 不要改时间,不要输出 start/end(时间由程序保留)。
|
|
253
|
-
- 只清洗 text,不要增删事实、不要脑补画面。
|
|
254
|
-
只返回 JSON:{"segments":[{"i":0,"text":"清洗后的文本","speaker":"可选说话人"}, ...]},i 为输入段的下标。"""
|
|
255
|
-
return hashlib.md5(prompt.encode("utf-8")).hexdigest()
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
def _index_prompt_fingerprint():
|
|
259
|
-
prompt = """你在根据逐场景画面分析、ASR对白、background_research术语表,为一个视频建立【全局理解索引】,供后续写解说词时保持人物/关系/主线一致。
|
|
260
|
-
规则:
|
|
261
|
-
- visual/asr 是当前视频事实证据;每个人物、关系、剧情节点、物件尽量给 evidence_ids。
|
|
262
|
-
- background_research 只能用于人名/别名/术语/身份消歧,默认 support=context_only;不能把后续剧情或未出现关系升级为当前画面事实。
|
|
263
|
-
- ASR 中出现但画面描述未命名的人名,应进入 characters[*].asr_mentions。
|
|
264
|
-
只返回 JSON:
|
|
265
|
-
{"characters":[{"name":"角色名或外观指代","description":"身份/特征","aliases":[],"visual_descriptions":[],"asr_mentions":[],"research_role":"","evidence_ids":[],"confidence":"high|medium|low"}],
|
|
266
|
-
"relationships":[{"a":"角色","b":"角色","relation":"关系","evidence_ids":[],"support":"direct|indirect|context_only"}],
|
|
267
|
-
"plot_points":[{"time":"00:00","text":"按时间顺序的关键剧情节点","evidence_ids":[]}],
|
|
268
|
-
"entities":[{"name":"重要物件/地点/线索","evidence_ids":[]}],
|
|
269
|
-
"research_glossary":[{"name":"名字/术语","aliases":[],"role":"说明","support":"context_only"}]}"""
|
|
270
|
-
return hashlib.md5(prompt.encode("utf-8")).hexdigest()
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
def _load_consolidation(work_dir, scenes_analysis):
|
|
274
|
-
"""Load consolidate.py's understanding_index.json only when its provenance matches
|
|
275
|
-
the current vlm_analysis.json, model and index prompt ({} otherwise)."""
|
|
276
|
-
work_dir = Path(work_dir)
|
|
277
|
-
path = work_dir / "understanding_index.json"
|
|
278
|
-
meta_path = work_dir / "understanding_index.json.meta.json"
|
|
279
|
-
if not path.exists() or not meta_path.exists():
|
|
280
|
-
return {}
|
|
281
|
-
try:
|
|
282
|
-
meta = json.loads(meta_path.read_text(encoding="utf-8"))
|
|
283
|
-
source_md5 = hashlib.md5(
|
|
284
|
-
(work_dir / "vlm_analysis.json").read_bytes()
|
|
285
|
-
).hexdigest()
|
|
286
|
-
except (OSError, json.JSONDecodeError):
|
|
287
|
-
return {}
|
|
288
|
-
if not isinstance(meta, dict):
|
|
289
|
-
return {}
|
|
290
|
-
expected = {
|
|
291
|
-
"source_md5": source_md5,
|
|
292
|
-
"model": _consolidation_model(),
|
|
293
|
-
"prompt_md5": _index_prompt_fingerprint(),
|
|
294
|
-
}
|
|
295
|
-
if scenes_analysis:
|
|
296
|
-
expected["scene_count"] = len(scenes_analysis)
|
|
297
|
-
if not meta.items() >= expected.items():
|
|
298
|
-
return {}
|
|
299
|
-
try:
|
|
300
|
-
index = json.loads(path.read_text(encoding="utf-8"))
|
|
301
|
-
except (OSError, json.JSONDecodeError):
|
|
302
|
-
return {}
|
|
303
|
-
if not isinstance(index, dict) or not all(
|
|
304
|
-
isinstance(index.get(key), list)
|
|
305
|
-
for key in ("characters", "relationships", "plot_points", "entities")
|
|
306
|
-
):
|
|
307
|
-
return {}
|
|
308
|
-
if not all(isinstance(item, dict) for item in index["characters"]):
|
|
309
|
-
return {}
|
|
310
|
-
if not all(isinstance(item, dict) for item in index["relationships"]):
|
|
311
|
-
return {}
|
|
312
|
-
return index
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
def _format_consolidation(index):
|
|
316
|
-
"""Render consolidate.py's understanding_index.json into a compact brief section.
|
|
317
|
-
|
|
318
|
-
plot_points/entities items are model output and may be bare strings or objects."""
|
|
319
|
-
if not index:
|
|
320
|
-
return []
|
|
321
|
-
lines = ["## Understanding index (from consolidate.py)", ""]
|
|
322
|
-
chars = index["characters"]
|
|
323
|
-
if chars:
|
|
324
|
-
lines.append("- Characters:")
|
|
325
|
-
for c in chars:
|
|
326
|
-
lines.append(
|
|
327
|
-
f" - {c.get('name', '?')}: {str(c.get('description', '')).strip()}"
|
|
328
|
-
)
|
|
329
|
-
rels = index["relationships"]
|
|
330
|
-
if rels:
|
|
331
|
-
lines.append("- Relationships:")
|
|
332
|
-
for r in rels:
|
|
333
|
-
lines.append(
|
|
334
|
-
f" - {r.get('a', '?')} — {r.get('relation', '?')} — {r.get('b', '?')}"
|
|
335
|
-
)
|
|
336
|
-
plot = index["plot_points"]
|
|
337
|
-
if plot:
|
|
338
|
-
lines.append("- Plot spine:")
|
|
339
|
-
lines.extend(
|
|
340
|
-
f" {i + 1}. {p.get('text', p) if isinstance(p, dict) else p}"
|
|
341
|
-
for i, p in enumerate(plot)
|
|
342
|
-
)
|
|
343
|
-
ents = index["entities"]
|
|
344
|
-
if ents:
|
|
345
|
-
ent_names = [str(e.get("name", e) if isinstance(e, dict) else e) for e in ents]
|
|
346
|
-
lines.append(f"- Entities: {', '.join(ent_names)}")
|
|
347
|
-
lines.append("")
|
|
348
|
-
return lines
|