dsh-remotion 0.3.4 → 0.4.0

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 (293) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.en.md +26 -65
  3. package/README.md +25 -67
  4. package/docs/USAGE.en.md +93 -0
  5. package/docs/USAGE.md +97 -0
  6. package/docs/VALIDATION.md +22 -0
  7. package/lib/client.js +90 -0
  8. package/lib/index.d.ts +3 -0
  9. package/lib/index.js +5 -1
  10. package/lib/media-store.d.ts +25 -0
  11. package/lib/media-store.js +174 -0
  12. package/lib/media-templates.d.ts +52 -0
  13. package/lib/media-templates.js +91 -0
  14. package/lib/media-workbench.d.ts +42 -0
  15. package/lib/media-workbench.js +476 -0
  16. package/package.json +18 -5
  17. package/scripts/build-client.mjs +4 -0
  18. package/skills/remotion-best-practices/SKILL.md +54 -54
  19. package/skills/remotion-best-practices/agents/openai.yaml +7 -7
  20. package/skills/remotion-best-practices/assets/remotion-icon.svg +4 -4
  21. package/skills/remotion-best-practices/remotion-captions/REFERENCE.md +36 -36
  22. package/skills/remotion-best-practices/remotion-captions/agents/openai.yaml +7 -7
  23. package/skills/remotion-best-practices/remotion-captions/assets/remotion-icon.svg +4 -4
  24. package/skills/remotion-best-practices/remotion-captions/display-captions.md +190 -190
  25. package/skills/remotion-best-practices/remotion-captions/import-srt-captions.md +73 -73
  26. package/skills/remotion-best-practices/remotion-captions/transcribe-captions.md +70 -70
  27. package/skills/remotion-best-practices/remotion-create/REFERENCE.md +85 -85
  28. package/skills/remotion-best-practices/remotion-create/agents/openai.yaml +7 -7
  29. package/skills/remotion-best-practices/remotion-create/assets/remotion-icon.svg +4 -4
  30. package/skills/remotion-best-practices/remotion-create/tailwind.md +11 -11
  31. package/skills/remotion-best-practices/remotion-create/video-layout.md +9 -9
  32. package/skills/remotion-best-practices/remotion-docs/REFERENCE.md +46 -46
  33. package/skills/remotion-best-practices/remotion-docs/agents/openai.yaml +7 -7
  34. package/skills/remotion-best-practices/remotion-docs/assets/remotion-icon.svg +4 -4
  35. package/skills/remotion-best-practices/remotion-interactivity/REFERENCE.md +315 -315
  36. package/skills/remotion-best-practices/remotion-interactivity/agents/openai.yaml +7 -7
  37. package/skills/remotion-best-practices/remotion-interactivity/assets/remotion-icon.svg +4 -4
  38. package/skills/remotion-best-practices/remotion-maps/REFERENCE.md +36 -36
  39. package/skills/remotion-best-practices/remotion-maps/agents/openai.yaml +7 -7
  40. package/skills/remotion-best-practices/remotion-maps/assets/remotion-icon.svg +4 -4
  41. package/skills/remotion-best-practices/remotion-maps/techniques/cesium/TECHNIQUE.md +89 -89
  42. package/skills/remotion-best-practices/remotion-maps/techniques/cesium/assets/CesiumFlythrough.tsx +284 -284
  43. package/skills/remotion-best-practices/remotion-maps/techniques/cesium/assets/cesium-path.json +299 -299
  44. package/skills/remotion-best-practices/remotion-maps/techniques/cesium/assets/city-path.json +12 -12
  45. package/skills/remotion-best-practices/remotion-maps/techniques/cesium/assets/example-Root.tsx +39 -39
  46. package/skills/remotion-best-practices/remotion-maps/techniques/cesium/assets/flight-path.ts +35 -35
  47. package/skills/remotion-best-practices/remotion-maps/techniques/cesium/assets/sample-river.geojson +215 -215
  48. package/skills/remotion-best-practices/remotion-maps/techniques/cesium/references/3d-data-sources.md +43 -43
  49. package/skills/remotion-best-practices/remotion-maps/techniques/cesium/references/3d-flyover-architecture.md +128 -128
  50. package/skills/remotion-best-practices/remotion-maps/techniques/cesium/references/3d-troubleshooting.md +57 -57
  51. package/skills/remotion-best-practices/remotion-maps/techniques/cesium/scripts/prep-cesium-path.mjs +161 -161
  52. package/skills/remotion-best-practices/remotion-maps/techniques/mapbox/TECHNIQUE.md +449 -449
  53. package/skills/remotion-best-practices/remotion-maps/techniques/mapbox/references/render-stability.md +64 -64
  54. package/skills/remotion-best-practices/remotion-maps/techniques/maplibre/TECHNIQUE.md +484 -484
  55. package/skills/remotion-best-practices/remotion-maps/techniques/maplibre/references/render-stability.md +64 -64
  56. package/skills/remotion-best-practices/remotion-maps/techniques/maptiler/TECHNIQUE.md +80 -80
  57. package/skills/remotion-best-practices/remotion-maps/techniques/maptiler/assets/CountryLabel.tsx +70 -70
  58. package/skills/remotion-best-practices/remotion-maps/techniques/maptiler/assets/MapTilerVectorElement.ts +80 -80
  59. package/skills/remotion-best-practices/remotion-maps/techniques/maptiler/assets/RiverReveal.tsx +363 -363
  60. package/skills/remotion-best-practices/remotion-maps/techniques/maptiler/assets/example-Root.tsx +22 -22
  61. package/skills/remotion-best-practices/remotion-maps/techniques/maptiler/assets/sample-data/country-meta.json +7535 -7535
  62. package/skills/remotion-best-practices/remotion-maps/techniques/maptiler/assets/sample-data/yarlung-flow.json +605 -605
  63. package/skills/remotion-best-practices/remotion-maps/techniques/maptiler/assets/tokens.ts +34 -34
  64. package/skills/remotion-best-practices/remotion-maps/techniques/maptiler/references/map-data-sources.md +91 -91
  65. package/skills/remotion-best-practices/remotion-maps/techniques/maptiler/references/map-explainer-architecture.md +129 -129
  66. package/skills/remotion-best-practices/remotion-maps/techniques/maptiler/references/map-geo-prep.md +85 -85
  67. package/skills/remotion-best-practices/remotion-maps/techniques/maptiler/references/render-stability.md +64 -64
  68. package/skills/remotion-best-practices/remotion-maps/techniques/maptiler/scripts/prep-geo.mjs +202 -202
  69. package/skills/remotion-best-practices/remotion-maps/techniques/static-map/TECHNIQUE.md +37 -37
  70. package/skills/remotion-best-practices/remotion-markup/3d.md +86 -86
  71. package/skills/remotion-best-practices/remotion-markup/REFERENCE.md +358 -358
  72. package/skills/remotion-best-practices/remotion-markup/agents/openai.yaml +7 -7
  73. package/skills/remotion-best-practices/remotion-markup/assets/remotion-icon.svg +4 -4
  74. package/skills/remotion-best-practices/remotion-markup/audio-visualization.md +198 -198
  75. package/skills/remotion-best-practices/remotion-markup/audio.md +169 -169
  76. package/skills/remotion-best-practices/remotion-markup/calculate-metadata.md +136 -136
  77. package/skills/remotion-best-practices/remotion-markup/compositions.md +123 -123
  78. package/skills/remotion-best-practices/remotion-markup/connected-compositions.md +79 -79
  79. package/skills/remotion-best-practices/remotion-markup/cropping.md +34 -34
  80. package/skills/remotion-best-practices/remotion-markup/effects.md +251 -251
  81. package/skills/remotion-best-practices/remotion-markup/embedding-videos.md +171 -171
  82. package/skills/remotion-best-practices/remotion-markup/ffmpeg.md +38 -38
  83. package/skills/remotion-best-practices/remotion-markup/gifs.md +187 -187
  84. package/skills/remotion-best-practices/remotion-markup/google-fonts.md +76 -76
  85. package/skills/remotion-best-practices/remotion-markup/html-in-canvas.md +128 -128
  86. package/skills/remotion-best-practices/remotion-markup/images.md +73 -73
  87. package/skills/remotion-best-practices/remotion-markup/light-leaks.md +106 -106
  88. package/skills/remotion-best-practices/remotion-markup/local-fonts.md +69 -69
  89. package/skills/remotion-best-practices/remotion-markup/lottie.md +73 -73
  90. package/skills/remotion-best-practices/remotion-markup/measuring-dom-nodes.md +38 -38
  91. package/skills/remotion-best-practices/remotion-markup/measuring-text.md +146 -146
  92. package/skills/remotion-best-practices/remotion-markup/motion-blur.md +60 -60
  93. package/skills/remotion-best-practices/remotion-markup/multi-scene-video.md +81 -81
  94. package/skills/remotion-best-practices/remotion-markup/parameters.md +111 -111
  95. package/skills/remotion-best-practices/remotion-markup/remotion-maps/REFERENCE.md +36 -36
  96. package/skills/remotion-best-practices/remotion-markup/remotion-maps/agents/openai.yaml +7 -7
  97. package/skills/remotion-best-practices/remotion-markup/remotion-maps/assets/remotion-icon.svg +4 -4
  98. package/skills/remotion-best-practices/remotion-markup/remotion-maps/techniques/cesium/TECHNIQUE.md +89 -89
  99. package/skills/remotion-best-practices/remotion-markup/remotion-maps/techniques/cesium/assets/CesiumFlythrough.tsx +284 -284
  100. package/skills/remotion-best-practices/remotion-markup/remotion-maps/techniques/cesium/assets/cesium-path.json +299 -299
  101. package/skills/remotion-best-practices/remotion-markup/remotion-maps/techniques/cesium/assets/city-path.json +12 -12
  102. package/skills/remotion-best-practices/remotion-markup/remotion-maps/techniques/cesium/assets/example-Root.tsx +39 -39
  103. package/skills/remotion-best-practices/remotion-markup/remotion-maps/techniques/cesium/assets/flight-path.ts +35 -35
  104. package/skills/remotion-best-practices/remotion-markup/remotion-maps/techniques/cesium/assets/sample-river.geojson +215 -215
  105. package/skills/remotion-best-practices/remotion-markup/remotion-maps/techniques/cesium/references/3d-data-sources.md +43 -43
  106. package/skills/remotion-best-practices/remotion-markup/remotion-maps/techniques/cesium/references/3d-flyover-architecture.md +128 -128
  107. package/skills/remotion-best-practices/remotion-markup/remotion-maps/techniques/cesium/references/3d-troubleshooting.md +57 -57
  108. package/skills/remotion-best-practices/remotion-markup/remotion-maps/techniques/cesium/scripts/prep-cesium-path.mjs +161 -161
  109. package/skills/remotion-best-practices/remotion-markup/remotion-maps/techniques/mapbox/TECHNIQUE.md +449 -449
  110. package/skills/remotion-best-practices/remotion-markup/remotion-maps/techniques/mapbox/references/render-stability.md +64 -64
  111. package/skills/remotion-best-practices/remotion-markup/remotion-maps/techniques/maplibre/TECHNIQUE.md +484 -484
  112. package/skills/remotion-best-practices/remotion-markup/remotion-maps/techniques/maplibre/references/render-stability.md +64 -64
  113. package/skills/remotion-best-practices/remotion-markup/remotion-maps/techniques/maptiler/TECHNIQUE.md +80 -80
  114. package/skills/remotion-best-practices/remotion-markup/remotion-maps/techniques/maptiler/assets/CountryLabel.tsx +70 -70
  115. package/skills/remotion-best-practices/remotion-markup/remotion-maps/techniques/maptiler/assets/MapTilerVectorElement.ts +80 -80
  116. package/skills/remotion-best-practices/remotion-markup/remotion-maps/techniques/maptiler/assets/RiverReveal.tsx +363 -363
  117. package/skills/remotion-best-practices/remotion-markup/remotion-maps/techniques/maptiler/assets/example-Root.tsx +22 -22
  118. package/skills/remotion-best-practices/remotion-markup/remotion-maps/techniques/maptiler/assets/sample-data/country-meta.json +7535 -7535
  119. package/skills/remotion-best-practices/remotion-markup/remotion-maps/techniques/maptiler/assets/sample-data/yarlung-flow.json +605 -605
  120. package/skills/remotion-best-practices/remotion-markup/remotion-maps/techniques/maptiler/assets/tokens.ts +34 -34
  121. package/skills/remotion-best-practices/remotion-markup/remotion-maps/techniques/maptiler/references/map-data-sources.md +91 -91
  122. package/skills/remotion-best-practices/remotion-markup/remotion-maps/techniques/maptiler/references/map-explainer-architecture.md +129 -129
  123. package/skills/remotion-best-practices/remotion-markup/remotion-maps/techniques/maptiler/references/map-geo-prep.md +85 -85
  124. package/skills/remotion-best-practices/remotion-markup/remotion-maps/techniques/maptiler/references/render-stability.md +64 -64
  125. package/skills/remotion-best-practices/remotion-markup/remotion-maps/techniques/maptiler/scripts/prep-geo.mjs +202 -202
  126. package/skills/remotion-best-practices/remotion-markup/remotion-maps/techniques/static-map/TECHNIQUE.md +37 -37
  127. package/skills/remotion-best-practices/remotion-markup/sequencing.md +169 -169
  128. package/skills/remotion-best-practices/remotion-markup/sfx.md +51 -51
  129. package/skills/remotion-best-practices/remotion-markup/silence-detection.md +71 -71
  130. package/skills/remotion-best-practices/remotion-markup/text-highlights.md +56 -56
  131. package/skills/remotion-best-practices/remotion-markup/timing.md +111 -111
  132. package/skills/remotion-best-practices/remotion-markup/transitions.md +239 -239
  133. package/skills/remotion-best-practices/remotion-markup/video-editing.md +90 -90
  134. package/skills/remotion-best-practices/remotion-markup/voiceover.md +99 -99
  135. package/skills/remotion-best-practices/remotion-multimedia/REFERENCE.md +20 -20
  136. package/skills/remotion-best-practices/remotion-multimedia/agents/openai.yaml +7 -7
  137. package/skills/remotion-best-practices/remotion-multimedia/assets/remotion-icon.svg +4 -4
  138. package/skills/remotion-best-practices/remotion-multimedia/get-audio-duration.md +56 -56
  139. package/skills/remotion-best-practices/remotion-multimedia/get-video-dimensions.md +66 -66
  140. package/skills/remotion-best-practices/remotion-multimedia/get-video-duration.md +58 -58
  141. package/skills/remotion-best-practices/remotion-render/REFERENCE.md +35 -35
  142. package/skills/remotion-best-practices/remotion-render/agents/openai.yaml +7 -7
  143. package/skills/remotion-best-practices/remotion-render/assets/remotion-icon.svg +4 -4
  144. package/skills/remotion-best-practices/remotion-render/transparent-videos.md +106 -106
  145. package/skills/remotion-best-practices/remotion-saas/REFERENCE.md +33 -33
  146. package/skills/remotion-best-practices/remotion-saas/agents/openai.yaml +7 -7
  147. package/skills/remotion-best-practices/remotion-saas/assets/remotion-icon.svg +4 -4
  148. package/skills/remotion-best-practices/remotion-saas/framework.md +19 -19
  149. package/skills/remotion-best-practices/remotion-saas/player.md +33 -33
  150. package/skills/remotion-best-practices/remotion-saas/rendering.md +69 -69
  151. package/skills/remotion-best-practices/remotion-studio/REFERENCE.md +24 -24
  152. package/skills/remotion-best-practices/remotion-studio/agents/openai.yaml +7 -7
  153. package/skills/remotion-best-practices/remotion-studio/assets/remotion-icon.svg +4 -4
  154. package/skills/remotion-best-practices/remotion-upgrade/REFERENCE.md +31 -31
  155. package/skills/remotion-best-practices/remotion-upgrade/agents/openai.yaml +7 -7
  156. package/skills/remotion-best-practices/remotion-upgrade/assets/remotion-icon.svg +4 -4
  157. package/skills/remotion-captions/SKILL.md +31 -31
  158. package/skills/remotion-captions/agents/openai.yaml +7 -7
  159. package/skills/remotion-captions/assets/remotion-icon.svg +4 -4
  160. package/skills/remotion-captions/display-captions.md +190 -190
  161. package/skills/remotion-captions/import-srt-captions.md +73 -73
  162. package/skills/remotion-captions/transcribe-captions.md +70 -70
  163. package/skills/remotion-create/SKILL.md +80 -80
  164. package/skills/remotion-create/agents/openai.yaml +7 -7
  165. package/skills/remotion-create/assets/remotion-icon.svg +4 -4
  166. package/skills/remotion-create/tailwind.md +11 -11
  167. package/skills/remotion-create/video-layout.md +9 -9
  168. package/skills/remotion-docs/SKILL.md +41 -41
  169. package/skills/remotion-docs/agents/openai.yaml +7 -7
  170. package/skills/remotion-docs/assets/remotion-icon.svg +4 -4
  171. package/skills/remotion-interactivity/SKILL.md +310 -310
  172. package/skills/remotion-interactivity/agents/openai.yaml +7 -7
  173. package/skills/remotion-interactivity/assets/remotion-icon.svg +4 -4
  174. package/skills/remotion-maps/SKILL.md +31 -31
  175. package/skills/remotion-maps/agents/openai.yaml +7 -7
  176. package/skills/remotion-maps/assets/remotion-icon.svg +4 -4
  177. package/skills/remotion-maps/techniques/cesium/TECHNIQUE.md +89 -89
  178. package/skills/remotion-maps/techniques/cesium/assets/CesiumFlythrough.tsx +284 -284
  179. package/skills/remotion-maps/techniques/cesium/assets/cesium-path.json +299 -299
  180. package/skills/remotion-maps/techniques/cesium/assets/city-path.json +12 -12
  181. package/skills/remotion-maps/techniques/cesium/assets/example-Root.tsx +39 -39
  182. package/skills/remotion-maps/techniques/cesium/assets/flight-path.ts +35 -35
  183. package/skills/remotion-maps/techniques/cesium/assets/sample-river.geojson +215 -215
  184. package/skills/remotion-maps/techniques/cesium/references/3d-data-sources.md +43 -43
  185. package/skills/remotion-maps/techniques/cesium/references/3d-flyover-architecture.md +128 -128
  186. package/skills/remotion-maps/techniques/cesium/references/3d-troubleshooting.md +57 -57
  187. package/skills/remotion-maps/techniques/cesium/scripts/prep-cesium-path.mjs +161 -161
  188. package/skills/remotion-maps/techniques/mapbox/TECHNIQUE.md +449 -449
  189. package/skills/remotion-maps/techniques/mapbox/references/render-stability.md +64 -64
  190. package/skills/remotion-maps/techniques/maplibre/TECHNIQUE.md +484 -484
  191. package/skills/remotion-maps/techniques/maplibre/references/render-stability.md +64 -64
  192. package/skills/remotion-maps/techniques/maptiler/TECHNIQUE.md +80 -80
  193. package/skills/remotion-maps/techniques/maptiler/assets/CountryLabel.tsx +70 -70
  194. package/skills/remotion-maps/techniques/maptiler/assets/MapTilerVectorElement.ts +80 -80
  195. package/skills/remotion-maps/techniques/maptiler/assets/RiverReveal.tsx +363 -363
  196. package/skills/remotion-maps/techniques/maptiler/assets/example-Root.tsx +22 -22
  197. package/skills/remotion-maps/techniques/maptiler/assets/sample-data/country-meta.json +7535 -7535
  198. package/skills/remotion-maps/techniques/maptiler/assets/sample-data/yarlung-flow.json +605 -605
  199. package/skills/remotion-maps/techniques/maptiler/assets/tokens.ts +34 -34
  200. package/skills/remotion-maps/techniques/maptiler/references/map-data-sources.md +91 -91
  201. package/skills/remotion-maps/techniques/maptiler/references/map-explainer-architecture.md +129 -129
  202. package/skills/remotion-maps/techniques/maptiler/references/map-geo-prep.md +85 -85
  203. package/skills/remotion-maps/techniques/maptiler/references/render-stability.md +64 -64
  204. package/skills/remotion-maps/techniques/maptiler/scripts/prep-geo.mjs +202 -202
  205. package/skills/remotion-maps/techniques/static-map/TECHNIQUE.md +37 -37
  206. package/skills/remotion-markup/3d.md +86 -86
  207. package/skills/remotion-markup/SKILL.md +353 -353
  208. package/skills/remotion-markup/agents/openai.yaml +7 -7
  209. package/skills/remotion-markup/assets/remotion-icon.svg +4 -4
  210. package/skills/remotion-markup/audio-visualization.md +198 -198
  211. package/skills/remotion-markup/audio.md +169 -169
  212. package/skills/remotion-markup/calculate-metadata.md +136 -136
  213. package/skills/remotion-markup/compositions.md +123 -123
  214. package/skills/remotion-markup/connected-compositions.md +79 -79
  215. package/skills/remotion-markup/cropping.md +34 -34
  216. package/skills/remotion-markup/effects.md +251 -251
  217. package/skills/remotion-markup/embedding-videos.md +171 -171
  218. package/skills/remotion-markup/ffmpeg.md +38 -38
  219. package/skills/remotion-markup/gifs.md +187 -187
  220. package/skills/remotion-markup/google-fonts.md +76 -76
  221. package/skills/remotion-markup/html-in-canvas.md +128 -128
  222. package/skills/remotion-markup/images.md +73 -73
  223. package/skills/remotion-markup/light-leaks.md +106 -106
  224. package/skills/remotion-markup/local-fonts.md +69 -69
  225. package/skills/remotion-markup/lottie.md +73 -73
  226. package/skills/remotion-markup/measuring-dom-nodes.md +38 -38
  227. package/skills/remotion-markup/measuring-text.md +146 -146
  228. package/skills/remotion-markup/motion-blur.md +60 -60
  229. package/skills/remotion-markup/multi-scene-video.md +81 -81
  230. package/skills/remotion-markup/parameters.md +111 -111
  231. package/skills/remotion-markup/remotion-maps/REFERENCE.md +36 -36
  232. package/skills/remotion-markup/remotion-maps/agents/openai.yaml +7 -7
  233. package/skills/remotion-markup/remotion-maps/assets/remotion-icon.svg +4 -4
  234. package/skills/remotion-markup/remotion-maps/techniques/cesium/TECHNIQUE.md +89 -89
  235. package/skills/remotion-markup/remotion-maps/techniques/cesium/assets/CesiumFlythrough.tsx +284 -284
  236. package/skills/remotion-markup/remotion-maps/techniques/cesium/assets/cesium-path.json +299 -299
  237. package/skills/remotion-markup/remotion-maps/techniques/cesium/assets/city-path.json +12 -12
  238. package/skills/remotion-markup/remotion-maps/techniques/cesium/assets/example-Root.tsx +39 -39
  239. package/skills/remotion-markup/remotion-maps/techniques/cesium/assets/flight-path.ts +35 -35
  240. package/skills/remotion-markup/remotion-maps/techniques/cesium/assets/sample-river.geojson +215 -215
  241. package/skills/remotion-markup/remotion-maps/techniques/cesium/references/3d-data-sources.md +43 -43
  242. package/skills/remotion-markup/remotion-maps/techniques/cesium/references/3d-flyover-architecture.md +128 -128
  243. package/skills/remotion-markup/remotion-maps/techniques/cesium/references/3d-troubleshooting.md +57 -57
  244. package/skills/remotion-markup/remotion-maps/techniques/cesium/scripts/prep-cesium-path.mjs +161 -161
  245. package/skills/remotion-markup/remotion-maps/techniques/mapbox/TECHNIQUE.md +449 -449
  246. package/skills/remotion-markup/remotion-maps/techniques/mapbox/references/render-stability.md +64 -64
  247. package/skills/remotion-markup/remotion-maps/techniques/maplibre/TECHNIQUE.md +484 -484
  248. package/skills/remotion-markup/remotion-maps/techniques/maplibre/references/render-stability.md +64 -64
  249. package/skills/remotion-markup/remotion-maps/techniques/maptiler/TECHNIQUE.md +80 -80
  250. package/skills/remotion-markup/remotion-maps/techniques/maptiler/assets/CountryLabel.tsx +70 -70
  251. package/skills/remotion-markup/remotion-maps/techniques/maptiler/assets/MapTilerVectorElement.ts +80 -80
  252. package/skills/remotion-markup/remotion-maps/techniques/maptiler/assets/RiverReveal.tsx +363 -363
  253. package/skills/remotion-markup/remotion-maps/techniques/maptiler/assets/example-Root.tsx +22 -22
  254. package/skills/remotion-markup/remotion-maps/techniques/maptiler/assets/sample-data/country-meta.json +7535 -7535
  255. package/skills/remotion-markup/remotion-maps/techniques/maptiler/assets/sample-data/yarlung-flow.json +605 -605
  256. package/skills/remotion-markup/remotion-maps/techniques/maptiler/assets/tokens.ts +34 -34
  257. package/skills/remotion-markup/remotion-maps/techniques/maptiler/references/map-data-sources.md +91 -91
  258. package/skills/remotion-markup/remotion-maps/techniques/maptiler/references/map-explainer-architecture.md +129 -129
  259. package/skills/remotion-markup/remotion-maps/techniques/maptiler/references/map-geo-prep.md +85 -85
  260. package/skills/remotion-markup/remotion-maps/techniques/maptiler/references/render-stability.md +64 -64
  261. package/skills/remotion-markup/remotion-maps/techniques/maptiler/scripts/prep-geo.mjs +202 -202
  262. package/skills/remotion-markup/remotion-maps/techniques/static-map/TECHNIQUE.md +37 -37
  263. package/skills/remotion-markup/sequencing.md +169 -169
  264. package/skills/remotion-markup/sfx.md +51 -51
  265. package/skills/remotion-markup/silence-detection.md +71 -71
  266. package/skills/remotion-markup/text-highlights.md +56 -56
  267. package/skills/remotion-markup/timing.md +111 -111
  268. package/skills/remotion-markup/transitions.md +239 -239
  269. package/skills/remotion-markup/video-editing.md +90 -90
  270. package/skills/remotion-markup/voiceover.md +99 -99
  271. package/skills/remotion-multimedia/SKILL.md +15 -15
  272. package/skills/remotion-multimedia/agents/openai.yaml +7 -7
  273. package/skills/remotion-multimedia/assets/remotion-icon.svg +4 -4
  274. package/skills/remotion-multimedia/get-audio-duration.md +56 -56
  275. package/skills/remotion-multimedia/get-video-dimensions.md +66 -66
  276. package/skills/remotion-multimedia/get-video-duration.md +58 -58
  277. package/skills/remotion-render/SKILL.md +30 -30
  278. package/skills/remotion-render/agents/openai.yaml +7 -7
  279. package/skills/remotion-render/assets/remotion-icon.svg +4 -4
  280. package/skills/remotion-render/transparent-videos.md +106 -106
  281. package/skills/remotion-saas/SKILL.md +28 -28
  282. package/skills/remotion-saas/agents/openai.yaml +7 -7
  283. package/skills/remotion-saas/assets/remotion-icon.svg +4 -4
  284. package/skills/remotion-saas/framework.md +19 -19
  285. package/skills/remotion-saas/player.md +33 -33
  286. package/skills/remotion-saas/rendering.md +69 -69
  287. package/skills/remotion-studio/SKILL.md +19 -19
  288. package/skills/remotion-studio/agents/openai.yaml +7 -7
  289. package/skills/remotion-studio/assets/remotion-icon.svg +4 -4
  290. package/skills/remotion-upgrade/SKILL.md +26 -26
  291. package/skills/remotion-upgrade/agents/openai.yaml +7 -7
  292. package/skills/remotion-upgrade/assets/remotion-icon.svg +4 -4
  293. package/templates/DESIGN.md +26 -0
