@oh-story/dsh 0.1.12 → 0.1.13
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 +17 -11
- package/lib/client.js +85 -35
- package/lib/client.js.map +3 -3
- package/lib/drama/manifest.json +41 -36
- package/lib/drama/skills/short-drama/scripts/creator_markdown_check.py +340 -13
- package/lib/drama/skills/short-drama-assets/references/voice-direction.md +4 -2
- package/lib/drama/skills/short-drama-edit/SKILL.md +32 -12
- package/lib/drama/skills/short-drama-edit/references/still-cuts.md +90 -0
- package/lib/drama/skills/short-drama-edit/scripts/edit_tool.py +1035 -145
- package/lib/drama/skills/short-drama-edit/scripts/selftest.py +48 -0
- package/lib/drama/skills/short-drama-produce/SKILL.md +6 -1
- package/lib/drama/skills/short-drama-produce/references/adapter-contract.md +12 -0
- package/lib/drama/skills/short-drama-produce/scripts/production_tool.py +138 -9
- package/lib/drama/skills/short-drama-produce/scripts/provider_adapters.py +6 -0
- package/lib/drama/skills/short-drama-storyboard/SKILL.md +2 -0
- package/lib/drama/skills/short-drama-storyboard/references/production-shot-grammar.md +3 -1
- package/lib/drama/skills/short-drama-video-prompts/SKILL.md +7 -0
- package/lib/drama/skills/short-drama-video-prompts/references/performance-action-timing.md +14 -0
- package/lib/drama/skills/short-drama-video-prompts/references/seedance-2.5.md +19 -6
- package/lib/drama/skills/short-drama-video-prompts/references/stage-contract.md +1 -0
- package/lib/drama/skills/short-drama-video-prompts/references/target-model-profile.md +5 -0
- package/lib/drama/skills/short-drama-video-prompts/references/wan-3.0.md +4 -1
- package/lib/index.js +16 -7
- package/lib/index.js.map +2 -2
- package/lib/novel-to-game/README.md +98 -44
- package/lib/novel-to-game/README_ZH.md +100 -46
- package/lib/novel-to-game/VERSION +1 -1
- package/lib/novel-to-game/examples/jin-ping-mei/build/BUILD_BRIEF.md +27 -202
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/RUN.md +23 -115
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/chamber.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/court_night.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/court_rain.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/garden.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/hall.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/kitchen.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/street.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/street_dawn.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/temple.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/yue/moon.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/meng/angry.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/meng/blush.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/meng/neutral.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/meng/sad.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/meng/smile.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/meng/surprised.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pan/angry.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pan/blush.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pan/neutral.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pan/sad.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pan/smile.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pan/surprised.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pinger/angry.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pinger/blush.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pinger/neutral.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pinger/sad.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pinger/smile.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pinger/surprised.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/xuee/angry.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/xuee/blush.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/xuee/neutral.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/xuee/sad.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/xuee/smile.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/xuee/surprised.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/yue/angry.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/yue/blush.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/yue/neutral.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/yue/sad.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/yue/smile.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/yue/surprised.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/css/style.css +326 -2503
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/index.html +71 -4
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/adv.js +221 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/audio.js +113 -257
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/main.js +511 -3026
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/menus.js +277 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/petals.js +67 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/stage.js +180 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/store.js +70 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/common.js +198 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/common2.js +204 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/common3.js +198 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/dates.js +186 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/index.js +122 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/meng.js +190 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/pan.js +212 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/pinger.js +198 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/xuee.js +199 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/yue.js +214 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/package.json +2 -2
- package/lib/novel-to-game/examples/jin-ping-mei/build/art/generated-art.json +182 -45
- package/lib/novel-to-game/examples/jin-ping-mei/qa/README.md +7 -17
- package/lib/novel-to-game/examples/jin-ping-mei/qa/verification.json +9 -9
- package/lib/novel-to-game/manifest.json +303 -78
- package/lib/novel-to-game/skills/game-art-direction/references/art-direction-method.md +7 -2
- package/lib/novel-to-game/skills/game-concept/references/concept-method.md +2 -0
- package/lib/novel-to-game/skills/game-world-design/references/narrative-design-method.md +2 -0
- package/lib/novel-to-game/skills/game-world-design/references/world-design-method.md +9 -0
- package/lib/types/client/drama-production-view.d.ts +11 -0
- package/lib/types/client/drama-production-view.d.ts.map +1 -1
- package/lib/types/client/file-activity.d.ts +1 -1
- package/lib/types/client/file-activity.d.ts.map +1 -1
- package/lib/types/client/production-prompts.d.ts +7 -1
- package/lib/types/client/production-prompts.d.ts.map +1 -1
- package/lib/types/client/production-runtime.d.ts +7 -2
- package/lib/types/client/production-runtime.d.ts.map +1 -1
- package/lib/types/drama-adapters.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/video-recap/CHANGELOG.md +108 -1
- package/lib/video-recap/README.en.md +18 -14
- package/lib/video-recap/README.md +18 -14
- package/lib/video-recap/manifest.json +297 -321
- package/lib/video-recap/skills/video-assemble/SKILL.md +18 -16
- package/lib/video-recap/skills/video-assemble/references/explicit-audio-mix.md +3 -1
- package/lib/video-recap/skills/video-assemble/references/jianying/SOURCE.md +5 -5
- package/lib/video-recap/skills/video-assemble/references/packaging.md +4 -4
- package/lib/video-recap/skills/video-assemble/references/subtitle-track.md +10 -10
- package/lib/video-recap/skills/video-assemble/scripts/adoption/audio_mix_binding.py +13 -2
- package/lib/video-recap/skills/video-assemble/scripts/adoption/av_clock.py +109 -0
- package/lib/video-recap/skills/video-assemble/scripts/adoption/strict_publish.py +4 -1
- package/lib/video-recap/skills/video-assemble/scripts/artifacts.py +2 -9
- package/lib/video-recap/skills/video-assemble/scripts/assemble.py +188 -51
- package/lib/video-recap/skills/video-assemble/scripts/assemble_constants.py +0 -1
- package/lib/video-recap/skills/video-assemble/scripts/assembly_contract.py +32 -75
- package/lib/video-recap/skills/video-assemble/scripts/assembly_settings.py +8 -15
- package/lib/video-recap/skills/video-assemble/scripts/audio_mix.py +202 -161
- package/lib/video-recap/skills/video-assemble/scripts/codec_peak.py +112 -0
- package/lib/video-recap/skills/video-assemble/scripts/export_jianying.py +10 -101
- package/lib/video-recap/skills/video-assemble/scripts/jianying/builders.py +44 -475
- package/lib/video-recap/skills/video-assemble/scripts/jianying/model.py +1 -22
- package/lib/video-recap/skills/video-assemble/scripts/jianying/templates.py +0 -3
- package/lib/video-recap/skills/video-assemble/scripts/jianying/timeline_contract.py +36 -103
- package/lib/video-recap/skills/video-assemble/scripts/jianying/tracks.py +0 -8
- package/lib/video-recap/skills/video-assemble/scripts/jianying/writer.py +28 -203
- package/lib/video-recap/skills/video-assemble/scripts/lib.py +20 -34
- package/lib/video-recap/skills/video-assemble/scripts/loudness.py +332 -0
- package/lib/video-recap/skills/video-assemble/scripts/media.py +158 -38
- package/lib/video-recap/skills/video-assemble/scripts/narration_audio.py +30 -16
- package/lib/video-recap/skills/video-assemble/scripts/render_preflight.py +49 -8
- package/lib/video-recap/skills/video-assemble/scripts/source_subtitles.py +17 -6
- package/lib/video-recap/skills/video-assemble/scripts/subtitles/core.py +24 -2
- package/lib/video-recap/skills/video-assemble/scripts/subtitles/track.py +2 -5
- package/lib/video-recap/skills/video-assemble/scripts/subtitles/track_binding.py +2 -9
- package/lib/video-recap/skills/video-assemble/scripts/timeline.py +12 -34
- package/lib/video-recap/skills/video-assemble/scripts/timeline_emit.py +3 -4
- package/lib/video-recap/skills/video-assemble/scripts/visual_render.py +28 -2
- package/lib/video-recap/skills/video-cut/SKILL.md +37 -11
- package/lib/video-recap/skills/video-cut/references/shot-review.md +1 -1
- package/lib/video-recap/skills/video-cut/scripts/cut.py +2 -34
- package/lib/video-recap/skills/video-cut/scripts/cut_cli.py +96 -124
- package/lib/video-recap/skills/video-cut/scripts/cut_contract.py +97 -182
- package/lib/video-recap/skills/video-cut/scripts/cut_render.py +177 -166
- package/lib/video-recap/skills/video-cut/scripts/frame_grid.py +249 -0
- package/lib/video-recap/skills/video-cut/scripts/lib.py +0 -3
- package/lib/video-recap/skills/video-cut/scripts/media_geometry.py +173 -7
- package/lib/video-recap/skills/video-cut/scripts/sentence_boundaries.py +234 -151
- package/lib/video-recap/skills/video-cut/scripts/sentence_gate.py +189 -0
- package/lib/video-recap/skills/video-cut/scripts/shot_review.py +2 -6
- package/lib/video-recap/skills/video-recap/SKILL.md +24 -27
- package/lib/video-recap/skills/video-recap/references/audio-routing.md +8 -6
- package/lib/video-recap/skills/video-recap/references/config-playbook.md +8 -11
- package/lib/video-recap/skills/video-recap/references/data-schema.md +131 -74
- package/lib/video-recap/skills/video-recap/references/resource-library.md +17 -1
- package/lib/video-recap/skills/video-recap/scripts/doctor.py +51 -268
- package/lib/video-recap/skills/video-recap/scripts/final_qc.py +89 -254
- package/lib/video-recap/skills/video-recap/scripts/lib.py +81 -70
- package/lib/video-recap/skills/video-recap/scripts/library.py +40 -4
- package/lib/video-recap/skills/video-recap/scripts/materials.py +13 -80
- package/lib/video-recap/skills/video-recap/scripts/recap_cli.py +49 -17
- package/lib/video-recap/skills/video-recap/scripts/recap_inspect.py +1 -3
- package/lib/video-recap/skills/video-recap/scripts/recap_runner.py +86 -116
- package/lib/video-recap/skills/video-recap/scripts/recap_runtime.py +87 -23
- package/lib/video-recap/skills/video-recap/scripts/recap_source.py +0 -39
- package/lib/video-recap/skills/video-recap/scripts/recap_stage_qc.py +37 -135
- package/lib/video-recap/skills/video-recap/scripts/recap_timeline.py +118 -127
- package/lib/video-recap/skills/video-recap/scripts/resources/__init__.py +4 -0
- package/lib/video-recap/skills/video-recap/scripts/{resource_lock.py → resources/lock.py} +1 -1
- package/lib/video-recap/skills/video-recap/scripts/{project_binding.py → resources/project_binding.py} +78 -17
- package/lib/video-recap/skills/video-reference/SKILL.md +108 -0
- package/lib/video-recap/skills/video-reference/references/reference-schema.md +139 -0
- package/lib/video-recap/skills/video-reference/scripts/lib.py +85 -0
- package/lib/video-recap/skills/video-reference/scripts/reference.py +96 -0
- package/lib/video-recap/skills/video-reference/scripts/reference_check.py +553 -0
- package/lib/video-recap/skills/video-reference/scripts/reference_frames.py +122 -0
- package/lib/video-recap/skills/video-reference/scripts/reference_measure.py +253 -0
- package/lib/video-recap/skills/video-reference/scripts/reference_profile.py +271 -0
- package/lib/video-recap/skills/video-script/SKILL.md +9 -7
- package/lib/video-recap/skills/video-script/scripts/agent_text.py +18 -267
- package/lib/video-recap/skills/video-script/scripts/deslop_qc.py +12 -30
- package/lib/video-recap/skills/video-script/scripts/entry_suggestion.py +125 -0
- package/lib/video-recap/skills/video-script/scripts/evidence_bundle.py +2 -26
- package/lib/video-recap/skills/video-script/scripts/lib.py +5 -15
- package/lib/video-recap/skills/video-script/scripts/lint_summary.py +184 -0
- package/lib/video-recap/skills/video-script/scripts/narration_lint.py +120 -100
- package/lib/video-recap/skills/video-script/scripts/review.py +3 -34
- package/lib/video-recap/skills/video-script/scripts/review_grounding.py +20 -46
- package/lib/video-recap/skills/video-script/scripts/review_response.py +120 -258
- package/lib/video-recap/skills/video-script/scripts/review_runner.py +2 -10
- package/lib/video-recap/skills/video-script/scripts/speech_ownership.py +89 -5
- package/lib/video-recap/skills/video-script/scripts/validate.py +26 -34
- package/lib/video-recap/skills/video-understanding/SKILL.md +29 -4
- package/lib/video-recap/skills/video-understanding/references/data-schema.md +5 -22
- package/lib/video-recap/skills/video-understanding/scripts/agent_text.py +24 -121
- package/lib/video-recap/skills/video-understanding/scripts/asr.py +11 -5
- package/lib/video-recap/skills/video-understanding/scripts/asr_timing_evidence.py +15 -24
- package/lib/video-recap/skills/video-understanding/scripts/briefing/builder.py +59 -25
- package/lib/video-recap/skills/video-understanding/scripts/briefing/context.py +39 -3
- package/lib/video-recap/skills/video-understanding/scripts/briefing/inputs.py +15 -27
- package/lib/video-recap/skills/video-understanding/scripts/briefing/timeline.py +136 -37
- package/lib/video-recap/skills/video-understanding/scripts/consolidate.py +121 -37
- package/lib/video-recap/skills/video-understanding/scripts/detect.py +118 -16
- package/lib/video-recap/skills/video-understanding/scripts/index_normalize.py +359 -0
- package/lib/video-recap/skills/video-understanding/scripts/lib.py +62 -21
- package/lib/video-recap/skills/video-understanding/scripts/storyboard.py +181 -51
- package/lib/video-recap/skills/video-understanding/scripts/timeline_fusion.py +0 -26
- package/lib/video-recap/skills/video-understanding/scripts/understanding_brief.py +37 -14
- package/lib/video-recap/skills/video-understanding/scripts/understanding_cache.py +40 -32
- package/lib/video-recap/skills/video-understanding/scripts/understanding_runner.py +100 -127
- package/lib/video-recap/skills/video-understanding/scripts/understanding_storyboard.py +182 -30
- package/lib/video-recap/skills/video-understanding/scripts/vlm.py +30 -27
- package/lib/video-recap/skills/video-voiceover/SKILL.md +19 -12
- package/lib/video-recap/skills/video-voiceover/references/index-tts.md +3 -3
- package/lib/video-recap/skills/video-voiceover/scripts/approved_text_policy.py +3 -1
- package/lib/video-recap/skills/video-voiceover/scripts/dub.py +60 -135
- package/lib/video-recap/skills/video-voiceover/scripts/lib.py +9 -34
- package/lib/video-recap/skills/video-voiceover/scripts/providers/fish_audio.py +6 -1
- package/lib/video-recap/skills/video-voiceover/scripts/tts_audio.py +64 -1
- package/lib/video-recap/skills/video-voiceover/scripts/tts_cache.py +99 -0
- package/lib/video-recap/skills/video-voiceover/scripts/voiceover.py +177 -200
- package/package.json +45 -45
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/inner_court_accord_five.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/public_day15.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/household/li_jiaoer.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/css/new-guofeng.css +0 -1494
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/assets.js +0 -147
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/data.js +0 -7470
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/engine.js +0 -13066
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/text.js +0 -32
- package/lib/novel-to-game/examples/jin-ping-mei/qa/playable-model.json +0 -99
- package/lib/novel-to-game/examples/jin-ping-mei/qa/verify_playable_model.mjs +0 -152
- package/lib/video-recap/skills/video-assemble/references/foreground-compose.md +0 -118
- package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_combination_segment.json +0 -84
- package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_combination_video_material.json +0 -98
- package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_draft.json +0 -204
- package/lib/video-recap/skills/video-assemble/references/pair-media.md +0 -74
- package/lib/video-recap/skills/video-assemble/scripts/compose_foreground.py +0 -293
- package/lib/video-recap/skills/video-assemble/scripts/pair_media.py +0 -216
- package/lib/video-recap/skills/video-recap/references/shift-left-qc-schema.md +0 -23
- package/lib/video-recap/skills/video-recap/scripts/mimo_qc.py +0 -33
- package/lib/video-recap/skills/video-recap/scripts/qc/__init__.py +0 -1
- package/lib/video-recap/skills/video-recap/scripts/qc/mimo_client.py +0 -13
- package/lib/video-recap/skills/video-recap/scripts/qc/mimo_contract.py +0 -9
- package/lib/video-recap/skills/video-recap/scripts/qc/mimo_evidence.py +0 -287
- package/lib/video-recap/skills/video-recap/scripts/qc/mimo_observations.py +0 -175
- package/lib/video-recap/skills/video-recap/scripts/qc/mimo_payload.py +0 -206
- package/lib/video-recap/skills/video-recap/scripts/qc/mimo_report.py +0 -318
- package/lib/video-recap/skills/video-recap/scripts/qc/mimo_runner.py +0 -130
- package/lib/video-recap/skills/video-recap/scripts/qc_contract.py +0 -320
- package/lib/video-recap/skills/video-script/scripts/timeline_fusion.py +0 -108
- package/lib/video-recap/skills/video-understanding/scripts/brief.py +0 -9
- package/lib/video-recap/skills/video-understanding/scripts/deslop_qc.py +0 -211
- package/lib/video-recap/skills/video-understanding/scripts/narration_lint.py +0 -707
- package/lib/video-recap/skills/video-understanding/scripts/speech_ownership.py +0 -143
- /package/lib/video-recap/skills/video-cut/scripts/{narration_mapping.py → cut_qc.py} +0 -0
|
@@ -2,9 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
4
|
import os
|
|
5
|
-
from copy import deepcopy
|
|
6
5
|
|
|
7
|
-
from jianying.schema import
|
|
6
|
+
from jianying.schema import us
|
|
8
7
|
from jianying.templates import template
|
|
9
8
|
from jianying.tracks import SEGMENT_RENDER_INDEX
|
|
10
9
|
|
|
@@ -13,16 +12,6 @@ def timerange(start_us, dur_us):
|
|
|
13
12
|
return {"start": int(start_us), "duration": int(dur_us)}
|
|
14
13
|
|
|
15
14
|
|
|
16
|
-
def speed_material(new_id, speed=1.0):
|
|
17
|
-
return {
|
|
18
|
-
"id": new_id(),
|
|
19
|
-
"speed": float(speed),
|
|
20
|
-
"type": "speed",
|
|
21
|
-
"mode": 0,
|
|
22
|
-
"curve_speed": None,
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
|
|
26
15
|
def volume_keyframes(keyframes, seg_start_s, new_id):
|
|
27
16
|
"""Build one KFTypeVolume keyframe list from timeline-absolute points."""
|
|
28
17
|
if not keyframes:
|
|
@@ -83,22 +72,20 @@ def windowed_volume_keyframes(keyframes, seg_start_s, seg_end_s, default_gain, n
|
|
|
83
72
|
|
|
84
73
|
|
|
85
74
|
def clip_from_segment(segment):
|
|
86
|
-
"""Map optional
|
|
75
|
+
"""Map the optional scale/position transform (validated as objects by the contract).
|
|
76
|
+
|
|
77
|
+
Positions use JianYing's normalized, canvas-center, Y-up coordinates.
|
|
78
|
+
"""
|
|
87
79
|
scale = segment.get("scale", {})
|
|
88
80
|
position = segment.get("position", {})
|
|
89
|
-
flip = segment.get("flip", {})
|
|
90
81
|
return {
|
|
91
|
-
"alpha":
|
|
92
|
-
"flip": {
|
|
93
|
-
|
|
94
|
-
"vertical": bool(flip.get("vertical", False)),
|
|
95
|
-
},
|
|
96
|
-
"rotation": float(segment.get("rotation_degrees", 0.0)),
|
|
82
|
+
"alpha": 1.0,
|
|
83
|
+
"flip": {"horizontal": False, "vertical": False},
|
|
84
|
+
"rotation": 0.0,
|
|
97
85
|
"scale": {
|
|
98
86
|
"x": float(scale.get("x", 1.0)),
|
|
99
87
|
"y": float(scale.get("y", 1.0)),
|
|
100
88
|
},
|
|
101
|
-
# Timeline v2 uses JianYing's normalized, canvas-center, Y-up coordinates.
|
|
102
89
|
"transform": {
|
|
103
90
|
"x": float(position.get("x", 0.0)),
|
|
104
91
|
"y": float(position.get("y", 0.0)),
|
|
@@ -128,362 +115,37 @@ def audio_segment_piece(material_id, target_start_us, target_dur_us, source_star
|
|
|
128
115
|
return seg
|
|
129
116
|
|
|
130
117
|
|
|
131
|
-
def
|
|
132
|
-
|
|
133
|
-
if len(value) == 8:
|
|
134
|
-
value = value[:6]
|
|
135
|
-
if len(value) != 6:
|
|
136
|
-
raise ValueError(f"invalid text color: {color!r}")
|
|
137
|
-
try:
|
|
138
|
-
return [round(int(value[index:index + 2], 16) / 255.0, 6) for index in (0, 2, 4)]
|
|
139
|
-
except ValueError as exc:
|
|
140
|
-
raise ValueError(f"invalid text color: {color!r}") from exc
|
|
118
|
+
def text_content(text):
|
|
119
|
+
"""duo-video's rich-text payload with one default style over the whole string.
|
|
141
120
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
authored.update({
|
|
121
|
+
Ranges are UTF-16 code units, matching JianYing rather than Python code points.
|
|
122
|
+
"""
|
|
123
|
+
style = template("text_style")["styles"][0]
|
|
124
|
+
style.update({
|
|
147
125
|
"fill": {
|
|
148
126
|
"alpha": 1.0,
|
|
149
|
-
"content": {
|
|
150
|
-
"render_type": "solid",
|
|
151
|
-
"solid": {"alpha": 1.0, "color": _hex_rgb(fill_color)},
|
|
152
|
-
},
|
|
127
|
+
"content": {"render_type": "solid", "solid": {"alpha": 1.0, "color": [1.0, 1.0, 1.0]}},
|
|
153
128
|
},
|
|
154
|
-
"range": [
|
|
155
|
-
"size":
|
|
156
|
-
"bold":
|
|
157
|
-
"italic":
|
|
158
|
-
"underline":
|
|
159
|
-
"strokes":
|
|
129
|
+
"range": [0, len(text.encode("utf-16-le")) // 2],
|
|
130
|
+
"size": 8.0,
|
|
131
|
+
"bold": False,
|
|
132
|
+
"italic": False,
|
|
133
|
+
"underline": False,
|
|
134
|
+
"strokes": [],
|
|
135
|
+
"use_letter_color": True,
|
|
160
136
|
})
|
|
161
|
-
if style.get("font_path") or style.get("font_id"):
|
|
162
|
-
authored["font"] = {
|
|
163
|
-
"id": str(style.get("font_id", "")),
|
|
164
|
-
"path": str(style.get("font_path", "")),
|
|
165
|
-
}
|
|
166
|
-
if style.get("stroke_color") and float(style.get("stroke_width", 0)) > 0:
|
|
167
|
-
width = float(style["stroke_width"])
|
|
168
|
-
authored["strokes"] = [{
|
|
169
|
-
"alpha": 1.0,
|
|
170
|
-
"content": {
|
|
171
|
-
"render_type": "solid",
|
|
172
|
-
"solid": {"alpha": 1.0, "color": _hex_rgb(style["stroke_color"])},
|
|
173
|
-
},
|
|
174
|
-
"width": round(0.00196 * (width ** 1.013), 6),
|
|
175
|
-
}]
|
|
176
|
-
if style.get("shadow_color"):
|
|
177
|
-
authored["shadows"] = [{
|
|
178
|
-
"alpha": float(style.get("shadow_opacity", 90)) / 100.0,
|
|
179
|
-
"angle": int(style.get("shadow_angle", -45)),
|
|
180
|
-
"distance": int(style.get("shadow_width", 5)),
|
|
181
|
-
"feather": float(style.get("shadow_vague", 45)) / 100.0,
|
|
182
|
-
"content": {
|
|
183
|
-
"render_type": "solid",
|
|
184
|
-
"solid": {"alpha": 1.0, "color": _hex_rgb(style["shadow_color"])},
|
|
185
|
-
},
|
|
186
|
-
}]
|
|
187
|
-
if isinstance(style.get("effect_style"), dict):
|
|
188
|
-
authored["effect_style"] = deepcopy(style["effect_style"])
|
|
189
|
-
authored["use_letter_color"] = True
|
|
190
|
-
return authored
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
def rich_text_content(text, base_style=None, words=None, style_presets=None):
|
|
194
|
-
"""Build duo-video text styles using UTF-16 code-unit ranges."""
|
|
195
|
-
base_style = dict(base_style or {})
|
|
196
|
-
length = len(text.encode("utf-16-le")) // 2
|
|
197
|
-
style_presets = style_presets or {}
|
|
198
|
-
normalized_words = []
|
|
199
|
-
boundaries = {0, length}
|
|
200
|
-
for word in words or []:
|
|
201
|
-
start = max(0, min(length, int(word.get("index", 0))))
|
|
202
|
-
end = max(start, min(length, start + int(word.get("length", 0))))
|
|
203
|
-
if end <= start:
|
|
204
|
-
continue
|
|
205
|
-
word_style = dict(style_presets.get(str(word.get("style_id")), {}))
|
|
206
|
-
word_style.update(word)
|
|
207
|
-
normalized_words.append((start, end, word_style))
|
|
208
|
-
boundaries.update((start, end))
|
|
209
|
-
|
|
210
|
-
styles = []
|
|
211
|
-
points = sorted(boundaries)
|
|
212
|
-
for start, end in zip(points, points[1:]):
|
|
213
|
-
style = dict(base_style)
|
|
214
|
-
for word_start, word_end, word in normalized_words:
|
|
215
|
-
if word_start <= start and end <= word_end:
|
|
216
|
-
style.update({key: value for key, value in word.items() if key not in {"index", "length"}})
|
|
217
|
-
styles.append(_text_style(style, start, end))
|
|
218
137
|
content = template("text_style")
|
|
219
138
|
content["text"] = text
|
|
220
|
-
content["styles"] =
|
|
139
|
+
content["styles"] = [style]
|
|
221
140
|
return content
|
|
222
141
|
|
|
223
142
|
|
|
224
|
-
RESOURCE_TRACKS = {
|
|
225
|
-
"sound": "audios",
|
|
226
|
-
"sticker": "stickers",
|
|
227
|
-
"text_template": "text_templates",
|
|
228
|
-
"video_effect": "video_effects",
|
|
229
|
-
"face_effect": "video_effects",
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
def _resource_material(segment, kind, new_id):
|
|
234
|
-
"""The contract guarantees exactly one of material / resource_config (resolved package)."""
|
|
235
|
-
config = segment.get("resource_config")
|
|
236
|
-
if config is None:
|
|
237
|
-
raw = deepcopy(segment["material"])
|
|
238
|
-
else:
|
|
239
|
-
raw = deepcopy(config["main_config"])
|
|
240
|
-
resource_id = config.get("resource_id")
|
|
241
|
-
if resource_id is not None:
|
|
242
|
-
raw.setdefault("resource_id", resource_id)
|
|
243
|
-
if config.get("resources"):
|
|
244
|
-
raw["_bundle_resources"] = deepcopy(config["resources"])
|
|
245
|
-
cover_img = config.get("cover_img")
|
|
246
|
-
if kind == "sticker" and cover_img:
|
|
247
|
-
raw["icon_url"] = cover_img
|
|
248
|
-
raw["preview_cover_url"] = cover_img
|
|
249
|
-
raw["id"] = new_id()
|
|
250
|
-
return raw
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
def build_resource_track(ctx, timeline_track):
|
|
254
|
-
kind = timeline_track["kind"]
|
|
255
|
-
materials_key = RESOURCE_TRACKS[kind]
|
|
256
|
-
track_name = timeline_track.get("name", kind)
|
|
257
|
-
for item in timeline_track["segments"]:
|
|
258
|
-
ts, te = float(item["timeline_start"]), float(item["timeline_end"])
|
|
259
|
-
duration_us = us(te - ts)
|
|
260
|
-
authored_item = item
|
|
261
|
-
package_name = item.get("resource_package")
|
|
262
|
-
if package_name is not None:
|
|
263
|
-
package = ctx.resource_packages.get(str(package_name))
|
|
264
|
-
if not isinstance(package, dict):
|
|
265
|
-
raise ValueError(f"unknown JianYing resource package: {package_name}")
|
|
266
|
-
authored_item = dict(item)
|
|
267
|
-
authored_item["resource_config"] = package
|
|
268
|
-
material = _resource_material(authored_item, kind, ctx.new_id)
|
|
269
|
-
ctx.materials[materials_key].append(material)
|
|
270
|
-
config = authored_item.get("resource_config") or {}
|
|
271
|
-
if kind == "text_template":
|
|
272
|
-
subordinate_resources = deepcopy(material.get("_bundle_resources", []))
|
|
273
|
-
subordinate_texts = deepcopy(config.get("texts", []))
|
|
274
|
-
subordinate_effects = deepcopy(config.get("effects", []))
|
|
275
|
-
if subordinate_resources:
|
|
276
|
-
for subordinate in subordinate_texts + subordinate_effects:
|
|
277
|
-
if isinstance(subordinate, dict):
|
|
278
|
-
subordinate["_bundle_resources"] = deepcopy(subordinate_resources)
|
|
279
|
-
ctx.materials["texts"].extend(subordinate_texts)
|
|
280
|
-
ctx.materials["effects"].extend(subordinate_effects)
|
|
281
|
-
seg = base_segment(material["id"], us(ts), duration_us, 1.0, [], ctx.new_id)
|
|
282
|
-
speed_value = float(item.get("speed", 1.0))
|
|
283
|
-
seg["source_timerange"] = timerange(0, round(duration_us * speed_value))
|
|
284
|
-
seg["track_render_index"] = 0
|
|
285
|
-
seg["extra_material_refs"] = []
|
|
286
|
-
seg["speed"] = speed_value
|
|
287
|
-
if abs(speed_value - 1.0) > 1e-9:
|
|
288
|
-
speed = speed_material(ctx.new_id, speed_value)
|
|
289
|
-
ctx.materials["speeds"].append(speed)
|
|
290
|
-
seg["extra_material_refs"].append(speed["id"])
|
|
291
|
-
seg["clip"] = clip_from_segment(item)
|
|
292
|
-
ctx.add_segment(kind, track_name, us(ts), duration_us, seg)
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
def _attachment_material(spec, kind, new_id):
|
|
296
|
-
material = deepcopy(spec)
|
|
297
|
-
config = material.pop("main_config", None)
|
|
298
|
-
resources = material.pop("resources", None)
|
|
299
|
-
resource_id = material.pop("resource_id", None)
|
|
300
|
-
if config is not None:
|
|
301
|
-
if not isinstance(config, dict):
|
|
302
|
-
raise ValueError(f"video {kind}.main_config must be an object")
|
|
303
|
-
merged = deepcopy(config)
|
|
304
|
-
merged.update(material)
|
|
305
|
-
material = merged
|
|
306
|
-
if resource_id is not None:
|
|
307
|
-
material.setdefault("resource_id", resource_id)
|
|
308
|
-
if resources:
|
|
309
|
-
material["_bundle_resources"] = deepcopy(resources)
|
|
310
|
-
material["id"] = new_id()
|
|
311
|
-
return material
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
def _resolve_attachment_spec(ctx, spec, kind):
|
|
315
|
-
if isinstance(spec, str):
|
|
316
|
-
package = ctx.resource_packages.get(spec)
|
|
317
|
-
if not isinstance(package, dict):
|
|
318
|
-
raise ValueError(f"unknown JianYing {kind} resource package: {spec}")
|
|
319
|
-
return package
|
|
320
|
-
return spec
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
def apply_video_attachments(ctx, clip, segment):
|
|
324
|
-
"""Attach duo-video transition, mask, and LUT authoring data."""
|
|
325
|
-
semantic_kind = "video"
|
|
326
|
-
transition_spec = clip.get("transition")
|
|
327
|
-
if transition_spec is not None:
|
|
328
|
-
transition_spec = _resolve_attachment_spec(ctx, transition_spec, "transition")
|
|
329
|
-
transition = _attachment_material(transition_spec, "transition", ctx.new_id)
|
|
330
|
-
ctx.materials["transitions"].append(transition)
|
|
331
|
-
segment["extra_material_refs"].append(transition["id"])
|
|
332
|
-
|
|
333
|
-
mask_spec = clip.get("mask")
|
|
334
|
-
if mask_spec is not None:
|
|
335
|
-
mask_spec = _resolve_attachment_spec(ctx, mask_spec, "mask")
|
|
336
|
-
mask = _attachment_material(mask_spec, "mask", ctx.new_id)
|
|
337
|
-
ctx.materials["masks"].append(mask)
|
|
338
|
-
ctx.materials["common_mask"].append(deepcopy(mask))
|
|
339
|
-
segment["extra_material_refs"].append(mask["id"])
|
|
340
|
-
semantic_kind = "mask"
|
|
341
|
-
|
|
342
|
-
lut_spec = clip.get("lut")
|
|
343
|
-
if lut_spec is not None:
|
|
344
|
-
lut_spec = _resolve_attachment_spec(ctx, lut_spec, "lut")
|
|
345
|
-
lut = _attachment_material(lut_spec, "lut", ctx.new_id)
|
|
346
|
-
lut["value"] = float(lut.pop("strength", 100)) / 100.0
|
|
347
|
-
lut.pop("skin_tone_correction", None)
|
|
348
|
-
ctx.materials["effects"].append(lut)
|
|
349
|
-
segment["extra_material_refs"].append(lut["id"])
|
|
350
|
-
if lut_spec.get("skin_tone_correction") is not None:
|
|
351
|
-
lumi_hub_path = str(lut.get("lumi_hub_path") or "")
|
|
352
|
-
if not lumi_hub_path:
|
|
353
|
-
raise ValueError(
|
|
354
|
-
"LUT skin_tone_correction requires an offline effect "
|
|
355
|
-
"main_config with lumi_hub_path"
|
|
356
|
-
)
|
|
357
|
-
effect_path = lumi_hub_path.rsplit("/", 1)[0]
|
|
358
|
-
skin_tone = deepcopy(lut)
|
|
359
|
-
skin_tone["id"] = ctx.new_id()
|
|
360
|
-
skin_tone["type"] = "skin_tone_correction"
|
|
361
|
-
skin_tone["version"] = "v3"
|
|
362
|
-
skin_tone["value"] = float(lut_spec["skin_tone_correction"]) / 100.0
|
|
363
|
-
skin_tone["path"] = effect_path
|
|
364
|
-
skin_tone["lumi_hub_path"] = effect_path
|
|
365
|
-
ctx.materials["effects"].append(skin_tone)
|
|
366
|
-
segment["extra_material_refs"].append(skin_tone["id"])
|
|
367
|
-
return semantic_kind
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
def _track_object(ctx, name, track_type, segments, flag):
|
|
371
|
-
return {
|
|
372
|
-
"attribute": 0,
|
|
373
|
-
"flag": flag,
|
|
374
|
-
"id": ctx.new_id(),
|
|
375
|
-
"is_default_name": True,
|
|
376
|
-
"name": name,
|
|
377
|
-
"segments": segments,
|
|
378
|
-
"type": track_type,
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
def build_compound_video(ctx, clip, material, foreground_segment, track_name, ts, te):
|
|
383
|
-
"""Build duo-video's nested green-screen/compound draft structure."""
|
|
384
|
-
background_spec = clip["green_background"]
|
|
385
|
-
chroma_spec = _resolve_attachment_spec(ctx, clip["chroma"], "chroma")
|
|
386
|
-
|
|
387
|
-
duration_us = us(te - ts)
|
|
388
|
-
full_material_duration_us = material["duration"]
|
|
389
|
-
outer_source_timerange = deepcopy(foreground_segment["source_timerange"])
|
|
390
|
-
outer_target_timerange = deepcopy(foreground_segment["target_timerange"])
|
|
391
|
-
background_path = background_spec["source_path"]
|
|
392
|
-
|
|
393
|
-
background_id = ctx.new_id()
|
|
394
|
-
background = template("video")
|
|
395
|
-
background.update({
|
|
396
|
-
"duration": full_material_duration_us,
|
|
397
|
-
"height": int(background_spec.get("height") or ctx.height),
|
|
398
|
-
"id": background_id,
|
|
399
|
-
"material_name": os.path.basename(background_path),
|
|
400
|
-
"path": background_path,
|
|
401
|
-
"type": background_spec.get("type", "photo"),
|
|
402
|
-
"width": int(background_spec.get("width") or ctx.width),
|
|
403
|
-
})
|
|
404
|
-
|
|
405
|
-
chroma = _attachment_material(chroma_spec, "chroma", ctx.new_id)
|
|
406
|
-
foreground_segment = deepcopy(foreground_segment)
|
|
407
|
-
foreground_segment["source_timerange"] = timerange(0, full_material_duration_us)
|
|
408
|
-
foreground_segment["target_timerange"] = timerange(0, full_material_duration_us)
|
|
409
|
-
foreground_segment["extra_material_refs"].append(chroma["id"])
|
|
410
|
-
|
|
411
|
-
background_segment = base_segment(
|
|
412
|
-
background_id, 0, full_material_duration_us, 1.0, [], ctx.new_id
|
|
413
|
-
)
|
|
414
|
-
background_segment["source_timerange"] = timerange(0, full_material_duration_us)
|
|
415
|
-
background_segment["render_index"] = 1
|
|
416
|
-
background_segment["track_render_index"] = 1
|
|
417
|
-
background_segment["clip"] = clip_from_segment(background_spec)
|
|
418
|
-
|
|
419
|
-
draft = template("draft")
|
|
420
|
-
draft["id"] = ctx.new_id()
|
|
421
|
-
draft["combination_id"] = ctx.new_id()
|
|
422
|
-
nested = draft["draft"]
|
|
423
|
-
nested["id"] = ctx.new_id()
|
|
424
|
-
nested["canvas_config"] = {
|
|
425
|
-
"width": ctx.width,
|
|
426
|
-
"height": ctx.height,
|
|
427
|
-
"ratio": "original",
|
|
428
|
-
}
|
|
429
|
-
nested["duration"] = full_material_duration_us
|
|
430
|
-
nested["fps"] = float(ctx.fps)
|
|
431
|
-
scrub_platform_identity(nested)
|
|
432
|
-
nested["materials"]["videos"] = [material, background]
|
|
433
|
-
nested["materials"]["chromas"] = [chroma]
|
|
434
|
-
referenced = set(foreground_segment["extra_material_refs"])
|
|
435
|
-
nested_speeds = [
|
|
436
|
-
item for item in ctx.materials["speeds"] if item.get("id") in referenced
|
|
437
|
-
]
|
|
438
|
-
if nested_speeds:
|
|
439
|
-
nested["materials"]["speeds"] = nested_speeds
|
|
440
|
-
ctx.materials["speeds"] = [
|
|
441
|
-
item for item in ctx.materials["speeds"] if item.get("id") not in referenced
|
|
442
|
-
]
|
|
443
|
-
nested["tracks"] = [
|
|
444
|
-
_track_object(ctx, "green_background", "video", [background_segment], 0),
|
|
445
|
-
_track_object(ctx, "video", "video", [foreground_segment], 2),
|
|
446
|
-
]
|
|
447
|
-
ctx.materials["drafts"].append(draft)
|
|
448
|
-
|
|
449
|
-
combination_material = template("combination_video")
|
|
450
|
-
combination_material.update({
|
|
451
|
-
"duration": full_material_duration_us,
|
|
452
|
-
"height": ctx.height,
|
|
453
|
-
"id": ctx.new_id(),
|
|
454
|
-
"width": ctx.width,
|
|
455
|
-
})
|
|
456
|
-
ctx.materials["videos"].append(combination_material)
|
|
457
|
-
|
|
458
|
-
combination_segment = template("combination_segment")
|
|
459
|
-
combination_segment.update({
|
|
460
|
-
"id": ctx.new_id(),
|
|
461
|
-
"material_id": combination_material["id"],
|
|
462
|
-
"extra_material_refs": [draft["id"]],
|
|
463
|
-
"source_timerange": outer_source_timerange,
|
|
464
|
-
"target_timerange": outer_target_timerange,
|
|
465
|
-
})
|
|
466
|
-
semantic_kind = apply_video_attachments(ctx, clip, combination_segment)
|
|
467
|
-
authored_track_name = "mask" if semantic_kind == "mask" else track_name
|
|
468
|
-
ctx.add_segment(
|
|
469
|
-
semantic_kind, authored_track_name, us(ts), duration_us, combination_segment
|
|
470
|
-
)
|
|
471
|
-
|
|
472
|
-
|
|
473
143
|
def build_video_track(ctx, timeline_track):
|
|
474
144
|
track_name = timeline_track.get("name", "video")
|
|
475
145
|
for clip in timeline_track["clips"]:
|
|
476
146
|
ts, te = float(clip["timeline_start"]), float(clip["timeline_end"])
|
|
477
147
|
ss, se = float(clip["source_start"]), float(clip["source_end"])
|
|
478
148
|
path = clip["source_path"]
|
|
479
|
-
speed_value = float(clip.get("speed", 1.0))
|
|
480
|
-
reverse = bool(clip.get("reverse", False))
|
|
481
|
-
if reverse:
|
|
482
|
-
# The contract allows omitting reverse_path so export_timeline_to_jianying
|
|
483
|
-
# can generate it; building directly from such a clip is an authoring error.
|
|
484
|
-
if "reverse_path" not in clip:
|
|
485
|
-
raise ValueError("reverse video clips require a local reverse_path")
|
|
486
|
-
path = clip["reverse_path"]
|
|
487
149
|
src_dur_us, width, height = ctx.probe(path)
|
|
488
150
|
if src_dur_us <= 0:
|
|
489
151
|
raise ValueError(f"JianYing video source has no probed duration: {path}")
|
|
@@ -497,33 +159,20 @@ def build_video_track(ctx, timeline_track):
|
|
|
497
159
|
"path": path,
|
|
498
160
|
"width": width or ctx.width,
|
|
499
161
|
})
|
|
162
|
+
ctx.materials["videos"].append(material)
|
|
500
163
|
audio = clip.get("audio", {})
|
|
501
164
|
keyframes = volume_keyframes(audio.get("volume_keyframes"), ts, ctx.new_id)
|
|
502
165
|
volume = audio.get("base_gain", 1.0) if not keyframes else 1.0
|
|
503
166
|
seg = base_segment(mat_id, us(ts), us(te - ts), volume, keyframes, ctx.new_id)
|
|
504
|
-
|
|
505
|
-
if reverse:
|
|
506
|
-
source_start = max(0.0, (src_dur_us / 1_000_000) - se)
|
|
507
|
-
seg["source_timerange"] = timerange(us(source_start), us(se - ss))
|
|
508
|
-
seg["speed"] = speed_value
|
|
509
|
-
seg["extra_material_refs"] = []
|
|
510
|
-
if abs(speed_value - 1.0) > 1e-9:
|
|
511
|
-
speed = speed_material(ctx.new_id, speed_value)
|
|
512
|
-
ctx.materials["speeds"].append(speed)
|
|
513
|
-
seg["extra_material_refs"].append(speed["id"])
|
|
167
|
+
seg["source_timerange"] = timerange(us(ss), us(se - ss))
|
|
514
168
|
seg["clip"] = clip_from_segment(clip)
|
|
515
|
-
|
|
516
|
-
build_compound_video(ctx, clip, material, seg, track_name, ts, te)
|
|
517
|
-
continue
|
|
518
|
-
ctx.materials["videos"].append(material)
|
|
519
|
-
semantic_kind = apply_video_attachments(ctx, clip, seg)
|
|
520
|
-
authored_track_name = "mask" if semantic_kind == "mask" else track_name
|
|
521
|
-
ctx.add_segment(semantic_kind, authored_track_name, us(ts), us(te - ts), seg)
|
|
169
|
+
ctx.add_segment("video", track_name, us(ts), us(te - ts), seg)
|
|
522
170
|
|
|
523
171
|
|
|
524
172
|
def build_audio_track(ctx, timeline_track):
|
|
525
173
|
role = timeline_track.get("role", timeline_track.get("name", "audio"))
|
|
526
174
|
track_name = timeline_track.get("name", role)
|
|
175
|
+
looped_bgm = role == "bgm" and timeline_track.get("loop")
|
|
527
176
|
for segment in timeline_track["segments"]:
|
|
528
177
|
ts, te = float(segment["timeline_start"]), float(segment["timeline_end"])
|
|
529
178
|
path = segment["source_path"]
|
|
@@ -531,15 +180,10 @@ def build_audio_track(ctx, timeline_track):
|
|
|
531
180
|
if mat_dur_us <= 0:
|
|
532
181
|
raise ValueError(f"JianYing audio source has no probed duration: {path}")
|
|
533
182
|
want_us = us(te - ts)
|
|
534
|
-
speed_value = float(segment.get("speed", 1.0))
|
|
535
183
|
place_us = want_us
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
if role == "bgm"
|
|
539
|
-
place_us = want_us
|
|
540
|
-
else:
|
|
541
|
-
place_us = int(mat_dur_us / speed_value)
|
|
542
|
-
if role == "bgm" and not timeline_track.get("loop"):
|
|
184
|
+
if want_us > mat_dur_us and not looped_bgm:
|
|
185
|
+
place_us = int(mat_dur_us)
|
|
186
|
+
if role == "bgm":
|
|
543
187
|
ctx.note(
|
|
544
188
|
f"BGM 素材({mat_dur_us/1e6:.1f}s) 短于时间线({(te - ts):.1f}s),"
|
|
545
189
|
"剪映中未循环铺满(可在剪映里手动复制延长)"
|
|
@@ -554,12 +198,10 @@ def build_audio_track(ctx, timeline_track):
|
|
|
554
198
|
ctx.materials["audios"].append(material)
|
|
555
199
|
keyframes = volume_keyframes(segment.get("volume_keyframes"), ts, ctx.new_id)
|
|
556
200
|
volume = segment.get("gain", 1.0) if not keyframes else 1.0
|
|
557
|
-
if
|
|
201
|
+
if looped_bgm and want_us > mat_dur_us:
|
|
558
202
|
cursor = 0
|
|
559
203
|
while cursor < want_us:
|
|
560
|
-
piece = min(int(mat_dur_us
|
|
561
|
-
if piece <= 0:
|
|
562
|
-
break
|
|
204
|
+
piece = min(int(mat_dur_us), want_us - cursor)
|
|
563
205
|
piece_start_s = ts + (cursor / 1_000_000)
|
|
564
206
|
piece_end_s = ts + ((cursor + piece) / 1_000_000)
|
|
565
207
|
piece_kfs = windowed_volume_keyframes(
|
|
@@ -567,24 +209,13 @@ def build_audio_track(ctx, timeline_track):
|
|
|
567
209
|
segment.get("gain", 1.0), ctx.new_id)
|
|
568
210
|
piece_volume = segment.get("gain", 1.0) if not piece_kfs else 1.0
|
|
569
211
|
piece_seg = audio_segment_piece(
|
|
570
|
-
mat_id, us(ts) + cursor, piece, 0,
|
|
212
|
+
mat_id, us(ts) + cursor, piece, 0, piece,
|
|
571
213
|
piece_volume, piece_kfs, ctx.new_id)
|
|
572
|
-
piece_seg["speed"] = speed_value
|
|
573
|
-
if abs(speed_value - 1.0) > 1e-9:
|
|
574
|
-
speed = speed_material(ctx.new_id, speed_value)
|
|
575
|
-
ctx.materials["speeds"].append(speed)
|
|
576
|
-
piece_seg["extra_material_refs"].append(speed["id"])
|
|
577
214
|
ctx.add_segment("audio", track_name, us(ts) + cursor, piece, piece_seg)
|
|
578
215
|
cursor += piece
|
|
579
216
|
else:
|
|
580
217
|
audio_seg = audio_segment_piece(
|
|
581
|
-
mat_id, us(ts), place_us, 0,
|
|
582
|
-
volume, keyframes, ctx.new_id)
|
|
583
|
-
audio_seg["speed"] = speed_value
|
|
584
|
-
if abs(speed_value - 1.0) > 1e-9:
|
|
585
|
-
speed = speed_material(ctx.new_id, speed_value)
|
|
586
|
-
ctx.materials["speeds"].append(speed)
|
|
587
|
-
audio_seg["extra_material_refs"].append(speed["id"])
|
|
218
|
+
mat_id, us(ts), place_us, 0, place_us, volume, keyframes, ctx.new_id)
|
|
588
219
|
ctx.add_segment("audio", track_name, us(ts), place_us, audio_seg)
|
|
589
220
|
|
|
590
221
|
|
|
@@ -593,69 +224,19 @@ def build_text_track(ctx, timeline_track):
|
|
|
593
224
|
track_kind = "subtitle" if track_name == "subtitle" else "text"
|
|
594
225
|
for segment in timeline_track["segments"]:
|
|
595
226
|
ts, te = float(segment["timeline_start"]), float(segment["timeline_end"])
|
|
596
|
-
text = segment["text"]
|
|
597
227
|
mat_id = ctx.new_id()
|
|
598
|
-
authored_style = dict(ctx.style_presets.get(str(segment.get("style_id")), {}))
|
|
599
|
-
authored_style.update(segment.get("style") or {})
|
|
600
|
-
content = rich_text_content(
|
|
601
|
-
text, authored_style, segment.get("words"), ctx.style_presets
|
|
602
|
-
)
|
|
603
228
|
material = template("text")
|
|
604
229
|
material.update({
|
|
605
230
|
"id": mat_id,
|
|
606
|
-
"content": json.dumps(
|
|
607
|
-
"type":
|
|
608
|
-
"alignment":
|
|
609
|
-
"font_size":
|
|
610
|
-
"text_color":
|
|
611
|
-
"line_spacing":
|
|
612
|
-
"letter_spacing":
|
|
231
|
+
"content": json.dumps(text_content(segment["text"]), ensure_ascii=False),
|
|
232
|
+
"type": track_kind,
|
|
233
|
+
"alignment": 1,
|
|
234
|
+
"font_size": 8.0,
|
|
235
|
+
"text_color": "#FFFFFF",
|
|
236
|
+
"line_spacing": 0.02,
|
|
237
|
+
"letter_spacing": 0.0,
|
|
613
238
|
"check_flag": 15,
|
|
614
239
|
})
|
|
615
|
-
bundle_resources = []
|
|
616
|
-
for content_style in content["styles"]:
|
|
617
|
-
font_path = content_style.get("font", {}).get("path")
|
|
618
|
-
if font_path and not str(font_path).startswith(("Resources/", "##_draftpath_placeholder_")):
|
|
619
|
-
bundle_resources.append({
|
|
620
|
-
"source_path": str(font_path),
|
|
621
|
-
"resource_kind": "fonts",
|
|
622
|
-
})
|
|
623
|
-
effect_path = content_style.get("effect_style", {}).get("path")
|
|
624
|
-
if effect_path and not str(effect_path).startswith(("Resources/", "##_draftpath_placeholder_")):
|
|
625
|
-
bundle_resources.append({
|
|
626
|
-
"source_path": str(effect_path),
|
|
627
|
-
"resource_kind": "effect",
|
|
628
|
-
})
|
|
629
|
-
if bundle_resources:
|
|
630
|
-
material["_bundle_resources"] = bundle_resources
|
|
631
|
-
if authored_style.get("background_color"):
|
|
632
|
-
material.update({
|
|
633
|
-
"background_color": authored_style["background_color"],
|
|
634
|
-
"background_alpha": float(authored_style.get("background_opacity", 100)) / 100.0,
|
|
635
|
-
"background_style": 1,
|
|
636
|
-
"background_height": float(authored_style.get("background_height", 14)) / 100.0,
|
|
637
|
-
"background_width": float(authored_style.get("background_width", 14)) / 100.0,
|
|
638
|
-
"background_horizontal_offset": float(authored_style.get("background_offset_x", 50)) * 0.02 - 1,
|
|
639
|
-
"background_vertical_offset": float(authored_style.get("background_offset_y", 50)) * 0.02 - 1,
|
|
640
|
-
"background_round_radius": float(authored_style.get("background_radius", 6)) / 100.0,
|
|
641
|
-
"check_flag": 31,
|
|
642
|
-
})
|
|
643
|
-
if authored_style.get("stroke_color") and float(authored_style.get("stroke_width", 0)) > 0:
|
|
644
|
-
material["bold_width"] = round(
|
|
645
|
-
0.00196 * (float(authored_style["stroke_width"]) ** 1.013), 6
|
|
646
|
-
)
|
|
647
|
-
material["border_color"] = authored_style["stroke_color"]
|
|
648
|
-
if authored_style.get("shadow_color"):
|
|
649
|
-
material.update({
|
|
650
|
-
"has_shadow": True,
|
|
651
|
-
"shadow_color": authored_style["shadow_color"],
|
|
652
|
-
"shadow_alpha": float(authored_style.get("shadow_opacity", 90)) / 100.0,
|
|
653
|
-
"shadow_angle": float(authored_style.get("shadow_angle", -45)),
|
|
654
|
-
"shadow_distance": float(authored_style.get("shadow_width", 5)),
|
|
655
|
-
"shadow_smoothing": float(authored_style.get("shadow_vague", 45)) / 100.0,
|
|
656
|
-
})
|
|
657
|
-
if authored_style or segment.get("words"):
|
|
658
|
-
material["is_rich_text"] = True
|
|
659
240
|
ctx.materials["texts"].append(material)
|
|
660
241
|
duration_us = us(te - ts)
|
|
661
242
|
seg = base_segment(mat_id, us(ts), duration_us, 1.0, [], ctx.new_id)
|
|
@@ -687,19 +268,9 @@ def build_image_track(ctx, timeline_track):
|
|
|
687
268
|
})
|
|
688
269
|
ctx.materials["videos"].append(material)
|
|
689
270
|
seg = base_segment(mat_id, us(ts), duration_us, 1.0, [], ctx.new_id)
|
|
690
|
-
|
|
691
|
-
seg["source_timerange"] = timerange(0, round(duration_us * speed_value))
|
|
271
|
+
seg["source_timerange"] = timerange(0, duration_us)
|
|
692
272
|
seg["clip"] = clip_from_segment(segment)
|
|
693
|
-
|
|
694
|
-
if abs(speed_value - 1.0) > 1e-9:
|
|
695
|
-
speed = speed_material(ctx.new_id, speed_value)
|
|
696
|
-
ctx.materials["speeds"].append(speed)
|
|
697
|
-
seg["extra_material_refs"].append(speed["id"])
|
|
698
|
-
semantic_kind = apply_video_attachments(ctx, segment, seg)
|
|
699
|
-
authored_track_name = "mask" if semantic_kind == "mask" else track_name
|
|
700
|
-
if semantic_kind == "video":
|
|
701
|
-
semantic_kind = "image"
|
|
702
|
-
ctx.add_segment(semantic_kind, authored_track_name, us(ts), duration_us, seg)
|
|
273
|
+
ctx.add_segment("image", track_name, us(ts), duration_us, seg)
|
|
703
274
|
|
|
704
275
|
|
|
705
276
|
def build_timeline_track(ctx, timeline_track):
|
|
@@ -712,7 +283,5 @@ def build_timeline_track(ctx, timeline_track):
|
|
|
712
283
|
build_audio_track(ctx, timeline_track)
|
|
713
284
|
elif kind == "text":
|
|
714
285
|
build_text_track(ctx, timeline_track)
|
|
715
|
-
|
|
286
|
+
else: # normalize_timeline admits only video/audio/text/image track kinds
|
|
716
287
|
build_image_track(ctx, timeline_track)
|
|
717
|
-
else: # normalize_timeline admits only RESOURCE_TRACKS kinds past this point
|
|
718
|
-
build_resource_track(ctx, timeline_track)
|