@oh-story/dsh 0.1.9 → 0.1.11
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 +14 -14
- package/lib/client.js +209 -73
- package/lib/client.js.map +3 -3
- package/lib/drama/manifest.json +248 -168
- package/lib/drama/skills/short-drama/SKILL.md +16 -4
- 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/creator_markdown_check.py +14 -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/character-and-look.md +10 -0
- package/lib/drama/skills/short-drama-assets/references/continuity-lock.md +6 -9
- 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-assets/references/voice-direction.md +28 -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 +65 -44
- 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 +6 -4
- package/lib/drama/skills/short-drama-edit/references/cut-craft.md +26 -67
- package/lib/drama/skills/short-drama-edit/references/delivery-verify.md +31 -56
- package/lib/drama/skills/short-drama-edit/references/generated-footage.md +42 -78
- package/lib/drama/skills/short-drama-edit/references/sound-and-subtitles.md +44 -92
- package/lib/drama/skills/short-drama-edit/references/stage-contract.md +14 -16
- package/lib/drama/skills/short-drama-edit/scripts/edit_tool.py +1272 -125
- package/lib/drama/skills/short-drama-edit/scripts/selftest.py +113 -9
- package/lib/drama/skills/short-drama-image-prompts/SKILL.md +2 -0
- package/lib/drama/skills/short-drama-image-prompts/references/common-recipe.md +11 -0
- 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-produce/SKILL.md +5 -1
- package/lib/drama/skills/short-drama-produce/references/adapter-contract.md +14 -20
- package/lib/drama/skills/short-drama-produce/references/providers/minimax-h3-video.md +8 -26
- package/lib/drama/skills/short-drama-produce/references/providers/minimax-speech.md +41 -0
- package/lib/drama/skills/short-drama-produce/scripts/provider_adapters.py +142 -1
- package/lib/drama/skills/short-drama-review/SKILL.md +4 -5
- package/lib/drama/skills/short-drama-review/references/production-quality-gates.md +6 -19
- 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 +24 -35
- package/lib/drama/skills/short-drama-review/references/stage-contract.md +3 -0
- package/lib/drama/skills/short-drama-storyboard/SKILL.md +23 -11
- package/lib/drama/skills/short-drama-storyboard/references/blocking-playbooks.md +1 -1
- package/lib/drama/skills/short-drama-storyboard/references/lighting-craft.md +39 -0
- 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 +139 -34
- package/lib/drama/skills/short-drama-storyboard/references/stage-contract.md +5 -3
- package/lib/drama/skills/short-drama-video-prompts/SKILL.md +15 -12
- package/lib/drama/skills/short-drama-video-prompts/references/camera-audio-continuity.md +29 -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 +52 -39
- package/lib/drama/skills/short-drama-video-prompts/references/motion-recipe.md +24 -21
- package/lib/drama/skills/short-drama-video-prompts/references/performance-action-timing.md +9 -23
- package/lib/drama/skills/short-drama-video-prompts/references/production-prompt-grammar.md +14 -26
- package/lib/drama/skills/short-drama-video-prompts/references/review-and-fixtures.md +1 -1
- package/lib/drama/skills/short-drama-video-prompts/references/seedance-2.0.md +2 -3
- package/lib/drama/skills/short-drama-video-prompts/references/seedance-2.5.md +34 -9
- package/lib/drama/skills/short-drama-video-prompts/references/stage-contract.md +3 -2
- 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 +10 -7
- 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 +346 -98
- package/lib/index.js.map +4 -4
- package/lib/novel-to-game/README.md +299 -59
- package/lib/novel-to-game/README_ZH.md +299 -56
- package/lib/novel-to-game/VERSION +1 -1
- package/lib/novel-to-game/examples/jin-ping-mei/build/BUILD_BRIEF.md +3 -1
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/RUN.md +11 -3
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/css/new-guofeng.css +397 -8
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/css/style.css +0 -31
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/data.js +71 -73
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/engine.js +78 -89
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/main.js +37 -14
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/text.js +5 -5
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/package.json +2 -1
- package/lib/novel-to-game/examples/jin-ping-mei/qa/README.md +11 -16
- package/lib/novel-to-game/examples/jin-ping-mei/qa/verification.json +11 -11
- package/lib/novel-to-game/examples/jin-ping-mei/qa/verify_playable_model.mjs +23 -0
- package/lib/novel-to-game/manifest.json +77 -87
- package/lib/novel-to-game/skills/game-art-direction/SKILL.md +7 -12
- package/lib/novel-to-game/skills/game-art-direction/references/art-direction-method.md +27 -106
- package/lib/novel-to-game/skills/game-build/SKILL.md +13 -14
- package/lib/novel-to-game/skills/game-build/references/blender-asset-workflow.md +24 -0
- package/lib/novel-to-game/skills/game-build/references/build-brief-contract.md +45 -82
- package/lib/novel-to-game/skills/game-build/references/generative-media-pipeline.md +14 -45
- package/lib/novel-to-game/skills/game-build/references/playable-model-contract.md +30 -65
- package/lib/novel-to-game/skills/game-build/references/tts-production-contract.md +13 -27
- package/lib/novel-to-game/skills/game-concept/SKILL.md +13 -15
- package/lib/novel-to-game/skills/game-concept/references/concept-method.md +28 -75
- package/lib/novel-to-game/skills/game-qa/SKILL.md +6 -7
- package/lib/novel-to-game/skills/game-qa/references/test-design-method.md +9 -5
- package/lib/novel-to-game/skills/game-world-design/SKILL.md +12 -16
- package/lib/novel-to-game/skills/game-world-design/references/narrative-design-method.md +23 -72
- package/lib/novel-to-game/skills/game-world-design/references/numeric-design-method.md +14 -48
- package/lib/novel-to-game/skills/game-world-design/references/world-design-method.md +36 -103
- package/lib/novel-to-game/skills/novel-game-analyze/SKILL.md +4 -10
- package/lib/novel-to-game/skills/novel-game-analyze/references/gameability-protocol.md +4 -4
- package/lib/novel-to-game/skills/novel-to-game/SKILL.md +9 -21
- package/lib/novel-to-game/skills/novel-to-game/references/intake-method.md +5 -4
- package/lib/novel-to-game/skills/novel-to-game/references/pipeline-contract.md +5 -8
- package/lib/oh-story/manifest.json +607 -327
- package/lib/oh-story/roles/chapter-extractor.md +177 -237
- package/lib/oh-story/roles/character-designer.md +1 -2
- package/lib/oh-story/roles/consistency-checker.md +15 -13
- package/lib/oh-story/roles/narrative-writer.md +36 -38
- 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 +15 -9
- 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 +46 -94
- package/lib/oh-story/skills/story/scripts/author_memory_commit.py +934 -170
- package/lib/oh-story/skills/story-cover/SKILL.md +10 -1
- package/lib/oh-story/skills/story-deslop/SKILL.md +59 -73
- 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 +12 -11
- 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 +46 -94
- package/lib/oh-story/skills/story-deslop/references/banned-words.md +2 -2
- package/lib/oh-story/skills/story-deslop/references/deslop-gates.md +22 -5
- 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 +934 -170
- package/lib/oh-story/skills/story-deslop/scripts/check-ai-patterns.js +155 -8
- package/lib/oh-story/skills/story-import/SKILL.md +64 -584
- package/lib/oh-story/skills/story-import/references/character-state-reverse.md +1 -1
- 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/state-tracking.md +4 -69
- package/lib/oh-story/skills/story-import/references/structure-mapping-long.md +98 -92
- 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 +15 -11
- 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 +132 -27
- package/lib/oh-story/skills/story-import/scripts/tracking_commit.py +478 -15
- package/lib/oh-story/skills/story-import/scripts/wordcount_core.py +182 -23
- package/lib/oh-story/skills/story-long-analyze/SKILL.md +81 -270
- package/lib/oh-story/skills/story-long-analyze/references/author-facing.md +252 -0
- package/lib/oh-story/skills/story-long-analyze/references/deconstruction-notes.md +3 -3
- 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/inspiration-library.md +105 -0
- package/lib/oh-story/skills/story-long-analyze/references/pipeline-ops.md +138 -64
- package/lib/oh-story/skills/story-long-analyze/references/semantic-acceptance-fixtures.md +84 -0
- 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 +55 -128
- package/lib/oh-story/skills/story-long-analyze/references/style-profile-protocol.md +6 -6
- package/lib/oh-story/skills/story-long-analyze/references/synthesis-inputs.md +39 -0
- package/lib/oh-story/skills/story-long-analyze/scripts/build_chapter_index.py +466 -0
- package/lib/oh-story/skills/story-long-analyze/scripts/inspect_existing_assets.py +702 -0
- package/lib/oh-story/skills/story-long-analyze/scripts/inspiration_index.py +864 -0
- package/lib/oh-story/skills/story-long-analyze/scripts/manage_analysis_run.py +1145 -0
- package/lib/oh-story/skills/story-long-analyze/scripts/render_relation_chart.py +425 -0
- package/lib/oh-story/skills/story-long-scan/SKILL.md +45 -194
- 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 +49 -81
- 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 +12 -11
- package/lib/oh-story/skills/story-long-write/references/artifact-protocols.md +24 -202
- 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 +46 -94
- 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 +7 -11
- package/lib/oh-story/skills/story-long-write/references/character-relations.md +49 -1
- package/lib/oh-story/skills/story-long-write/references/cross-book-recall.md +26 -0
- package/lib/oh-story/skills/story-long-write/references/deslop-gates.md +171 -0
- package/lib/oh-story/skills/story-long-write/references/dialogue-mastery.md +4 -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/emotional-methods.md +2 -2
- 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 +7 -15
- 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 +19 -13
- package/lib/oh-story/skills/story-long-write/references/reader-contract-and-progression.md +13 -5
- package/lib/oh-story/skills/story-long-write/references/reference-index.md +5 -6
- 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 +15 -11
- package/lib/oh-story/skills/story-long-write/references/workflow-chapter.md +81 -121
- package/lib/oh-story/skills/story-long-write/references/workflow-daily.md +41 -139
- 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 +57 -67
- package/lib/oh-story/skills/story-long-write/references/workflow-setup.md +112 -253
- 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 +6 -4
- package/lib/oh-story/skills/story-long-write/references/writing-craft.md +19 -65
- package/lib/oh-story/skills/story-long-write/scripts/author_memory_commit.py +934 -170
- 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 +226 -25
- 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 +132 -27
- 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 +7 -4
- package/lib/oh-story/skills/story-long-write/scripts/storyctl.py +304 -46
- package/lib/oh-story/skills/story-long-write/scripts/tracking_commit.py +478 -15
- package/lib/oh-story/skills/story-long-write/scripts/wordcount_core.py +182 -23
- package/lib/oh-story/skills/story-review/SKILL.md +31 -302
- 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 +12 -11
- 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 +46 -94
- 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/dialogue-mastery.md +4 -0
- package/lib/oh-story/skills/story-review/references/review-quality.md +6 -11
- 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 +15 -11
- package/lib/oh-story/skills/story-review/scripts/author_memory_commit.py +934 -170
- 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 +478 -15
- package/lib/oh-story/skills/story-review/scripts/wordcount_core.py +182 -23
- package/lib/oh-story/skills/story-setup/SKILL.md +71 -452
- 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 +12 -11
- 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 +22 -5
- package/lib/oh-story/skills/story-setup/references/agent-references/dialogue-mastery.md +4 -0
- 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-emotional-methods.md +2 -2
- 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 +19 -65
- package/lib/oh-story/skills/story-setup/references/diagnostics.md +4 -4
- package/lib/oh-story/skills/story-short-analyze/SKILL.md +56 -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/stage4-6-reversal-summary.md +611 -0
- package/lib/oh-story/skills/story-short-scan/SKILL.md +35 -65
- 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 -175
- 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 +46 -94
- 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/dialogue-mastery.md +4 -0
- 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 -47
- 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 +21 -13
- package/lib/oh-story/skills/story-short-write/references/workflow-draft.md +116 -0
- package/lib/oh-story/skills/story-short-write/references/workflow-revision.md +99 -0
- 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 +934 -170
- 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/drama-production-view.d.ts.map +1 -1
- package/lib/types/client/file-activity.d.ts +19 -1
- package/lib/types/client/file-activity.d.ts.map +1 -1
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/production-prompts.d.ts +7 -0
- package/lib/types/client/production-prompts.d.ts.map +1 -1
- package/lib/types/client/production-runtime.d.ts +16 -0
- package/lib/types/client/production-runtime.d.ts.map +1 -1
- package/lib/types/client/video-studio.d.ts.map +1 -1
- package/lib/types/drama-adapters.d.ts +5 -3
- package/lib/types/drama-adapters.d.ts.map +1 -1
- package/lib/types/native-hooks.d.ts +53 -1
- package/lib/types/native-hooks.d.ts.map +1 -1
- package/lib/types/role-identity.d.ts +17 -0
- package/lib/types/role-identity.d.ts.map +1 -0
- 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-script/scripts/agent_brief.py → video-understanding/scripts/briefing/builder.py} +38 -35
- package/lib/video-recap/skills/video-understanding/scripts/{brief_context.py → 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-script/scripts/brief_timeline.py → video-understanding/scripts/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 +45 -45
- package/lib/novel-to-game/skills/game-world-design/references/dialogue-design-method.md +0 -58
- package/lib/novel-to-game/skills/game-world-design/references/game-writing-craft.md +0 -53
- package/lib/novel-to-game/skills/novel-to-game/references/intake-benchmark-reference.md +0 -38
- package/lib/oh-story/skills/story-long-analyze/references/material-decomposition.md +0 -530
- package/lib/oh-story/skills/story-long-analyze/references/output-templates.md +0 -559
- package/lib/oh-story/skills/story-long-scan/references/scan-output-format.md +0 -207
- package/lib/oh-story/skills/story-long-write/references/state-tracking.md +0 -108
- package/lib/oh-story/skills/story-short-analyze/references/material-decomposition.md +0 -678
- 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/brief_context.py +0 -348
- package/lib/video-recap/skills/video-script/scripts/narration.py +0 -12
- package/lib/video-recap/skills/video-understanding/scripts/agent_brief.py +0 -399
- package/lib/video-recap/skills/video-understanding/scripts/brief_inputs.py +0 -290
- package/lib/video-recap/skills/video-understanding/scripts/brief_timeline.py +0 -327
- /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,87 +1,86 @@
|
|
|
1
|
-
import React
|
|
2
|
-
import { AbsoluteFill, useCurrentFrame, useVideoConfig } from "remotion";
|
|
3
|
-
import { Cue
|
|
4
|
-
import { assertFamilyResolves
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { AbsoluteFill, spring, useCurrentFrame, useVideoConfig } from "remotion";
|
|
3
|
+
import { Cue } from "./schema";
|
|
4
|
+
import { assertFamilyResolves } from "./font";
|
|
5
|
+
import { lineEms } from "./rules.mjs";
|
|
6
|
+
import { SANS, SUBTITLE } from "./screen/tokens";
|
|
5
7
|
|
|
6
|
-
|
|
8
|
+
/** Splits a line into plain and highlighted runs, earliest keyword first. */
|
|
9
|
+
const runs = (text: string, keys: string[]): { text: string; key: boolean }[] => {
|
|
10
|
+
const out: { text: string; key: boolean }[] = [];
|
|
11
|
+
let rest = text;
|
|
12
|
+
while (rest) {
|
|
13
|
+
const hit = keys
|
|
14
|
+
.map((key) => ({ key, at: rest.indexOf(key) }))
|
|
15
|
+
.filter((found) => found.at >= 0)
|
|
16
|
+
.sort((a, b) => a.at - b.at)[0];
|
|
17
|
+
if (!hit) {
|
|
18
|
+
out.push({ text: rest, key: false });
|
|
19
|
+
break;
|
|
20
|
+
}
|
|
21
|
+
if (hit.at) out.push({ text: rest.slice(0, hit.at), key: false });
|
|
22
|
+
out.push({ text: hit.key, key: true });
|
|
23
|
+
rest = rest.slice(hit.at + hit.key.length);
|
|
24
|
+
}
|
|
25
|
+
return out;
|
|
26
|
+
};
|
|
7
27
|
|
|
8
28
|
/**
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* numbers hold for 768×1344 and 1080×1920. That is the whole reason this file
|
|
13
|
-
* exists in pixels rather than in an ASS style: the subtitle renderer's own
|
|
14
|
-
* `PlayRes` scaling was applied twice once already, and type three times too
|
|
15
|
-
* large shipped in a sample film.
|
|
29
|
+
* The subtitle layer: Noto Sans SC 900, white or voice-coloured fill over a
|
|
30
|
+
* dark rim drawn beneath it, baseline about a quarter up the frame so the
|
|
31
|
+
* platform's own UI never covers it. Sizes are fractions of frame height.
|
|
16
32
|
*/
|
|
17
|
-
export const Subtitles: React.FC<
|
|
18
|
-
cues,
|
|
19
|
-
fontScale,
|
|
20
|
-
bottomScale,
|
|
21
|
-
fontFamily,
|
|
22
|
-
}) => {
|
|
33
|
+
export const Subtitles: React.FC<{ cues: Cue[] }> = ({ cues }) => {
|
|
23
34
|
const frame = useCurrentFrame();
|
|
24
35
|
const { fps, height, width } = useVideoConfig();
|
|
25
36
|
const now = frame / fps;
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
const fontSize = height * fontScale;
|
|
29
|
-
// A dark rim on every side keeps white type legible over a bright frame
|
|
30
|
-
// without a caption box, which is what vertical drama expects. It depends
|
|
31
|
-
// only on the frame size, so it is built once — and it stays above the early
|
|
32
|
-
// return, because a hook may not be skipped on the frames with no line.
|
|
33
|
-
const shadow = useMemo(() => {
|
|
34
|
-
const rim = Math.max(2, height * 0.0024);
|
|
35
|
-
return [
|
|
36
|
-
`0 0 ${rim * 3}px rgba(0,0,0,0.85)`,
|
|
37
|
-
`${rim}px ${rim}px 0 rgba(0,0,0,0.92)`,
|
|
38
|
-
`-${rim}px ${rim}px 0 rgba(0,0,0,0.92)`,
|
|
39
|
-
`${rim}px -${rim}px 0 rgba(0,0,0,0.92)`,
|
|
40
|
-
`-${rim}px -${rim}px 0 rgba(0,0,0,0.92)`,
|
|
41
|
-
].join(", ");
|
|
42
|
-
}, [height]);
|
|
43
|
-
|
|
44
|
-
// Checked against a line about to be filmed, so a missing font stops the
|
|
37
|
+
const cue = cues.find((item) => now >= item.start && now < item.end);
|
|
38
|
+
// Checked against a line about to be filmed, so a missing face stops the
|
|
45
39
|
// render on the first subtitle frame rather than after the whole pass.
|
|
46
|
-
assertFamilyResolves(
|
|
47
|
-
if (!
|
|
48
|
-
|
|
49
|
-
// A short lift on entry reads as the line arriving with the delivery. It is
|
|
50
|
-
// over well before the first syllable ends, so it never delays reading.
|
|
51
|
-
const age = now - active.start;
|
|
52
|
-
const lift = Math.min(1, age / 0.12);
|
|
53
|
-
const eased = 1 - Math.pow(1 - lift, 3);
|
|
40
|
+
assertFamilyResolves(SANS, cue?.text ?? "");
|
|
41
|
+
if (!cue) return null;
|
|
54
42
|
|
|
43
|
+
const u = height / 100;
|
|
44
|
+
// One line: long lines are split upstream; what still does not fit shrinks.
|
|
45
|
+
const size = Math.min(u * 4.3, (width * 0.9) / (lineEms(cue.text) * 1.04));
|
|
46
|
+
const age = now - cue.start;
|
|
47
|
+
const pop = spring({ frame: Math.round(age * fps), fps, config: { damping: 12, stiffness: 260, mass: 0.5 } });
|
|
55
48
|
return (
|
|
56
|
-
<AbsoluteFill
|
|
57
|
-
style={{
|
|
58
|
-
justifyContent: "flex-end",
|
|
59
|
-
alignItems: "center",
|
|
60
|
-
paddingBottom: height * bottomScale,
|
|
61
|
-
paddingLeft: width * 0.06,
|
|
62
|
-
paddingRight: width * 0.06,
|
|
63
|
-
}}
|
|
64
|
-
>
|
|
49
|
+
<AbsoluteFill style={{ justifyContent: "flex-end", alignItems: "center", paddingBottom: u * 24 }}>
|
|
65
50
|
<div
|
|
66
51
|
style={{
|
|
67
|
-
fontFamily,
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
color:
|
|
52
|
+
fontFamily: SANS,
|
|
53
|
+
fontWeight: 900,
|
|
54
|
+
fontSize: size,
|
|
55
|
+
color: SUBTITLE.fill[cue.kind],
|
|
56
|
+
letterSpacing: size * 0.04,
|
|
71
57
|
textAlign: "center",
|
|
72
|
-
lineHeight: 1.
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
maxWidth: "100%",
|
|
80
|
-
wordBreak: "break-word",
|
|
81
|
-
whiteSpace: "pre-wrap",
|
|
58
|
+
lineHeight: 1.2,
|
|
59
|
+
whiteSpace: "nowrap",
|
|
60
|
+
WebkitTextStroke: `${u * 0.42}px ${SUBTITLE.rim}`,
|
|
61
|
+
paintOrder: "stroke fill",
|
|
62
|
+
textShadow: `0 ${u * 0.25}px ${u * 0.6}px rgba(0,0,0,0.55)`,
|
|
63
|
+
opacity: Math.min(1, age / 0.06),
|
|
64
|
+
transform: `scale(${0.94 + 0.06 * pop})`,
|
|
82
65
|
}}
|
|
83
66
|
>
|
|
84
|
-
{
|
|
67
|
+
{runs(cue.text, cue.keys).map((run, index) =>
|
|
68
|
+
run.key ? (
|
|
69
|
+
<span
|
|
70
|
+
key={index}
|
|
71
|
+
style={{
|
|
72
|
+
color: SUBTITLE.keyword,
|
|
73
|
+
display: "inline-block",
|
|
74
|
+
// A 120 ms pop as the word lands.
|
|
75
|
+
transform: `scale(${1.08 + 0.15 * Math.max(0, 1 - age / 0.12)})`,
|
|
76
|
+
}}
|
|
77
|
+
>
|
|
78
|
+
{run.text}
|
|
79
|
+
</span>
|
|
80
|
+
) : (
|
|
81
|
+
<span key={index}>{run.text}</span>
|
|
82
|
+
),
|
|
83
|
+
)}
|
|
85
84
|
</div>
|
|
86
85
|
</AbsoluteFill>
|
|
87
86
|
);
|
|
@@ -1,46 +1,56 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
import { cancelRender, continueRender, delayRender } from "remotion";
|
|
3
|
+
import { FONT_WAIT_MS, familyIsMissing, fontLoadProblem, settleWithin } from "./rules.mjs";
|
|
4
|
+
|
|
5
|
+
type Plan = { font: string; text: string }[];
|
|
2
6
|
|
|
3
7
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
8
|
+
* Holds the first frame until every face in `plan` has the glyphs it will draw.
|
|
9
|
+
*
|
|
10
|
+
* Registered per mount rather than at module load, the way Remotion expects;
|
|
11
|
+
* bounded by `FONT_WAIT_MS`; and a face that is undeclared, fails, or never
|
|
12
|
+
* finishes cancels the render with the reason instead of shipping a fallback.
|
|
13
|
+
* Returns false until then, so nothing is drawn -- or measured -- early; the
|
|
14
|
+
* frame is released only after the ready tree has been committed.
|
|
7
15
|
*/
|
|
8
|
-
export const
|
|
9
|
-
const handle = delayRender("
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
() =>
|
|
13
|
-
|
|
16
|
+
export const useFaces = (plan: Plan): boolean => {
|
|
17
|
+
const [handle] = useState(() => delayRender("加载字体"));
|
|
18
|
+
const [ready, setReady] = useState(false);
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
const loads = Promise.all(plan.map((entry) => document.fonts.load(entry.font, entry.text)));
|
|
21
|
+
settleWithin(loads, FONT_WAIT_MS).then((outcome) => {
|
|
22
|
+
const problem = fontLoadProblem(plan, outcome, (font, text) => document.fonts.check(font, text));
|
|
23
|
+
if (problem) cancelRender(new Error(problem));
|
|
24
|
+
else setReady(true);
|
|
25
|
+
});
|
|
26
|
+
}, [plan]);
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
if (ready) continueRender(handle);
|
|
29
|
+
}, [ready, handle]);
|
|
30
|
+
return ready;
|
|
14
31
|
};
|
|
15
32
|
|
|
16
33
|
/**
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* the film ships in the wrong typeface. Measuring is the only reliable test:
|
|
20
|
-
* identical widths against a family that cannot exist mean nothing resolved.
|
|
34
|
+
* A second guard at draw time: measuring is the only test of what the frame
|
|
35
|
+
* actually uses, so a stack whose families all fall back stops the render.
|
|
21
36
|
*/
|
|
22
|
-
|
|
37
|
+
const checked = new Set<string>();
|
|
23
38
|
|
|
24
39
|
export const assertFamilyResolves = (fontFamily: string, sample: string): void => {
|
|
25
40
|
// Runs on every frame, so measure once per family.
|
|
26
|
-
if (!sample ||
|
|
27
|
-
const
|
|
28
|
-
const context = canvas.getContext("2d");
|
|
41
|
+
if (!sample || checked.has(fontFamily)) return;
|
|
42
|
+
const context = document.createElement("canvas").getContext("2d");
|
|
29
43
|
if (!context) return;
|
|
30
44
|
|
|
31
|
-
const measure = (
|
|
32
|
-
context.font = `700 64px ${
|
|
33
|
-
return context.measureText(
|
|
45
|
+
const measure = (stack: string, text: string): number => {
|
|
46
|
+
context.font = `700 64px ${stack}`;
|
|
47
|
+
return context.measureText(text).width;
|
|
34
48
|
};
|
|
35
|
-
|
|
36
|
-
const fallbackOnly = measure('"__no_such_family__"');
|
|
37
|
-
const requested = measure(`${fontFamily}, "__no_such_family__"`);
|
|
38
|
-
if (requested === fallbackOnly) {
|
|
49
|
+
if (familyIsMissing(measure, fontFamily, sample)) {
|
|
39
50
|
throw new Error(
|
|
40
|
-
|
|
41
|
-
"
|
|
42
|
-
"或给 render 传一个本机确实有的字体族。",
|
|
51
|
+
`字体 ${fontFamily} 在渲染环境里一个都没有,画面会落到浏览器的兜底字体。` +
|
|
52
|
+
"在 Remotion 工作区运行 npm install 装上字体包。",
|
|
43
53
|
);
|
|
44
54
|
}
|
|
45
|
-
|
|
55
|
+
checked.add(fontFamily);
|
|
46
56
|
};
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
// Decisions the overlay makes that do not need a browser. Plain JavaScript so
|
|
2
|
+
// the suite's tests can import this file with Node and no build step.
|
|
3
|
+
|
|
4
|
+
// ---------------------------------------------------------------- fonts
|
|
5
|
+
|
|
6
|
+
/** The faces the overlay draws with, as `document.fonts.load` names them. */
|
|
7
|
+
export const FACES = {
|
|
8
|
+
heavy: '900 16px "Noto Sans SC"',
|
|
9
|
+
bold: '700 16px "Noto Sans SC"',
|
|
10
|
+
mono: '800 16px "JetBrains Mono"',
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
/** Fixed words the panels draw themselves, so their glyphs are loaded too. */
|
|
14
|
+
export const CHROME_TEXT = "【系统提示】【新任务】进度传说史诗稀有·";
|
|
15
|
+
const DIGITS = "0123456789天:/, ";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Which characters each face must have loaded before the first frame.
|
|
19
|
+
*
|
|
20
|
+
* The faces are split by unicode range, and a browser fetches a range only
|
|
21
|
+
* when text in it is laid out -- after the frame has been captured. Asking for
|
|
22
|
+
* every character the film will draw, up front, is what makes frame 0 look
|
|
23
|
+
* like frame 1000.
|
|
24
|
+
*
|
|
25
|
+
* @param {{ text: string }[]} cues
|
|
26
|
+
* @param {{ items: { text: string }[] }[]} screenTexts
|
|
27
|
+
*/
|
|
28
|
+
export const fontLoadPlan = (cues, screenTexts) => {
|
|
29
|
+
const unique = (/** @type {string} */ text) => [...new Set(Array.from(text))].join("");
|
|
30
|
+
const drawn = unique(
|
|
31
|
+
[...cues.map((cue) => cue.text), ...screenTexts.flatMap((piece) => piece.items.map((item) => item.text)),
|
|
32
|
+
CHROME_TEXT, DIGITS].join(""),
|
|
33
|
+
);
|
|
34
|
+
return [
|
|
35
|
+
{ font: FACES.heavy, text: drawn },
|
|
36
|
+
{ font: FACES.bold, text: drawn },
|
|
37
|
+
{ font: FACES.mono, text: DIGITS },
|
|
38
|
+
];
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export const TIMED_OUT = "timed out";
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* What is wrong with a finished load, or null when every face is ready.
|
|
45
|
+
*
|
|
46
|
+
* A face that no stylesheet declares loads as an empty list and raises
|
|
47
|
+
* nothing; the browser would substitute and the film would ship in the wrong
|
|
48
|
+
* typeface. So an empty list is a failure, as is a load that never finished.
|
|
49
|
+
*
|
|
50
|
+
* @param {{ font: string, text: string }[]} plan
|
|
51
|
+
* @param {unknown} outcome what `settleWithin(Promise.all(loads))` resolved to
|
|
52
|
+
* @param {(font: string, text: string) => boolean} check `document.fonts.check`
|
|
53
|
+
*/
|
|
54
|
+
export const fontLoadProblem = (plan, outcome, check) => {
|
|
55
|
+
if (outcome === TIMED_OUT) return `字体在 ${FONT_WAIT_MS / 1000} 秒内没有加载完:${plan.map((p) => p.font).join("、")}`;
|
|
56
|
+
if (outcome instanceof Error) return `字体加载失败:${outcome.message}`;
|
|
57
|
+
const loaded = /** @type {unknown[][]} */ (outcome);
|
|
58
|
+
const undeclared = plan.filter((_, index) => !loaded[index] || loaded[index].length === 0);
|
|
59
|
+
if (undeclared.length) {
|
|
60
|
+
return `没有声明这些字体(Remotion 工作区的字体包没装?):${undeclared.map((p) => p.font).join("、")}`;
|
|
61
|
+
}
|
|
62
|
+
const pending = plan.filter((p) => !check(p.font, p.text));
|
|
63
|
+
if (pending.length) return `字体还没就绪:${pending.map((p) => p.font).join("、")}`;
|
|
64
|
+
return null;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export const FONT_WAIT_MS = 10000;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Resolves with `promise`'s value, its error, or `TIMED_OUT` after `ms` --
|
|
71
|
+
* whichever comes first -- so a load that never settles cannot hold a frame
|
|
72
|
+
* until the whole render times out, and never resolves as if it had succeeded.
|
|
73
|
+
*
|
|
74
|
+
* @param {Promise<unknown>} promise
|
|
75
|
+
* @param {number} ms
|
|
76
|
+
* @returns {Promise<unknown>}
|
|
77
|
+
*/
|
|
78
|
+
export const settleWithin = (promise, ms) =>
|
|
79
|
+
new Promise((resolve) => {
|
|
80
|
+
const timer = setTimeout(() => resolve(TIMED_OUT), ms);
|
|
81
|
+
promise.then(
|
|
82
|
+
(value) => {
|
|
83
|
+
clearTimeout(timer);
|
|
84
|
+
resolve(value);
|
|
85
|
+
},
|
|
86
|
+
(error) => {
|
|
87
|
+
clearTimeout(timer);
|
|
88
|
+
resolve(error instanceof Error ? error : new Error(String(error)));
|
|
89
|
+
},
|
|
90
|
+
);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const GENERIC_FAMILIES = new Set([
|
|
94
|
+
"serif", "sans-serif", "monospace", "cursive", "fantasy", "system-ui", "math",
|
|
95
|
+
"emoji", "fangsong", "ui-serif", "ui-sans-serif", "ui-monospace", "ui-rounded",
|
|
96
|
+
]);
|
|
97
|
+
|
|
98
|
+
/** The named families in a CSS font stack; generic keywords always resolve, so they prove nothing. */
|
|
99
|
+
export const namedFamilies = (/** @type {string} */ stack) =>
|
|
100
|
+
stack
|
|
101
|
+
.split(",")
|
|
102
|
+
.map((part) => part.trim())
|
|
103
|
+
.filter((part) => part && !GENERIC_FAMILIES.has(part.replace(/^["']|["']$/g, "").toLowerCase()));
|
|
104
|
+
|
|
105
|
+
// Latin glyphs in the probe, because a CJK-only probe cannot tell PingFang from
|
|
106
|
+
// "nothing installed" on a machine whose CJK fallback is PingFang itself.
|
|
107
|
+
const LATIN_PROBE = " Hamburgefonstiv 0123456789";
|
|
108
|
+
// Two generic tails that never share Latin glyphs. A family that happens to
|
|
109
|
+
// look like one of them still differs from the other.
|
|
110
|
+
const PROBE_TAILS = ["serif", "monospace"];
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* True when none of the stack's named families resolves.
|
|
114
|
+
*
|
|
115
|
+
* `measure(stack, text)` returns the rendered width of `text` in that stack.
|
|
116
|
+
* A family that resolves changes the width against at least one tail; only
|
|
117
|
+
* when the stack measures exactly like both bare tails did nothing resolve.
|
|
118
|
+
*
|
|
119
|
+
* @param {(stack: string, text: string) => number} measure
|
|
120
|
+
* @param {string} stack
|
|
121
|
+
* @param {string} sample
|
|
122
|
+
*/
|
|
123
|
+
export const familyIsMissing = (measure, stack, sample) => {
|
|
124
|
+
const named = namedFamilies(stack);
|
|
125
|
+
if (named.length === 0) return false;
|
|
126
|
+
const text = sample + LATIN_PROBE;
|
|
127
|
+
return PROBE_TAILS.every(
|
|
128
|
+
(tail) => measure(`${named.join(", ")}, ${tail}`, text) === measure(tail, text),
|
|
129
|
+
);
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
// ---------------------------------------------------------------- text
|
|
133
|
+
|
|
134
|
+
/** Width of a line in ems: CJK and full-width forms are one, the rest a little over half. */
|
|
135
|
+
export const lineEms = (/** @type {string} */ text) =>
|
|
136
|
+
Array.from(text).reduce((sum, ch) => sum + (ch.charCodeAt(0) > 0x2e7f ? 1 : 0.58), 0);
|
|
137
|
+
|
|
138
|
+
/** Characters of `text` a typewriter shows `elapsed` seconds in, at 35 ms each after `delay`. */
|
|
139
|
+
export const typedLength = (/** @type {number} */ elapsed, delay = 0) =>
|
|
140
|
+
Math.max(0, Math.floor((elapsed - delay) / 0.035 + 1e-9));
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* `2 / 1,000,000` → { done: 2, total: 1000000, ratio }, or null when the item is
|
|
144
|
+
* not a progress reading. The ratio is clamped so the bar never overruns.
|
|
145
|
+
*
|
|
146
|
+
* @param {string} text
|
|
147
|
+
*/
|
|
148
|
+
export const parseProgress = (text) => {
|
|
149
|
+
const found = text.match(/^\s*([0-9][0-9,]*)\s*\/\s*([0-9][0-9,]*)\s*$/);
|
|
150
|
+
if (!found) return null;
|
|
151
|
+
const done = Number(found[1].replace(/,/g, ""));
|
|
152
|
+
const total = Number(found[2].replace(/,/g, ""));
|
|
153
|
+
if (!(total > 0)) return null;
|
|
154
|
+
return { done, total, ratio: Math.min(1, Math.max(0, done / total)) };
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
// ---------------------------------------------------------------- countdown
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Whole seconds still showing `elapsed` seconds after a countdown that read
|
|
161
|
+
* `countdown` at its first frame. Rounded up, as a timer reads: it shows its
|
|
162
|
+
* starting value for the whole first second. A pure function of the frame, so
|
|
163
|
+
* every render of the same frame shows the same digits.
|
|
164
|
+
*
|
|
165
|
+
* @param {number} countdown
|
|
166
|
+
* @param {number} elapsed
|
|
167
|
+
*/
|
|
168
|
+
export const secondsLeft = (countdown, elapsed) =>
|
|
169
|
+
Math.max(0, Math.ceil(countdown - elapsed - 1e-6));
|
|
170
|
+
|
|
171
|
+
/** Seconds since the displayed digit last changed; drives the per-second pulse. */
|
|
172
|
+
export const sinceTick = (/** @type {number} */ countdown, /** @type {number} */ elapsed) => {
|
|
173
|
+
const remaining = countdown - elapsed;
|
|
174
|
+
return Math.ceil(remaining - 1e-6) - remaining;
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
const two = (/** @type {number} */ n) => String(n).padStart(2, "0");
|
|
178
|
+
|
|
179
|
+
/** `4天 23:59:58`, `01:05:00` or `00:42`, by the largest unit still in play. */
|
|
180
|
+
export const formatCountdown = (/** @type {number} */ seconds) => {
|
|
181
|
+
const days = Math.floor(seconds / 86400);
|
|
182
|
+
const hours = Math.floor((seconds % 86400) / 3600);
|
|
183
|
+
const minutes = Math.floor((seconds % 3600) / 60);
|
|
184
|
+
const rest = seconds % 60;
|
|
185
|
+
if (days > 0) return `${days}天 ${two(hours)}:${two(minutes)}:${two(rest)}`;
|
|
186
|
+
if (hours > 0) return `${two(hours)}:${two(minutes)}:${two(rest)}`;
|
|
187
|
+
return `${two(minutes)}:${two(rest)}`;
|
|
188
|
+
};
|
|
@@ -1,32 +1,45 @@
|
|
|
1
|
+
export type CueKind = "line" | "vo" | "system";
|
|
2
|
+
|
|
1
3
|
export type Cue = {
|
|
2
4
|
/** Output-time seconds, measured from the rendered segments. */
|
|
3
5
|
start: number;
|
|
4
6
|
end: number;
|
|
5
|
-
/**
|
|
7
|
+
/** One on-screen line of a 剧本.md line in its burned form. */
|
|
6
8
|
text: string;
|
|
9
|
+
/** Dialogue, an inner or narrating voice, or the system's voice -- read from 剧本.md. */
|
|
10
|
+
kind: CueKind;
|
|
11
|
+
/** Words to highlight, each occurring in `text`. */
|
|
12
|
+
keys: string[];
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export type ScreenTextStyle = "card" | "system" | "task" | "corner";
|
|
16
|
+
export type Rarity = "传说" | "史诗" | "稀有";
|
|
17
|
+
|
|
18
|
+
export type ScreenText = {
|
|
19
|
+
/** Output-time seconds. */
|
|
20
|
+
start: number;
|
|
21
|
+
end: number;
|
|
22
|
+
style: ScreenTextStyle;
|
|
23
|
+
/** Rows or items, each traced to a [画面文字] line in 剧本.md. */
|
|
24
|
+
items: { text: string; rarity: Rarity | null }[];
|
|
25
|
+
/** Seconds left at `start`, already resolved for 「接续」; null when nothing counts down. */
|
|
26
|
+
countdown: number | null;
|
|
7
27
|
};
|
|
8
28
|
|
|
9
|
-
export type
|
|
29
|
+
export type OverlayProps = {
|
|
10
30
|
cues: Cue[];
|
|
31
|
+
screenTexts: ScreenText[];
|
|
11
32
|
width: number;
|
|
12
33
|
height: number;
|
|
13
34
|
fps: number;
|
|
14
35
|
durationInSeconds: number;
|
|
15
|
-
/** Type size as a fraction of frame height, so one number survives any delivery. */
|
|
16
|
-
fontScale: number;
|
|
17
|
-
/** Distance from the bottom edge, also a fraction of frame height. */
|
|
18
|
-
bottomScale: number;
|
|
19
|
-
fontFamily: string;
|
|
20
36
|
};
|
|
21
37
|
|
|
22
|
-
export const defaultProps:
|
|
38
|
+
export const defaultProps: OverlayProps = {
|
|
23
39
|
cues: [],
|
|
40
|
+
screenTexts: [],
|
|
24
41
|
width: 1080,
|
|
25
42
|
height: 1920,
|
|
26
|
-
fps:
|
|
43
|
+
fps: 30,
|
|
27
44
|
durationInSeconds: 1,
|
|
28
|
-
fontScale: 0.034,
|
|
29
|
-
bottomScale: 0.055,
|
|
30
|
-
fontFamily:
|
|
31
|
-
'"PingFang SC", "Noto Sans CJK SC", "Source Han Sans SC", "Microsoft YaHei", sans-serif',
|
|
32
45
|
};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { AbsoluteFill, useCurrentFrame, useVideoConfig } from "remotion";
|
|
3
|
+
import { COLOURS, MONO, SANS, StyleProps, TOP_U, entrance, exit, unit } from "./tokens";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* "主号 2" → name 主号, value 2; "主号 粉丝 2" adds 粉丝 as a small unit before
|
|
7
|
+
* the value. A row with one word is all name.
|
|
8
|
+
*/
|
|
9
|
+
const splitRow = (row: string): { name: string; unit: string; value: string } => {
|
|
10
|
+
const words = row.split(/[\s ]+/).filter(Boolean);
|
|
11
|
+
if (words.length < 2) return { name: row, unit: "", value: "" };
|
|
12
|
+
return { name: words[0], unit: words.slice(1, -1).join(" "), value: words[words.length - 1] };
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/** A clean app dashboard card: white, rounded, a coloured dot per row, big red numbers. */
|
|
16
|
+
export const Card: React.FC<StyleProps> = ({ text }) => {
|
|
17
|
+
const frame = useCurrentFrame();
|
|
18
|
+
const { fps, height, width } = useVideoConfig();
|
|
19
|
+
const u = unit(height);
|
|
20
|
+
const shown = entrance(frame, fps, 0, 15);
|
|
21
|
+
return (
|
|
22
|
+
<AbsoluteFill style={{ alignItems: "center", paddingTop: u * TOP_U.card, opacity: exit(frame, fps, text, 0.15) }}>
|
|
23
|
+
<div
|
|
24
|
+
style={{
|
|
25
|
+
width: width * 0.8,
|
|
26
|
+
background: COLOURS.cardSurface,
|
|
27
|
+
borderRadius: u * 1.6,
|
|
28
|
+
boxShadow: `0 ${u}px ${u * 4}px rgba(0,0,0,0.5)`,
|
|
29
|
+
padding: `${u * 0.7}px ${u * 2.2}px`,
|
|
30
|
+
fontFamily: SANS,
|
|
31
|
+
transform: `translateY(${(1 - shown) * u * 4}px) scale(${0.94 + 0.06 * shown})`,
|
|
32
|
+
opacity: shown,
|
|
33
|
+
}}
|
|
34
|
+
>
|
|
35
|
+
{text.items.map((item, index) => {
|
|
36
|
+
const row = splitRow(item.text);
|
|
37
|
+
return (
|
|
38
|
+
<div
|
|
39
|
+
key={index}
|
|
40
|
+
style={{
|
|
41
|
+
display: "flex",
|
|
42
|
+
alignItems: "center",
|
|
43
|
+
gap: u * 1.2,
|
|
44
|
+
padding: `${u * 1.1}px 0`,
|
|
45
|
+
borderTop: index ? `1px solid ${COLOURS.cardRule}` : "none",
|
|
46
|
+
opacity: entrance(frame, fps, 0.08 + index * 0.07),
|
|
47
|
+
}}
|
|
48
|
+
>
|
|
49
|
+
<span
|
|
50
|
+
style={{
|
|
51
|
+
width: u * 3,
|
|
52
|
+
height: u * 3,
|
|
53
|
+
borderRadius: "50%",
|
|
54
|
+
background: COLOURS.cardDots[index % COLOURS.cardDots.length],
|
|
55
|
+
}}
|
|
56
|
+
/>
|
|
57
|
+
<span style={{ flex: 1, fontSize: u * 2.5, fontWeight: 700, color: COLOURS.cardName }}>{row.name}</span>
|
|
58
|
+
{row.unit ? (
|
|
59
|
+
<span style={{ fontSize: u * 1.7, fontWeight: 700, color: COLOURS.cardUnit, marginRight: u * 0.6 }}>
|
|
60
|
+
{row.unit}
|
|
61
|
+
</span>
|
|
62
|
+
) : null}
|
|
63
|
+
<span style={{ fontFamily: MONO, fontSize: u * 3.6, fontWeight: 800, color: COLOURS.cardValue }}>
|
|
64
|
+
{row.value}
|
|
65
|
+
</span>
|
|
66
|
+
</div>
|
|
67
|
+
);
|
|
68
|
+
})}
|
|
69
|
+
</div>
|
|
70
|
+
</AbsoluteFill>
|
|
71
|
+
);
|
|
72
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { AbsoluteFill, useCurrentFrame, useVideoConfig } from "remotion";
|
|
3
|
+
import { formatCountdown, secondsLeft } from "../rules.mjs";
|
|
4
|
+
import { Frame } from "./chrome";
|
|
5
|
+
import { COLOURS, MONO, StyleProps, TOP_U, entrance, exit, unit } from "./tokens";
|
|
6
|
+
|
|
7
|
+
/** The task chrome in miniature, top right: the label, and the countdown when there is one. */
|
|
8
|
+
export const CornerChip: React.FC<StyleProps> = ({ text }) => {
|
|
9
|
+
const frame = useCurrentFrame();
|
|
10
|
+
const { fps, height } = useVideoConfig();
|
|
11
|
+
const u = unit(height);
|
|
12
|
+
const shown = entrance(frame, fps);
|
|
13
|
+
return (
|
|
14
|
+
<AbsoluteFill
|
|
15
|
+
style={{ alignItems: "flex-end", paddingTop: u * TOP_U.corner, paddingRight: u * 2.4, opacity: exit(frame, fps, text) }}
|
|
16
|
+
>
|
|
17
|
+
<div style={{ opacity: shown, transform: `translateX(${(1 - shown) * u * 6}px)` }}>
|
|
18
|
+
<Frame colour={COLOURS.task} width={u * 24} style={{ padding: `${u * 0.7}px ${u * 1.1}px` }}>
|
|
19
|
+
<div style={{ fontSize: u * 1.35, color: COLOURS.chipLabel, fontWeight: 700, letterSpacing: u * 0.2 }}>
|
|
20
|
+
{text.items.map((item) => item.text).join(" · ")}
|
|
21
|
+
</div>
|
|
22
|
+
{text.countdown === null ? null : (
|
|
23
|
+
<div
|
|
24
|
+
style={{
|
|
25
|
+
fontFamily: MONO,
|
|
26
|
+
fontWeight: 800,
|
|
27
|
+
fontSize: u * 2.4,
|
|
28
|
+
color: COLOURS.alarm,
|
|
29
|
+
textShadow: `0 0 ${u * 0.4}px ${COLOURS.alarm}`,
|
|
30
|
+
}}
|
|
31
|
+
>
|
|
32
|
+
{formatCountdown(secondsLeft(text.countdown, frame / fps))}
|
|
33
|
+
</div>
|
|
34
|
+
)}
|
|
35
|
+
</Frame>
|
|
36
|
+
</div>
|
|
37
|
+
</AbsoluteFill>
|
|
38
|
+
);
|
|
39
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Sequence, useVideoConfig } from "remotion";
|
|
3
|
+
import { ScreenTextStyle } from "../schema";
|
|
4
|
+
import { assertFamilyResolves } from "../font";
|
|
5
|
+
import { Card } from "./Card";
|
|
6
|
+
import { CornerChip } from "./CornerChip";
|
|
7
|
+
import { SystemPanel } from "./SystemPanel";
|
|
8
|
+
import { TaskPanel } from "./TaskPanel";
|
|
9
|
+
import { SANS, StyleProps } from "./tokens";
|
|
10
|
+
|
|
11
|
+
/** One component per style; swapping a look never touches parsing or timing. */
|
|
12
|
+
const STYLES: Record<ScreenTextStyle, React.FC<StyleProps>> = {
|
|
13
|
+
card: Card,
|
|
14
|
+
system: SystemPanel,
|
|
15
|
+
task: TaskPanel,
|
|
16
|
+
corner: CornerChip,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const Checked: React.FC<StyleProps> = ({ text }) => {
|
|
20
|
+
assertFamilyResolves(SANS, text.items.map((item) => item.text).join(""));
|
|
21
|
+
const Style = STYLES[text.style];
|
|
22
|
+
return <Style text={text} />;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/** Places one piece in output time; inside it, frame 0 is the piece's first frame. */
|
|
26
|
+
export const ScreenTextLayer: React.FC<StyleProps> = ({ text }) => {
|
|
27
|
+
const { fps } = useVideoConfig();
|
|
28
|
+
const from = Math.round(text.start * fps);
|
|
29
|
+
const until = Math.round(text.end * fps);
|
|
30
|
+
if (until <= from) return null;
|
|
31
|
+
return (
|
|
32
|
+
<Sequence from={from} durationInFrames={until - from} layout="none">
|
|
33
|
+
<Checked text={text} />
|
|
34
|
+
</Sequence>
|
|
35
|
+
);
|
|
36
|
+
};
|