@@ -0,0 +1,476 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { spawn, execFile } from 'node:child_process';
3
+ import { existsSync } from 'node:fs';
4
+ import { mkdir, readFile, writeFile, stat, symlink, copyFile, open, unlink, realpath } from 'node:fs/promises';
5
+ import { basename, dirname, join, delimiter, toNamespacedPath } from 'node:path';
6
+ import { createServer } from 'node:net';
7
+ import lockfile from 'proper-lockfile';
8
+ import { zipSync, strToU8 } from 'fflate';
9
+ import { MediaStore, mediaId, MediaConflict } from './media-store.js';
10
+ import { MEDIA_VERSIONS, MEDIA_TEMPLATES, projectSources } from './media-templates.js';
11
+ const mime = { png: 'image/png', jpg: 'image/jpeg', mp3: 'audio/mpeg', wav: 'audio/wav', mp4: 'video/mp4' };
12
+ const clean = (text) => text.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, '').replace(/(https?:\/\/)[^\s/@]+:[^\s/@]+@/gi, '$1[redacted]@').replace(/(token|password|authorization)(\s*[=:]\s*)[^\s,;]+/gi, '$1$2[redacted]').slice(0, 1600);
13
+ const errorMessage = (e) => clean(e instanceof Error ? e.message : String(e));
14
+ function installedChromePaths() {
15
+ if (process.platform === 'darwin')
16
+ return ['/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'];
17
+ if (process.platform !== 'win32')
18
+ return ['/usr/bin/google-chrome', '/usr/bin/google-chrome-stable', '/usr/bin/chromium', '/usr/bin/chromium-browser'];
19
+ return [join(process.env.ProgramFiles || 'C:/Program Files', 'Google/Chrome/Application/chrome.exe'), join(process.env['ProgramFiles(x86)'] || 'C:/Program Files (x86)', 'Google/Chrome/Application/chrome.exe'), ...(process.env.LOCALAPPDATA ? [join(process.env.LOCALAPPDATA, 'Google/Chrome/Application/chrome.exe')] : [])];
20
+ }
21
+ function npmCli() {
22
+ const paths = [join(dirname(process.execPath), 'node_modules/npm/bin/npm-cli.js'), join(dirname(process.execPath), '../lib/node_modules/npm/bin/npm-cli.js')];
23
+ for (const path of (process.env.PATH || '').split(delimiter))
24
+ paths.push(join(path, 'node_modules/npm/bin/npm-cli.js'), join(path, '../node_modules/npm/bin/npm-cli.js'));
25
+ if (process.env.APPDATA)
26
+ paths.push(join(process.env.APPDATA, 'npm/node_modules/npm/bin/npm-cli.js'));
27
+ const file = paths.find(existsSync);
28
+ if (!file)
29
+ throw new Error('没有找到 npm。请安装 Node.js 22.19+ 或 24+,重启 DSH 后再准备环境。');
30
+ return file;
31
+ }
32
+ function childEnv(root) {
33
+ const result = {};
34
+ for (const [key, value] of Object.entries(process.env))
35
+ if (/^(path|pathext|systemroot|windir|userprofile|home|appdata|localappdata|https?_proxy|all_proxy|no_proxy|comspec|programfiles|programfiles\(x86\))$/i.test(key))
36
+ result[key] = value;
37
+ return { ...result, ELECTRON_RUN_AS_NODE: '1', DSH_DESKTOP_NODE_EXECUTABLE: process.execPath, HOME: join(root, 'user-home'), USERPROFILE: join(root, 'user-home'), APPDATA: join(root, 'user-home', 'AppData/Roaming'), LOCALAPPDATA: join(root, 'user-home', 'AppData/Local'), CI: '1', NO_COLOR: '1', BROWSER: 'none', TEMP: join(root, 'tmp'), TMP: join(root, 'tmp'), TMPDIR: join(root, 'tmp'), npm_config_userconfig: join(root, 'local.npmrc'), npm_config_cache: join(root, 'npm-cache'), PUPPETEER_CACHE_DIR: join(root, 'browser'), HYPERFRAMES_NO_UPDATE_CHECK: '1', HYPERFRAMES_SKIP_SKILLS: '1', HYPERFRAMES_TELEMETRY_DISABLED: '1', DO_NOT_TRACK: '1', HYPERFRAMES_EXTRACT_CACHE_DIR: join(root, 'frames') };
38
+ }
39
+ async function freePort() {
40
+ return new Promise((resolve, reject) => { const server = createServer(); server.once('error', reject); server.listen(0, '127.0.0.1', () => { const address = server.address(); const port = typeof address === 'object' && address ? address.port : 0; server.close(() => resolve(port)); }); });
41
+ }
42
+ /** Restrict all servers in our renderer child to loopback using Node's public API. */
43
+ const LOOPBACK = `const net=require('node:net');const listen=net.Server.prototype.listen;net.Server.prototype.listen=function(...args){if(args[0]&&typeof args[0]==='object'&&'port'in args[0])args[0]={...args[0],host:'127.0.0.1'};else if(typeof args[0]==='number'){if(typeof args[1]==='string')args[1]='127.0.0.1';else args.splice(1,0,'127.0.0.1')}return listen.apply(this,args)};\n`;
44
+ export class MediaWorkbench {
45
+ engine;
46
+ store;
47
+ route;
48
+ runtime;
49
+ jobs = new Map();
50
+ disposed = false;
51
+ constructor(engine, root) {
52
+ this.engine = engine;
53
+ this.store = new MediaStore(engine, root);
54
+ this.route = '/api/dsh-' + engine + '/workbench';
55
+ this.runtime = join(this.store.root, 'runtime');
56
+ }
57
+ async browserFile(value) {
58
+ if (typeof value !== 'string')
59
+ throw new Error('请重新准备渲染环境,浏览器路径尚未验证。');
60
+ const file = await realpath(value), normalize = (p) => process.platform === 'win32' ? p.toLowerCase() : p;
61
+ const known = await Promise.all(installedChromePaths().map(p => realpath(p).catch(() => '')));
62
+ if (!known.some(p => normalize(p) === normalize(file)))
63
+ await this.store.checkPath(file);
64
+ if (!(await stat(file)).isFile())
65
+ throw new Error('浏览器文件不存在,请重新准备环境。');
66
+ return file;
67
+ }
68
+ async environment() {
69
+ const version = MEDIA_VERSIONS[this.engine];
70
+ let ready = false;
71
+ try {
72
+ const pkg = JSON.parse(await readFile(join(this.runtime, 'node_modules', this.engine === 'hyperframes' ? 'hyperframes' : '@remotion/cli', 'package.json'), 'utf8'));
73
+ const stamp = JSON.parse(await readFile(join(this.runtime, 'ready.json'), 'utf8'));
74
+ const file = await this.browserFile(stamp.browserPath);
75
+ ready = pkg.version === version && stamp.version === version && !(this.engine === 'hyperframes' && process.platform === 'win32' && !/^chrome-headless-shell\.exe$/i.test(basename(file)));
76
+ }
77
+ catch { }
78
+ return { ready, version, node: process.version, hint: this.engine === 'hyperframes' ? '首次下载官方 CLI;Windows 使用官方专用渲染浏览器,其他系统优先复用 Chrome。还需 PATH 中的 FFmpeg。' : '首次下载官方 CLI。优先使用已安装的 Chrome,没有时下载浏览器。Remotion 使用官方许可证:https://www.remotion.dev/license' };
79
+ }
80
+ job(id) { const job = this.jobs.get(mediaId(id)); if (!job)
81
+ throw new Error('任务不存在或 DSH 已重启。工程仍保留,请检查成品后重新操作。'); return structuredClone(job.view); }
82
+ log(job, text) {
83
+ const normalize = (line) => line.replace(/^[◓◑◒◐]\s*/, '').replace(/[. ]+$/, '');
84
+ for (const line of text.split(/[\r\n]+/).filter(Boolean).map(clean)) {
85
+ if (normalize(line) === normalize(job.view.lines.at(-1) || ''))
86
+ continue;
87
+ job.view.lines.push(line);
88
+ if (job.view.lines.length > 40)
89
+ job.view.lines.shift();
90
+ }
91
+ }
92
+ stopChild(job) {
93
+ const child = job.child;
94
+ if (!child?.pid || child.exitCode !== null || child.signalCode !== null)
95
+ return;
96
+ if (process.platform === 'win32')
97
+ execFile(join(process.env.SystemRoot || 'C:/Windows', 'System32/taskkill.exe'), ['/PID', String(child.pid), '/T', '/F'], { windowsHide: true }, () => { });
98
+ else {
99
+ try {
100
+ process.kill(-child.pid, 'SIGKILL');
101
+ }
102
+ catch {
103
+ child.kill('SIGKILL');
104
+ }
105
+ }
106
+ }
107
+ async run(job, file, args, cwd, environment = {}) {
108
+ job.controller.signal.throwIfAborted();
109
+ await mkdir(join(this.store.root, 'tmp'), { recursive: true });
110
+ await mkdir(join(this.store.root, 'user-home/AppData/Roaming'), { recursive: true });
111
+ await mkdir(join(this.store.root, 'user-home/AppData/Local'), { recursive: true });
112
+ return new Promise((resolve, reject) => {
113
+ const child = spawn(file, args, { cwd, env: { ...childEnv(this.store.root), ...environment }, windowsHide: true, shell: false, detached: process.platform !== 'win32', stdio: ['ignore', 'pipe', 'pipe'] });
114
+ job.child = child;
115
+ const abort = () => this.stopChild(job);
116
+ job.controller.signal.addEventListener('abort', abort, { once: true });
117
+ child.stdout?.on('data', data => this.log(job, data.toString()));
118
+ child.stderr?.on('data', data => this.log(job, data.toString()));
119
+ const finish = () => { job.controller.signal.removeEventListener('abort', abort); if (job.child === child)
120
+ job.child = undefined; };
121
+ child.once('error', e => { finish(); reject(e); });
122
+ child.once('close', code => { finish(); if (job.controller.signal.aborted)
123
+ reject(new Error('操作已取消。'));
124
+ else if (code !== 0) {
125
+ const useful = job.view.lines.filter(line => /^(?:\w*Error:|npm error)|\b(?:ENOENT|EACCES|ECONNRESET)\b|Unknown flag|Failed to |DevTools remote debugging/.test(line));
126
+ reject(new Error(`官方 ${this.engine} 命令未完成(退出码 ${code})。\n` + (useful.length ? useful.slice(0, 3) : job.view.lines.slice(-5)).join('\n')));
127
+ }
128
+ else
129
+ resolve(); });
130
+ });
131
+ }
132
+ cli() { return join(this.runtime, 'node_modules', this.engine === 'hyperframes' ? 'hyperframes/bin/hyperframes.mjs' : '@remotion/cli/remotion-cli.js'); }
133
+ async start(kind, id, revision) {
134
+ if (this.disposed)
135
+ throw new Error('插件已卸载,请重新加载。');
136
+ if ([...this.jobs.values()].some(j => j.view.state === 'running' && (j.view.kind === kind || j.view.kind === 'prepare' || kind === 'prepare')))
137
+ throw new Error('已有同类任务运行,请等待完成或明确取消。');
138
+ if (kind !== 'prepare' && !(await this.environment()).ready)
139
+ throw new Error('请先点击“准备渲染环境”,完成首次下载。');
140
+ const snapshot = kind === 'prepare' ? undefined : await this.store.snapshot(mediaId(id), revision);
141
+ if (snapshot)
142
+ projectSources(snapshot.project, this.engine);
143
+ const job = { view: { id: randomUUID(), kind, projectId: snapshot?.project.id, revision: snapshot?.project.revision, state: 'running', stage: kind === 'prepare' ? '下载环境' : '准备修订快照', lines: [] }, controller: new AbortController() };
144
+ if (this.jobs.size >= 30) {
145
+ const old = [...this.jobs.values()].find(j => j.view.state !== 'running');
146
+ if (old)
147
+ this.jobs.delete(old.view.id);
148
+ else
149
+ throw new Error('运行任务过多。');
150
+ }
151
+ this.jobs.set(job.view.id, job);
152
+ void this.execute(job, snapshot);
153
+ return structuredClone(job.view);
154
+ }
155
+ async execute(job, snapshot) {
156
+ let unlock;
157
+ const timeout = setTimeout(() => job.controller.abort(), job.view.kind === 'preview' ? 30 * 60000 : 20 * 60000);
158
+ timeout.unref();
159
+ try {
160
+ await this.store.init();
161
+ await mkdir(this.runtime, { recursive: true });
162
+ const slot = join(this.store.root, 'workbench-slot');
163
+ await mkdir(slot, { recursive: true });
164
+ unlock = await lockfile.lock(slot, { retries: 0, stale: 30000, update: 10000, onCompromised: () => job.controller.abort() });
165
+ if (job.view.kind === 'prepare') {
166
+ await unlink(join(this.runtime, 'ready.json')).catch(() => { });
167
+ if (this.engine === 'hyperframes') {
168
+ job.view.stage = '检查 FFmpeg';
169
+ await this.run(job, 'ffmpeg', ['-version'], this.runtime);
170
+ }
171
+ const dependencies = this.engine === 'hyperframes' ? { hyperframes: MEDIA_VERSIONS.hyperframes, gsap: MEDIA_VERSIONS.gsap } : { '@remotion/cli': MEDIA_VERSIONS.remotion, remotion: MEDIA_VERSIONS.remotion, react: '18.3.1', 'react-dom': '18.3.1' };
172
+ await writeFile(join(this.runtime, 'package.json'), JSON.stringify({ private: true, dependencies }));
173
+ job.view.stage = '下载官方依赖';
174
+ await this.run(job, process.execPath, [npmCli(), 'install', '--ignore-scripts', '--no-audit', '--no-fund', '--registry=https://registry.npmjs.org'], this.runtime);
175
+ job.view.stage = '检查官方环境';
176
+ await this.run(job, process.execPath, [this.cli(), ...(this.engine === 'hyperframes' ? ['--version'] : ['versions'])], this.runtime);
177
+ job.view.stage = '检查或下载 Chrome';
178
+ if (this.engine === 'hyperframes') {
179
+ if (process.platform === 'win32') {
180
+ job.view.stage = '下载专用渲染浏览器';
181
+ // ensure prefers the managed shell; path alone would fall back to desktop Chrome.
182
+ // Avoid --force: the pinned CLI leaves its first spinner running after a forced install.
183
+ await this.run(job, process.execPath, [this.cli(), 'browser', 'ensure'], this.runtime);
184
+ }
185
+ await this.run(job, process.execPath, [this.cli(), 'browser', 'path'], this.runtime);
186
+ }
187
+ else {
188
+ const installed = installedChromePaths().find(existsSync);
189
+ if (!installed)
190
+ await this.run(job, process.execPath, [this.cli(), 'browser', 'ensure'], this.runtime);
191
+ await this.run(job, process.execPath, ['-e', "require('@remotion/renderer').ensureBrowser({logLevel:'error',browserExecutable:process.argv[1]||null}).then(v=>console.log(require('node:fs').realpathSync(v.path))).catch(e=>{console.error(e.message);process.exitCode=1})", installed || ''], this.runtime);
192
+ }
193
+ const browserPath = await this.browserFile(job.view.lines.at(-1).trim());
194
+ await writeFile(join(this.runtime, 'ready.json'), JSON.stringify({ version: MEDIA_VERSIONS[this.engine], browserPath }));
195
+ }
196
+ else if (snapshot) {
197
+ const p = snapshot.project, dir = join(await this.store.path(p.id), 'runs', job.view.id);
198
+ await mkdir(dirname(dir), { recursive: true });
199
+ if (this.engine === 'hyperframes')
200
+ await this.run(job, process.execPath, [this.cli(), 'init', dir, '--example', 'blank', '--non-interactive', '--skip-transcribe'], dirname(dir));
201
+ await mkdir(join(dir, 'public/assets'), { recursive: true });
202
+ for (const [file, bytes] of snapshot.assets)
203
+ await writeFile(join(dir, 'public/assets', file), bytes, { flag: 'wx' });
204
+ for (const [file, content] of Object.entries(projectSources(p, this.engine))) {
205
+ await mkdir(dirname(join(dir, file)), { recursive: true });
206
+ await writeFile(join(dir, file), content);
207
+ }
208
+ if (this.engine === 'hyperframes')
209
+ await writeFile(join(dir, 'meta.json'), JSON.stringify({ id: job.view.id, name: p.title, createdAt: p.updated }));
210
+ await writeFile(join(dir, 'loopback.cjs'), LOOPBACK);
211
+ await symlink(join(this.runtime, 'node_modules'), join(dir, 'node_modules'), process.platform === 'win32' ? 'junction' : 'dir');
212
+ if (this.engine === 'hyperframes') {
213
+ await mkdir(join(dir, 'vendor'), { recursive: true });
214
+ await copyFile(join(this.runtime, 'node_modules/gsap/dist/gsap.min.js'), join(dir, 'vendor/gsap.min.js'));
215
+ }
216
+ if (job.view.kind === 'preview') {
217
+ const port = await freePort(), url = `http://127.0.0.1:${port}/`;
218
+ job.view.stage = '启动官方预览';
219
+ const args = this.engine === 'hyperframes' ? ['preview', '.', '--port', String(port), '--no-open', '--foreground'] : ['studio', 'src/index.jsx', '--port', String(port), '--no-open', '--ipv4', '--disable-ask-ai'];
220
+ let exited = false;
221
+ const running = this.run(job, process.execPath, ['--require', join(dir, 'loopback.cjs'), this.cli(), ...args], dir).finally(() => { exited = true; });
222
+ // Poll the actual server; a chosen port or spawned process is not readiness.
223
+ const poll = async () => {
224
+ for (let i = 0; i < 180; i++) {
225
+ job.controller.signal.throwIfAborted();
226
+ if (exited)
227
+ return;
228
+ try {
229
+ const response = await fetch(url, { signal: AbortSignal.timeout(1500) });
230
+ if (response.ok) {
231
+ job.view.url = this.engine === 'hyperframes' ? url + '#project/' + job.view.id : url;
232
+ job.view.stage = '官方预览已就绪';
233
+ return;
234
+ }
235
+ }
236
+ catch { }
237
+ await new Promise(resolve => setTimeout(resolve, 500));
238
+ }
239
+ job.controller.abort();
240
+ };
241
+ await Promise.all([running, poll()]);
242
+ }
243
+ else {
244
+ if (this.engine === 'hyperframes') {
245
+ job.view.stage = '检查合成';
246
+ await this.run(job, process.execPath, [this.cli(), 'lint', '.', '--json'], dir);
247
+ }
248
+ job.view.stage = '渲染 MP4';
249
+ const output = join(dir, 'final.mp4');
250
+ const browserFile = await this.browserFile(JSON.parse(await readFile(join(this.runtime, 'ready.json'), 'utf8')).browserPath);
251
+ const browserPath = browserFile.length >= 240 ? toNamespacedPath(browserFile) : browserFile;
252
+ const chromeMode = /^chrome-headless-shell(?:\.exe)?$/i.test(basename(browserFile)) ? 'headless-shell' : 'chrome-for-testing';
253
+ const args = this.engine === 'hyperframes' ? ['render', '.', '--output', output, '--fps', '24', '--quality', 'draft', '--workers', '1', '--strict', '--no-best-effort', '--no-browser-gpu'] : ['render', 'src/index.jsx', 'ManagedVideo', output, '--codec', 'h264', '--concurrency', '1', '--browser-executable', browserPath, '--chrome-mode', chromeMode];
254
+ await this.run(job, process.execPath, ['--require', join(dir, 'loopback.cjs'), this.cli(), ...args], dir, this.engine === 'hyperframes' ? { HYPERFRAMES_BROWSER_PATH: browserPath } : {});
255
+ const bytes = (await stat(output)).size;
256
+ if (bytes < 100 || bytes > 200 * 1024 * 1024)
257
+ throw new Error('MP4 输出为空或超过 200 MB,请检查运行记录。原工程已保留。');
258
+ const handle = await open(output, 'r'), header = Buffer.alloc(12);
259
+ try {
260
+ await handle.read(header, 0, 12, 0);
261
+ }
262
+ finally {
263
+ await handle.close();
264
+ }
265
+ if (header.toString('ascii', 4, 8) !== 'ftyp')
266
+ throw new Error('渲染器输出不是 MP4 文件,原工程已保留。');
267
+ job.controller.signal.throwIfAborted();
268
+ await this.store.edit(p.id, undefined, current => { current.renders = [{ id: job.view.id, revision: p.revision, bytes, created: new Date().toISOString() }, ...current.renders].slice(0, 5); }, false);
269
+ job.view.output = job.view.id;
270
+ }
271
+ }
272
+ job.view.state = job.controller.signal.aborted ? 'cancelled' : 'done';
273
+ job.view.stage = job.view.state === 'done' ? '已完成' : '已取消';
274
+ }
275
+ catch (e) {
276
+ const message = e?.code === 'ELOCKED' ? '另一个 DSH 实例正在运行本插件任务,请等待完成或在那个实例中停止任务。' : errorMessage(e);
277
+ job.view.state = job.controller.signal.aborted ? 'cancelled' : 'failed';
278
+ job.view.stage = job.view.state === 'cancelled' ? '已取消;工程已保留' : /^官方 .*命令未完成/.test(message) ? '官方任务未完成,请展开运行记录后重试。工程已保留。' : /spawn ffmpeg ENOENT/.test(message) ? '未找到 FFmpeg,请安装并加入 PATH,重启 DSH 后重试。工程已保留。' : message;
279
+ this.log(job, message);
280
+ }
281
+ finally {
282
+ clearTimeout(timeout);
283
+ if (unlock)
284
+ await unlock().catch(() => { });
285
+ job.view.url = job.view.state === 'running' ? job.view.url : undefined;
286
+ }
287
+ }
288
+ cancel(id) { const job = this.jobs.get(mediaId(id)); if (!job)
289
+ throw new Error('任务不存在。'); if (job.view.state === 'running')
290
+ job.controller.abort(); return structuredClone(job.view); }
291
+ dispose() { this.disposed = true; for (const job of this.jobs.values())
292
+ if (job.view.state === 'running')
293
+ job.controller.abort(); }
294
+ guard(request) {
295
+ const url = new URL(request.url), origin = request.headers.get('origin'), site = request.headers.get('sec-fetch-site');
296
+ // The official carrier keeps Host but rewrites Request.url to its internal URL.
297
+ if (site && !['same-origin', 'none'].includes(site))
298
+ throw new Error('请从 DSH 页面操作。');
299
+ if (origin) {
300
+ const source = new URL(origin);
301
+ if (!['http:', 'https:'].includes(source.protocol) || source.host !== (request.headers.get('host') || url.host))
302
+ throw new Error('请从 DSH 页面操作。');
303
+ }
304
+ if (request.method === 'POST' && request.headers.get('x-dsh-media') !== '1')
305
+ throw new Error('操作来源无效。');
306
+ }
307
+ async fetch(request) {
308
+ try {
309
+ this.guard(request);
310
+ const url = new URL(request.url);
311
+ if (request.method === 'GET') {
312
+ const id = mediaId(url.searchParams.get('id')), p = await this.store.get(id), kind = url.searchParams.get('kind');
313
+ if (kind === 'source') {
314
+ const snapshot = await this.store.snapshot(id, p.revision), files = {};
315
+ for (const [file, content] of Object.entries(projectSources(p, this.engine)))
316
+ files[file] = strToU8(content);
317
+ for (const [file, bytes] of snapshot.assets)
318
+ files['public/assets/' + file] = bytes;
319
+ const bytes = zipSync(files, { level: 1 });
320
+ return new Response(bytes, { headers: { 'content-type': 'application/zip', 'content-disposition': `attachment; filename="${this.engine}-${id}.zip"`, 'cache-control': 'no-store', 'x-content-type-options': 'nosniff' } });
321
+ }
322
+ let file, type;
323
+ if (kind === 'asset') {
324
+ const a = p.assets.find(a => a.id === mediaId(url.searchParams.get('asset')));
325
+ if (!a)
326
+ throw new Error('素材不存在。');
327
+ file = join(await this.store.path(id), 'assets', a.file);
328
+ type = mime[a.file.split('.').at(-1)];
329
+ }
330
+ else if (kind === 'mp4') {
331
+ const r = p.renders.find(r => r.id === mediaId(url.searchParams.get('render')));
332
+ if (!r)
333
+ throw new Error('成品不存在。');
334
+ file = join(await this.store.path(id), 'runs', r.id, 'final.mp4');
335
+ type = mime.mp4;
336
+ }
337
+ else
338
+ throw new Error('下载类型无效。');
339
+ await this.store.checkPath(file);
340
+ const size = (await stat(file)).size;
341
+ if (size > 200 * 1024 * 1024)
342
+ throw new Error('成品过大,请从本地工程目录取回。');
343
+ const bytes = await readFile(file), range = request.headers.get('range'), headers = { 'content-type': type, 'cache-control': 'no-store', 'x-content-type-options': 'nosniff', 'accept-ranges': 'bytes' };
344
+ if (range) {
345
+ const match = /^bytes=(\d+)-(\d*)$/.exec(range);
346
+ if (!match)
347
+ return new Response(null, { status: 416, headers: { 'content-range': `bytes */${size}` } });
348
+ const start = Number(match[1]), end = match[2] ? Math.min(Number(match[2]), size - 1) : size - 1;
349
+ if (start > end || start >= size)
350
+ return new Response(null, { status: 416, headers: { 'content-range': `bytes */${size}` } });
351
+ return new Response(bytes.subarray(start, end + 1), { status: 206, headers: { ...headers, 'content-range': `bytes ${start}-${end}/${size}`, 'content-length': String(end - start + 1) } });
352
+ }
353
+ if (url.searchParams.get('download') === '1')
354
+ headers['content-disposition'] = `attachment; filename="${this.engine}-${id}.${kind === 'mp4' ? 'mp4' : file.split('.').at(-1)}"`;
355
+ return new Response(bytes, { headers: { ...headers, 'content-length': String(size) } });
356
+ }
357
+ if (request.method !== 'POST')
358
+ return new Response(null, { status: 405 });
359
+ let value;
360
+ if (url.pathname.endsWith('/upload')) {
361
+ const reader = request.body?.getReader(), chunks = [];
362
+ let size = 0;
363
+ if (!reader)
364
+ throw new Error('请选择素材。');
365
+ try {
366
+ while (true) {
367
+ const chunk = await reader.read();
368
+ if (chunk.done)
369
+ break;
370
+ size += chunk.value.length;
371
+ if (size > 20 * 1024 * 1024)
372
+ throw new Error('单个素材不能超过 20 MB。');
373
+ chunks.push(chunk.value);
374
+ }
375
+ }
376
+ finally {
377
+ await reader.cancel().catch(() => { });
378
+ }
379
+ value = await this.store.upload(mediaId(request.headers.get('x-project-id')), Number(request.headers.get('x-project-revision')), decodeURIComponent(request.headers.get('x-file-name') || ''), Buffer.concat(chunks));
380
+ }
381
+ else {
382
+ if (!request.headers.get('content-type')?.startsWith('application/json'))
383
+ throw new Error('请求格式无效。');
384
+ const text = await request.text();
385
+ if (Buffer.byteLength(text) > 16384)
386
+ throw new Error('请求过大。');
387
+ const v = JSON.parse(text);
388
+ switch (v.action) {
389
+ case 'list':
390
+ value = { projects: await this.store.list(), templates: MEDIA_TEMPLATES, environment: await this.environment(), jobs: [...this.jobs.values()].map(j => structuredClone(j.view)) };
391
+ break;
392
+ case 'create':
393
+ value = await this.store.create(v.fields);
394
+ break;
395
+ case 'get':
396
+ value = await this.store.get(mediaId(v.id));
397
+ break;
398
+ case 'update':
399
+ value = await this.store.update(mediaId(v.id), v.revision, v.fields);
400
+ break;
401
+ case 'removeAsset':
402
+ value = await this.store.removeAsset(mediaId(v.id), v.revision, mediaId(v.asset));
403
+ break;
404
+ case 'prepare':
405
+ value = await this.start('prepare');
406
+ break;
407
+ case 'preview':
408
+ case 'render':
409
+ value = await this.start(v.action, mediaId(v.id), v.revision);
410
+ break;
411
+ case 'job':
412
+ value = this.job(mediaId(v.job));
413
+ break;
414
+ case 'cancel':
415
+ value = this.cancel(mediaId(v.job));
416
+ break;
417
+ default: throw new Error('操作无效。');
418
+ }
419
+ }
420
+ return Response.json({ ok: true, value }, { headers: { 'cache-control': 'no-store' } });
421
+ }
422
+ catch (e) {
423
+ return Response.json({ ok: false, message: errorMessage(e) }, { status: e instanceof MediaConflict ? 409 : 400, headers: { 'cache-control': 'no-store' } });
424
+ }
425
+ }
426
+ }
427
+ export function installMediaWorkbench(ctx, engine) {
428
+ const workbench = new MediaWorkbench(engine);
429
+ ctx.inject?.(['connection'], (host) => {
430
+ host.connection?.fetch?.register({ path: workbench.route, methods: ['GET', 'POST'], requestBody: 'buffered', fetch: (request) => workbench.fetch(request) });
431
+ host.connection?.fetch?.register({ path: workbench.route + '/upload', methods: ['POST'], requestBody: 'streaming', fetch: (request) => workbench.fetch(request) });
432
+ });
433
+ ctx.on?.('dispose', () => workbench.dispose());
434
+ for (const action of ['project', 'render']) {
435
+ const dispose = ctx.tools?.register({
436
+ name: engine + '_' + action,
437
+ description: action === 'project' ? '创建或读取本插件受管理视频工程,列出模板与环境。不会自动安装依赖或渲染。' : '准备官方渲染环境、启动预览/MP4任务、查询进度或取消。工程保存在 DSH 本地数据目录;首次准备环境会下载依赖。',
438
+ parameters: { type: 'object', properties: { action: { type: 'string', enum: action === 'project' ? ['list', 'create', 'get', 'update'] : ['prepare', 'preview', 'render', 'job', 'cancel'] }, id: { type: 'string' }, revision: { type: 'number' }, job: { type: 'string' }, fields: { type: 'object', properties: { template: { type: 'string', enum: ['title', 'product', 'slideshow'] }, title: { type: 'string' }, body: { type: 'string' }, format: { type: 'string', enum: ['landscape', 'portrait', 'square'] }, duration: { type: 'number' } }, required: ['template', 'title', 'body', 'format', 'duration'] } }, required: ['action'] },
439
+ output: {
440
+ schema: { type: 'object', additionalProperties: true },
441
+ render(_args, value) {
442
+ const lines = [engine === 'hyperframes' ? 'HyperFrames 视频工作台' : 'Remotion 视频工作台'];
443
+ if (Array.isArray(value.projects)) {
444
+ lines.push('已保存工程:' + value.projects.length);
445
+ for (const project of value.projects)
446
+ lines.push('- ' + project.title + '(版本 ' + project.revision + ')');
447
+ lines.push(value.environment?.ready ? '渲染环境已就绪。' : '首次预览或导出前,请显式准备渲染环境。');
448
+ }
449
+ else if (value.title)
450
+ lines.push('工程:' + value.title + '(版本 ' + value.revision + ')');
451
+ else if (value.state) {
452
+ lines.push('任务状态:' + value.state, value.stage || '');
453
+ if (value.url)
454
+ lines.push('预览:' + value.url);
455
+ if (value.message)
456
+ lines.push(value.message);
457
+ }
458
+ return [{ type: 'text', text: lines.filter(Boolean).join('\n') }];
459
+ },
460
+ },
461
+ async execute(args) {
462
+ const allowed = action === 'project' ? ['list', 'create', 'get', 'update'] : ['prepare', 'preview', 'render', 'job', 'cancel'];
463
+ if (!allowed.includes(args.action))
464
+ throw new Error('操作无效。');
465
+ const response = await workbench.fetch(new Request('http://dsh.internal' + workbench.route, { method: 'POST', headers: { 'content-type': 'application/json', 'x-dsh-media': '1' }, body: JSON.stringify(args) }));
466
+ const result = await response.json();
467
+ if (!result.ok)
468
+ throw new Error(result.message);
469
+ return result.value;
470
+ },
471
+ });
472
+ if (typeof dispose === 'function')
473
+ ctx.on?.('dispose', dispose);
474
+ }
475
+ return workbench;
476
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-remotion",
3
- "version": "0.3.4",
4
- "description": "DSH 视频创作技能插件:注册 Remotion 官方 12 个技能(React 编程式视频:动画/音频/字幕/3D/地图/图表),安装即用。",
3
+ "version": "0.4.0",
4
+ "description": "DSH 视频插件:Remotion 技能、模板与素材工作台、官方 Studio 预览和本地 MP4 导出。",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
7
7
  "types": "lib/index.d.ts",
@@ -11,7 +11,8 @@
11
11
  "default": "./lib/index.js"
12
12
  },
