@oh-story/dsh 0.1.11 → 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
@@ -21,27 +21,8 @@ class DraftBuildContext:
21
21
  total_us: int
22
22
  new_id: NewIdFn
23
23
  probe: ProbeFn
24
- resource_packages: dict = field(default_factory=dict)
25
- style_presets: dict = field(default_factory=dict)
26
24
  materials: dict[str, list] = field(
27
- default_factory=lambda: {
28
- key: []
29
- for key in (
30
- "audios",
31
- "chromas",
32
- "common_mask",
33
- "drafts",
34
- "effects",
35
- "masks",
36
- "speeds",
37
- "stickers",
38
- "text_templates",
39
- "texts",
40
- "transitions",
41
- "video_effects",
42
- "videos",
43
- )
44
- }
25
+ default_factory=lambda: {"audios": [], "texts": [], "videos": []}
45
26
  )
46
27
  tracks: list[dict] = field(default_factory=list)
47
28
  notes: list[str] = field(default_factory=list)
@@ -58,8 +39,6 @@ class DraftBuildContext:
58
39
  total_us=us(timeline["duration"]),
59
40
  new_id=new_id,
60
41
  probe=probe,
61
- resource_packages=timeline.get("resource_packages", {}),
62
- style_presets=timeline.get("style_presets", {}),
63
42
  )
64
43
 
65
44
  def add_segment(self, kind, base_name, start_us, duration_us, segment):
@@ -14,9 +14,6 @@ _TEMPLATE_FILES = {
14
14
  "text": "empty_yj_material_text.json",
15
15
  "text_style": "empty_jy_text_styles.json",
16
16
  "segment": "empty_jy_segment.json",
17
- "draft": "empty_jy_draft.json",
18
- "combination_segment": "empty_jy_combination_segment.json",
19
- "combination_video": "empty_jy_combination_video_material.json",
20
17
  "meta": "empty_draft_meta_info.json",
21
18
  "meta_material": "empty_jy_meta_material_value.json",
22
19
  }
@@ -1,13 +1,21 @@
1
- """Timeline validation and migration at the JianYing adapter boundary."""
1
+ """Timeline validation at the JianYing adapter boundary."""
2
2
 
3
3
  import copy
4
4
  import math
5
5
 
6
6
 
7
7
  CURRENT_SCHEMA_VERSION = 2
8
- RESOURCE_TRACK_KINDS = {
9
- "face_effect", "sound", "sticker", "text_template", "video_effect",
10
- }
8
+ # Hand-authoring fields the exporter once mapped to JianYing (speed, reverse,
9
+ # transitions, masks, LUTs, green screen, rich text, resource tracks). No pipeline
10
+ # stage emits them; they are rejected so an old hand-written timeline fails loudly
11
+ # instead of silently exporting a draft without the effect it asked for.
12
+ REMOVED_ITEM_FIELDS = (
13
+ "chroma", "compound", "flip", "green_background", "lut", "mask", "opacity",
14
+ "reverse", "reverse_path", "rotation_degrees", "speed", "style", "style_id",
15
+ "transition", "words",
16
+ )
17
+ REMOVED_ROOT_FIELDS = ("resource_packages", "style_presets")
18
+
11
19
 
12
20
  def _error(path, expectation):
13
21
  raise ValueError(f"invalid timeline {path}: {expectation}")
@@ -38,6 +46,12 @@ def _require_string(container, key, path):
38
46
  return value
39
47
 
40
48
 
49
+ def _reject_removed(container, fields, path):
50
+ for field in fields:
51
+ if field in container:
52
+ _error(_field_path(path, field), "JianYing authoring extension is no longer supported")
53
+
54
+
41
55
  def _validate_span(item, path, start_key="timeline_start", end_key="timeline_end"):
42
56
  start = _require_number(item, start_key, path, minimum=0)
43
57
  end = _require_number(item, end_key, path, minimum=0)
