@oh-story/dsh 0.1.8 → 0.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -11
- package/lib/client.js +35 -14
- package/lib/client.js.map +2 -2
- package/lib/drama/manifest.json +165 -69
- package/lib/drama/skills/short-drama/SKILL.md +5 -1
- package/lib/drama/skills/short-drama/scripts/creator_markdown_check.py +126 -9
- package/lib/drama/skills/short-drama/scripts/project_tool.py +11 -0
- package/lib/drama/skills/short-drama-assets/references/character-and-look.md +18 -0
- package/lib/drama/skills/short-drama-assets/references/continuity-lock.md +10 -1
- package/lib/drama/skills/short-drama-edit/SKILL.md +148 -0
- package/lib/drama/skills/short-drama-edit/agents/openai.yaml +4 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/README.md +60 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/package.json +15 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/remotion.config.ts +6 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/Root.tsx +26 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/Subtitles.tsx +88 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/font.ts +46 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/index.ts +4 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/schema.ts +32 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/tsconfig.json +13 -0
- package/lib/drama/skills/short-drama-edit/assets//345/211/252/350/276/221/345/215/225.md +43 -0
- package/lib/drama/skills/short-drama-edit/references/cut-craft.md +81 -0
- package/lib/drama/skills/short-drama-edit/references/delivery-verify.md +58 -0
- package/lib/drama/skills/short-drama-edit/references/generated-footage.md +96 -0
- package/lib/drama/skills/short-drama-edit/references/sound-and-subtitles.md +113 -0
- package/lib/drama/skills/short-drama-edit/references/stage-contract.md +55 -0
- package/lib/drama/skills/short-drama-edit/scripts/edit_tool.py +1041 -0
- package/lib/drama/skills/short-drama-edit/scripts/selftest.py +456 -0
- package/lib/drama/skills/short-drama-image-prompts/assets/image-prompts.md +5 -0
- package/lib/drama/skills/short-drama-image-prompts/assets/lookdev-prompts.md +3 -0
- package/lib/drama/skills/short-drama-novel-analyze/references/aggregation-and-entities.md +2 -2
- package/lib/drama/skills/short-drama-novel-analyze/references/chapter-extraction.md +4 -1
- package/lib/drama/skills/short-drama-produce/SKILL.md +24 -3
- package/lib/drama/skills/short-drama-produce/references/adapter-contract.md +28 -0
- package/lib/drama/skills/short-drama-produce/references/providers/minimax-h3-video.md +24 -5
- package/lib/drama/skills/short-drama-produce/references/providers/seedance.md +7 -0
- package/lib/drama/skills/short-drama-produce/scripts/fixture_adapter.py +18 -1
- package/lib/drama/skills/short-drama-produce/scripts/production_tool.py +181 -2
- package/lib/drama/skills/short-drama-produce/scripts/provider_adapters.py +57 -2
- package/lib/drama/skills/short-drama-review/SKILL.md +3 -0
- package/lib/drama/skills/short-drama-review/references/production-quality-gates.md +20 -0
- package/lib/drama/skills/short-drama-review/references/rubric-visual-motion.md +36 -0
- package/lib/drama/skills/short-drama-storyboard/SKILL.md +7 -0
- package/lib/drama/skills/short-drama-storyboard/assets/keyframe-prompts.md +4 -0
- package/lib/drama/skills/short-drama-storyboard/references/shot-craft.md +39 -0
- package/lib/drama/skills/short-drama-storyboard/references/stage-contract.md +2 -0
- package/lib/drama/skills/short-drama-video-prompts/SKILL.md +7 -1
- package/lib/drama/skills/short-drama-video-prompts/references/minimax-h3.md +101 -6
- package/lib/drama/skills/short-drama-video-prompts/references/motion-recipe.md +7 -1
- package/lib/drama/skills/short-drama-video-prompts/references/performance-action-timing.md +35 -0
- package/lib/drama/skills/short-drama-video-prompts/references/production-prompt-grammar.md +3 -1
- package/lib/drama/skills/short-drama-video-prompts/references/seedance-2.0.md +13 -3
- package/lib/drama/skills/short-drama-video-prompts/references/seedance-2.5.md +35 -4
- package/lib/drama/skills/short-drama-video-prompts/references/stage-contract.md +1 -1
- package/lib/drama/skills/short-drama-write/SKILL.md +13 -2
- package/lib/drama/skills/short-drama-write/references/screenplay-format.md +6 -0
- package/lib/drama/skills/short-drama-write/references/stage-contract.md +1 -0
- package/lib/index.js +4 -2
- package/lib/index.js.map +2 -2
- package/lib/novel-to-game/VERSION +1 -1
- package/lib/novel-to-game/manifest.json +28 -28
- package/lib/novel-to-game/skills/game-art-direction/SKILL.md +6 -27
- package/lib/novel-to-game/skills/game-build/SKILL.md +12 -29
- package/lib/novel-to-game/skills/game-concept/SKILL.md +10 -26
- package/lib/novel-to-game/skills/game-qa/SKILL.md +5 -15
- package/lib/novel-to-game/skills/game-qa/references/test-design-method.md +1 -1
- package/lib/novel-to-game/skills/game-world-design/SKILL.md +19 -28
- package/lib/novel-to-game/skills/game-world-design/references/dialogue-design-method.md +1 -2
- package/lib/novel-to-game/skills/novel-game-analyze/SKILL.md +6 -9
- package/lib/novel-to-game/skills/novel-to-game/SKILL.md +6 -11
- package/lib/novel-to-game/skills/novel-to-game/references/intake-benchmark-reference.md +0 -4
- package/lib/novel-to-game/skills/novel-to-game/references/intake-method.md +1 -3
- package/lib/novel-to-game/skills/novel-to-game/references/pipeline-contract.md +3 -5
- package/lib/oh-story/manifest.json +292 -172
- package/lib/oh-story/roles/character-designer.md +1 -1
- package/lib/oh-story/roles/narrative-writer.md +56 -242
- package/lib/oh-story/roles/story-architect.md +1 -1
- package/lib/oh-story/skills/story/SKILL.md +1 -1
- package/lib/oh-story/skills/story/VERSION +1 -1
- package/lib/oh-story/skills/story/references/author-memory.md +10 -8
- package/lib/oh-story/skills/story-deslop/SKILL.md +20 -182
- package/lib/oh-story/skills/story-deslop/references/anti-ai-writing.md +19 -47
- package/lib/oh-story/skills/story-deslop/references/author-memory.md +10 -8
- package/lib/oh-story/skills/story-deslop/references/banned-words.md +3 -1
- package/lib/oh-story/skills/story-deslop/references/deslop-gates.md +154 -0
- package/lib/oh-story/skills/story-deslop/references/style-resolution.md +27 -0
- package/lib/oh-story/skills/story-deslop/scripts/check-ai-patterns.js +6 -1
- package/lib/oh-story/skills/story-deslop/scripts/check-degeneration.js +39 -20
- package/lib/oh-story/skills/story-deslop/scripts/normalize-punctuation.js +10 -5
- package/lib/oh-story/skills/story-deslop/scripts/style-whitelist.js +39 -0
- package/lib/oh-story/skills/story-import/SKILL.md +2 -2
- package/lib/oh-story/skills/story-import/references/format-and-structure.md +2 -2
- package/lib/oh-story/skills/story-import/references/tracking-initialization.md +31 -0
- package/lib/oh-story/skills/story-import/references/tracking-transaction.md +2 -29
- package/lib/oh-story/skills/story-import/scripts/check-outline-contract.js +176 -12
- package/lib/oh-story/skills/story-long-analyze/SKILL.md +1 -1
- package/lib/oh-story/skills/story-long-write/SKILL.md +20 -193
- package/lib/oh-story/skills/story-long-write/references/anti-ai-writing.md +19 -47
- package/lib/oh-story/skills/story-long-write/references/artifact-protocols.md +62 -7
- package/lib/oh-story/skills/story-long-write/references/author-memory.md +10 -8
- package/lib/oh-story/skills/story-long-write/references/banned-words.md +3 -1
- package/lib/oh-story/skills/story-long-write/references/benchmark-recall.md +20 -0
- package/lib/oh-story/skills/story-long-write/references/dialogue-mastery.md +6 -4
- package/lib/oh-story/skills/story-long-write/references/long-chapter-quality.md +2 -2
- package/lib/oh-story/skills/story-long-write/references/long-format.md +2 -0
- package/lib/oh-story/skills/story-long-write/references/outline-structure-theory.md +2 -0
- package/lib/oh-story/skills/story-long-write/references/project-files.md +108 -0
- package/lib/oh-story/skills/story-long-write/references/reference-index.md +87 -0
- package/lib/oh-story/skills/story-long-write/references/style-combat-face.md +1 -1
- package/lib/oh-story/skills/story-long-write/references/style-craft.md +9 -29
- package/lib/oh-story/skills/story-long-write/references/style-genre-modules.md +1 -1
- package/lib/oh-story/skills/story-long-write/references/style-resolution.md +27 -0
- package/lib/oh-story/skills/story-long-write/references/tracking-initialization.md +31 -0
- package/lib/oh-story/skills/story-long-write/references/tracking-transaction.md +2 -29
- package/lib/oh-story/skills/story-long-write/references/workflow-chapter.md +51 -52
- package/lib/oh-story/skills/story-long-write/references/workflow-daily.md +8 -7
- package/lib/oh-story/skills/story-long-write/references/workflow-setup.md +36 -15
- package/lib/oh-story/skills/story-long-write/references/writer-prompt-fallback.md +22 -0
- package/lib/oh-story/skills/story-long-write/references/writing-craft.md +69 -2
- package/lib/oh-story/skills/story-long-write/scripts/build_writer_prompt.py +410 -0
- package/lib/oh-story/skills/story-long-write/scripts/check-ai-patterns.js +6 -1
- package/lib/oh-story/skills/story-long-write/scripts/check-degeneration.js +39 -20
- package/lib/oh-story/skills/story-long-write/scripts/check-outline-contract.js +176 -12
- package/lib/oh-story/skills/story-long-write/scripts/check-outline-copy.js +90 -23
- package/lib/oh-story/skills/story-long-write/scripts/normalize-punctuation.js +10 -5
- package/lib/oh-story/skills/story-long-write/scripts/outline_view.py +283 -0
- package/lib/oh-story/skills/story-long-write/scripts/style-whitelist.js +39 -0
- package/lib/oh-story/skills/story-review/SKILL.md +13 -3
- package/lib/oh-story/skills/story-review/references/anti-ai-writing.md +19 -47
- package/lib/oh-story/skills/story-review/references/author-memory.md +10 -8
- package/lib/oh-story/skills/story-review/references/banned-words.md +3 -1
- package/lib/oh-story/skills/story-review/references/dialogue-mastery.md +6 -4
- package/lib/oh-story/skills/story-review/references/review-quality.md +2 -2
- package/lib/oh-story/skills/story-review/references/style-resolution.md +27 -0
- package/lib/oh-story/skills/story-review/references/tracking-initialization.md +31 -0
- package/lib/oh-story/skills/story-review/references/tracking-transaction.md +2 -29
- package/lib/oh-story/skills/story-review/scripts/check-ai-patterns.js +6 -1
- package/lib/oh-story/skills/story-review/scripts/check-degeneration.js +39 -20
- package/lib/oh-story/skills/story-review/scripts/normalize-punctuation.js +10 -5
- package/lib/oh-story/skills/story-review/scripts/style-whitelist.js +39 -0
- package/lib/oh-story/skills/story-setup/SKILL.md +17 -10
- package/lib/oh-story/skills/story-setup/references/agent-references/anti-ai-writing.md +19 -47
- package/lib/oh-story/skills/story-setup/references/agent-references/banned-words.md +3 -1
- package/lib/oh-story/skills/story-setup/references/agent-references/deslop-gates.md +154 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/dialogue-mastery.md +6 -4
- package/lib/oh-story/skills/story-setup/references/agent-references/format-and-structure.md +2 -2
- package/lib/oh-story/skills/story-setup/references/agent-references/short-genre-formulas.md +1 -1
- package/lib/oh-story/skills/story-setup/references/agent-references/style-combat-face.md +1 -1
- package/lib/oh-story/skills/story-setup/references/agent-references/style-genre-modules.md +1 -1
- package/lib/oh-story/skills/story-setup/references/agent-references/style-resolution.md +27 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/writing-craft.md +69 -2
- package/lib/oh-story/skills/story-setup/references/diagnostics.md +38 -0
- package/lib/oh-story/skills/story-short-analyze/references/analysis-writing-techniques.md +1 -1
- package/lib/oh-story/skills/story-short-analyze/references/source-story-quality.md +2 -2
- package/lib/oh-story/skills/story-short-write/SKILL.md +18 -186
- package/lib/oh-story/skills/story-short-write/references/author-memory.md +10 -8
- package/lib/oh-story/skills/story-short-write/references/banned-words.md +3 -1
- package/lib/oh-story/skills/story-short-write/references/benchmark-recall.md +60 -0
- package/lib/oh-story/skills/story-short-write/references/dialogue-mastery.md +6 -4
- package/lib/oh-story/skills/story-short-write/references/genre-styles//344/270/226/346/203/205/346/211/223/350/204/270.md +2 -2
- package/lib/oh-story/skills/story-short-write/references/genre-styles//345/217/214/347/224/267/344/270/273.md +1 -1
- package/lib/oh-story/skills/story-short-write/references/genre-styles//345/244/215/344/273/207/346/211/223/350/204/270.md +3 -3
- package/lib/oh-story/skills/story-short-write/references/genre-styles//345/256/205/346/226/227/345/256/253/346/226/227.md +3 -3
- package/lib/oh-story/skills/story-short-write/references/genre-styles//346/200/273/350/243/201/350/261/252/351/227/250.md +3 -3
- package/lib/oh-story/skills/story-short-write/references/genre-styles//346/202/254/347/226/221.md +5 -5
- package/lib/oh-story/skills/story-short-write/references/genre-styles//346/260/221/344/277/227/346/200/252/350/260/210.md +4 -4
- package/lib/oh-story/skills/story-short-write/references/genre-styles//347/224/234/345/256/240.md +5 -5
- package/lib/oh-story/skills/story-short-write/references/genre-styles//350/277/275/345/246/273/347/201/253/350/221/254/345/234/272.md +3 -3
- package/lib/oh-story/skills/story-short-write/references/genre-writing-formulas.md +1 -1
- package/lib/oh-story/skills/story-short-write/references/genre-writing-techniques.md +1 -1
- package/lib/oh-story/skills/story-short-write/references/reference-index.md +53 -0
- package/lib/oh-story/skills/story-short-write/references/short-craft.md +12 -14
- package/lib/oh-story/skills/story-short-write/references/short-deslop.md +10 -10
- package/lib/oh-story/skills/story-short-write/references/short-format.md +3 -1
- package/lib/oh-story/skills/story-short-write/references/short-prose-quality.md +2 -2
- package/lib/oh-story/skills/story-short-write/references/style-resolution.md +27 -0
- package/lib/oh-story/skills/story-short-write/references/workflow-design.md +77 -0
- package/lib/oh-story/skills/story-short-write/references/writing-workflow.md +5 -5
- package/lib/oh-story/skills/story-short-write/scripts/check-ai-patterns.js +6 -1
- package/lib/oh-story/skills/story-short-write/scripts/check-degeneration.js +39 -20
- package/lib/oh-story/skills/story-short-write/scripts/check-delivery-contract.js +34 -5
- package/lib/oh-story/skills/story-short-write/scripts/check-outline-copy.js +90 -23
- package/lib/oh-story/skills/story-short-write/scripts/normalize-punctuation.js +10 -5
- package/lib/oh-story/skills/story-short-write/scripts/style-whitelist.js +39 -0
- package/lib/types/client/drama-production-view.d.ts.map +1 -1
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/production-prompts.d.ts.map +1 -1
- package/lib/types/client/production-runtime.d.ts +17 -0
- package/lib/types/client/production-runtime.d.ts.map +1 -1
- package/lib/types/skill-provider.d.ts.map +1 -1
- package/lib/video-recap/CHANGELOG.md +31 -1
- package/lib/video-recap/manifest.json +162 -162
- package/lib/video-recap/skills/video-assemble/scripts/artifacts.py +4 -9
- package/lib/video-recap/skills/video-assemble/scripts/assemble.py +47 -60
- package/lib/video-recap/skills/video-assemble/scripts/assembly_contract.py +77 -115
- package/lib/video-recap/skills/video-assemble/scripts/assembly_settings.py +41 -40
- package/lib/video-recap/skills/video-assemble/scripts/audio_automation.py +22 -32
- package/lib/video-recap/skills/video-assemble/scripts/audio_mix.py +102 -192
- package/lib/video-recap/skills/video-assemble/scripts/jianying_builders.py +14 -35
- package/lib/video-recap/skills/video-assemble/scripts/jianying_model.py +14 -12
- package/lib/video-recap/skills/video-assemble/scripts/jianying_optional.py +8 -9
- package/lib/video-recap/skills/video-assemble/scripts/jianying_schema.py +3 -14
- package/lib/video-recap/skills/video-assemble/scripts/jianying_timeline_contract.py +23 -12
- package/lib/video-recap/skills/video-assemble/scripts/jianying_tracks.py +1 -4
- package/lib/video-recap/skills/video-assemble/scripts/jianying_writer.py +24 -25
- package/lib/video-recap/skills/video-assemble/scripts/lib.py +31 -85
- package/lib/video-recap/skills/video-assemble/scripts/media.py +100 -144
- package/lib/video-recap/skills/video-assemble/scripts/narration_audio.py +101 -195
- package/lib/video-recap/skills/video-assemble/scripts/render_preflight.py +10 -21
- package/lib/video-recap/skills/video-assemble/scripts/source_subtitles.py +90 -169
- package/lib/video-recap/skills/video-assemble/scripts/subtitle_core.py +69 -122
- package/lib/video-recap/skills/video-assemble/scripts/subtitle_render.py +10 -17
- package/lib/video-recap/skills/video-assemble/scripts/timeline.py +33 -48
- package/lib/video-recap/skills/video-assemble/scripts/timeline_emit.py +14 -20
- package/lib/video-recap/skills/video-assemble/scripts/visual_render.py +135 -213
- package/lib/video-recap/skills/video-cut/scripts/cut_cli.py +49 -57
- package/lib/video-recap/skills/video-cut/scripts/cut_contract.py +38 -74
- package/lib/video-recap/skills/video-cut/scripts/cut_render.py +46 -95
- package/lib/video-recap/skills/video-cut/scripts/lib.py +23 -25
- package/lib/video-recap/skills/video-cut/scripts/media_geometry.py +70 -173
- package/lib/video-recap/skills/video-cut/scripts/narration_mapping.py +28 -53
- package/lib/video-recap/skills/video-cut/scripts/sentence_boundaries.py +162 -322
- package/lib/video-recap/skills/video-recap/references/timeline-and-jianying.md +6 -7
- package/lib/video-recap/skills/video-recap/scripts/doctor.py +104 -124
- package/lib/video-recap/skills/video-recap/scripts/final_qc.py +255 -412
- package/lib/video-recap/skills/video-recap/scripts/lib.py +30 -52
- package/lib/video-recap/skills/video-recap/scripts/materials.py +97 -120
- package/lib/video-recap/skills/video-recap/scripts/mimo_qc_client.py +7 -12
- package/lib/video-recap/skills/video-recap/scripts/mimo_qc_evidence.py +46 -97
- package/lib/video-recap/skills/video-recap/scripts/mimo_qc_observations.py +18 -36
- package/lib/video-recap/skills/video-recap/scripts/mimo_qc_payload.py +29 -47
- package/lib/video-recap/skills/video-recap/scripts/mimo_qc_report.py +43 -76
- package/lib/video-recap/skills/video-recap/scripts/mimo_qc_runner.py +2 -1
- package/lib/video-recap/skills/video-recap/scripts/qc_contract.py +2 -1
- package/lib/video-recap/skills/video-recap/scripts/recap_cli.py +2 -2
- package/lib/video-recap/skills/video-recap/scripts/recap_inspect.py +129 -145
- package/lib/video-recap/skills/video-recap/scripts/recap_review.py +27 -20
- package/lib/video-recap/skills/video-recap/scripts/recap_runner.py +20 -31
- package/lib/video-recap/skills/video-recap/scripts/recap_runtime.py +31 -106
- package/lib/video-recap/skills/video-recap/scripts/recap_stage_qc.py +43 -113
- package/lib/video-recap/skills/video-recap/scripts/recap_timeline.py +196 -377
- package/lib/video-recap/skills/video-script/scripts/agent_brief.py +1 -1
- package/lib/video-recap/skills/video-script/scripts/agent_text.py +63 -172
- package/lib/video-recap/skills/video-script/scripts/brief_context.py +80 -135
- package/lib/video-recap/skills/video-script/scripts/brief_inputs.py +125 -167
- package/lib/video-recap/skills/video-script/scripts/brief_timeline.py +93 -208
- package/lib/video-recap/skills/video-script/scripts/deslop_qc.py +20 -46
- package/lib/video-recap/skills/video-script/scripts/lib.py +30 -26
- package/lib/video-recap/skills/video-script/scripts/narration_lint.py +145 -140
- package/lib/video-recap/skills/video-script/scripts/review_response.py +12 -42
- package/lib/video-recap/skills/video-script/scripts/review_runner.py +0 -6
- package/lib/video-recap/skills/video-script/scripts/speech_ownership.py +39 -100
- package/lib/video-recap/skills/video-script/scripts/timeline_fusion.py +33 -124
- package/lib/video-recap/skills/video-understanding/scripts/agent_brief.py +1 -1
- package/lib/video-recap/skills/video-understanding/scripts/agent_text.py +63 -172
- package/lib/video-recap/skills/video-understanding/scripts/asr.py +1 -2
- package/lib/video-recap/skills/video-understanding/scripts/brief_context.py +80 -135
- package/lib/video-recap/skills/video-understanding/scripts/brief_inputs.py +125 -167
- package/lib/video-recap/skills/video-understanding/scripts/brief_timeline.py +93 -208
- package/lib/video-recap/skills/video-understanding/scripts/consolidate.py +1 -1
- package/lib/video-recap/skills/video-understanding/scripts/deslop_qc.py +20 -46
- package/lib/video-recap/skills/video-understanding/scripts/lib.py +31 -26
- package/lib/video-recap/skills/video-understanding/scripts/narration_lint.py +145 -140
- package/lib/video-recap/skills/video-understanding/scripts/speech_ownership.py +39 -100
- package/lib/video-recap/skills/video-understanding/scripts/timeline_fusion.py +33 -124
- package/lib/video-recap/skills/video-understanding/scripts/vlm.py +1 -4
- package/lib/video-recap/skills/video-voiceover/scripts/dub.py +7 -13
- package/lib/video-recap/skills/video-voiceover/scripts/fish_audio.py +12 -30
- package/lib/video-recap/skills/video-voiceover/scripts/lib.py +98 -131
- package/lib/video-recap/skills/video-voiceover/scripts/tts_audio.py +17 -28
- package/lib/video-recap/skills/video-voiceover/scripts/voiceover.py +151 -240
- package/package.json +41 -41
|
@@ -8,6 +8,7 @@ from artifacts import _explicit_source_video, _value_fingerprint
|
|
|
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 when it matches the raw plan; None in full mode."""
|
|
11
12
|
raw_plan_path = Path(work_dir) / "clip_plan.json"
|
|
12
13
|
validated_plan_path = Path(work_dir) / "clip_plan_validated.json"
|
|
13
14
|
if not validated_plan_path.exists():
|
|
@@ -16,73 +17,79 @@ def _load_cut_timeline_plan(work_dir):
|
|
|
16
17
|
return json.loads(validated_plan_path.read_text(encoding="utf-8"))
|
|
17
18
|
raw_plan = json.loads(raw_plan_path.read_text(encoding="utf-8"))
|
|
18
19
|
validated_plan = json.loads(validated_plan_path.read_text(encoding="utf-8"))
|
|
19
|
-
if (
|
|
20
|
-
isinstance(validated_plan, dict)
|
|
21
|
-
and validated_plan.get("raw_plan_fingerprint") == _value_fingerprint(raw_plan)
|
|
22
|
-
):
|
|
20
|
+
if validated_plan.get("raw_plan_fingerprint") == _value_fingerprint(raw_plan):
|
|
23
21
|
return validated_plan
|
|
24
22
|
return raw_plan
|
|
25
23
|
|
|
26
24
|
|
|
27
|
-
def
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
25
|
+
def _plan_clip_spans(work_dir):
|
|
26
|
+
"""Cut-mode clip spans [{source_start, source_end, output_start, output_end, entry}], or None.
|
|
27
|
+
|
|
28
|
+
clip_plan.json is either a bare list or {"clips": [...]}; a clip names its source range as
|
|
29
|
+
source_start/source_end or start/end. Clips without explicit output_start/output_end are laid
|
|
30
|
+
out back to back on the output timeline.
|
|
31
|
+
"""
|
|
32
|
+
plan = _load_cut_timeline_plan(work_dir)
|
|
33
|
+
if plan is None:
|
|
34
|
+
return None
|
|
35
|
+
entries = plan["clips"] if isinstance(plan, dict) else plan
|
|
36
|
+
spans, cursor = [], 0.0
|
|
37
|
+
for entry in entries:
|
|
38
|
+
ss = float(entry.get("source_start", entry.get("start")))
|
|
39
|
+
se = float(entry.get("source_end", entry.get("end")))
|
|
40
|
+
if "output_start" in entry:
|
|
41
|
+
out_s, out_e = float(entry["output_start"]), float(entry["output_end"])
|
|
42
|
+
cursor = max(cursor, out_e)
|
|
36
43
|
else:
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
44
|
+
out_s, out_e = cursor, cursor + (se - ss)
|
|
45
|
+
cursor = out_e
|
|
46
|
+
spans.append({
|
|
47
|
+
"source_start": ss, "source_end": se,
|
|
48
|
+
"output_start": out_s, "output_end": out_e,
|
|
49
|
+
"entry": entry,
|
|
50
|
+
})
|
|
51
|
+
return spans
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _ratio_to_float(value, default=1.0):
|
|
55
|
+
"""Parse an ffprobe ratio ("4:3", "16/9" or a bare number); unknown ratios yield `default`."""
|
|
56
|
+
value = value.strip()
|
|
57
|
+
if value in {"", "0:1", "0/1", "N/A"}:
|
|
41
58
|
return default
|
|
59
|
+
if ":" in value:
|
|
60
|
+
num, den = value.split(":", 1)
|
|
61
|
+
elif "/" in value:
|
|
62
|
+
num, den = value.split("/", 1)
|
|
63
|
+
else:
|
|
64
|
+
return float(value)
|
|
65
|
+
return float(num) / float(den) if float(den) else default
|
|
42
66
|
|
|
43
67
|
|
|
44
68
|
def _fps_from_rate(value, default=30.0):
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
return round(float(value), 3)
|
|
51
|
-
except (TypeError, ValueError, ZeroDivisionError):
|
|
52
|
-
return default
|
|
69
|
+
"""Parse an ffprobe frame rate ("30000/1001" or a bare number); a 0/0 rate yields `default`."""
|
|
70
|
+
if "/" in value:
|
|
71
|
+
num, den = value.split("/", 1)
|
|
72
|
+
return round(float(num) / float(den), 3) if float(den) else default
|
|
73
|
+
return round(float(value), 3)
|
|
53
74
|
|
|
54
75
|
|
|
55
76
|
def _stream_rotation(stream):
|
|
56
77
|
"""Extract rotation from tags or side_data_list in ffprobe JSON."""
|
|
57
|
-
|
|
58
|
-
return 0
|
|
59
|
-
for source in (
|
|
60
|
-
(stream.get("tags") or {}).get("rotate"),
|
|
61
|
-
stream.get("rotation"),
|
|
62
|
-
):
|
|
78
|
+
for source in (stream.get("tags", {}).get("rotate"), stream.get("rotation")):
|
|
63
79
|
if source not in (None, ""):
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
for item in stream.get("side_data_list") or []:
|
|
69
|
-
if not isinstance(item, dict):
|
|
70
|
-
continue
|
|
71
|
-
for key in ("rotation", "displaymatrix"):
|
|
72
|
-
if item.get(key) not in (None, ""):
|
|
73
|
-
try:
|
|
74
|
-
return int(round(float(item.get(key)))) % 360
|
|
75
|
-
except (TypeError, ValueError):
|
|
76
|
-
pass
|
|
80
|
+
return int(round(float(source))) % 360
|
|
81
|
+
for item in stream.get("side_data_list", []):
|
|
82
|
+
if item.get("rotation") not in (None, ""):
|
|
83
|
+
return int(round(float(item["rotation"]))) % 360
|
|
77
84
|
return 0
|
|
78
85
|
|
|
79
86
|
|
|
80
|
-
def _canvas_from_stream(stream
|
|
81
|
-
storage_w =
|
|
82
|
-
storage_h =
|
|
83
|
-
fps = _fps_from_rate(stream
|
|
84
|
-
sar_text = stream.get("sample_aspect_ratio"
|
|
85
|
-
dar_text = stream.get("display_aspect_ratio"
|
|
87
|
+
def _canvas_from_stream(stream):
|
|
88
|
+
storage_w = stream["width"]
|
|
89
|
+
storage_h = stream["height"]
|
|
90
|
+
fps = _fps_from_rate(stream["r_frame_rate"])
|
|
91
|
+
sar_text = stream.get("sample_aspect_ratio", "1:1")
|
|
92
|
+
dar_text = stream.get("display_aspect_ratio", "")
|
|
86
93
|
sar = _ratio_to_float(sar_text, 1.0)
|
|
87
94
|
rotation = _stream_rotation(stream)
|
|
88
95
|
|
|
@@ -91,9 +98,9 @@ def _canvas_from_stream(stream, *, default_width=1280, default_height=720, defau
|
|
|
91
98
|
if dar_text and dar_text not in {"0:1", "N/A"}:
|
|
92
99
|
dar = _ratio_to_float(dar_text, 0.0)
|
|
93
100
|
# ffprobe sources are not consistent: some report DAR before rotation
|
|
94
|
-
# (landscape value > 1 for a 90° stream), while
|
|
95
|
-
#
|
|
96
|
-
#
|
|
101
|
+
# (landscape value > 1 for a 90° stream), while some containers report the
|
|
102
|
+
# already-rotated portrait DAR (< 1). Only apply DAR before swapping when it
|
|
103
|
+
# describes the stored orientation.
|
|
97
104
|
if dar > 0 and not (rotation in {90, 270} and dar < 1.0):
|
|
98
105
|
# Preserve height and adjust width. This keeps legacy square-pixel landscape
|
|
99
106
|
# byte-identical while honoring non-square pixel DAR metadata.
|
|
@@ -110,10 +117,6 @@ def _canvas_from_stream(stream, *, default_width=1280, default_height=720, defau
|
|
|
110
117
|
"rotation": rotation,
|
|
111
118
|
"sample_aspect_ratio": sar_text,
|
|
112
119
|
"display_aspect_ratio": dar_text or f"{display_w}:{display_h}",
|
|
113
|
-
"sar": sar_text,
|
|
114
|
-
"dar": dar_text or f"{display_w}:{display_h}",
|
|
115
|
-
"display_width": display_w,
|
|
116
|
-
"display_height": display_h,
|
|
117
120
|
}
|
|
118
121
|
|
|
119
122
|
|
|
@@ -122,44 +125,19 @@ def _probe_canvas(video_path, *, command_runner=run_cmd):
|
|
|
122
125
|
|
|
123
126
|
``width``/``height`` are the display canvas used by subtitle/overlay geometry.
|
|
124
127
|
For legacy square-pixel landscape sources, these remain the raw storage dimensions.
|
|
125
|
-
Extra storage/rotation/SAR/DAR fields are visual QC facts and are safe for callers
|
|
126
|
-
that only consume width/height/fps.
|
|
127
128
|
"""
|
|
128
|
-
defaults = {"width": 1280, "height": 720, "fps": 30.0}
|
|
129
129
|
res = command_runner([
|
|
130
130
|
"ffprobe", "-v", "error", "-select_streams", "v:0",
|
|
131
131
|
"-show_entries",
|
|
132
132
|
"stream=width,height,r_frame_rate,avg_frame_rate,sample_aspect_ratio,display_aspect_ratio:stream_tags=rotate:stream_side_data=rotation",
|
|
133
133
|
"-of", "json", str(video_path),
|
|
134
134
|
])
|
|
135
|
-
if res.returncode
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
except (ValueError, TypeError, KeyError):
|
|
142
|
-
pass
|
|
143
|
-
# Tests and older mocks may still feed default=nw=1:nk=0 style lines.
|
|
144
|
-
stream = {}
|
|
145
|
-
for line in (res.stdout or "").splitlines():
|
|
146
|
-
line = line.strip()
|
|
147
|
-
if not line or "=" not in line:
|
|
148
|
-
continue
|
|
149
|
-
key, value = line.split("=", 1)
|
|
150
|
-
stream[key] = value
|
|
151
|
-
if stream:
|
|
152
|
-
return _canvas_from_stream(stream)
|
|
153
|
-
return {
|
|
154
|
-
**defaults,
|
|
155
|
-
"storage_width": defaults["width"],
|
|
156
|
-
"storage_height": defaults["height"],
|
|
157
|
-
"rotation": 0,
|
|
158
|
-
"sample_aspect_ratio": "1:1",
|
|
159
|
-
"display_aspect_ratio": "16:9",
|
|
160
|
-
"display_width": defaults["width"],
|
|
161
|
-
"display_height": defaults["height"],
|
|
162
|
-
}
|
|
135
|
+
if res.returncode != 0:
|
|
136
|
+
raise RuntimeError(f"ffprobe 无法读取视频流 {video_path}: {res.stderr}")
|
|
137
|
+
streams = json.loads(res.stdout)["streams"]
|
|
138
|
+
if not streams:
|
|
139
|
+
raise RuntimeError(f"{video_path} 没有视频流")
|
|
140
|
+
return _canvas_from_stream(streams[0])
|
|
163
141
|
|
|
164
142
|
|
|
165
143
|
def _has_audio_stream(video_path, *, command_runner=run_cmd):
|
|
@@ -183,61 +161,39 @@ def _build_video_clips(
|
|
|
183
161
|
|
|
184
162
|
In cut mode each plan entry becomes a clip referencing the ORIGINAL source
|
|
185
163
|
range. Multi-source validated plans carry per-clip source_path and do not
|
|
186
|
-
require an explicit ambient --source-video.
|
|
164
|
+
require an explicit ambient --source-video. Without any declared source (full
|
|
165
|
+
mode, or cut mode rendered without --source-video) the rendered input is one clip.
|
|
187
166
|
"""
|
|
188
167
|
explicit_source_video = source_video_getter()
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
# instead of collapsing the whole multi-source timeline.
|
|
222
|
-
seg = max(0.0, float(timeline_end) - float(timeline_start))
|
|
223
|
-
logger(f" 时间线: source_path 不存在,该片段降级为剪后成片片段: {source_path or '(unset)'}")
|
|
224
|
-
clips.append({"source_id": c.get("source_id"),
|
|
225
|
-
"source_path": str(input_video),
|
|
226
|
-
"source_start": float(timeline_start),
|
|
227
|
-
"source_end": float(timeline_start) + seg,
|
|
228
|
-
"timeline_start": float(timeline_start),
|
|
229
|
-
"timeline_end": float(timeline_end),
|
|
230
|
-
"provenance_degraded": True,
|
|
231
|
-
"provenance_reason": f"missing_source_path:{source_path or 'unset'}"})
|
|
232
|
-
continue
|
|
233
|
-
clips.append({"source_id": c.get("source_id"),
|
|
234
|
-
"source_path": source_path, "source_start": ss,
|
|
235
|
-
"source_end": se, "timeline_start": timeline_start,
|
|
236
|
-
"timeline_end": timeline_end})
|
|
237
|
-
if clips:
|
|
238
|
-
return clips
|
|
239
|
-
except (TypeError, ValueError, KeyError, OSError) as exc:
|
|
240
|
-
logger(f" 时间线: clip_plan 解析失败,回退单片 ({exc})")
|
|
241
|
-
return [{"source_path": str(input_video), "source_start": 0.0,
|
|
242
|
-
"source_end": float(duration_s), "timeline_start": 0.0,
|
|
243
|
-
"timeline_end": float(duration_s)}]
|
|
168
|
+
spans = _plan_clip_spans(work_dir)
|
|
169
|
+
multi_source = spans is not None and any(span["entry"].get("source_path") for span in spans)
|
|
170
|
+
if spans is None or not (explicit_source_video or multi_source):
|
|
171
|
+
return [{"source_path": str(input_video), "source_start": 0.0,
|
|
172
|
+
"source_end": float(duration_s), "timeline_start": 0.0,
|
|
173
|
+
"timeline_end": float(duration_s)}]
|
|
174
|
+
clips = []
|
|
175
|
+
for span in spans:
|
|
176
|
+
entry = span["entry"]
|
|
177
|
+
source_path = entry.get("source_path") or explicit_source_video
|
|
178
|
+
timeline_start, timeline_end = span["output_start"], span["output_end"]
|
|
179
|
+
if not source_path or not os.path.exists(source_path):
|
|
180
|
+
# Degrade ONLY this clip — point it at the rendered cut for its own output
|
|
181
|
+
# window — and keep real provenance for every present source, instead of
|
|
182
|
+
# collapsing the whole multi-source timeline.
|
|
183
|
+
logger(f" 时间线: source_path 不存在,该片段降级为剪后成片片段: {source_path or '(unset)'}")
|
|
184
|
+
clips.append({"source_id": entry.get("source_id"),
|
|
185
|
+
"source_path": str(input_video),
|
|
186
|
+
"source_start": timeline_start,
|
|
187
|
+
"source_end": timeline_end,
|
|
188
|
+
"timeline_start": timeline_start,
|
|
189
|
+
"timeline_end": timeline_end,
|
|
190
|
+
"provenance_degraded": True,
|
|
191
|
+
"provenance_reason": f"missing_source_path:{source_path or 'unset'}"})
|
|
192
|
+
continue
|
|
193
|
+
clips.append({"source_id": entry.get("source_id"),
|
|
194
|
+
"source_path": source_path,
|
|
195
|
+
"source_start": span["source_start"],
|
|
196
|
+
"source_end": span["source_end"],
|
|
197
|
+
"timeline_start": timeline_start,
|
|
198
|
+
"timeline_end": timeline_end})
|
|
199
|
+
return clips
|