13
13
  "./cordis.patch.yml": "./cordis.patch.yml",
14
- "./package.json": "./package.json"
14
+ "./package.json": "./package.json",
15
+ "./client": "./lib/client.js"
15
16
  },
16
17
  "files": [
17
18
  "lib",
@@ -19,10 +20,13 @@
19
20
  "cordis.patch.yml",
20
21
  "README.md",
21
22
  "README.en.md",
22
- "scripts"
23
+ "scripts",
24
+ "docs",
25
+ "CHANGELOG.md",
26
+ "templates/DESIGN.md"
23
27
  ],
24
28
  "scripts": {
25
- "build": "tsc -p tsconfig.json",
29
+ "build": "tsc -p tsconfig.json && node scripts/build-client.mjs",
26
30
  "typecheck": "tsc -p tsconfig.json --noEmit",
27
31
  "test": "pnpm run build && node --test \"test/*.test.mjs\"",
28
32
  "prepublishOnly": "pnpm run build"
@@ -30,6 +34,12 @@
30
34
  "dsh": {
31
35
  "bundle": {
32
36
  "patch": "./cordis.patch.yml"
37
+ },
38
+ "client": {
39
+ "inject": [
40
+ "@deepseek-ai/dsh-client-ui-settings"
41
+ ],
42
+ "platform": "web"
33
43
  }
34
44
  },
35
45
  "keywords": [
@@ -45,10 +55,13 @@
45
55
  "node": "^22.19.0 || >=24.0.0"
46
56
  },
47
57
  "dependencies": {
58
+ "fflate": "0.8.3",
59
+ "proper-lockfile": "4.1.2",
48
60
  "yaml": "^2.9.0"
49
61
  },
50
62
  "devDependencies": {
51
63
  "@types/node": "^24.0.0",
64
+ "@types/proper-lockfile": "4.1.4",
52
65
  "typescript": "^5.6.0"
53
66
  },
54
67
  "repository": {
@@ -0,0 +1,4 @@
1
+ import { readFileSync, writeFileSync } from 'node:fs'
2
+ const engine = JSON.parse(readFileSync('package.json', 'utf8')).name.slice(4)
3
+ if (!['hyperframes','remotion'].includes(engine)) throw new Error('Unexpected package')
4
+ writeFileSync('lib/client.js', readFileSync('templates/client.js', 'utf8').replaceAll('__ENGINE__', engine))