@@ -45,101 +59,30 @@ def _validate_span(item, path, start_key="timeline_start", end_key="timeline_end
45
59
  _error(f"{path}.{end_key}", f"must be greater than {start_key}")
46
60
 
47
61
 
48
- def _validate_transform(item, path):
49
- for field in ("scale", "position", "flip"):
62
+ def _validate_item(item, path):
63
+ if not isinstance(item, dict):
64
+ _error(path, "must be an object")
65
+ _reject_removed(item, REMOVED_ITEM_FIELDS, path)
66
+ _validate_span(item, path)
67
+ for field in ("scale", "position"):
50
68
  if field in item and not isinstance(item[field], dict):
51
69
  _error(f"{path}.{field}", "must be an object")
52
70
 
53
71
 
54
- def _validate_resources(resources, path):
55
- if not isinstance(resources, list) or any(not isinstance(item, dict) for item in resources):
56
- _error(path, "must contain source_path objects")
57
- for index, item in enumerate(resources):
58
- _require_string(item, "source_path", f"{path}[{index}]")
59
-
60
-
61
72
  def _validate_video_clip(clip, path):
62
- if not isinstance(clip, dict):
63
- _error(path, "must be an object")
73
+ _validate_item(clip, path)
64
74
  _require_string(clip, "source_path", path)
65
- _validate_span(clip, path)
66
75
  _validate_span(clip, path, "source_start", "source_end")
67
76
  if "audio" in clip and not isinstance(clip["audio"], dict):
68
77
  _error(f"{path}.audio", "must be an object")
69
- if "speed" in clip:
70
- speed = _require_number(clip, "speed", path)
71
- if speed <= 0:
72
- _error(f"{path}.speed", "must be greater than 0")
73
- source_duration = float(clip["source_end"]) - float(clip["source_start"])
74
- target_duration = float(clip["timeline_end"]) - float(clip["timeline_start"])
75
- expected_source_duration = target_duration * float(speed)
76
- if not math.isclose(source_duration, expected_source_duration, rel_tol=1e-6, abs_tol=1e-4):
77
- _error(
78
- path,
79
- "source duration must equal target duration multiplied by speed "
80
- f"({source_duration} != {target_duration} * {speed})",
81
- )
82
- if "reverse" in clip and not isinstance(clip["reverse"], bool):
83
- _error(f"{path}.reverse", "must be a boolean")
84
- # A reversed clip may omit reverse_path: export_timeline_to_jianying generates it.
85
- if "reverse_path" in clip:
86
- _require_string(clip, "reverse_path", path)
87
- _validate_transform(clip, path)
88
- for field in ("transition", "mask", "lut", "chroma"):
89
- if field not in clip:
90
- continue
91
- spec = clip[field]
92
- if isinstance(spec, dict):
93
- if "resources" in spec:
94
- _validate_resources(spec["resources"], f"{path}.{field}.resources")
95
- elif not isinstance(spec, str):
96
- _error(f"{path}.{field}", "must be an object or resource-package name")
97
- if "compound" in clip and not isinstance(clip["compound"], bool):
98
- _error(f"{path}.compound", "must be a boolean")
99
- if clip.get("compound") or "green_background" in clip or "chroma" in clip:
100
- # Any one of these makes the clip a green-screen compound, which needs both.
101
- background = clip.get("green_background")
102
- if not isinstance(background, dict):
103
- _error(f"{path}.green_background", "must be a local media object")
104
- _require_string(background, "source_path", f"{path}.green_background")
105
- _validate_transform(background, f"{path}.green_background")
106
- if "chroma" not in clip:
107
- _error(f"{path}.chroma", "compound green-screen clips require a chroma object")
108
-
109
-
110
- def _validate_resource_config(config, path):
111
- if not isinstance(config, dict):
112
- _error(path, "must be an object")
113
- if not isinstance(config.get("main_config"), dict):
114
- _error(f"{path}.main_config", "must be an object")
115
- _validate_resources(config.get("resources", []), f"{path}.resources")
116
78
 
117
79
 
118
80
  def _validate_segment(segment, path, kind):
119
- if not isinstance(segment, dict):
120
- _error(path, "must be an object")
121
- _validate_span(segment, path)
122
- _validate_transform(segment, path)
123
- if "speed" in segment:
124
- speed = _require_number(segment, "speed", path)
125
- if speed <= 0:
126
- _error(f"{path}.speed", "must be greater than 0")
81
+ _validate_item(segment, path)
127
82
  if kind in {"audio", "image"}:
128
83
  _require_string(segment, "source_path", path)
129
- elif kind == "text" and not isinstance(segment.get("text"), str):
84
+ elif not isinstance(segment.get("text"), str):
130
85
  _error(f"{path}.text", "must be a string")
131
- elif kind in RESOURCE_TRACK_KINDS:
132
- sources = [key for key in ("material", "resource_config", "resource_package") if key in segment]
133
- if len(sources) != 1:
134
- _error(path, "must define exactly one of material, resource_config, or resource_package")
135
- source = segment[sources[0]]
136
- if sources[0] == "resource_package":
137
- if not isinstance(source, str) or not source:
138
- _error(f"{path}.resource_package", "must be a non-empty string")
139
- elif sources[0] == "material" and not isinstance(source, dict):
140
- _error(f"{path}.material", "must be an object")
141
- elif sources[0] == "resource_config":
142
- _validate_resource_config(source, f"{path}.resource_config")
143
86
 
144
87
 
145
88
  def _validate_track(track, path):
@@ -157,7 +100,7 @@ def _validate_track(track, path):
157
100
  _validate_video_clip(clip, f"{path}.clips[{index}]")
158
101
  return
159
102
 
160
- if kind in {"audio", "image", "text"} | RESOURCE_TRACK_KINDS:
103
+ if kind in {"audio", "image", "text"}:
161
104
  segments = track.get("segments")
162
105
  if not isinstance(segments, list):
163
106
  _error(f"{path}.segments", "must be an array")
@@ -173,7 +116,7 @@ def _validate_track(track, path):
173
116
  _error(f"{path}.kind", f"unsupported track kind {kind!r}")
174
117
 
175
118
 
176
- def _validate_v2(timeline):
119
+ def _validate(timeline):
177
120
  canvas = timeline.get("canvas")
178
121
  if not isinstance(canvas, dict):
179
122
  _error("canvas", "must be an object")
@@ -186,13 +129,7 @@ def _validate_v2(timeline):
186
129
  _error("canvas.fps", "must be greater than 0")
187
130
 
188
131
  _require_number(timeline, "duration", "", minimum=0)
189
- resource_packages = timeline.get("resource_packages", {})
190
- if not isinstance(resource_packages, dict):
191
- _error("resource_packages", "must be an object")
192
- for name, config in resource_packages.items():
193
- _validate_resource_config(config, f"resource_packages.{name}")
194
- if "style_presets" in timeline and not isinstance(timeline["style_presets"], dict):
195
- _error("style_presets", "must be an object")
132
+ _reject_removed(timeline, REMOVED_ROOT_FIELDS, "")
196
133
  tracks = timeline.get("tracks")
197
134
  if not isinstance(tracks, list):
198
135
  _error("tracks", "must be an array")
@@ -201,23 +138,19 @@ def _validate_v2(timeline):
201
138
 
202
139
 
203
140
  def normalize_timeline(timeline):
204
- """Return a validated schema-v2 copy, migrating schema v1 when necessary."""
141
+ """Return a validated copy of a schema-v2 timeline (the version timeline.py emits)."""
205
142
  if not isinstance(timeline, dict):
206
143
  _error("root", "must be an object")
207
144
  schema_version = timeline.get("schema_version")
208
145
  if not isinstance(schema_version, int) or isinstance(schema_version, bool):
209
- _error("schema_version", "must be integer 1 or 2")
210
- if schema_version not in {1, CURRENT_SCHEMA_VERSION}:
146
+ _error("schema_version", f"must be integer {CURRENT_SCHEMA_VERSION}")
147
+ if schema_version != CURRENT_SCHEMA_VERSION:
211
148
  raise ValueError(
212
149
  f"unsupported timeline schema_version {schema_version}; "
213
- f"supported versions are 1 and {CURRENT_SCHEMA_VERSION}"
150
+ f"only {CURRENT_SCHEMA_VERSION} is supported (a v1 timeline only needs "
151
+ f"schema_version set to {CURRENT_SCHEMA_VERSION})"
214
152
  )
215
153
 
216
154
  normalized = copy.deepcopy(timeline)
217
- if schema_version == 1:
218
- # Schema v2 is an additive extension of v1 (local image tracks). The
219
- # migration therefore preserves all authored v1 fields and only advances
220
- # the version before applying the current contract.
221
- normalized["schema_version"] = CURRENT_SCHEMA_VERSION
222
- _validate_v2(normalized)
155
+ _validate(normalized)
223
156
  return normalized
@@ -20,19 +20,11 @@ SEGMENT_RENDER_INDEX = 2
20
20
 
21
21
 
22
22
  TRACK_LAYOUT_BANDS = {
23
- "sound": TrackBand("sound", "audio", 10_000, "sound effects"),
24
23
  "audio": TrackBand("audio", "audio", 20_000, "narration, music, and general audio"),
25
- "green_screen": TrackBand("green_screen", "video", 30_000, "green-screen background"),
26
24
  "video": TrackBand("video", "video", 40_000, "base video"),
27
25
  "image": TrackBand("image", "video", 50_000, "image and photo overlays"),
28
- "mask": TrackBand("mask", "video", 60_000, "masks"),
29
- "effect": TrackBand("effect", "effect", 70_000, "video effects"),
30
- "video_effect": TrackBand("video_effect", "effect", 70_000, "video effects"),
31
- "face_effect": TrackBand("face_effect", "effect", 70_010, "face effects"),
32
- "sticker": TrackBand("sticker", "sticker", 80_000, "stickers"),
33
26
  "subtitle": TrackBand("subtitle", "text", 90_000, "subtitles"),
34
27
  "text": TrackBand("text", "text", 100_000, "plain text"),
35
- "text_template": TrackBand("text_template", "text", 110_000, "text templates"),
36
28
  }
37
29
 
38
30
 
@@ -7,7 +7,6 @@ import shutil
7
7
  import tempfile
8
8
  import time
9
9
  import uuid
10
- import zipfile
11
10
 
12
11
  from jianying.templates import template
13
12
 
@@ -37,19 +36,6 @@ def _resource_kind(material, materials_key):
37
36
  return "video", "video"
38
37
 
39
38
 
40
- RESOURCE_DIRECTORY_BY_MATERIALS_KEY = {
41
- "chromas": "effect",
42
- "common_mask": "mask",
43
- "effects": "effect",
44
- "masks": "mask",
45
- "stickers": "sticker",
46
- "texts": "text",
47
- "text_templates": "text_template",
48
- "transitions": "transition",
49
- "video_effects": "effect",
50
- }
51
-
52
-
53
39
  def _unused_name(directory, basename, used):
54
40
  """Return a collision-free filename within one resource directory."""
55
41
  name = basename
@@ -96,82 +82,13 @@ def _meta_value(material, relative_path, metetype, copied_path, timestamp_ms):
96
82
  return value
97
83
 
98
84
 
99
- def _material_sets(content):
100
- """Yield root and nested compound-draft material dictionaries."""
101
- materials = content["materials"]
102
- yield materials
103
- for draft in materials["drafts"]:
104
- yield from _material_sets(draft["draft"])
105
-
106
-
107
- def _replace_value(value, old, new):
108
- if isinstance(value, dict):
109
- for key, item in value.items():
110
- value[key] = _replace_value(item, old, new)
111
- elif isinstance(value, list):
112
- for index, item in enumerate(value):
113
- value[index] = _replace_value(item, old, new)
114
- elif isinstance(value, str):
115
- if value == old:
116
- return new
117
- return value
118
-
119
-
120
- def _replace_material_resource_path(material, materials_key, old, new):
121
- """Rewrite one declared resource, including the known rich-text JSON field."""
122
- _replace_value(material, old, new)
123
- if materials_key != "texts" or not isinstance(material.get("content"), str):
124
- return
125
- content = json.loads(material["content"])
126
- _replace_value(content, old, new)
127
- material["content"] = json.dumps(content, ensure_ascii=False, separators=(",", ":"))
128
-
129
-
130
- def _safe_resource_target(target_path):
131
- normalized = os.path.normpath(str(target_path).replace("\\", "/"))
132
- if normalized in {"", "."} or os.path.isabs(normalized):
133
- raise ValueError(f"invalid JianYing resource target_path: {target_path}")
134
- if normalized == ".." or normalized.startswith("../"):
135
- raise ValueError(f"JianYing resource target_path escapes package: {target_path}")
136
- return normalized
137
-
138
-
139
- def _extract_zip(source, destination):
140
- os.makedirs(destination, exist_ok=False)
141
- destination_real = os.path.realpath(destination)
142
- with zipfile.ZipFile(source) as archive:
143
- for member in archive.infolist():
144
- member_path = os.path.realpath(os.path.join(destination, member.filename))
145
- if os.path.commonpath((destination_real, member_path)) != destination_real:
146
- raise ValueError(f"unsafe path in JianYing resource archive: {member.filename}")
147
- archive.extractall(destination)
148
-
149
-
150
- def _copy_resource(source, resource_kind, resources_root, used, target_path=None):
85
+ def _copy_resource(source, resource_kind, resources_root, used):
151
86
  resource_dir = os.path.join(resources_root, resource_kind)
152
87
  os.makedirs(resource_dir, exist_ok=True)
153
- is_zip = os.path.isfile(source) and zipfile.is_zipfile(source)
154
- if target_path is None:
155
- basename = os.path.basename(source.rstrip(os.sep))
156
- if is_zip:
157
- basename = os.path.splitext(basename)[0]
158
- relative_target = _unused_name(resource_dir, basename, used[resource_kind])
159
- else:
160
- relative_target = _safe_resource_target(target_path)
88
+ relative_target = _unused_name(resource_dir, os.path.basename(source), used[resource_kind])
161
89
  copied_path = os.path.join(resource_dir, relative_target)
162
- copied_real = os.path.realpath(copied_path)
163
- if os.path.commonpath((os.path.realpath(resource_dir), copied_real)) != os.path.realpath(resource_dir):
164
- raise ValueError(f"JianYing resource target escapes package: {target_path}")
165
- os.makedirs(os.path.dirname(copied_path), exist_ok=True)
166
- if os.path.exists(copied_path):
167
- raise FileExistsError(f"duplicate JianYing resource target: {relative_target}")
168
- if is_zip:
169
- _extract_zip(source, copied_path)
170
- elif os.path.isdir(source):
171
- shutil.copytree(source, copied_path)
172
- else:
173
- shutil.copy2(source, copied_path)
174
- relative_path = f"Resources/local/{resource_kind}/{relative_target.replace(os.sep, '/')}"
90
+ shutil.copy2(source, copied_path)
91
+ relative_path = f"Resources/local/{resource_kind}/{relative_target}"
175
92
  return copied_path, relative_path, f"{DRAFT_PATH_PLACEHOLDER}/{relative_path}"
176
93
 
177
94
 
@@ -179,41 +96,6 @@ def _is_packaged_path(value):
179
96
  return str(value).startswith((DRAFT_PATH_PLACEHOLDER, "Resources/", "./Resources/"))
180
97
 
181
98
 
182
- def _descriptor(raw, default_kind, *, required):
183
- """`raw` is a contract-validated resource entry: an object with a non-empty source_path."""
184
- source = raw["source_path"]
185
- resource_kind = raw.get("resource_kind", default_kind)
186
- target_path = raw.get("target_path")
187
- if not isinstance(resource_kind, str) or resource_kind not in {
188
- "audio", "effect", "fonts", "image", "lut", "mask", "sticker",
189
- "text", "text_template", "transition", "video",
190
- }:
191
- raise ValueError(f"invalid JianYing resource kind: {resource_kind}")
192
- return {
193
- "source_path": source,
194
- "resource_kind": resource_kind,
195
- "target_path": target_path,
196
- "required": required,
197
- }
198
-
199
-
200
- def _material_resource_descriptors(material, default_kind):
201
- descriptors = [
202
- _descriptor(raw, default_kind, required=True)
203
- for raw in material.get("_bundle_resources", [])
204
- ]
205
- path = material.get("path")
206
- if isinstance(path, str) and path and not _is_packaged_path(path):
207
- if not any(item["source_path"] == path for item in descriptors):
208
- descriptors.append({
209
- "source_path": path,
210
- "resource_kind": default_kind,
211
- "target_path": None,
212
- "required": False,
213
- })
214
- return descriptors
215
-
216
-
217
99
  def bundle_media(content, meta, draft_dir):
218
100
  """Copy media into duo-video's Resources/local contract and index it.
219
101
 
@@ -224,83 +106,34 @@ def bundle_media(content, meta, draft_dir):
224
106
  """
225
107
  resources_root = os.path.join(draft_dir, "Resources", "local")
226
108
  copied = {}
227
- resource_kinds = {
228
- "audio", "effect", "fonts", "image", "lut", "mask", "sticker",
229
- "text", "text_template", "transition", "video",
230
- }
231
- used = {kind: set() for kind in resource_kinds}
109
+ used = {kind: set() for kind in ("audio", "image", "video")}
232
110
  meta_values = []
233
111
  notes = []
234
112
  timestamp_ms = int(time.time() * 1000)
235
113
 
236
- material_sets = list(_material_sets(content))
237
- for materials in material_sets:
238
- for materials_key in ("videos", "audios"):
239
- for material in materials.get(materials_key, []):
240
- src = material.get("path")
241
- if not src:
242
- continue
243
- resource_kind, metetype = _resource_kind(material, materials_key)
244
- source_key = (resource_kind, os.path.realpath(src))
245
- existing = copied.get(source_key)
246
- if existing is not None:
247
- material["path"] = existing["draft_path"]
248
- continue
249
- if not os.path.isfile(src):
250
- if not _is_packaged_path(src):
251
- notes.append(f"素材缺失,未打包: {src}")
252
- continue
253
-
254
- copied_path, relative_path, draft_path = _copy_resource(
255
- src, resource_kind, resources_root, used
256
- )
257
- material["path"] = draft_path
258
- copied[source_key] = {"draft_path": draft_path}
259
- meta_values.append(
260
- _meta_value(material, relative_path, metetype, copied_path, timestamp_ms)
261
- )
262
-
263
- for materials_key, resource_kind in RESOURCE_DIRECTORY_BY_MATERIALS_KEY.items():
264
- for material in materials.get(materials_key, []):
265
- descriptors = _material_resource_descriptors(
266
- material, resource_kind
267
- )
268
- seen_descriptors = set()
269
- for descriptor in descriptors:
270
- src = descriptor["source_path"]
271
- if _is_packaged_path(src):
272
- continue
273
- descriptor_key = (
274
- descriptor["resource_kind"],
275
- os.path.realpath(src),
276
- descriptor["target_path"],
277
- )
278
- if descriptor_key in seen_descriptors:
279
- continue
280
- seen_descriptors.add(descriptor_key)
281
- if not os.path.exists(src):
282
- message = f"声明的剪映资源缺失: {src}"
283
- if descriptor["required"]:
284
- raise ValueError(message)
285
- notes.append(message)
286
- continue
287
- kind = descriptor["resource_kind"]
288
- source_key = (kind, os.path.realpath(src), descriptor["target_path"])
289
- existing = copied.get(source_key)
290
- if existing is None:
291
- _copied_path, _relative_path, draft_path = _copy_resource(
292
- src,
293
- kind,
294
- resources_root,
295
- used,
296
- target_path=descriptor["target_path"],
297
- )
298
- copied[source_key] = {"draft_path": draft_path}
299
- else:
300
- draft_path = existing["draft_path"]
301
- _replace_material_resource_path(
302
- material, materials_key, src, draft_path
303
- )
114
+ materials = content["materials"]
115
+ for materials_key in ("videos", "audios"):
116
+ for material in materials[materials_key]:
117
+ src = material["path"]
118
+ resource_kind, metetype = _resource_kind(material, materials_key)
119
+ source_key = (resource_kind, os.path.realpath(src))
120
+ existing = copied.get(source_key)
121
+ if existing is not None:
122
+ material["path"] = existing
123
+ continue
124
+ if not os.path.isfile(src):
125
+ if not _is_packaged_path(src):
126
+ notes.append(f"素材缺失,未打包: {src}")
127
+ continue
128
+
129
+ copied_path, relative_path, draft_path = _copy_resource(
130
+ src, resource_kind, resources_root, used
131
+ )
132
+ material["path"] = draft_path
133
+ copied[source_key] = draft_path
134
+ meta_values.append(
135
+ _meta_value(material, relative_path, metetype, copied_path, timestamp_ms)
136
+ )
304
137
 
305
138
  material_group = next(group for group in meta["draft_materials"] if group["type"] == 0)
306
139
  material_group["value"] = meta_values
@@ -311,13 +144,6 @@ def bundle_media(content, meta, draft_dir):
311
144
  return notes
312
145
 
313
146
 
314
- def strip_internal_resource_fields(content):
315
- for materials in _material_sets(content):
316
- for entries in materials.values():
317
- for material in entries:
318
- material.pop("_bundle_resources", None)
319
-
320
-
321
147
  def draft_dir_has_user_content(draft_dir):
322
148
  """Return True when writing here could overwrite an existing draft/material."""
323
149
  if not os.path.exists(draft_dir):
@@ -359,7 +185,6 @@ def write_draft(content, meta, notes, out_dir, draft_name, bundle_media_enabled=
359
185
  os.makedirs(tmp_dir, exist_ok=False)
360
186
  if bundle_media_enabled:
361
187
  notes.extend(bundle_media(content, meta, tmp_dir))
362
- strip_internal_resource_fields(content)
363
188
  timestamp_ms = int(time.time() * 1000)
364
189
  meta["draft_name"] = actual_name
365
190
  meta["draft_fold_path"] = draft_dir