@oh-story/dsh 0.1.7 → 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 +30 -15
- package/lib/client.js +93 -16
- 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
|
@@ -7,7 +7,7 @@ from pathlib import Path
|
|
|
7
7
|
from artifacts import _load_work_json
|
|
8
8
|
from audio_mix import _seg_place_window
|
|
9
9
|
from lib import CONFIG
|
|
10
|
-
from media import
|
|
10
|
+
from media import _plan_clip_spans
|
|
11
11
|
from assemble_constants import (
|
|
12
12
|
_AUTO_ORIGINAL_READ_CPS,
|
|
13
13
|
_MAX_ORIGINAL_READ_CPS,
|
|
@@ -37,17 +37,15 @@ def _source_subtitle_mask_policy(work_dir=None):
|
|
|
37
37
|
legacy flag is represented as ``legacy_implicit`` and blocks the visual gate
|
|
38
38
|
instead of silently masking picture information.
|
|
39
39
|
"""
|
|
40
|
-
burn =
|
|
41
|
-
raw_policy =
|
|
42
|
-
legacy_flag =
|
|
40
|
+
burn = CONFIG["burn_subtitles"]
|
|
41
|
+
raw_policy = CONFIG["source_subtitle_mask_policy"]
|
|
42
|
+
legacy_flag = CONFIG["mask_source_subtitles"]
|
|
43
43
|
allowed = {"off", "opt_in", "safe", "forced"}
|
|
44
|
-
declared =
|
|
44
|
+
declared = CONFIG["source_subtitle_mask_policy_declared"] or raw_policy in {"opt_in", "safe", "forced"}
|
|
45
45
|
implicit = False
|
|
46
46
|
if legacy_flag and not declared:
|
|
47
47
|
raw_policy = "legacy_implicit"
|
|
48
48
|
implicit = True
|
|
49
|
-
elif not raw_policy:
|
|
50
|
-
raw_policy = "off"
|
|
51
49
|
elif raw_policy not in allowed:
|
|
52
50
|
implicit = True
|
|
53
51
|
user_subtitles = _has_user_subtitles(work_dir)
|
|
@@ -78,7 +76,7 @@ def _source_subtitle_mask_policy(work_dir=None):
|
|
|
78
76
|
"active": bool(active),
|
|
79
77
|
"scope": (
|
|
80
78
|
"measured_source_subtitle_band"
|
|
81
|
-
if active and 0 <=
|
|
79
|
+
if active and 0 <= CONFIG["subtitle_y_top"] < CONFIG["subtitle_y_bot"]
|
|
82
80
|
else ("bottom_source_subtitle_band" if active else "none")
|
|
83
81
|
),
|
|
84
82
|
"trigger": trigger,
|
|
@@ -86,107 +84,88 @@ def _source_subtitle_mask_policy(work_dir=None):
|
|
|
86
84
|
"burn_subtitles": burn,
|
|
87
85
|
"legacy_mask_flag": legacy_flag,
|
|
88
86
|
"user_subtitles_present": user_subtitles,
|
|
89
|
-
"blocking":
|
|
87
|
+
"blocking": implicit,
|
|
90
88
|
}
|
|
91
89
|
|
|
92
90
|
|
|
93
91
|
def _load_original_asr(work_dir):
|
|
94
|
-
"""The original speech transcription (asr_result.json), SOURCE-time,
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
continue
|
|
100
|
-
try:
|
|
101
|
-
start, end = float(s["start"]), float(s["end"])
|
|
102
|
-
except (KeyError, TypeError, ValueError):
|
|
103
|
-
continue
|
|
104
|
-
text = str(s.get("text", "")).strip()
|
|
105
|
-
if text and end > start:
|
|
106
|
-
segs.append({"start": start, "end": end, "text": text})
|
|
107
|
-
return segs
|
|
92
|
+
"""The original speech transcription (asr_result.json), SOURCE-time [{start,end,text}]; [] when absent."""
|
|
93
|
+
data = _load_work_json(work_dir, "asr_result.json")
|
|
94
|
+
if data is None:
|
|
95
|
+
return []
|
|
96
|
+
return [{"start": float(s["start"]), "end": float(s["end"]), "text": s["text"]} for s in data]
|
|
108
97
|
|
|
109
98
|
|
|
110
99
|
def _load_agent_original_subtitles(work_dir):
|
|
111
100
|
"""Agent-calibrated original-dialogue subtitles (original_subtitles.json): OUTPUT-time
|
|
112
101
|
[{start,end,text}] the writer authors alongside narration.json — the corrected, gap-aligned
|
|
113
102
|
transcript of what is ACTUALLY said in each original-audio gap (ASR errors/names fixed).
|
|
114
|
-
None when absent
|
|
103
|
+
None when absent or empty (then assemble falls back to a conservative auto-ASR mapping)."""
|
|
115
104
|
data = _load_work_json(work_dir, "original_subtitles.json")
|
|
116
|
-
if not
|
|
105
|
+
if not data:
|
|
117
106
|
return None
|
|
118
|
-
|
|
119
|
-
for s in data:
|
|
120
|
-
if not isinstance(s, dict):
|
|
121
|
-
continue
|
|
122
|
-
try:
|
|
123
|
-
start, end = float(s["start"]), float(s["end"])
|
|
124
|
-
except (KeyError, TypeError, ValueError):
|
|
125
|
-
continue
|
|
126
|
-
text = str(s.get("text", "")).strip()
|
|
127
|
-
if text and end > start:
|
|
128
|
-
out.append({"start": start, "end": end, "text": text})
|
|
129
|
-
return out or None
|
|
107
|
+
return [{"start": float(s["start"]), "end": float(s["end"]), "text": s["text"]} for s in data]
|
|
130
108
|
|
|
131
109
|
|
|
132
|
-
def
|
|
133
|
-
"""
|
|
110
|
+
def _user_subtitle_entries(rows, source):
|
|
111
|
+
"""Validate user-authored {start,end,text} rows; a malformed row is an error, not a skip."""
|
|
134
112
|
out = []
|
|
135
|
-
for
|
|
136
|
-
if not isinstance(s, dict):
|
|
137
|
-
continue
|
|
113
|
+
for index, row in enumerate(rows, start=1):
|
|
138
114
|
try:
|
|
139
|
-
start, end = float(
|
|
140
|
-
except (KeyError, TypeError, ValueError):
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
115
|
+
start, end, text = float(row["start"]), float(row["end"]), row["text"].strip()
|
|
116
|
+
except (KeyError, TypeError, ValueError, AttributeError) as exc:
|
|
117
|
+
raise ValueError(f"{source} 第 {index} 条缺少或无法解析 start/end/text: {row!r}") from exc
|
|
118
|
+
if end <= start or not text:
|
|
119
|
+
raise ValueError(f"{source} 第 {index} 条无效(需要 end > start 且 text 非空): {row!r}")
|
|
120
|
+
out.append({"start": start, "end": end, "text": text})
|
|
145
121
|
return out
|
|
146
122
|
|
|
147
123
|
|
|
148
124
|
def _parse_srt_timestamp(value):
|
|
149
|
-
"""Parse an SRT 'HH:MM:SS,mmm' (or ASS 'H:MM:SS.cc') timestamp into seconds
|
|
150
|
-
m = re.match(r"\s*(\d+):(\d{1,2}):(\d{1,2})[.,](\d{1,3})\s*$",
|
|
125
|
+
"""Parse an SRT 'HH:MM:SS,mmm' (or ASS 'H:MM:SS.cc') timestamp into seconds."""
|
|
126
|
+
m = re.match(r"\s*(\d+):(\d{1,2}):(\d{1,2})[.,](\d{1,3})\s*$", value)
|
|
151
127
|
if not m:
|
|
152
|
-
|
|
128
|
+
raise ValueError(f"无法解析字幕时间戳: {value!r}")
|
|
153
129
|
h, mm, ss, frac = m.groups()
|
|
154
130
|
return int(h) * 3600 + int(mm) * 60 + int(ss) + int(frac) / (10 ** len(frac))
|
|
155
131
|
|
|
156
132
|
|
|
157
|
-
def _parse_srt_text(text):
|
|
158
|
-
"""Minimal SRT parser → [{start,end,text}].
|
|
133
|
+
def _parse_srt_text(text, source):
|
|
134
|
+
"""Minimal SRT parser → [{start,end,text}]. Blank lines and missing indices are tolerated;
|
|
135
|
+
a block without a parseable timing line is an error. Cues with no text are dropped."""
|
|
159
136
|
segs = []
|
|
160
|
-
for block in re.split(r"\n\s*\n",
|
|
137
|
+
for block in re.split(r"\n\s*\n", text.replace("\r\n", "\n").replace("\r", "\n")):
|
|
161
138
|
lines = [ln for ln in block.split("\n") if ln.strip()]
|
|
162
139
|
if not lines:
|
|
163
140
|
continue
|
|
164
141
|
if lines[0].strip().isdigit():
|
|
165
142
|
lines = lines[1:]
|
|
166
143
|
if not lines or "-->" not in lines[0]:
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
144
|
+
raise ValueError(f"{source}: 字幕块缺少时间行: {block.strip()!r}")
|
|
145
|
+
start_text, end_text = lines[0].split("-->", 1)
|
|
146
|
+
start, end = _parse_srt_timestamp(start_text), _parse_srt_timestamp(end_text)
|
|
147
|
+
if end <= start:
|
|
148
|
+
raise ValueError(f"{source}: 字幕结束时间必须晚于开始时间: {lines[0].strip()!r}")
|
|
172
149
|
body = " ".join(lines[1:]).strip()
|
|
173
|
-
if
|
|
150
|
+
if body:
|
|
174
151
|
segs.append({"start": start, "end": end, "text": body})
|
|
175
152
|
return segs
|
|
176
153
|
|
|
177
154
|
|
|
178
|
-
def _parse_ass_text(text):
|
|
155
|
+
def _parse_ass_text(text, source):
|
|
179
156
|
"""Minimal ASS Dialogue parser → [{start,end,text}] (Start, End are fields 2 and 3)."""
|
|
180
157
|
segs = []
|
|
181
|
-
for line in
|
|
158
|
+
for line in text.replace("\r\n", "\n").replace("\r", "\n").split("\n"):
|
|
182
159
|
if not line.startswith("Dialogue:"):
|
|
183
160
|
continue
|
|
184
161
|
fields = line[len("Dialogue:"):].split(",", 9)
|
|
185
162
|
if len(fields) < 10:
|
|
186
|
-
|
|
163
|
+
raise ValueError(f"{source}: Dialogue 行字段不足: {line!r}")
|
|
187
164
|
start, end = _parse_srt_timestamp(fields[1]), _parse_srt_timestamp(fields[2])
|
|
165
|
+
if end <= start:
|
|
166
|
+
raise ValueError(f"{source}: 字幕结束时间必须晚于开始时间: {line!r}")
|
|
188
167
|
body = re.sub(r"\{[^}]*\}", "", fields[9]).replace("\\N", " ").replace("\\n", " ").strip()
|
|
189
|
-
if
|
|
168
|
+
if body:
|
|
190
169
|
segs.append({"start": start, "end": end, "text": body})
|
|
191
170
|
return segs
|
|
192
171
|
|
|
@@ -200,79 +179,39 @@ def _load_user_original_subtitles(work_dir):
|
|
|
200
179
|
via the cut clip spans, "output" (default) is used directly.
|
|
201
180
|
- user_subtitles.srt / user_subtitles.ass: parsed minimally and defaulted to SOURCE-time,
|
|
202
181
|
so they are remapped to OUTPUT via the cut clip spans.
|
|
203
|
-
Returns OUTPUT-time [{start,end,text}], or None when
|
|
182
|
+
Returns OUTPUT-time [{start,end,text}], or None when no user file exists. A malformed
|
|
183
|
+
file raises: the user asked for these subtitles, so silently falling back is wrong."""
|
|
204
184
|
work = Path(work_dir)
|
|
205
185
|
json_path = work / "user_subtitles.json"
|
|
206
186
|
if json_path.exists():
|
|
207
187
|
try:
|
|
208
188
|
data = json.loads(json_path.read_text(encoding="utf-8"))
|
|
209
|
-
except
|
|
210
|
-
|
|
211
|
-
if isinstance(data,
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
189
|
+
except ValueError as exc:
|
|
190
|
+
raise ValueError(f"{json_path} 不是合法 JSON: {exc}") from exc
|
|
191
|
+
if isinstance(data, dict):
|
|
192
|
+
timeline = data.get("timeline", "output")
|
|
193
|
+
if timeline not in {"source", "output"}:
|
|
194
|
+
raise ValueError(f"{json_path}: timeline 必须是 source 或 output,当前为 {timeline!r}")
|
|
195
|
+
rows = data["lines"]
|
|
216
196
|
else:
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
return None
|
|
197
|
+
timeline, rows = "output", data
|
|
198
|
+
segs = _user_subtitle_entries(rows, json_path.name)
|
|
220
199
|
if timeline == "source":
|
|
221
|
-
segs = _map_asr_to_output(segs,
|
|
222
|
-
return segs
|
|
200
|
+
segs = _map_asr_to_output(segs, _plan_clip_spans(work))
|
|
201
|
+
return segs
|
|
223
202
|
|
|
224
203
|
for name in ("user_subtitles.srt", "user_subtitles.ass"):
|
|
225
204
|
path = work / name
|
|
226
205
|
if not path.exists():
|
|
227
206
|
continue
|
|
228
|
-
try:
|
|
229
|
-
text = path.read_text(encoding="utf-8")
|
|
230
|
-
except OSError:
|
|
231
|
-
return None
|
|
232
207
|
parser = _parse_ass_text if name.endswith(".ass") else _parse_srt_text
|
|
233
|
-
segs =
|
|
234
|
-
if not segs:
|
|
235
|
-
return None
|
|
208
|
+
segs = parser(path.read_text(encoding="utf-8"), name)
|
|
236
209
|
# .srt/.ass default to SOURCE-time → remap onto the output timeline (identity in full mode).
|
|
237
|
-
|
|
238
|
-
return segs or None
|
|
210
|
+
return _map_asr_to_output(segs, _plan_clip_spans(work))
|
|
239
211
|
|
|
240
212
|
return None
|
|
241
213
|
|
|
242
214
|
|
|
243
|
-
def _output_clip_spans(work_dir):
|
|
244
|
-
"""Cut-mode source→output clip spans using the same freshness logic as video clips."""
|
|
245
|
-
try:
|
|
246
|
-
plan = _load_cut_timeline_plan(work_dir)
|
|
247
|
-
except (OSError, ValueError, json.JSONDecodeError):
|
|
248
|
-
plan = None
|
|
249
|
-
if not plan:
|
|
250
|
-
return None
|
|
251
|
-
entries = plan.get("clips", plan) if isinstance(plan, dict) else plan
|
|
252
|
-
if not isinstance(entries, list):
|
|
253
|
-
return None
|
|
254
|
-
spans, cursor = [], 0.0
|
|
255
|
-
for c in entries:
|
|
256
|
-
if not isinstance(c, dict):
|
|
257
|
-
continue
|
|
258
|
-
try:
|
|
259
|
-
ss = float(c.get("source_start", c.get("start")))
|
|
260
|
-
se = float(c.get("source_end", c.get("end")))
|
|
261
|
-
except (TypeError, ValueError):
|
|
262
|
-
continue
|
|
263
|
-
if se - ss <= 0:
|
|
264
|
-
continue
|
|
265
|
-
out_s, out_e = c.get("output_start"), c.get("output_end")
|
|
266
|
-
if out_s is None or out_e is None:
|
|
267
|
-
out_s, out_e = cursor, cursor + (se - ss)
|
|
268
|
-
cursor += se - ss
|
|
269
|
-
else:
|
|
270
|
-
out_s, out_e = float(out_s), float(out_e)
|
|
271
|
-
cursor = max(cursor, out_e)
|
|
272
|
-
spans.append({"source_start": ss, "source_end": se, "output_start": out_s, "output_end": out_e})
|
|
273
|
-
return spans or None
|
|
274
|
-
|
|
275
|
-
|
|
276
215
|
def _map_asr_to_output(asr_segs, clip_spans):
|
|
277
216
|
"""Map SOURCE-time ASR segments onto the OUTPUT timeline. Full mode (clip_spans None) is
|
|
278
217
|
identity; cut mode intersects each ASR span with each kept clip (a straddling line yields one
|
|
@@ -296,8 +235,7 @@ def _map_asr_to_output(asr_segs, clip_spans):
|
|
|
296
235
|
def _narration_gap_windows(tts_segments, video_duration, min_gap=_MIN_GAP_TO_SUBTITLE):
|
|
297
236
|
"""OUTPUT-timeline stretches with NO narration (the original-audio blocks): the complement of
|
|
298
237
|
the merged narration placement windows within [0, video_duration], keeping gaps >= min_gap."""
|
|
299
|
-
placed = sorted(
|
|
300
|
-
(_seg_place_window(s) for s in tts_segments if isinstance(s, dict)), key=lambda w: w[0])
|
|
238
|
+
placed = sorted(map(_seg_place_window, tts_segments), key=lambda w: w[0])
|
|
301
239
|
merged = []
|
|
302
240
|
for s, e in placed:
|
|
303
241
|
if e - s <= 0:
|
|
@@ -325,10 +263,9 @@ def _original_gap_subtitle_entries(tts_segments, work_dir, video_duration):
|
|
|
325
263
|
# the original dialogue shown, e.g. a clean/foreign source with mask OFF (no burned subs to
|
|
326
264
|
# double). Without a user file we keep the mask requirement so we don't double the source's own
|
|
327
265
|
# visible subs. subtitle_original_in_gaps is the explicit override either way.
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
and
|
|
331
|
-
and (mask_covers_gaps or _has_user_subtitles(work_dir))):
|
|
266
|
+
if not (CONFIG["burn_subtitles"]
|
|
267
|
+
and CONFIG["subtitle_original_in_gaps"]
|
|
268
|
+
and (_source_subtitle_mask_covers_gaps(work_dir) or _has_user_subtitles(work_dir))):
|
|
332
269
|
return []
|
|
333
270
|
gaps = _narration_gap_windows(tts_segments, video_duration)
|
|
334
271
|
if not gaps:
|
|
@@ -338,23 +275,17 @@ def _original_gap_subtitle_entries(tts_segments, work_dir, video_duration):
|
|
|
338
275
|
# conservative auto-ASR mapping. The user file and the agent file are time-precise (their spans
|
|
339
276
|
# are the real on-screen windows), so they take the interval-clip "precise" path; raw ASR is
|
|
340
277
|
# coarse and stays on the midpoint+over-render-guard fallback path.
|
|
278
|
+
max_chars = CONFIG["subtitle_max_chars"]
|
|
341
279
|
user = _load_user_original_subtitles(work_dir)
|
|
342
280
|
if user is not None:
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
return []
|
|
352
|
-
candidates, precise = _map_asr_to_output(asr, _output_clip_spans(work_dir)), False
|
|
353
|
-
|
|
354
|
-
max_chars = int(CONFIG.get("subtitle_max_chars", 20))
|
|
355
|
-
if precise:
|
|
356
|
-
return _precise_gap_entries(candidates, gaps, max_chars)
|
|
357
|
-
return _fallback_gap_entries(candidates, gaps, max_chars)
|
|
281
|
+
return _precise_gap_entries(user, gaps, max_chars)
|
|
282
|
+
agent = _load_agent_original_subtitles(work_dir)
|
|
283
|
+
if agent is not None:
|
|
284
|
+
return _precise_gap_entries(agent, gaps, max_chars)
|
|
285
|
+
asr = _load_original_asr(work_dir)
|
|
286
|
+
if not asr:
|
|
287
|
+
return []
|
|
288
|
+
return _fallback_gap_entries(_map_asr_to_output(asr, _plan_clip_spans(work_dir)), gaps, max_chars)
|
|
358
289
|
|
|
359
290
|
|
|
360
291
|
def _precise_gap_entries(candidates, gaps, max_chars):
|
|
@@ -364,25 +295,23 @@ def _precise_gap_entries(candidates, gaps, max_chars):
|
|
|
364
295
|
shorter than _MIN_READABLE_SECONDS are dropped. No over-render guard (the source is trusted)."""
|
|
365
296
|
entries = []
|
|
366
297
|
for seg in candidates:
|
|
367
|
-
text =
|
|
368
|
-
if not text:
|
|
369
|
-
continue
|
|
298
|
+
text = seg["text"]
|
|
370
299
|
seg_start, seg_end = float(seg["start"]), float(seg["end"])
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
overlaps.append((cs, ce))
|
|
300
|
+
overlaps = [
|
|
301
|
+
(max(seg_start, gs), min(seg_end, ge))
|
|
302
|
+
for gs, ge in gaps
|
|
303
|
+
if min(seg_end, ge) - max(seg_start, gs) >= _MIN_READABLE_SECONDS
|
|
304
|
+
]
|
|
377
305
|
if not overlaps:
|
|
378
306
|
continue
|
|
379
|
-
if len(overlaps) == 1
|
|
307
|
+
if len(overlaps) == 1:
|
|
380
308
|
# the common case (a line authored within one gap): show it whole in that gap
|
|
381
309
|
cs, ce = overlaps[0]
|
|
382
310
|
entries.extend(_bracketed_original_chunks(text, cs, ce, max_chars))
|
|
383
311
|
continue
|
|
384
312
|
# the line straddles a narration block: show each gap only ITS portion of the text
|
|
385
313
|
# (proportional to the time the line overlaps that gap) instead of the whole line twice.
|
|
314
|
+
seg_dur = seg_end - seg_start
|
|
386
315
|
n = len(text)
|
|
387
316
|
for cs, ce in overlaps:
|
|
388
317
|
lo = max(0, int(round((cs - seg_start) / seg_dur * n)))
|
|
@@ -397,7 +326,7 @@ def _split_sentences_keep_delims(text):
|
|
|
397
326
|
"""Split on terminal CJK sentence marks 。!? keeping each delimiter with its sentence. A
|
|
398
327
|
fragment that is only closing quotes/brackets (e.g. a trailing 」 after a 。 inside a quote) is
|
|
399
328
|
re-attached to the previous sentence so quoted speech is never split off into a bare 」."""
|
|
400
|
-
parts = [p.strip() for p in re.split(r"(?<=[。!?])",
|
|
329
|
+
parts = [p.strip() for p in re.split(r"(?<=[。!?])", text) if p.strip()]
|
|
401
330
|
merged = []
|
|
402
331
|
for part in parts:
|
|
403
332
|
if merged and all(ch in _SUBTITLE_CLOSING_QUOTES for ch in part):
|
|
@@ -418,11 +347,8 @@ def _fallback_gap_entries(candidates, gaps, max_chars):
|
|
|
418
347
|
# its char-proportional midpoint lands in (the only "which gap" signal coarse ASR gives).
|
|
419
348
|
buckets = {} # gap_index -> [(estimated_onset, sentence_text)]
|
|
420
349
|
for seg in candidates:
|
|
421
|
-
for
|
|
422
|
-
|
|
423
|
-
if not text:
|
|
424
|
-
continue
|
|
425
|
-
sub = _sentence_subspan(seg, sentence)
|
|
350
|
+
for text in _split_sentences_keep_delims(seg["text"]):
|
|
351
|
+
sub = _sentence_subspan(seg, text)
|
|
426
352
|
mid = (sub["start"] + sub["end"]) / 2.0
|
|
427
353
|
gi = next((i for i, (gs, ge) in enumerate(gaps) if gs <= mid < ge), None)
|
|
428
354
|
if gi is None:
|
|
@@ -447,8 +373,7 @@ def _fallback_gap_entries(candidates, gaps, max_chars):
|
|
|
447
373
|
if ce2 - cursor < _MIN_READABLE_SECONDS:
|
|
448
374
|
break
|
|
449
375
|
max_len = int((ce2 - cursor) * _MAX_ORIGINAL_READ_CPS)
|
|
450
|
-
|
|
451
|
-
entries.extend(_bracketed_original_chunks(shown, cursor, ce2, max_chars))
|
|
376
|
+
entries.extend(_bracketed_original_chunks(text[:max_len], cursor, ce2, max_chars))
|
|
452
377
|
cursor = ce2
|
|
453
378
|
return entries
|
|
454
379
|
|
|
@@ -456,22 +381,20 @@ def _fallback_gap_entries(candidates, gaps, max_chars):
|
|
|
456
381
|
def _sentence_subspan(seg, sentence):
|
|
457
382
|
"""The slice of seg's [start,end] window that this sentence occupies, by character proportion.
|
|
458
383
|
Single-sentence lines return the whole span unchanged."""
|
|
459
|
-
full =
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
idx = full.find(sentence.strip())
|
|
463
|
-
if idx < 0:
|
|
384
|
+
full = seg["text"].strip()
|
|
385
|
+
idx = full.find(sentence)
|
|
386
|
+
if not full or sentence == full or idx < 0:
|
|
464
387
|
return {"start": seg["start"], "end": seg["end"]}
|
|
465
388
|
span = seg["end"] - seg["start"]
|
|
466
389
|
s = seg["start"] + span * (idx / len(full))
|
|
467
|
-
e = seg["start"] + span * ((idx + len(sentence
|
|
390
|
+
e = seg["start"] + span * ((idx + len(sentence)) / len(full))
|
|
468
391
|
return {"start": s, "end": e}
|
|
469
392
|
|
|
470
393
|
|
|
471
394
|
def _combined_subtitle_entries(narration, work_dir, video_duration):
|
|
472
395
|
"""Narration subtitle entries plus original-dialogue entries in the gaps, sorted by start.
|
|
473
396
|
Original entries are confined to narration gaps, so they never overlap narration entries."""
|
|
474
|
-
entries =
|
|
397
|
+
entries = _subtitle_entries(narration)
|
|
475
398
|
entries.extend(_original_gap_subtitle_entries(narration, work_dir, video_duration))
|
|
476
399
|
entries.sort(key=lambda x: (x["start"], x["end"]))
|
|
477
400
|
return entries
|
|
@@ -479,8 +402,6 @@ def _combined_subtitle_entries(narration, work_dir, video_duration):
|
|
|
479
402
|
|
|
480
403
|
def _source_subtitle_mask_covers_gaps(work_dir=None):
|
|
481
404
|
"""Whether the effective source mask hides hardcoded subtitles outside narration."""
|
|
482
|
-
if not _source_subtitle_mask_policy(work_dir)
|
|
405
|
+
if not _source_subtitle_mask_policy(work_dir)["active"]:
|
|
483
406
|
return False
|
|
484
|
-
|
|
485
|
-
timing = str(CONFIG.get("source_subtitle_mask_timing", "narration") or "narration").lower()
|
|
486
|
-
return opacity >= 1.0 - 1e-9 and timing == "all"
|
|
407
|
+
return CONFIG["subtitle_mask_opacity"] >= 1.0 - 1e-9 and CONFIG["source_subtitle_mask_timing"] == "all"
|