@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,20 +1,11 @@
|
|
|
1
|
-
"""Normalize cut plans and maintain render-cache
|
|
2
|
-
|
|
3
|
-
import hashlib
|
|
1
|
+
"""Normalize cut plans and maintain the render-cache sidecar."""
|
|
4
2
|
|
|
5
3
|
import json
|
|
6
|
-
import os
|
|
7
|
-
|
|
8
4
|
import re
|
|
9
5
|
|
|
10
|
-
|
|
11
6
|
from pathlib import Path
|
|
12
7
|
|
|
13
|
-
from lib import CONFIG, get_video_duration, log
|
|
14
|
-
|
|
15
|
-
EDITED_SOURCE_RENDER_ALGORITHM_VERSION = "edited-source-render-v3"
|
|
16
|
-
|
|
17
|
-
GEOMETRY_RENDER_ALGORITHM_VERSION = "geometry-weighted-orientation-area-fps-v2"
|
|
8
|
+
from lib import CONFIG, file_identity, get_video_duration, log
|
|
18
9
|
|
|
19
10
|
|
|
20
11
|
def parse_duration_seconds(value):
|
|
@@ -101,111 +92,42 @@ def load_clip_plan(path):
|
|
|
101
92
|
return json.loads(Path(path).read_text(encoding="utf-8"))
|
|
102
93
|
|
|
103
94
|
|
|
104
|
-
def _stable_json_dumps(value):
|
|
105
|
-
return json.dumps(
|
|
106
|
-
value, ensure_ascii=False, sort_keys=True, separators=(",", ":"), default=str
|
|
107
|
-
)
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
def value_fingerprint(value):
|
|
111
|
-
"""Return a stable fingerprint for JSON-serializable non-secret values."""
|
|
112
|
-
return hashlib.md5(_stable_json_dumps(value).encode("utf-8")).hexdigest()
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
def cut_plan_fingerprint(validated_plan):
|
|
116
|
-
"""Hash the exact normalized clip plan that determines edited_source.mp4 bytes."""
|
|
117
|
-
payload = dict(validated_plan)
|
|
118
|
-
# Provenance for raw-plan freshness is not part of the edited media bytes.
|
|
119
|
-
payload.pop("raw_plan_fingerprint", None)
|
|
120
|
-
# QC is observability derived from the media plan, not an input range decision.
|
|
121
|
-
payload.pop("qc", None)
|
|
122
|
-
return value_fingerprint(payload)
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
_FILE_FINGERPRINT_MEMO = {}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
def _file_identity(path):
|
|
129
|
-
"""(device, inode, size, mtime_ns) — changes whenever the bytes could have changed."""
|
|
130
|
-
st = os.stat(os.fspath(path))
|
|
131
|
-
return (st.st_dev, st.st_ino, st.st_size, st.st_mtime_ns)
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
def file_fingerprint(path, chunk_size=1024 * 1024):
|
|
135
|
-
"""Return a full-content fingerprint for cache-correct identity checks.
|
|
136
|
-
|
|
137
|
-
The digest covers CONTENT only — never the path or mtime — so a copied video or
|
|
138
|
-
artifact is still recognised as the same asset, while any byte change invalidates
|
|
139
|
-
the cache even if timestamps, size, head, or tail bytes are misleading.
|
|
140
|
-
|
|
141
|
-
Identity metadata is used ONLY to memoize within a single process. One understanding
|
|
142
|
-
run fingerprints the same source video 8-10 times and the whole extracted frame set
|
|
143
|
-
2-3 times; on a 40-minute video at fps=1 that is gigabytes of redundant reads before
|
|
144
|
-
any real work starts. A file rewritten in place gets a new (size, mtime_ns) and is
|
|
145
|
-
re-hashed, so the memo can never serve a stale digest.
|
|
146
|
-
"""
|
|
147
|
-
key = _file_identity(path)
|
|
148
|
-
memoized = _FILE_FINGERPRINT_MEMO.get(key)
|
|
149
|
-
if memoized is not None:
|
|
150
|
-
return memoized
|
|
151
|
-
h = hashlib.sha256()
|
|
152
|
-
with open(os.fspath(path), "rb") as f:
|
|
153
|
-
for chunk in iter(lambda: f.read(chunk_size), b""):
|
|
154
|
-
h.update(chunk)
|
|
155
|
-
digest = h.hexdigest()
|
|
156
|
-
_FILE_FINGERPRINT_MEMO[key] = digest
|
|
157
|
-
return digest
|
|
158
|
-
|
|
159
|
-
|
|
160
95
|
def _edited_source_meta_path(output_path):
|
|
161
96
|
return Path(str(output_path) + ".meta.json")
|
|
162
97
|
|
|
163
98
|
|
|
164
99
|
def _load_edited_source_meta(output_path):
|
|
100
|
+
"""None when never rendered; a sidecar this skill wrote but cannot parse raises."""
|
|
165
101
|
meta_path = _edited_source_meta_path(output_path)
|
|
166
102
|
if not meta_path.exists():
|
|
167
103
|
return None
|
|
168
|
-
|
|
169
|
-
meta = json.loads(meta_path.read_text(encoding="utf-8"))
|
|
170
|
-
except (OSError, ValueError):
|
|
171
|
-
return None
|
|
172
|
-
return meta if isinstance(meta, dict) else None
|
|
104
|
+
return json.loads(meta_path.read_text(encoding="utf-8"))
|
|
173
105
|
|
|
174
106
|
|
|
175
|
-
def
|
|
176
|
-
"""
|
|
107
|
+
def _source_identities_for_plan(validated_plan, input_video=None):
|
|
108
|
+
"""{path: {size, mtime_ns}} for every media file that can affect edited_source.mp4."""
|
|
177
109
|
# Single-source plans carry no per-clip source_path; the CLI video is the only input.
|
|
178
110
|
paths = {clip["source_path"] for clip in validated_plan["clips"] if "source_path" in clip}
|
|
179
111
|
if not paths and input_video is not None:
|
|
180
112
|
paths.add(str(input_video))
|
|
181
|
-
return {str(Path(path)):
|
|
113
|
+
return {str(Path(path)): file_identity(path) for path in sorted(paths)}
|
|
182
114
|
|
|
183
115
|
|
|
184
116
|
def edited_source_render_cache_payload():
|
|
185
117
|
"""Render-affecting settings that invalidate edited_source.mp4 cache reuse.
|
|
186
118
|
|
|
187
|
-
Keep this payload limited to inputs
|
|
119
|
+
Keep this payload limited to inputs that can change rendered media bytes.
|
|
188
120
|
Observational QC produced after validation/render is intentionally excluded.
|
|
189
121
|
"""
|
|
190
|
-
return {
|
|
191
|
-
"render_algorithm_version": EDITED_SOURCE_RENDER_ALGORITHM_VERSION,
|
|
192
|
-
"geometry_render_algorithm_version": GEOMETRY_RENDER_ALGORITHM_VERSION,
|
|
193
|
-
"clip_join_audio_fade_ms": round(CONFIG["clip_join_audio_fade_ms"], 3),
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
def edited_source_render_fingerprint():
|
|
198
|
-
return value_fingerprint(edited_source_render_cache_payload())
|
|
122
|
+
return {"clip_join_audio_fade_ms": round(CONFIG["clip_join_audio_fade_ms"], 3)}
|
|
199
123
|
|
|
200
124
|
|
|
201
125
|
def _write_edited_source_meta(output_path, validated_plan, input_video=None):
|
|
202
126
|
meta = {
|
|
203
|
-
"schema_version":
|
|
204
|
-
"
|
|
205
|
-
"render_fingerprint": edited_source_render_fingerprint(),
|
|
127
|
+
"schema_version": 3,
|
|
128
|
+
"plan": validated_plan["clips"],
|
|
206
129
|
"render_cache": edited_source_render_cache_payload(),
|
|
207
|
-
"
|
|
208
|
-
"edited_source_fingerprint": file_fingerprint(output_path),
|
|
130
|
+
"sources": _source_identities_for_plan(validated_plan, input_video),
|
|
209
131
|
"total_duration": validated_plan["total_duration"],
|
|
210
132
|
"clip_count": len(validated_plan["clips"]),
|
|
211
133
|
}
|
|
@@ -215,25 +137,18 @@ def _write_edited_source_meta(output_path, validated_plan, input_video=None):
|
|
|
215
137
|
|
|
216
138
|
|
|
217
139
|
def should_reuse_edited_source(output_path, validated_plan, input_video=None):
|
|
218
|
-
"""
|
|
140
|
+
"""True only when a non-empty edited_source.mp4 matches the plan, settings and sources."""
|
|
219
141
|
output_path = Path(output_path)
|
|
220
|
-
if not output_path.exists():
|
|
142
|
+
if not output_path.exists() or output_path.stat().st_size == 0:
|
|
221
143
|
return False
|
|
222
144
|
meta = _load_edited_source_meta(output_path)
|
|
223
|
-
if meta
|
|
224
|
-
return False
|
|
225
|
-
|
|
226
|
-
meta
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
):
|
|
231
|
-
return False
|
|
232
|
-
try:
|
|
233
|
-
output_fingerprint = file_fingerprint(output_path)
|
|
234
|
-
except OSError:
|
|
235
|
-
return False
|
|
236
|
-
return meta.get("edited_source_fingerprint") == output_fingerprint
|
|
145
|
+
if not isinstance(meta, dict) or meta.get("schema_version") != 3:
|
|
146
|
+
return False # never rendered, or a sidecar from an older schema: re-render
|
|
147
|
+
return (
|
|
148
|
+
meta["plan"] == validated_plan["clips"]
|
|
149
|
+
and meta["render_cache"] == edited_source_render_cache_payload()
|
|
150
|
+
and meta["sources"] == _source_identities_for_plan(validated_plan, input_video)
|
|
151
|
+
)
|
|
237
152
|
|
|
238
153
|
|
|
239
154
|
def _manifest_source_entries(sources_manifest):
|
|
@@ -5,10 +5,10 @@ import json
|
|
|
5
5
|
|
|
6
6
|
from pathlib import Path
|
|
7
7
|
|
|
8
|
-
from lib import CONFIG, get_video_duration, log, run_cmd
|
|
8
|
+
from lib import CONFIG, filter_file_args, get_video_duration, log, run_cmd
|
|
9
9
|
|
|
10
10
|
from cut_contract import _write_edited_source_meta
|
|
11
|
-
from media_geometry import _has_audio_stream
|
|
11
|
+
from media_geometry import _has_audio_stream
|
|
12
12
|
from sentence_boundaries import _continuous_source_join
|
|
13
13
|
|
|
14
14
|
|
|
@@ -129,10 +129,20 @@ def write_cut_delivery_qc(work_dir, validated_plan):
|
|
|
129
129
|
|
|
130
130
|
|
|
131
131
|
def build_edited_source_video(input_video, validated_plan, work_dir, output_path=None):
|
|
132
|
-
"""Build `edited_source.mp4` by concatenating validated source ranges.
|
|
132
|
+
"""Build `edited_source.mp4` by concatenating validated source ranges.
|
|
133
|
+
|
|
134
|
+
`validated_plan["qc"]["output_geometry"]` is required: the caller (cut_cli, or any
|
|
135
|
+
public user of this API) selects the canvas with `_select_output_geometry` first, so the
|
|
136
|
+
same geometry is recorded in clip_plan_validated.json and used for the render."""
|
|
133
137
|
work_dir = Path(work_dir)
|
|
134
138
|
output_path = Path(output_path or work_dir / "edited_source.mp4")
|
|
135
139
|
clips = validated_plan["clips"]
|
|
140
|
+
qc = validated_plan["qc"]
|
|
141
|
+
if "output_geometry" not in qc:
|
|
142
|
+
raise KeyError(
|
|
143
|
+
"validated_plan['qc']['output_geometry'] is required: select the canvas with "
|
|
144
|
+
"media_geometry._select_output_geometry before build_edited_source_video"
|
|
145
|
+
)
|
|
136
146
|
|
|
137
147
|
source_paths = []
|
|
138
148
|
for clip in clips:
|
|
@@ -142,12 +152,7 @@ def build_edited_source_video(input_video, validated_plan, work_dir, output_path
|
|
|
142
152
|
source_index = {path: idx for idx, path in enumerate(source_paths)}
|
|
143
153
|
audio_by_input = {path: _has_audio_stream(path) for path in source_paths}
|
|
144
154
|
join_fade_ms = CONFIG["clip_join_audio_fade_ms"]
|
|
145
|
-
qc = validated_plan.setdefault("qc", {})
|
|
146
155
|
qc["join_fade_ms"] = round(join_fade_ms, 3)
|
|
147
|
-
if "output_geometry" not in qc:
|
|
148
|
-
_, _, _, geometry_qc = _select_output_geometry(source_paths, clips)
|
|
149
|
-
qc["output_geometry"] = geometry_qc
|
|
150
|
-
qc["output_geometry_reason"] = geometry_qc["reason"]
|
|
151
156
|
|
|
152
157
|
parts = []
|
|
153
158
|
concat_inputs = []
|
|
@@ -250,7 +255,7 @@ def build_edited_source_video(input_video, validated_plan, work_dir, output_path
|
|
|
250
255
|
if len(filter_complex.encode("utf-8")) > 7000:
|
|
251
256
|
filter_script = work_dir / "edit_filter_complex.txt"
|
|
252
257
|
filter_script.write_text(filter_complex, encoding="utf-8")
|
|
253
|
-
filter_args =
|
|
258
|
+
filter_args = filter_file_args("filter_complex", filter_script)
|
|
254
259
|
else:
|
|
255
260
|
filter_args = ["-filter_complex", filter_complex]
|
|
256
261
|
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
"""Self-contained utilities for the video-cut skill (no cross-skill imports)."""
|
|
2
|
+
import functools
|
|
2
3
|
import math
|
|
3
4
|
import os
|
|
5
|
+
import shutil
|
|
4
6
|
import subprocess
|
|
7
|
+
import tempfile
|
|
5
8
|
|
|
6
9
|
|
|
7
10
|
def log(msg):
|
|
@@ -56,6 +59,12 @@ CONFIG = {
|
|
|
56
59
|
}
|
|
57
60
|
|
|
58
61
|
|
|
62
|
+
def file_identity(path):
|
|
63
|
+
"""{size, mtime_ns} — the cache identity of an input file (no content read)."""
|
|
64
|
+
st = os.stat(os.fspath(path))
|
|
65
|
+
return {"size": st.st_size, "mtime_ns": st.st_mtime_ns}
|
|
66
|
+
|
|
67
|
+
|
|
59
68
|
def run_cmd(cmd, **kwargs):
|
|
60
69
|
"""Run a command list and return the CompletedProcess (stdout/stderr captured)."""
|
|
61
70
|
display = " ".join(
|
|
@@ -65,6 +74,36 @@ def run_cmd(cmd, **kwargs):
|
|
|
65
74
|
return subprocess.run(cmd, capture_output=True, text=True, **kwargs)
|
|
66
75
|
|
|
67
76
|
|
|
77
|
+
# ffmpeg 7 added `-/option path` to read any option's value from a file; ffmpeg 9 removed the
|
|
78
|
+
# older `-filter_complex_script` / `-filter_script` spellings, which are all ffmpeg <= 6 knows.
|
|
79
|
+
_LEGACY_FILTER_FILE_OPTIONS = {
|
|
80
|
+
"filter_complex": "-filter_complex_script",
|
|
81
|
+
"filter:v:0": "-filter_script:v:0",
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@functools.lru_cache(maxsize=None)
|
|
86
|
+
def _ffmpeg_reads_option_files():
|
|
87
|
+
"""Whether the ffmpeg on PATH accepts `-/option path` (asked once per process)."""
|
|
88
|
+
if shutil.which("ffmpeg") is None:
|
|
89
|
+
return False
|
|
90
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
91
|
+
graph = os.path.join(tmp, "probe_filter.txt")
|
|
92
|
+
with open(graph, "w", encoding="utf-8") as fh:
|
|
93
|
+
fh.write("null")
|
|
94
|
+
result = subprocess.run(["ffmpeg", "-hide_banner", "-/filter_complex", graph],
|
|
95
|
+
stdin=subprocess.DEVNULL, capture_output=True, text=True,
|
|
96
|
+
timeout=20)
|
|
97
|
+
return "Unrecognized option" not in result.stderr
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def filter_file_args(option, path):
|
|
101
|
+
"""ffmpeg arguments that load `option`'s filtergraph from `path`, spelled for this ffmpeg."""
|
|
102
|
+
if _ffmpeg_reads_option_files():
|
|
103
|
+
return [f"-/{option}", str(path)]
|
|
104
|
+
return [_LEGACY_FILTER_FILE_OPTIONS[option], str(path)]
|
|
105
|
+
|
|
106
|
+
|
|
68
107
|
def get_video_duration(video_path):
|
|
69
108
|
"""Return media duration in seconds via ffprobe."""
|
|
70
109
|
cmd = ["ffprobe", "-v", "error", "-show_entries", "format=duration",
|
|
@@ -1,157 +1,4 @@
|
|
|
1
|
-
"""
|
|
2
|
-
|
|
3
|
-
from lib import log
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
def source_time_to_output_time(source_time, clips):
|
|
7
|
-
"""Map a source timestamp into the post-concat output timeline (None when cut away)."""
|
|
8
|
-
for clip in clips:
|
|
9
|
-
start = clip["source_start"]
|
|
10
|
-
end = clip["source_end"]
|
|
11
|
-
if start <= source_time <= end:
|
|
12
|
-
mapped = clip["output_start"] + (source_time - start)
|
|
13
|
-
return round(max(clip["output_start"], min(mapped, clip["output_end"])), 3)
|
|
14
|
-
return None
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
def _clips_for_midpoint(start, end, clips):
|
|
18
|
-
mid = (start + end) / 2
|
|
19
|
-
return [clip for clip in clips if clip["source_start"] <= mid <= clip["source_end"]]
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
def map_narration_to_clips(narration, validated_plan, min_duration=0.3):
|
|
23
|
-
"""Convert source-time narration segments to edited-output timeline segments."""
|
|
24
|
-
clips = validated_plan["clips"]
|
|
25
|
-
mapped = []
|
|
26
|
-
for raw in narration:
|
|
27
|
-
source_start = raw["start"]
|
|
28
|
-
source_end = raw["end"]
|
|
29
|
-
if raw.get("source_clip_id") is not None:
|
|
30
|
-
requested_clip_id = int(raw["source_clip_id"])
|
|
31
|
-
clip = next((c for c in clips if c["clip_id"] == requested_clip_id), None)
|
|
32
|
-
if clip and not (
|
|
33
|
-
clip["source_start"] <= (source_start + source_end) / 2 <= clip["source_end"]
|
|
34
|
-
):
|
|
35
|
-
clip = None
|
|
36
|
-
else:
|
|
37
|
-
matches = _clips_for_midpoint(source_start, source_end, clips)
|
|
38
|
-
if len(matches) > 1:
|
|
39
|
-
log(
|
|
40
|
-
f" 丢弃重复片段中未标 source_clip_id 的解说: {source_start:.1f}-{source_end:.1f}s"
|
|
41
|
-
)
|
|
42
|
-
continue
|
|
43
|
-
clip = matches[0] if matches else None
|
|
44
|
-
if not clip:
|
|
45
|
-
log(f" 丢弃未落入剪辑片段的解说: {source_start:.1f}-{source_end:.1f}s")
|
|
46
|
-
continue
|
|
47
|
-
clipped_source_start = max(source_start, clip["source_start"])
|
|
48
|
-
clipped_source_end = min(source_end, clip["source_end"])
|
|
49
|
-
if clipped_source_end - clipped_source_start < min_duration:
|
|
50
|
-
log(f" 丢弃过短映射解说: {source_start:.1f}-{source_end:.1f}s")
|
|
51
|
-
continue
|
|
52
|
-
item = dict(raw)
|
|
53
|
-
item["source_start"] = round(clipped_source_start, 3)
|
|
54
|
-
item["source_end"] = round(clipped_source_end, 3)
|
|
55
|
-
item["source_clip_id"] = clip["clip_id"]
|
|
56
|
-
item["start"] = source_time_to_output_time(clipped_source_start, [clip])
|
|
57
|
-
item["end"] = source_time_to_output_time(clipped_source_end, [clip])
|
|
58
|
-
# Tag beats trimmed to a clip edge: their TEXT was written for a longer span and may
|
|
59
|
-
# now describe footage that was cut away (a stale-text desync the lint surfaces).
|
|
60
|
-
item["clamped"] = bool(
|
|
61
|
-
clipped_source_start > source_start + 1e-3
|
|
62
|
-
or clipped_source_end < source_end - 1e-3
|
|
63
|
-
)
|
|
64
|
-
mapped.append(item)
|
|
65
|
-
|
|
66
|
-
mapped.sort(key=lambda seg: seg["start"])
|
|
67
|
-
return mapped
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
def lint_mapped_narration(
|
|
71
|
-
mapped,
|
|
72
|
-
original_count,
|
|
73
|
-
output_duration,
|
|
74
|
-
*,
|
|
75
|
-
min_spm=6.0,
|
|
76
|
-
max_gap_seconds=12.0,
|
|
77
|
-
drop_ratio_limit=0.3,
|
|
78
|
-
):
|
|
79
|
-
"""Advisory re-lint of narration AFTER it is mapped onto the cut OUTPUT timeline.
|
|
80
|
-
|
|
81
|
-
The mapper silently drops beats whose midpoint is outside every kept clip and clamps
|
|
82
|
-
boundary-crossers, so a narration authored against the full source can pass the
|
|
83
|
-
source-time validate yet leave the cut sparse or describing footage the viewer never
|
|
84
|
-
sees. This surfaces that on the real output timeline (narration_mapped_lint.json) and
|
|
85
|
-
returns a `blocking` verdict (heavy drop / too sparse / long gap) that the cut stage
|
|
86
|
-
enforces unless --allow-sparse-cut. Clamped beats are always blocking because their TTS
|
|
87
|
-
sentence would be cut at a clip edge; sparse-cut intent never authorizes speech truncation.
|
|
88
|
-
"""
|
|
89
|
-
mapped = sorted(mapped, key=lambda s: s["start"])
|
|
90
|
-
mapped_count = len(mapped)
|
|
91
|
-
dropped = max(0, original_count - mapped_count)
|
|
92
|
-
drop_ratio = dropped / original_count if original_count else 0.0
|
|
93
|
-
spm = mapped_count / (output_duration / 60) if output_duration > 0 else 0.0
|
|
94
|
-
gaps = [b["start"] - a["end"] for a, b in zip(mapped, mapped[1:])]
|
|
95
|
-
max_gap = max(gaps) if gaps else 0.0
|
|
96
|
-
covered = sum(max(0.0, b["end"] - b["start"]) for b in mapped)
|
|
97
|
-
coverage = covered / output_duration if output_duration > 0 else 0.0
|
|
98
|
-
|
|
99
|
-
warnings = []
|
|
100
|
-
if drop_ratio >= drop_ratio_limit:
|
|
101
|
-
warnings.append(
|
|
102
|
-
{
|
|
103
|
-
"code": "many_beats_dropped",
|
|
104
|
-
"message": "大量解说段落落在保留片段之外被丢弃——请按保留的片段写解说,而不是整段原片。",
|
|
105
|
-
"dropped": dropped,
|
|
106
|
-
"original": original_count,
|
|
107
|
-
"drop_ratio": round(drop_ratio, 2),
|
|
108
|
-
}
|
|
109
|
-
)
|
|
110
|
-
if mapped_count >= 2 and spm and spm < min_spm:
|
|
111
|
-
warnings.append(
|
|
112
|
-
{
|
|
113
|
-
"code": "low_density_output",
|
|
114
|
-
"message": "映射后的解说在成片里偏稀疏——在保留片段内补充解说 beat。",
|
|
115
|
-
"segments_per_minute": round(spm, 2),
|
|
116
|
-
"min_segments_per_minute": min_spm,
|
|
117
|
-
}
|
|
118
|
-
)
|
|
119
|
-
if max_gap > max_gap_seconds:
|
|
120
|
-
warnings.append(
|
|
121
|
-
{
|
|
122
|
-
"code": "long_gap_output",
|
|
123
|
-
"message": "成片里有一长段没有解说。",
|
|
124
|
-
"max_gap_seconds": round(max_gap, 2),
|
|
125
|
-
"max_gap_limit_seconds": max_gap_seconds,
|
|
126
|
-
}
|
|
127
|
-
)
|
|
128
|
-
clamped = [b for b in mapped if b["clamped"]]
|
|
129
|
-
if clamped:
|
|
130
|
-
warnings.append(
|
|
131
|
-
{
|
|
132
|
-
"code": "clamped_beats",
|
|
133
|
-
"message": "有解说段被裁到片段边界,旁白句子会被截断——必须移动片段边界或重写整句。",
|
|
134
|
-
"count": len(clamped),
|
|
135
|
-
}
|
|
136
|
-
)
|
|
137
|
-
blocking_codes = {
|
|
138
|
-
"many_beats_dropped",
|
|
139
|
-
"low_density_output",
|
|
140
|
-
"long_gap_output",
|
|
141
|
-
"clamped_beats",
|
|
142
|
-
}
|
|
143
|
-
return {
|
|
144
|
-
"mapped_count": mapped_count,
|
|
145
|
-
"dropped": dropped,
|
|
146
|
-
"drop_ratio": round(drop_ratio, 2),
|
|
147
|
-
"output_duration": round(output_duration, 2),
|
|
148
|
-
"segments_per_minute": round(spm, 2),
|
|
149
|
-
"max_gap_seconds": round(max_gap, 2),
|
|
150
|
-
"coverage": round(coverage, 2),
|
|
151
|
-
"clamped_count": len(clamped),
|
|
152
|
-
"warnings": warnings,
|
|
153
|
-
"blocking": any(w["code"] in blocking_codes for w in warnings),
|
|
154
|
-
}
|
|
1
|
+
"""Cut QC bookkeeping for clip_plan_validated.json."""
|
|
155
2
|
|
|
156
3
|
|
|
157
4
|
def update_cut_qc(plan, *, allow_duration_drift=False, duration_drift_allowed_by=None):
|
|
@@ -169,7 +16,7 @@ def update_cut_qc(plan, *, allow_duration_drift=False, duration_drift_allowed_by
|
|
|
169
16
|
"total_duration": round(total, 3),
|
|
170
17
|
}
|
|
171
18
|
else:
|
|
172
|
-
ratio = total / target
|
|
19
|
+
ratio = total / target # parse_duration_seconds guarantees target > 0
|
|
173
20
|
if ratio < 0.85:
|
|
174
21
|
target_status = "under"
|
|
175
22
|
elif ratio > 1.15:
|