@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.
Files changed (264) hide show
  1. package/README.md +17 -11
  2. package/lib/client.js +85 -35
  3. package/lib/client.js.map +3 -3
  4. package/lib/drama/manifest.json +41 -36
  5. package/lib/drama/skills/short-drama/scripts/creator_markdown_check.py +340 -13
  6. package/lib/drama/skills/short-drama-assets/references/voice-direction.md +4 -2
  7. package/lib/drama/skills/short-drama-edit/SKILL.md +32 -12
  8. package/lib/drama/skills/short-drama-edit/references/still-cuts.md +90 -0
  9. package/lib/drama/skills/short-drama-edit/scripts/edit_tool.py +1035 -145
  10. package/lib/drama/skills/short-drama-edit/scripts/selftest.py +48 -0
  11. package/lib/drama/skills/short-drama-produce/SKILL.md +6 -1
  12. package/lib/drama/skills/short-drama-produce/references/adapter-contract.md +12 -0
  13. package/lib/drama/skills/short-drama-produce/scripts/production_tool.py +138 -9
  14. package/lib/drama/skills/short-drama-produce/scripts/provider_adapters.py +6 -0
  15. package/lib/drama/skills/short-drama-storyboard/SKILL.md +2 -0
  16. package/lib/drama/skills/short-drama-storyboard/references/production-shot-grammar.md +3 -1
  17. package/lib/drama/skills/short-drama-video-prompts/SKILL.md +7 -0
  18. package/lib/drama/skills/short-drama-video-prompts/references/performance-action-timing.md +14 -0
  19. package/lib/drama/skills/short-drama-video-prompts/references/seedance-2.5.md +19 -6
  20. package/lib/drama/skills/short-drama-video-prompts/references/stage-contract.md +1 -0
  21. package/lib/drama/skills/short-drama-video-prompts/references/target-model-profile.md +5 -0
  22. package/lib/drama/skills/short-drama-video-prompts/references/wan-3.0.md +4 -1
  23. package/lib/index.js +16 -7
  24. package/lib/index.js.map +2 -2
  25. package/lib/novel-to-game/README.md +98 -44
  26. package/lib/novel-to-game/README_ZH.md +100 -46
  27. package/lib/novel-to-game/VERSION +1 -1
  28. package/lib/novel-to-game/examples/jin-ping-mei/build/BUILD_BRIEF.md +27 -202
  29. package/lib/novel-to-game/examples/jin-ping-mei/build/app/RUN.md +23 -115
  30. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/chamber.webp +0 -0
  31. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/court_night.webp +0 -0
  32. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/court_rain.webp +0 -0
  33. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/garden.webp +0 -0
  34. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/hall.webp +0 -0
  35. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/kitchen.webp +0 -0
  36. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/street.webp +0 -0
  37. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/street_dawn.webp +0 -0
  38. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/temple.webp +0 -0
  39. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/yue/moon.webp +0 -0
  40. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/meng/angry.webp +0 -0
  41. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/meng/blush.webp +0 -0
  42. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/meng/neutral.webp +0 -0
  43. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/meng/sad.webp +0 -0
  44. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/meng/smile.webp +0 -0
  45. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/meng/surprised.webp +0 -0
  46. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pan/angry.webp +0 -0
  47. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pan/blush.webp +0 -0
  48. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pan/neutral.webp +0 -0
  49. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pan/sad.webp +0 -0
  50. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pan/smile.webp +0 -0
  51. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pan/surprised.webp +0 -0
  52. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pinger/angry.webp +0 -0
  53. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pinger/blush.webp +0 -0
  54. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pinger/neutral.webp +0 -0
  55. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pinger/sad.webp +0 -0
  56. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pinger/smile.webp +0 -0
  57. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/pinger/surprised.webp +0 -0
  58. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/xuee/angry.webp +0 -0
  59. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/xuee/blush.webp +0 -0
  60. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/xuee/neutral.webp +0 -0
  61. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/xuee/sad.webp +0 -0
  62. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/xuee/smile.webp +0 -0
  63. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/xuee/surprised.webp +0 -0
  64. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/yue/angry.webp +0 -0
  65. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/yue/blush.webp +0 -0
  66. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/yue/neutral.webp +0 -0
  67. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/yue/sad.webp +0 -0
  68. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/yue/smile.webp +0 -0
  69. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/sprite/yue/surprised.webp +0 -0
  70. package/lib/novel-to-game/examples/jin-ping-mei/build/app/css/style.css +326 -2503
  71. package/lib/novel-to-game/examples/jin-ping-mei/build/app/index.html +71 -4
  72. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/adv.js +221 -0
  73. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/audio.js +113 -257
  74. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/main.js +511 -3026
  75. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/menus.js +277 -0
  76. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/petals.js +67 -0
  77. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/stage.js +180 -0
  78. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/store.js +70 -0
  79. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/common.js +198 -0
  80. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/common2.js +204 -0
  81. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/common3.js +198 -0
  82. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/dates.js +186 -0
  83. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/index.js +122 -0
  84. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/meng.js +190 -0
  85. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/pan.js +212 -0
  86. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/pinger.js +198 -0
  87. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/xuee.js +199 -0
  88. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/story/yue.js +214 -0
  89. package/lib/novel-to-game/examples/jin-ping-mei/build/app/package.json +2 -2
  90. package/lib/novel-to-game/examples/jin-ping-mei/build/art/generated-art.json +182 -45
  91. package/lib/novel-to-game/examples/jin-ping-mei/qa/README.md +7 -17
  92. package/lib/novel-to-game/examples/jin-ping-mei/qa/verification.json +9 -9
  93. package/lib/novel-to-game/manifest.json +303 -78
  94. package/lib/novel-to-game/skills/game-art-direction/references/art-direction-method.md +7 -2
  95. package/lib/novel-to-game/skills/game-concept/references/concept-method.md +2 -0
  96. package/lib/novel-to-game/skills/game-world-design/references/narrative-design-method.md +2 -0
  97. package/lib/novel-to-game/skills/game-world-design/references/world-design-method.md +9 -0
  98. package/lib/types/client/drama-production-view.d.ts +11 -0
  99. package/lib/types/client/drama-production-view.d.ts.map +1 -1
  100. package/lib/types/client/file-activity.d.ts +1 -1
  101. package/lib/types/client/file-activity.d.ts.map +1 -1
  102. package/lib/types/client/production-prompts.d.ts +7 -1
  103. package/lib/types/client/production-prompts.d.ts.map +1 -1
  104. package/lib/types/client/production-runtime.d.ts +7 -2
  105. package/lib/types/client/production-runtime.d.ts.map +1 -1
  106. package/lib/types/drama-adapters.d.ts.map +1 -1
  107. package/lib/types/skill-provider.d.ts.map +1 -1
  108. package/lib/types/video-project.d.ts.map +1 -1
  109. package/lib/video-recap/CHANGELOG.md +108 -1
  110. package/lib/video-recap/README.en.md +18 -14
  111. package/lib/video-recap/README.md +18 -14
  112. package/lib/video-recap/manifest.json +297 -321
  113. package/lib/video-recap/skills/video-assemble/SKILL.md +18 -16
  114. package/lib/video-recap/skills/video-assemble/references/explicit-audio-mix.md +3 -1
  115. package/lib/video-recap/skills/video-assemble/references/jianying/SOURCE.md +5 -5
  116. package/lib/video-recap/skills/video-assemble/references/packaging.md +4 -4
  117. package/lib/video-recap/skills/video-assemble/references/subtitle-track.md +10 -10
  118. package/lib/video-recap/skills/video-assemble/scripts/adoption/audio_mix_binding.py +13 -2
  119. package/lib/video-recap/skills/video-assemble/scripts/adoption/av_clock.py +109 -0
  120. package/lib/video-recap/skills/video-assemble/scripts/adoption/strict_publish.py +4 -1
  121. package/lib/video-recap/skills/video-assemble/scripts/artifacts.py +2 -9
  122. package/lib/video-recap/skills/video-assemble/scripts/assemble.py +188 -51
  123. package/lib/video-recap/skills/video-assemble/scripts/assemble_constants.py +0 -1
  124. package/lib/video-recap/skills/video-assemble/scripts/assembly_contract.py +32 -75
  125. package/lib/video-recap/skills/video-assemble/scripts/assembly_settings.py +8 -15
  126. package/lib/video-recap/skills/video-assemble/scripts/audio_mix.py +202 -161
  127. package/lib/video-recap/skills/video-assemble/scripts/codec_peak.py +112 -0
  128. package/lib/video-recap/skills/video-assemble/scripts/export_jianying.py +10 -101
  129. package/lib/video-recap/skills/video-assemble/scripts/jianying/builders.py +44 -475
  130. package/lib/video-recap/skills/video-assemble/scripts/jianying/model.py +1 -22
  131. package/lib/video-recap/skills/video-assemble/scripts/jianying/templates.py +0 -3
  132. package/lib/video-recap/skills/video-assemble/scripts/jianying/timeline_contract.py +36 -103
  133. package/lib/video-recap/skills/video-assemble/scripts/jianying/tracks.py +0 -8
  134. package/lib/video-recap/skills/video-assemble/scripts/jianying/writer.py +28 -203
  135. package/lib/video-recap/skills/video-assemble/scripts/lib.py +20 -34
  136. package/lib/video-recap/skills/video-assemble/scripts/loudness.py +332 -0
  137. package/lib/video-recap/skills/video-assemble/scripts/media.py +158 -38
  138. package/lib/video-recap/skills/video-assemble/scripts/narration_audio.py +30 -16
  139. package/lib/video-recap/skills/video-assemble/scripts/render_preflight.py +49 -8
  140. package/lib/video-recap/skills/video-assemble/scripts/source_subtitles.py +17 -6
  141. package/lib/video-recap/skills/video-assemble/scripts/subtitles/core.py +24 -2
  142. package/lib/video-recap/skills/video-assemble/scripts/subtitles/track.py +2 -5
  143. package/lib/video-recap/skills/video-assemble/scripts/subtitles/track_binding.py +2 -9
  144. package/lib/video-recap/skills/video-assemble/scripts/timeline.py +12 -34
  145. package/lib/video-recap/skills/video-assemble/scripts/timeline_emit.py +3 -4
  146. package/lib/video-recap/skills/video-assemble/scripts/visual_render.py +28 -2
  147. package/lib/video-recap/skills/video-cut/SKILL.md +37 -11
  148. package/lib/video-recap/skills/video-cut/references/shot-review.md +1 -1
  149. package/lib/video-recap/skills/video-cut/scripts/cut.py +2 -34
  150. package/lib/video-recap/skills/video-cut/scripts/cut_cli.py +96 -124
  151. package/lib/video-recap/skills/video-cut/scripts/cut_contract.py +97 -182
  152. package/lib/video-recap/skills/video-cut/scripts/cut_render.py +177 -166
  153. package/lib/video-recap/skills/video-cut/scripts/frame_grid.py +249 -0
  154. package/lib/video-recap/skills/video-cut/scripts/lib.py +0 -3
  155. package/lib/video-recap/skills/video-cut/scripts/media_geometry.py +173 -7
  156. package/lib/video-recap/skills/video-cut/scripts/sentence_boundaries.py +234 -151
  157. package/lib/video-recap/skills/video-cut/scripts/sentence_gate.py +189 -0
  158. package/lib/video-recap/skills/video-cut/scripts/shot_review.py +2 -6
  159. package/lib/video-recap/skills/video-recap/SKILL.md +24 -27
  160. package/lib/video-recap/skills/video-recap/references/audio-routing.md +8 -6
  161. package/lib/video-recap/skills/video-recap/references/config-playbook.md +8 -11
  162. package/lib/video-recap/skills/video-recap/references/data-schema.md +131 -74
  163. package/lib/video-recap/skills/video-recap/references/resource-library.md +17 -1
  164. package/lib/video-recap/skills/video-recap/scripts/doctor.py +51 -268
  165. package/lib/video-recap/skills/video-recap/scripts/final_qc.py +89 -254
  166. package/lib/video-recap/skills/video-recap/scripts/lib.py +81 -70
  167. package/lib/video-recap/skills/video-recap/scripts/library.py +40 -4
  168. package/lib/video-recap/skills/video-recap/scripts/materials.py +13 -80
  169. package/lib/video-recap/skills/video-recap/scripts/recap_cli.py +49 -17
  170. package/lib/video-recap/skills/video-recap/scripts/recap_inspect.py +1 -3
  171. package/lib/video-recap/skills/video-recap/scripts/recap_runner.py +86 -116
  172. package/lib/video-recap/skills/video-recap/scripts/recap_runtime.py +87 -23
  173. package/lib/video-recap/skills/video-recap/scripts/recap_source.py +0 -39
  174. package/lib/video-recap/skills/video-recap/scripts/recap_stage_qc.py +37 -135
  175. package/lib/video-recap/skills/video-recap/scripts/recap_timeline.py +118 -127
  176. package/lib/video-recap/skills/video-recap/scripts/resources/__init__.py +4 -0
  177. package/lib/video-recap/skills/video-recap/scripts/{resource_lock.py → resources/lock.py} +1 -1
  178. package/lib/video-recap/skills/video-recap/scripts/{project_binding.py → resources/project_binding.py} +78 -17
  179. package/lib/video-recap/skills/video-reference/SKILL.md +108 -0
  180. package/lib/video-recap/skills/video-reference/references/reference-schema.md +139 -0
  181. package/lib/video-recap/skills/video-reference/scripts/lib.py +85 -0
  182. package/lib/video-recap/skills/video-reference/scripts/reference.py +96 -0
  183. package/lib/video-recap/skills/video-reference/scripts/reference_check.py +553 -0
  184. package/lib/video-recap/skills/video-reference/scripts/reference_frames.py +122 -0
  185. package/lib/video-recap/skills/video-reference/scripts/reference_measure.py +253 -0
  186. package/lib/video-recap/skills/video-reference/scripts/reference_profile.py +271 -0
  187. package/lib/video-recap/skills/video-script/SKILL.md +9 -7
  188. package/lib/video-recap/skills/video-script/scripts/agent_text.py +18 -267
  189. package/lib/video-recap/skills/video-script/scripts/deslop_qc.py +12 -30
  190. package/lib/video-recap/skills/video-script/scripts/entry_suggestion.py +125 -0
  191. package/lib/video-recap/skills/video-script/scripts/evidence_bundle.py +2 -26
  192. package/lib/video-recap/skills/video-script/scripts/lib.py +5 -15
  193. package/lib/video-recap/skills/video-script/scripts/lint_summary.py +184 -0
  194. package/lib/video-recap/skills/video-script/scripts/narration_lint.py +120 -100
  195. package/lib/video-recap/skills/video-script/scripts/review.py +3 -34
  196. package/lib/video-recap/skills/video-script/scripts/review_grounding.py +20 -46
  197. package/lib/video-recap/skills/video-script/scripts/review_response.py +120 -258
  198. package/lib/video-recap/skills/video-script/scripts/review_runner.py +2 -10
  199. package/lib/video-recap/skills/video-script/scripts/speech_ownership.py +89 -5
  200. package/lib/video-recap/skills/video-script/scripts/validate.py +26 -34
  201. package/lib/video-recap/skills/video-understanding/SKILL.md +29 -4
  202. package/lib/video-recap/skills/video-understanding/references/data-schema.md +5 -22
  203. package/lib/video-recap/skills/video-understanding/scripts/agent_text.py +24 -121
  204. package/lib/video-recap/skills/video-understanding/scripts/asr.py +11 -5
  205. package/lib/video-recap/skills/video-understanding/scripts/asr_timing_evidence.py +15 -24
  206. package/lib/video-recap/skills/video-understanding/scripts/briefing/builder.py +59 -25
  207. package/lib/video-recap/skills/video-understanding/scripts/briefing/context.py +39 -3
  208. package/lib/video-recap/skills/video-understanding/scripts/briefing/inputs.py +15 -27
  209. package/lib/video-recap/skills/video-understanding/scripts/briefing/timeline.py +136 -37
  210. package/lib/video-recap/skills/video-understanding/scripts/consolidate.py +121 -37
  211. package/lib/video-recap/skills/video-understanding/scripts/detect.py +118 -16
  212. package/lib/video-recap/skills/video-understanding/scripts/index_normalize.py +359 -0
  213. package/lib/video-recap/skills/video-understanding/scripts/lib.py +62 -21
  214. package/lib/video-recap/skills/video-understanding/scripts/storyboard.py +181 -51
  215. package/lib/video-recap/skills/video-understanding/scripts/timeline_fusion.py +0 -26
  216. package/lib/video-recap/skills/video-understanding/scripts/understanding_brief.py +37 -14
  217. package/lib/video-recap/skills/video-understanding/scripts/understanding_cache.py +40 -32
  218. package/lib/video-recap/skills/video-understanding/scripts/understanding_runner.py +100 -127
  219. package/lib/video-recap/skills/video-understanding/scripts/understanding_storyboard.py +182 -30
  220. package/lib/video-recap/skills/video-understanding/scripts/vlm.py +30 -27
  221. package/lib/video-recap/skills/video-voiceover/SKILL.md +19 -12
  222. package/lib/video-recap/skills/video-voiceover/references/index-tts.md +3 -3
  223. package/lib/video-recap/skills/video-voiceover/scripts/approved_text_policy.py +3 -1
  224. package/lib/video-recap/skills/video-voiceover/scripts/dub.py +60 -135
  225. package/lib/video-recap/skills/video-voiceover/scripts/lib.py +9 -34
  226. package/lib/video-recap/skills/video-voiceover/scripts/providers/fish_audio.py +6 -1
  227. package/lib/video-recap/skills/video-voiceover/scripts/tts_audio.py +64 -1
  228. package/lib/video-recap/skills/video-voiceover/scripts/tts_cache.py +99 -0
  229. package/lib/video-recap/skills/video-voiceover/scripts/voiceover.py +177 -200
  230. package/package.json +45 -45
  231. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/inner_court_accord_five.webp +0 -0
  232. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/public_day15.webp +0 -0
  233. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/household/li_jiaoer.webp +0 -0
  234. package/lib/novel-to-game/examples/jin-ping-mei/build/app/css/new-guofeng.css +0 -1494
  235. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/assets.js +0 -147
  236. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/data.js +0 -7470
  237. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/engine.js +0 -13066
  238. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/text.js +0 -32
  239. package/lib/novel-to-game/examples/jin-ping-mei/qa/playable-model.json +0 -99
  240. package/lib/novel-to-game/examples/jin-ping-mei/qa/verify_playable_model.mjs +0 -152
  241. package/lib/video-recap/skills/video-assemble/references/foreground-compose.md +0 -118
  242. package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_combination_segment.json +0 -84
  243. package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_combination_video_material.json +0 -98
  244. package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_draft.json +0 -204
  245. package/lib/video-recap/skills/video-assemble/references/pair-media.md +0 -74
  246. package/lib/video-recap/skills/video-assemble/scripts/compose_foreground.py +0 -293
  247. package/lib/video-recap/skills/video-assemble/scripts/pair_media.py +0 -216
  248. package/lib/video-recap/skills/video-recap/references/shift-left-qc-schema.md +0 -23
  249. package/lib/video-recap/skills/video-recap/scripts/mimo_qc.py +0 -33
  250. package/lib/video-recap/skills/video-recap/scripts/qc/__init__.py +0 -1
  251. package/lib/video-recap/skills/video-recap/scripts/qc/mimo_client.py +0 -13
  252. package/lib/video-recap/skills/video-recap/scripts/qc/mimo_contract.py +0 -9
  253. package/lib/video-recap/skills/video-recap/scripts/qc/mimo_evidence.py +0 -287
  254. package/lib/video-recap/skills/video-recap/scripts/qc/mimo_observations.py +0 -175
  255. package/lib/video-recap/skills/video-recap/scripts/qc/mimo_payload.py +0 -206
  256. package/lib/video-recap/skills/video-recap/scripts/qc/mimo_report.py +0 -318
  257. package/lib/video-recap/skills/video-recap/scripts/qc/mimo_runner.py +0 -130
  258. package/lib/video-recap/skills/video-recap/scripts/qc_contract.py +0 -320
  259. package/lib/video-recap/skills/video-script/scripts/timeline_fusion.py +0 -108
  260. package/lib/video-recap/skills/video-understanding/scripts/brief.py +0 -9
  261. package/lib/video-recap/skills/video-understanding/scripts/deslop_qc.py +0 -211
  262. package/lib/video-recap/skills/video-understanding/scripts/narration_lint.py +0 -707
  263. package/lib/video-recap/skills/video-understanding/scripts/speech_ownership.py +0 -143
  264. /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 scrub_platform_identity, us
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 authoring transforms (validated as objects by the timeline contract)."""
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": round(float(segment.get("opacity", 1.0)), 4),
92
- "flip": {
93
- "horizontal": bool(flip.get("horizontal", False)),
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 _hex_rgb(color):
132
- value = str(color or "#FFFFFF").lstrip("#")
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
- def _text_style(style, start, end):
144
- fill_color = style.get("fill_color", "#FFFFFF")
145
- authored = template("text_style")["styles"][0]
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": [int(start), int(end)],
155
- "size": float(style.get("font_size", 8.0)),
156
- "bold": bool(style.get("bold", False)),
157
- "italic": bool(style.get("italic", False)),
158
- "underline": bool(style.get("underline", False)),
159
- "strokes": list(style.get("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"] = 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
- source_start = ss
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
- if clip.get("compound") or clip.get("green_background") or clip.get("chroma"):
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
- required_source_us = int(round(want_us * speed_value))
537
- if required_source_us > mat_dur_us:
538
- if role == "bgm" and timeline_track.get("loop"):
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 role == "bgm" and timeline_track.get("loop") and required_source_us > mat_dur_us:
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 / speed_value), want_us - cursor)
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, int(round(piece * speed_value)),
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, int(round(place_us * speed_value)),
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(content, ensure_ascii=False),
607
- "type": "subtitle" if track_kind == "subtitle" else "text",
608
- "alignment": int(authored_style.get("text_align", 1)),
609
- "font_size": float(authored_style.get("font_size", 8.0)),
610
- "text_color": authored_style.get("fill_color", "#FFFFFF"),
611
- "line_spacing": float(authored_style.get("line_spacing", 0.02)),
612
- "letter_spacing": float(authored_style.get("letter_spacing", 0.0)),
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
- speed_value = float(segment.get("speed", 1.0))
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
- seg["speed"] = speed_value
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
- elif kind == "image":
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)