@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
|
@@ -4,22 +4,20 @@ import json
|
|
|
4
4
|
import os
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
|
|
7
|
-
from artifacts import _explicit_source_video
|
|
7
|
+
from artifacts import _explicit_source_video
|
|
8
8
|
from lib import log, run_cmd
|
|
9
9
|
|
|
10
10
|
def _load_cut_timeline_plan(work_dir):
|
|
11
|
-
"""The cut plan, preferring clip_plan_validated.json
|
|
11
|
+
"""The cut plan, preferring clip_plan_validated.json unless the raw plan is newer; None in full mode."""
|
|
12
12
|
raw_plan_path = Path(work_dir) / "clip_plan.json"
|
|
13
13
|
validated_plan_path = Path(work_dir) / "clip_plan_validated.json"
|
|
14
14
|
if not validated_plan_path.exists():
|
|
15
15
|
return json.loads(raw_plan_path.read_text(encoding="utf-8")) if raw_plan_path.exists() else None
|
|
16
16
|
if not raw_plan_path.exists():
|
|
17
17
|
return json.loads(validated_plan_path.read_text(encoding="utf-8"))
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
return validated_plan
|
|
22
|
-
return raw_plan
|
|
18
|
+
if validated_plan_path.stat().st_mtime_ns >= raw_plan_path.stat().st_mtime_ns:
|
|
19
|
+
return json.loads(validated_plan_path.read_text(encoding="utf-8"))
|
|
20
|
+
return json.loads(raw_plan_path.read_text(encoding="utf-8"))
|
|
23
21
|
|
|
24
22
|
|
|
25
23
|
def _plan_clip_spans(work_dir):
|
|
@@ -120,13 +118,13 @@ def _canvas_from_stream(stream):
|
|
|
120
118
|
}
|
|
121
119
|
|
|
122
120
|
|
|
123
|
-
def _probe_canvas(video_path
|
|
121
|
+
def _probe_canvas(video_path):
|
|
124
122
|
"""Return rotation/SAR/DAR-aware canvas facts for a video.
|
|
125
123
|
|
|
126
124
|
``width``/``height`` are the display canvas used by subtitle/overlay geometry.
|
|
127
125
|
For legacy square-pixel landscape sources, these remain the raw storage dimensions.
|
|
128
126
|
"""
|
|
129
|
-
res =
|
|
127
|
+
res = run_cmd([
|
|
130
128
|
"ffprobe", "-v", "error", "-select_streams", "v:0",
|
|
131
129
|
"-show_entries",
|
|
132
130
|
"stream=width,height,r_frame_rate,avg_frame_rate,sample_aspect_ratio,display_aspect_ratio:stream_tags=rotate:stream_side_data=rotation",
|
|
@@ -140,23 +138,16 @@ def _probe_canvas(video_path, *, command_runner=run_cmd):
|
|
|
140
138
|
return _canvas_from_stream(streams[0])
|
|
141
139
|
|
|
142
140
|
|
|
143
|
-
def _has_audio_stream(video_path
|
|
141
|
+
def _has_audio_stream(video_path):
|
|
144
142
|
"""Return True when the input has an audio stream usable as [0:a]."""
|
|
145
|
-
result =
|
|
143
|
+
result = run_cmd([
|
|
146
144
|
"ffprobe", "-v", "error", "-select_streams", "a:0",
|
|
147
145
|
"-show_entries", "stream=index", "-of", "csv=p=0", str(video_path),
|
|
148
146
|
])
|
|
149
147
|
return result.returncode == 0 and bool(result.stdout.strip())
|
|
150
148
|
|
|
151
149
|
|
|
152
|
-
def _build_video_clips(
|
|
153
|
-
input_video,
|
|
154
|
-
work_dir,
|
|
155
|
-
duration_s,
|
|
156
|
-
*,
|
|
157
|
-
logger=log,
|
|
158
|
-
source_video_getter=_explicit_source_video,
|
|
159
|
-
):
|
|
150
|
+
def _build_video_clips(input_video, work_dir, duration_s):
|
|
160
151
|
"""Video-track clips for the timeline.
|
|
161
152
|
|
|
162
153
|
In cut mode each plan entry becomes a clip referencing the ORIGINAL source
|
|
@@ -164,7 +155,7 @@ def _build_video_clips(
|
|
|
164
155
|
require an explicit ambient --source-video. Without any declared source (full
|
|
165
156
|
mode, or cut mode rendered without --source-video) the rendered input is one clip.
|
|
166
157
|
"""
|
|
167
|
-
explicit_source_video =
|
|
158
|
+
explicit_source_video = _explicit_source_video()
|
|
168
159
|
spans = _plan_clip_spans(work_dir)
|
|
169
160
|
multi_source = spans is not None and any(span["entry"].get("source_path") for span in spans)
|
|
170
161
|
if spans is None or not (explicit_source_video or multi_source):
|
|
@@ -180,7 +171,7 @@ def _build_video_clips(
|
|
|
180
171
|
# Degrade ONLY this clip — point it at the rendered cut for its own output
|
|
181
172
|
# window — and keep real provenance for every present source, instead of
|
|
182
173
|
# collapsing the whole multi-source timeline.
|
|
183
|
-
|
|
174
|
+
log(f" 时间线: source_path 不存在,该片段降级为剪后成片片段: {source_path or '(unset)'}")
|
|
184
175
|
clips.append({"source_id": entry.get("source_id"),
|
|
185
176
|
"source_path": str(input_video),
|
|
186
177
|
"source_start": timeline_start,
|
|
@@ -10,9 +10,7 @@ def _apply_narration_speed(
|
|
|
10
10
|
tts_segments,
|
|
11
11
|
work_dir,
|
|
12
12
|
*,
|
|
13
|
-
|
|
14
|
-
duration_probe=get_video_duration,
|
|
15
|
-
logger=log,
|
|
13
|
+
tempo_policy=None,
|
|
16
14
|
):
|
|
17
15
|
"""Globally speed up narration audio via atempo (CONFIG['narration_speed']).
|
|
18
16
|
|
|
@@ -20,7 +18,7 @@ def _apply_narration_speed(
|
|
|
20
18
|
snappier without the chipmunk effect. Rewrites each segment's audio_path/duration
|
|
21
19
|
to the sped copy so the rest of assembly is unchanged. No-op at speed 1.0.
|
|
22
20
|
"""
|
|
23
|
-
speed = CONFIG["narration_speed"]
|
|
21
|
+
speed = tempo_policy["global_atempo"] if tempo_policy else CONFIG["narration_speed"]
|
|
24
22
|
if abs(speed - 1.0) <= 1e-3:
|
|
25
23
|
return
|
|
26
24
|
done = 0
|
|
@@ -29,14 +27,15 @@ def _apply_narration_speed(
|
|
|
29
27
|
if not os.path.exists(src):
|
|
30
28
|
continue # reported as a skipped segment during placement
|
|
31
29
|
out = str(Path(work_dir) / f"_spd_{seg['index']}.wav")
|
|
32
|
-
res =
|
|
30
|
+
res = run_cmd(["ffmpeg", "-y", "-i", src, "-filter:a", f"atempo={speed:.3f}",
|
|
33
31
|
"-ar", "44100", "-ac", "1", "-acodec", "pcm_s16le", out])
|
|
34
32
|
if res.returncode != 0:
|
|
35
33
|
raise RuntimeError(f"解说提速失败 {src}: {res.stderr}")
|
|
36
34
|
seg["audio_path"] = out
|
|
37
|
-
seg["
|
|
35
|
+
seg["narration_conversion_path"] = out
|
|
36
|
+
seg["audio_duration"] = get_video_duration(out)
|
|
38
37
|
done += 1
|
|
39
|
-
|
|
38
|
+
log(f"解说整体提速: atempo={speed:.2f} ({done} 段)")
|
|
40
39
|
|
|
41
40
|
|
|
42
41
|
def _adjust_tts_speed(
|
|
@@ -44,9 +43,7 @@ def _adjust_tts_speed(
|
|
|
44
43
|
target_duration,
|
|
45
44
|
tts_rate_offset=0.0,
|
|
46
45
|
*,
|
|
47
|
-
|
|
48
|
-
duration_probe=get_video_duration,
|
|
49
|
-
logger=log,
|
|
46
|
+
tempo_policy=None,
|
|
50
47
|
):
|
|
51
48
|
"""Fit overlong TTS with bounded atempo; never time-trim speech in assemble.
|
|
52
49
|
|
|
@@ -55,8 +52,16 @@ def _adjust_tts_speed(
|
|
|
55
52
|
untouched for QC to block instead of guessing a spoken_text truncation.
|
|
56
53
|
"""
|
|
57
54
|
audio_path = Path(audio_path)
|
|
58
|
-
current_dur =
|
|
55
|
+
current_dur = get_video_duration(audio_path)
|
|
59
56
|
budget = narration_tempo_budget(tts_rate_offset)
|
|
57
|
+
if tempo_policy:
|
|
58
|
+
budget.update({
|
|
59
|
+
"global_narration_speed": tempo_policy["global_atempo"],
|
|
60
|
+
"tts_rate_factor": 1.0,
|
|
61
|
+
"segment_tempo_max": tempo_policy["segment_tempo_max"],
|
|
62
|
+
"cumulative_tempo_max": tempo_policy["cumulative_tempo_max"],
|
|
63
|
+
"cumulative_tempo_hard_max": tempo_policy["cumulative_tempo_hard_max"],
|
|
64
|
+
})
|
|
60
65
|
meta = {
|
|
61
66
|
"fit_status": "fits",
|
|
62
67
|
"blocking": False,
|
|
@@ -75,6 +80,14 @@ def _adjust_tts_speed(
|
|
|
75
80
|
if current_dur <= target_duration:
|
|
76
81
|
return (str(audio_path), current_dur, meta)
|
|
77
82
|
|
|
83
|
+
if tempo_policy and not tempo_policy["bounded_segment_fit"]:
|
|
84
|
+
meta.update({
|
|
85
|
+
"fit_status": "no_safe_fit", "blocking": True,
|
|
86
|
+
"truncate_reason": "no_safe_boundary", "placed_audio_duration": 0.0,
|
|
87
|
+
"needed_tempo_factor": current_dur / target_duration,
|
|
88
|
+
})
|
|
89
|
+
return (str(audio_path), current_dur, meta)
|
|
90
|
+
|
|
78
91
|
ratio = current_dur / target_duration
|
|
79
92
|
effective_max = budget["segment_tempo_max"]
|
|
80
93
|
if ratio > effective_max:
|
|
@@ -85,7 +98,7 @@ def _adjust_tts_speed(
|
|
|
85
98
|
"placed_audio_duration": 0.0,
|
|
86
99
|
"needed_tempo_factor": ratio,
|
|
87
100
|
})
|
|
88
|
-
|
|
101
|
+
log(
|
|
89
102
|
f" TTS 无安全放置: {current_dur:.1f}s 需 x{ratio:.2f},"
|
|
90
103
|
f"超过段内预算 x{effective_max:.2f}(assemble 不按时间硬切)"
|
|
91
104
|
)
|
|
@@ -98,10 +111,10 @@ def _adjust_tts_speed(
|
|
|
98
111
|
cmd = ["ffmpeg", "-y", "-i", str(audio_path),
|
|
99
112
|
"-filter:a", f"atempo={tempo:.6f}",
|
|
100
113
|
"-ar", "44100", "-ac", "1", str(adjusted_path)]
|
|
101
|
-
result =
|
|
114
|
+
result = run_cmd(cmd)
|
|
102
115
|
if result.returncode != 0:
|
|
103
116
|
raise RuntimeError(f"TTS 加速失败 {audio_path}: {result.stderr}")
|
|
104
|
-
new_dur =
|
|
117
|
+
new_dur = get_video_duration(adjusted_path)
|
|
105
118
|
if new_dur > target_duration + (1.0 / 44100.0):
|
|
106
119
|
adjusted_path.unlink(missing_ok=True)
|
|
107
120
|
meta.update({
|
|
@@ -111,7 +124,7 @@ def _adjust_tts_speed(
|
|
|
111
124
|
"placed_audio_duration": 0.0,
|
|
112
125
|
"needed_tempo_factor": new_dur / target_duration,
|
|
113
126
|
})
|
|
114
|
-
|
|
127
|
+
log(
|
|
115
128
|
f" TTS 加速后仍超出安全窗口 {new_dur - target_duration:.3f}s;"
|
|
116
129
|
"禁止裁尾,交由 Agent 缩短/移动文本"
|
|
117
130
|
)
|
|
@@ -123,7 +136,7 @@ def _adjust_tts_speed(
|
|
|
123
136
|
"placed_audio_duration": new_dur,
|
|
124
137
|
"effective_tempo": budget["global_narration_speed"] * budget["tts_rate_factor"] * tempo,
|
|
125
138
|
})
|
|
126
|
-
|
|
139
|
+
log(f" TTS 温和加速: {current_dur:.1f}s → {new_dur:.1f}s (x{tempo:.2f})")
|
|
127
140
|
return (str(adjusted_path), new_dur, meta)
|
|
128
141
|
|
|
129
142
|
|
|
@@ -170,9 +183,7 @@ def _build_timed_narration(
|
|
|
170
183
|
video_duration,
|
|
171
184
|
work_dir,
|
|
172
185
|
*,
|
|
173
|
-
|
|
174
|
-
command_runner=run_cmd,
|
|
175
|
-
logger=log,
|
|
186
|
+
tempo_policy=None,
|
|
176
187
|
):
|
|
177
188
|
"""将 TTS 片段按时间轴放置到一条与视频等长的音轨上"""
|
|
178
189
|
sample_rate = 44100
|
|
@@ -194,7 +205,7 @@ def _build_timed_narration(
|
|
|
194
205
|
|
|
195
206
|
for seg in tts_segments:
|
|
196
207
|
wav_path = seg["audio_path"]
|
|
197
|
-
pause_samples = int(seg
|
|
208
|
+
pause_samples = int(seg["pause_after_ms"] * sample_rate / 1000)
|
|
198
209
|
# 段落收紧:同一段落内(与上一句作者留白 <= run_gap)把这一句紧贴上一句的实际收尾播放,
|
|
199
210
|
# 句间间隔固定为 tight_pause,不受 slot 内居中延迟 / TTS 时长波动影响。段落之间(作者特意留
|
|
200
211
|
# 的大留白,让精彩原声透出)才放回原声。这样句间间隔稳定、不会出现"一句解说一段空白"。
|
|
@@ -210,12 +221,17 @@ def _build_timed_narration(
|
|
|
210
221
|
continue
|
|
211
222
|
|
|
212
223
|
original_wav_path = wav_path
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
224
|
+
try:
|
|
225
|
+
with wave.open(wav_path, "rb") as wf_check:
|
|
226
|
+
needs_resample = (
|
|
227
|
+
wf_check.getnchannels(), wf_check.getsampwidth(), wf_check.getframerate()
|
|
228
|
+
) != (1, 2, sample_rate)
|
|
229
|
+
except (wave.Error, EOFError):
|
|
230
|
+
# Valid post-processed WAV may use IEEE float, which Python's wave
|
|
231
|
+
# reader does not support. FFmpeg performs the explicit PCM conversion.
|
|
232
|
+
needs_resample = True
|
|
233
|
+
|
|
234
|
+
tts_rate_offset = seg["tts_rate_offset"]
|
|
219
235
|
tts_dur = seg["audio_duration"]
|
|
220
236
|
|
|
221
237
|
slot_duration = max(0.0, float(seg["end"]) - float(seg["start"]))
|
|
@@ -240,7 +256,15 @@ def _build_timed_narration(
|
|
|
240
256
|
available_samples = end_boundary - actual_start
|
|
241
257
|
available_duration = max(available_samples / sample_rate, 0)
|
|
242
258
|
if tts_dur > available_duration > 0:
|
|
243
|
-
|
|
259
|
+
if tempo_policy:
|
|
260
|
+
wav_path, _actual_dur, fit_meta = _adjust_tts_speed(
|
|
261
|
+
wav_path, available_duration, tts_rate_offset,
|
|
262
|
+
tempo_policy=tempo_policy,
|
|
263
|
+
)
|
|
264
|
+
else:
|
|
265
|
+
wav_path, _actual_dur, fit_meta = _adjust_tts_speed(
|
|
266
|
+
wav_path, available_duration, tts_rate_offset
|
|
267
|
+
)
|
|
244
268
|
seg.update({
|
|
245
269
|
"fit_status": fit_meta["fit_status"],
|
|
246
270
|
"segment_tempo_factor": fit_meta["segment_tempo_factor"],
|
|
@@ -257,6 +281,11 @@ def _build_timed_narration(
|
|
|
257
281
|
continue
|
|
258
282
|
else:
|
|
259
283
|
budget = narration_tempo_budget(tts_rate_offset)
|
|
284
|
+
if tempo_policy:
|
|
285
|
+
budget.update({
|
|
286
|
+
"global_narration_speed": tempo_policy["global_atempo"],
|
|
287
|
+
"tts_rate_factor": 1.0,
|
|
288
|
+
})
|
|
260
289
|
seg.update({
|
|
261
290
|
"fit_status": "fits",
|
|
262
291
|
"segment_tempo_factor": 1.0,
|
|
@@ -270,16 +299,17 @@ def _build_timed_narration(
|
|
|
270
299
|
needs_resample = False
|
|
271
300
|
if needs_resample:
|
|
272
301
|
tmp_path = str(Path(work_dir) / f"_rs_{seg['index']}.wav")
|
|
273
|
-
rs_result =
|
|
302
|
+
rs_result = run_cmd(["ffmpeg", "-y", "-i", wav_path,
|
|
274
303
|
"-ar", str(sample_rate), "-ac", "1",
|
|
275
304
|
"-acodec", "pcm_s16le", tmp_path])
|
|
276
305
|
if rs_result.returncode != 0:
|
|
277
|
-
|
|
306
|
+
log(f" 跳过: 重采样失败 {wav_path}: {rs_result.stderr}")
|
|
278
307
|
_unplaced(seg, seg["start"], "skipped", "resample_failed")
|
|
279
308
|
prev_pause_samples = pause_samples
|
|
280
309
|
skipped_count += 1
|
|
281
310
|
continue
|
|
282
311
|
wav_path = tmp_path
|
|
312
|
+
seg["narration_conversion_path"] = tmp_path
|
|
283
313
|
|
|
284
314
|
with wave.open(wav_path, "rb") as wf:
|
|
285
315
|
wf_data = bytearray(wf.readframes(wf.getnframes()))
|
|
@@ -290,7 +320,7 @@ def _build_timed_narration(
|
|
|
290
320
|
write_samples = audio_samples
|
|
291
321
|
|
|
292
322
|
if write_samples <= 0 or available <= 0:
|
|
293
|
-
|
|
323
|
+
log(f" 跳过: {seg['start']:.1f}s-{seg['end']:.1f}s (无空间)")
|
|
294
324
|
_unplaced(seg, seg["start"], "no_safe_fit", "no_room", blocking=True)
|
|
295
325
|
prev_pause_samples = pause_samples
|
|
296
326
|
no_safe_fit_count += 1
|
|
@@ -301,7 +331,7 @@ def _build_timed_narration(
|
|
|
301
331
|
# consonant/vowel release. _adjust_tts_speed must produce a complete file
|
|
302
332
|
# that fits; otherwise block and ask the Agent to shorten/move the block.
|
|
303
333
|
over = (audio_samples - available) / sample_rate
|
|
304
|
-
|
|
334
|
+
log(f" TTS 无安全放置: 段 {seg['index']} 超出可用窗口 {over:.3f}s;禁止裁尾,交由 QC 阻断")
|
|
305
335
|
_unplaced(seg, actual_start / sample_rate, "no_safe_fit", "no_safe_boundary", blocking=True)
|
|
306
336
|
prev_pause_samples = pause_samples
|
|
307
337
|
skipped_count += 1
|
|
@@ -312,7 +342,7 @@ def _build_timed_narration(
|
|
|
312
342
|
if actual_start < last_written_end:
|
|
313
343
|
overlap_ms = (last_written_end - actual_start) * 1000 / sample_rate
|
|
314
344
|
if last_written_end >= actual_start + write_samples:
|
|
315
|
-
|
|
345
|
+
log(f" 跳过重叠段: {actual_start/sample_rate:.1f}s "
|
|
316
346
|
f"(与前段重叠 {overlap_ms:.0f}ms)")
|
|
317
347
|
_unplaced(seg, seg["start"], "no_safe_fit", "no_room", blocking=True)
|
|
318
348
|
prev_pause_samples = pause_samples
|
|
@@ -321,7 +351,7 @@ def _build_timed_narration(
|
|
|
321
351
|
actual_start = last_written_end
|
|
322
352
|
available = end_boundary - actual_start
|
|
323
353
|
if write_samples > available:
|
|
324
|
-
|
|
354
|
+
log(f" 重叠 {overlap_ms:.0f}ms 后无安全完整窗口,跳过")
|
|
325
355
|
_unplaced(seg, actual_start / sample_rate, "no_safe_fit", "no_safe_boundary", blocking=True)
|
|
326
356
|
prev_pause_samples = pause_samples
|
|
327
357
|
skipped_count += 1
|
|
@@ -380,4 +410,4 @@ def _build_timed_narration(
|
|
|
380
410
|
"已中止以避免生成无解说视频"
|
|
381
411
|
)
|
|
382
412
|
|
|
383
|
-
|
|
413
|
+
log(f"解说音轨: {video_duration:.1f}s, {len(tts_segments)} 段")
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"""Static packaging layers (frame / header / logo images) burned over the whole recap.
|
|
2
|
+
|
|
3
|
+
The caller writes ``work_dir/packaging_layers.json`` (the orchestrator does so from a bound
|
|
4
|
+
``packaging`` template). Each layer is a local image scaled into a rect on a declared
|
|
5
|
+
canvas. ffmpeg reads the images with ``movie=`` sources, so the render stays a single-input
|
|
6
|
+
video filter; ``timeline.json`` gets matching image segments for editable export.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
import lib
|
|
13
|
+
from artifacts import file_identity
|
|
14
|
+
from visual_render import _escape_subtitle_filter_path
|
|
15
|
+
|
|
16
|
+
PACKAGING_LAYERS = "packaging_layers.json"
|
|
17
|
+
_IMAGE_EXTS = {".png", ".jpg", ".jpeg", ".webp"}
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def load_packaging_layers(work_dir, canvas):
|
|
21
|
+
"""Validated layers for this canvas, or [] when the work_dir declares none."""
|
|
22
|
+
path = Path(work_dir) / PACKAGING_LAYERS
|
|
23
|
+
if not path.exists():
|
|
24
|
+
return []
|
|
25
|
+
plan = json.loads(path.read_text(encoding="utf-8"))
|
|
26
|
+
declared = plan["canvas"]
|
|
27
|
+
if (declared["width"], declared["height"]) != (canvas["width"], canvas["height"]):
|
|
28
|
+
raise RuntimeError(
|
|
29
|
+
f"{PACKAGING_LAYERS} 按 {declared['width']}x{declared['height']} 设计,"
|
|
30
|
+
f"成片画布是 {canvas['width']}x{canvas['height']}"
|
|
31
|
+
)
|
|
32
|
+
layers = []
|
|
33
|
+
for layer in plan["layers"]:
|
|
34
|
+
image = Path(layer["path"]).expanduser().resolve()
|
|
35
|
+
rect = layer["rect"]
|
|
36
|
+
if not image.is_file() or image.suffix.lower() not in _IMAGE_EXTS:
|
|
37
|
+
raise RuntimeError(f"包装图层 {layer['name']} 的图片不可用: {image}")
|
|
38
|
+
if (min(rect["x"], rect["y"]) < 0 or rect["width"] <= 0 or rect["height"] <= 0
|
|
39
|
+
or rect["x"] + rect["width"] > canvas["width"]
|
|
40
|
+
or rect["y"] + rect["height"] > canvas["height"]):
|
|
41
|
+
raise RuntimeError(f"包装图层 {layer['name']} 超出画布")
|
|
42
|
+
layers.append({"name": layer["name"], "path": str(image), "rect": dict(rect),
|
|
43
|
+
"image_size": _image_size(image)})
|
|
44
|
+
return layers
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _image_size(image):
|
|
48
|
+
"""Pixel size of a layer image; the editor fits by it, the render stretches to the rect."""
|
|
49
|
+
res = lib.run_cmd(["ffprobe", "-v", "error", "-select_streams", "v:0",
|
|
50
|
+
"-show_entries", "stream=width,height", "-of", "json", str(image)])
|
|
51
|
+
try:
|
|
52
|
+
stream = json.loads(res.stdout)["streams"][0]
|
|
53
|
+
return {"width": int(stream["width"]), "height": int(stream["height"])}
|
|
54
|
+
except (ValueError, KeyError, IndexError):
|
|
55
|
+
raise RuntimeError(f"无法读取包装图层尺寸: {image}") from None
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def compose_video_filter(chain, layers, *, mask_first):
|
|
59
|
+
"""Join the existing filter chain, inserting packaging layers after the source mask.
|
|
60
|
+
|
|
61
|
+
Order: source-subtitle mask → packaging layers → text overlays / burned subtitles →
|
|
62
|
+
scaling. Without layers this is the plain comma-joined chain.
|
|
63
|
+
"""
|
|
64
|
+
if not layers:
|
|
65
|
+
return ",".join(chain)
|
|
66
|
+
head, tail = (chain[:1], chain[1:]) if mask_first else ([], list(chain))
|
|
67
|
+
graph = [
|
|
68
|
+
f"movie=filename='{_escape_subtitle_filter_path(layer['path'])}',"
|
|
69
|
+
f"scale={layer['rect']['width']}:{layer['rect']['height']},format=rgba[pk{i}]"
|
|
70
|
+
for i, layer in enumerate(layers)
|
|
71
|
+
]
|
|
72
|
+
current = "in"
|
|
73
|
+
if head:
|
|
74
|
+
graph.append(f"[in]{head[0]}[pm]")
|
|
75
|
+
current = "pm"
|
|
76
|
+
for i, layer in enumerate(layers):
|
|
77
|
+
out = "out" if i == len(layers) - 1 and not tail else f"po{i}"
|
|
78
|
+
graph.append(f"[{current}][pk{i}]overlay=x={layer['rect']['x']}:y={layer['rect']['y']}[{out}]")
|
|
79
|
+
current = out
|
|
80
|
+
if tail:
|
|
81
|
+
graph.append(f"[{current}]{','.join(tail)}[out]")
|
|
82
|
+
return ";".join(graph)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def timeline_image_segments(layers, canvas, duration_s):
|
|
86
|
+
"""Full-length image segments in the timeline's center-origin, Y-up transform.
|
|
87
|
+
|
|
88
|
+
The editor shows an image fitted inside the canvas (keeping its own aspect) at scale 1;
|
|
89
|
+
the render stretches it to the rect, so x and y scales are derived separately.
|
|
90
|
+
"""
|
|
91
|
+
width, height = canvas["width"], canvas["height"]
|
|
92
|
+
segments = []
|
|
93
|
+
for layer in layers:
|
|
94
|
+
rect, size = layer["rect"], layer["image_size"]
|
|
95
|
+
fit = min(width / size["width"], height / size["height"])
|
|
96
|
+
segments.append({
|
|
97
|
+
"source_path": layer["path"],
|
|
98
|
+
"timeline_start": 0.0,
|
|
99
|
+
"timeline_end": duration_s,
|
|
100
|
+
"scale": {"x": round(rect["width"] / (size["width"] * fit), 6),
|
|
101
|
+
"y": round(rect["height"] / (size["height"] * fit), 6)},
|
|
102
|
+
"position": {
|
|
103
|
+
"x": round((rect["x"] + rect["width"] / 2 - width / 2) / (width / 2), 6),
|
|
104
|
+
"y": round((height / 2 - rect["y"] - rect["height"] / 2) / (height / 2), 6),
|
|
105
|
+
},
|
|
106
|
+
})
|
|
107
|
+
return segments
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def packaging_settings(work_dir):
|
|
111
|
+
"""What the manifest records: the plan file and each layer image's identity."""
|
|
112
|
+
path = Path(work_dir) / PACKAGING_LAYERS if work_dir is not None else None
|
|
113
|
+
if path is None or not path.exists():
|
|
114
|
+
return {"artifact": PACKAGING_LAYERS, "present": False, "layers": []}
|
|
115
|
+
plan = json.loads(path.read_text(encoding="utf-8"))
|
|
116
|
+
layers = []
|
|
117
|
+
for layer in plan["layers"]:
|
|
118
|
+
image = Path(layer["path"]).expanduser().resolve()
|
|
119
|
+
layers.append({"name": layer["name"], "path": str(image), "rect": layer["rect"],
|
|
120
|
+
**(file_identity(image) if image.is_file() else {})})
|
|
121
|
+
return {"artifact": PACKAGING_LAYERS, "present": True, "identity": file_identity(path),
|
|
122
|
+
"template": plan.get("template"), "layers": layers}
|