dsh-drawai 0.4.1 → 0.4.3

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 (365) hide show
  1. package/README.md +96 -253
  2. package/lib/client.js +28 -7
  3. package/lib/index.js +474 -97
  4. package/lib/motion/asset-pack.js +109 -50
  5. package/lib/motion/assets.js +291 -291
  6. package/lib/motion/demo-assets-lock.js +10 -0
  7. package/lib/motion/mgstyles/vendor/README.md +28 -0
  8. package/lib/motion/mgstyles/vendor/modules/d3/LICENSE +13 -0
  9. package/lib/motion/mgstyles/vendor/modules/d3/dist/d3.min.js +2 -0
  10. package/lib/motion/mgstyles/vendor/modules/opentype.js/LICENSE +20 -0
  11. package/lib/motion/mgstyles/vendor/modules/opentype.js/dist/opentype.mjs +14460 -0
  12. package/lib/motion/mgstyles/vendor/modules/simplex-noise/LICENSE +21 -0
  13. package/lib/motion/mgstyles/vendor/modules/simplex-noise/dist/esm/simplex-noise.js +466 -0
  14. package/lib/motion/mgstyles/vendor/modules/topojson-client/LICENSE +13 -0
  15. package/lib/motion/mgstyles/vendor/modules/topojson-client/dist/topojson-client.min.js +2 -0
  16. package/lib/motion/preview.js +10 -3
  17. package/lib/motion/render.js +19 -1
  18. package/lib/motion/shotcraft/upstream/SKILL.md +286 -286
  19. package/lib/motion/shotcraft/upstream/assets/lib/Caption.tsx +54 -54
  20. package/lib/motion/shotcraft/upstream/assets/lib/ClipCard.tsx +200 -200
  21. package/lib/motion/shotcraft/upstream/assets/lib/DigitRoll.tsx +45 -45
  22. package/lib/motion/shotcraft/upstream/assets/lib/FlashCut.tsx +20 -20
  23. package/lib/motion/shotcraft/upstream/assets/lib/FlatPanel.tsx +65 -65
  24. package/lib/motion/shotcraft/upstream/assets/lib/PageCam.tsx +151 -151
  25. package/lib/motion/shotcraft/upstream/assets/lib/VerticalTicker.tsx +129 -129
  26. package/lib/motion/shotcraft/upstream/assets/lib/helpers/camera.tsx +75 -75
  27. package/lib/motion/shotcraft/upstream/assets/lib/helpers/motion.ts +40 -40
  28. package/lib/motion/shotcraft/upstream/assets/lib/helpers/rand.ts +11 -11
  29. package/lib/motion/shotcraft/upstream/assets/lib/helpers/shake.ts +10 -10
  30. package/lib/motion/shotcraft/upstream/assets/scripts/build-readme-cover.mjs +49 -49
  31. package/lib/motion/shotcraft/upstream/assets/scripts/capture-template.mjs +210 -210
  32. package/lib/motion/shotcraft/upstream/assets/scripts/sfx-source.py +423 -423
  33. package/lib/motion/shotcraft/upstream/assets/scripts/smoke-render-demos.py +170 -170
  34. package/lib/motion/shotcraft/upstream/assets/scripts/test_sfx_source.py +157 -157
  35. package/lib/motion/shotcraft/upstream/demos/README.md +120 -120
  36. package/lib/motion/shotcraft/upstream/demos/_fixtures/Brand.tsx +112 -112
  37. package/lib/motion/shotcraft/upstream/demos/_fixtures/Fixtures.tsx +647 -647
  38. package/lib/motion/shotcraft/upstream/demos/_fixtures/Look.tsx +316 -316
  39. package/lib/motion/shotcraft/upstream/demos/_fixtures/Motion.tsx +82 -82
  40. package/lib/motion/shotcraft/upstream/demos/_fixtures/PageCam2D.tsx +115 -115
  41. package/lib/motion/shotcraft/upstream/demos/_fixtures/Polish.tsx +243 -243
  42. package/lib/motion/shotcraft/upstream/demos/_textures/live-layout.json +388 -388
  43. package/lib/motion/shotcraft/upstream/demos/camera/basic-3d-scene/Basic3DScene.tsx +250 -250
  44. package/lib/motion/shotcraft/upstream/demos/camera/crash-zoom-punch/CrashImpactReal.tsx +305 -305
  45. package/lib/motion/shotcraft/upstream/demos/camera/crash-zoom-punch/CrashZoomReal.tsx +246 -246
  46. package/lib/motion/shotcraft/upstream/demos/camera/cursor-flyover/CursorFlyover.tsx +390 -390
  47. package/lib/motion/shotcraft/upstream/demos/camera/depth-layer-moves/MultiplaneReal.tsx +138 -138
  48. package/lib/motion/shotcraft/upstream/demos/camera/graze-face-tour/GrazeFaceTour.tsx +542 -542
  49. package/lib/motion/shotcraft/upstream/demos/camera/overhead-camera-moves/OverheadTabletopDrop.tsx +315 -315
  50. package/lib/motion/shotcraft/upstream/demos/camera/overhead-camera-moves/TiltReveal.tsx +299 -299
  51. package/lib/motion/shotcraft/upstream/demos/camera/space-camera-moves/DroneDiveLanding.tsx +173 -173
  52. package/lib/motion/shotcraft/upstream/demos/camera/space-camera-moves/ExplodedView.tsx +348 -348
  53. package/lib/motion/shotcraft/upstream/demos/camera/steep-tilt-glide/SteepTiltGlide.tsx +366 -366
  54. package/lib/motion/shotcraft/upstream/demos/camera/tension-camera-moves/BulletTimeFreezeOrbit.tsx +279 -279
  55. package/lib/motion/shotcraft/upstream/demos/camera/tension-camera-moves/PullBackIsolation.tsx +184 -184
  56. package/lib/motion/shotcraft/upstream/demos/camera/terminal-3d/Terminal3D.tsx +390 -390
  57. package/lib/motion/shotcraft/upstream/demos/data/before-after-slider-scrub/BeforeAfterSliderScrub.tsx +155 -155
  58. package/lib/motion/shotcraft/upstream/demos/data/chart-live-moves/AxisRescaleShockV2.tsx +303 -303
  59. package/lib/motion/shotcraft/upstream/demos/data/chart-live-moves/OscilloscopeStreamV2.tsx +290 -290
  60. package/lib/motion/shotcraft/upstream/demos/data/chart-live-moves/UnitDotSwarmRegroupV2.tsx +278 -278
  61. package/lib/motion/shotcraft/upstream/demos/data/gauge-readout-moves/NeedleSweepSelftest.tsx +307 -307
  62. package/lib/motion/shotcraft/upstream/demos/data/gauge-readout-moves/TapeScrollFixedPointer.tsx +215 -215
  63. package/lib/motion/shotcraft/upstream/demos/data/scroll-brake-moves/BrakeReticleLock.tsx +299 -299
  64. package/lib/motion/shotcraft/upstream/demos/data/scroll-brake-moves/ChangelogScrollBrake.tsx +232 -232
  65. package/lib/motion/shotcraft/upstream/demos/data/timeline-travel/TimelineTravel.tsx +290 -290
  66. package/lib/motion/shotcraft/upstream/demos/effects/assemble-then-type-flyin/AssembleThenTypeFlyin.tsx +273 -273
  67. package/lib/motion/shotcraft/upstream/demos/effects/aurora-bloom-bg-flip/AuroraBloomBgFlip.tsx +167 -167
  68. package/lib/motion/shotcraft/upstream/demos/effects/brand-frame-snap/BrandFrameSnap.tsx +536 -536
  69. package/lib/motion/shotcraft/upstream/demos/effects/fui-hud-moves/ReticleLockOn.tsx +164 -164
  70. package/lib/motion/shotcraft/upstream/demos/effects/glow-flyline-moves/FlylineArc.tsx +175 -175
  71. package/lib/motion/shotcraft/upstream/demos/effects/glow-flyline-moves/GlowOrbAmbient.tsx +197 -197
  72. package/lib/motion/shotcraft/upstream/demos/effects/glow-flyline-moves/OrbFlylineRelay.tsx +241 -241
  73. package/lib/motion/shotcraft/upstream/demos/effects/icon-performance-moves/AttentionBounce.tsx +279 -279
  74. package/lib/motion/shotcraft/upstream/demos/effects/impact-feedback/AnimeImpact.tsx +230 -230
  75. package/lib/motion/shotcraft/upstream/demos/effects/impact-feedback/HitCounter.tsx +345 -345
  76. package/lib/motion/shotcraft/upstream/demos/effects/light-play-moves/SheenSweepRetry.tsx +275 -275
  77. package/lib/motion/shotcraft/upstream/demos/effects/light-play-moves/SpotlightSweepReveal.tsx +229 -229
  78. package/lib/motion/shotcraft/upstream/demos/effects/radial-ripple-phone-chips/RadialRipplePhoneChips.tsx +312 -312
  79. package/lib/motion/shotcraft/upstream/demos/effects/riso-print-hits/RisoMisregistrationHit.tsx +222 -222
  80. package/lib/motion/shotcraft/upstream/demos/effects/scan-bracket-sweep/ScanBracketSweep.tsx +295 -295
  81. package/lib/motion/shotcraft/upstream/demos/effects/scanline-annotate-focus/ScanlineAnnotateFocus.tsx +329 -329
  82. package/lib/motion/shotcraft/upstream/demos/effects/scanline-assemble-flyin/ScanlineAssembleFlyin.tsx +315 -315
  83. package/lib/motion/shotcraft/upstream/demos/effects/slam-entrance-moves/ImpactBurstKit.tsx +264 -264
  84. package/lib/motion/shotcraft/upstream/demos/effects/slam-entrance-moves/KanadaPerspectiveSnap.tsx +260 -260
  85. package/lib/motion/shotcraft/upstream/demos/effects/slam-entrance-moves/ScoreSlam.tsx +263 -263
  86. package/lib/motion/shotcraft/upstream/demos/effects/spotlight-sweep-moves/CornerSpotlightReveal.tsx +123 -123
  87. package/lib/motion/shotcraft/upstream/demos/effects/spotlight-sweep-moves/GlowWakeSleepPanel.tsx +187 -187
  88. package/lib/motion/shotcraft/upstream/demos/effects/spotlight-sweep-moves/SlideSpotlightPan.tsx +175 -175
  89. package/lib/motion/shotcraft/upstream/demos/interaction/autolayout-gap-dial/AutolayoutGapDial.tsx +305 -305
  90. package/lib/motion/shotcraft/upstream/demos/interaction/canvas-materialize-moves/DiagramCascadeBuild.tsx +350 -350
  91. package/lib/motion/shotcraft/upstream/demos/interaction/canvas-materialize-moves/PanelToCanvasMaterialize.tsx +352 -352
  92. package/lib/motion/shotcraft/upstream/demos/interaction/chip-grid-single-select-blackout/ChipGridSingleSelectBlackout.tsx +218 -218
  93. package/lib/motion/shotcraft/upstream/demos/interaction/collab-cursor-moves/CursorCastEnsemble.tsx +297 -297
  94. package/lib/motion/shotcraft/upstream/demos/interaction/glass-pill-dictation-typing/GlassPillDictationTyping.tsx +157 -157
  95. package/lib/motion/shotcraft/upstream/demos/interaction/hashtag-to-pill-materialize/HashtagToPillMaterialize.tsx +218 -218
  96. package/lib/motion/shotcraft/upstream/demos/interaction/picker-carousel-feature-cycle/PickerCarouselFeatureCycle.tsx +202 -202
  97. package/lib/motion/shotcraft/upstream/demos/interaction/segmented-thumb-hero/SegmentedThumbHero.tsx +265 -265
  98. package/lib/motion/shotcraft/upstream/demos/interaction/theme-switch-moves/PaletteThemeRipple.tsx +457 -457
  99. package/lib/motion/shotcraft/upstream/demos/interaction/theme-switch-moves/ThemeSweepToggle.tsx +446 -446
  100. package/lib/motion/shotcraft/upstream/demos/interaction/type-and-filter/TypeAndFilter.tsx +249 -249
  101. package/lib/motion/shotcraft/upstream/demos/interaction/voice-waveform-live/VoiceWaveformLive.tsx +278 -278
  102. package/lib/motion/shotcraft/upstream/demos/opening/crane-rise-reveal/CraneRiseReveal.tsx +332 -332
  103. package/lib/motion/shotcraft/upstream/demos/opening/dataviz-landscape-open/DatavizLandscapeOpen.tsx +358 -358
  104. package/lib/motion/shotcraft/upstream/demos/opening/fracture/Fracture.tsx +201 -201
  105. package/lib/motion/shotcraft/upstream/demos/opening/icon-field-colorize/IconFieldColorize.tsx +200 -200
  106. package/lib/motion/shotcraft/upstream/demos/opening/letterspace-materialize/LetterspaceMaterialize.tsx +208 -208
  107. package/lib/motion/shotcraft/upstream/demos/opening/magician-card-flourish/MagicianCardFlourish.tsx +341 -341
  108. package/lib/motion/shotcraft/upstream/demos/opening/orbit-ring-title-open/OrbitRingTitleOpen.tsx +389 -389
  109. package/lib/motion/shotcraft/upstream/demos/opening/spotlight-hero-card/SpotlightHeroCard.tsx +293 -293
  110. package/lib/motion/shotcraft/upstream/demos/opening/stroke-segment-build/StrokeSegmentBuild.tsx +183 -183
  111. package/lib/motion/shotcraft/upstream/demos/opening/text-as-mask/TextAsMask.tsx +245 -245
  112. package/lib/motion/shotcraft/upstream/demos/outro/grain-dissolve/GrainDissolve.tsx +222 -222
  113. package/lib/motion/shotcraft/upstream/demos/outro/logo-shrink-wordmark-lockup/LogoShrinkWordmarkLockup.tsx +223 -223
  114. package/lib/motion/shotcraft/upstream/demos/outro/neon-triple-marquee/NeonTripleMarquee.tsx +217 -217
  115. package/lib/motion/shotcraft/upstream/demos/outro/outro-group-photo-launch/OutroGroupPhotoLaunch.tsx +343 -343
  116. package/lib/motion/shotcraft/upstream/demos/outro/ui-strip-away-outro/UiStripAwayOutro.tsx +357 -357
  117. package/lib/motion/shotcraft/upstream/demos/outro/ui-to-brand-morph/IconFlipBloomLogo.tsx +187 -187
  118. package/lib/motion/shotcraft/upstream/demos/outro/ui-to-brand-morph/InputMorphsIntoLogo.tsx +333 -333
  119. package/lib/motion/shotcraft/upstream/demos/rhythm/beat-cut-moves/PaparazziFlash.tsx +274 -274
  120. package/lib/motion/shotcraft/upstream/demos/rhythm/beat-step-list-theme-cycle/BeatStepListThemeCycle.tsx +270 -270
  121. package/lib/motion/shotcraft/upstream/demos/rhythm/panel-grid-moves/ComicPanelSplit.tsx +430 -430
  122. package/lib/motion/shotcraft/upstream/demos/rhythm/quad-split-parallel-scenes/QuadSplitParallelScenes.tsx +252 -252
  123. package/lib/motion/shotcraft/upstream/demos/rhythm/rhythm-interrupt-moves/JumpCutPunchIn.tsx +106 -106
  124. package/lib/motion/shotcraft/upstream/demos/rhythm/sakuga-timing-shift/SakugaTimingShift.tsx +288 -288
  125. package/lib/motion/shotcraft/upstream/demos/rhythm/speed-ramp-freeze/FreezeAnnotateReal.tsx +362 -362
  126. package/lib/motion/shotcraft/upstream/demos/rhythm/trailer-grammar-moves/TrailerBumper.tsx +168 -168
  127. package/lib/motion/shotcraft/upstream/demos/transition/bottom-push-stack-wipe/BottomPushStackWipe.tsx +310 -310
  128. package/lib/motion/shotcraft/upstream/demos/transition/card-flip-reveal/CardFlipReveal.tsx +261 -261
  129. package/lib/motion/shotcraft/upstream/demos/transition/card-flock-tumble/CardFlockTumble.tsx +459 -459
  130. package/lib/motion/shotcraft/upstream/demos/transition/circle-match-iris/CircleMatchIris.tsx +294 -294
  131. package/lib/motion/shotcraft/upstream/demos/transition/gradient-transition/GradientTransition.tsx +161 -161
  132. package/lib/motion/shotcraft/upstream/demos/transition/line-carry-transition/LineCarryTransition.tsx +248 -248
  133. package/lib/motion/shotcraft/upstream/demos/transition/mosaic-reframe/MosaicReframe.tsx +359 -359
  134. package/lib/motion/shotcraft/upstream/demos/transition/page-turn-transitions/BarnDoorSplit.tsx +227 -227
  135. package/lib/motion/shotcraft/upstream/demos/transition/print-texture-transitions/InkBleedReveal.tsx +162 -162
  136. package/lib/motion/shotcraft/upstream/demos/transition/shot-transitions/PortalWipeV2.tsx +248 -248
  137. package/lib/motion/shotcraft/upstream/demos/transition/shot-transitions/WhipBrakeReal.tsx +186 -186
  138. package/lib/motion/shotcraft/upstream/demos/transition/tear-streak-transitions/GlitchDisplace.tsx +355 -355
  139. package/lib/motion/shotcraft/upstream/demos/transition/transition-hidden-cut/InvisibleCut.tsx +272 -272
  140. package/lib/motion/shotcraft/upstream/demos/transition/transition-hidden-cut/LightLeakBurn.tsx +262 -262
  141. package/lib/motion/shotcraft/upstream/demos/transition/transition-hidden-cut/VersusSlam.tsx +217 -217
  142. package/lib/motion/shotcraft/upstream/demos/transition/transition-travel/LetterformZoom.tsx +285 -285
  143. package/lib/motion/shotcraft/upstream/demos/transition/white-flash-logo-simplify-cut/WhiteFlashLogoSimplifyCut.tsx +205 -205
  144. package/lib/motion/shotcraft/upstream/demos/transition/wipe-transitions/BlindsSlice.tsx +178 -178
  145. package/lib/motion/shotcraft/upstream/demos/typography/blur-slide/BlurSlide.tsx +147 -147
  146. package/lib/motion/shotcraft/upstream/demos/typography/brace-expand/BraceExpand.tsx +143 -143
  147. package/lib/motion/shotcraft/upstream/demos/typography/brand-ink-open/BrandInkOpen.tsx +134 -134
  148. package/lib/motion/shotcraft/upstream/demos/typography/countdown-arc-scatter/CountdownArcScatter.tsx +217 -217
  149. package/lib/motion/shotcraft/upstream/demos/typography/flying-words/FlyingWords.tsx +198 -198
  150. package/lib/motion/shotcraft/upstream/demos/typography/glitch-cycle/GlitchCycle.tsx +221 -221
  151. package/lib/motion/shotcraft/upstream/demos/typography/gradient-word-sweep/GradientWordSweep.tsx +312 -312
  152. package/lib/motion/shotcraft/upstream/demos/typography/lead-word-zoom-assemble/LeadWordZoomAssemble.tsx +261 -261
  153. package/lib/motion/shotcraft/upstream/demos/typography/marker-underline-title/MarkerUnderlineTitle.tsx +121 -121
  154. package/lib/motion/shotcraft/upstream/demos/typography/outline-word-fill/OutlineWordFill.tsx +153 -153
  155. package/lib/motion/shotcraft/upstream/demos/typography/paper-title-card/PaperTitleCard.tsx +179 -179
  156. package/lib/motion/shotcraft/upstream/demos/typography/pill-chip-slot-cycle-handled/PillChipSlotCycleHandled.tsx +140 -140
  157. package/lib/motion/shotcraft/upstream/demos/typography/pill-slot-cycle/PillSlotCycle.tsx +309 -309
  158. package/lib/motion/shotcraft/upstream/demos/typography/text-column-converge/TextColumnConverge.tsx +191 -191
  159. package/lib/motion/shotcraft/upstream/demos/typography/title-demote-to-label/TitleDemoteToLabel.tsx +292 -292
  160. package/lib/motion/shotcraft/upstream/demos/typography/type-assembly-moves/LetterformDriftAssembly.tsx +135 -135
  161. package/lib/motion/shotcraft/upstream/demos/typography/type-assembly-moves/SplitTextStagger.tsx +124 -124
  162. package/lib/motion/shotcraft/upstream/demos/typography/type-assembly-moves/TextOnPath.tsx +255 -255
  163. package/lib/motion/shotcraft/upstream/demos/typography/type-entrance-moves/LetterDropPhysics.tsx +173 -173
  164. package/lib/motion/shotcraft/upstream/demos/typography/type-rhythm-sync/FontWeightPump.tsx +225 -225
  165. package/lib/motion/shotcraft/upstream/demos/typography/type-rhythm-sync/KaraokeFillSync.tsx +141 -141
  166. package/lib/motion/shotcraft/upstream/demos/typography/typewriter-moves/TypewriterErrorRetype.tsx +137 -137
  167. package/lib/motion/shotcraft/upstream/demos/typography/vertical-word-roll-blur-cycle/VerticalWordRollBlurCycle.tsx +147 -147
  168. package/lib/motion/shotcraft/upstream/demos/typography/word-relay-filmstrip/WordRelayFilmstrip.tsx +409 -409
  169. package/lib/motion/shotcraft/upstream/demos/typography/word-relay-geometry/WordRelayGeometry.tsx +280 -280
  170. package/lib/motion/shotcraft/upstream/demos/ui-entrance/avatar-bracket-carousel/AvatarBracketCarousel.tsx +271 -271
  171. package/lib/motion/shotcraft/upstream/demos/ui-entrance/bezier-source-converge-merge/BezierSourceConvergeMerge.tsx +385 -385
  172. package/lib/motion/shotcraft/upstream/demos/ui-entrance/card-stack/CardStack.tsx +312 -312
  173. package/lib/motion/shotcraft/upstream/demos/ui-entrance/carousel-3d/Carousel3D.tsx +303 -303
  174. package/lib/motion/shotcraft/upstream/demos/ui-entrance/clipcard-looping/ClipCardLooping.tsx +77 -77
  175. package/lib/motion/shotcraft/upstream/demos/ui-entrance/cloner-depth-echo/ClonerDepthEcho.tsx +311 -311
  176. package/lib/motion/shotcraft/upstream/demos/ui-entrance/deck-deal-flyin/DeckDealFlyin.tsx +307 -307
  177. package/lib/motion/shotcraft/upstream/demos/ui-entrance/doc-park-left-pill-deal/DocParkLeftPillDeal.tsx +349 -349
  178. package/lib/motion/shotcraft/upstream/demos/ui-entrance/document-typewriter-reveal/DocumentTypewriterReveal.tsx +255 -255
  179. package/lib/motion/shotcraft/upstream/demos/ui-entrance/draw-svg-trace/DrawSvgTrace.tsx +244 -244
  180. package/lib/motion/shotcraft/upstream/demos/ui-entrance/element-body-moves/ContactShadowLift.tsx +217 -217
  181. package/lib/motion/shotcraft/upstream/demos/ui-entrance/integration-hub-map/IntegrationHubMap.tsx +652 -652
  182. package/lib/motion/shotcraft/upstream/demos/ui-entrance/list-reveal/ListReveal.tsx +153 -153
  183. package/lib/motion/shotcraft/upstream/demos/ui-entrance/list-stack-press/ListStackPress.tsx +238 -238
  184. package/lib/motion/shotcraft/upstream/demos/ui-entrance/neon-frame-forerun/NeonFrameForerun.tsx +324 -324
  185. package/lib/motion/shotcraft/upstream/demos/ui-entrance/neon-frame-orbit-drop/NeonFrameForerunOrbit.tsx +354 -354
  186. package/lib/motion/shotcraft/upstream/demos/ui-entrance/page-waterfall-wall/PageWaterfallWall.tsx +101 -101
  187. package/lib/motion/shotcraft/upstream/demos/ui-entrance/page-waterfall-wall/VerticalTicker.tsx +129 -129
  188. package/lib/motion/shotcraft/upstream/demos/ui-entrance/product-card-progressive-assemble/ProductCardProgressiveAssemble.tsx +297 -297
  189. package/lib/motion/shotcraft/upstream/demos/ui-entrance/radial-wave/RadialWave.tsx +57 -57
  190. package/lib/motion/shotcraft/upstream/demos/ui-entrance/research-card-stack-scroll/ResearchCardStackScroll.tsx +155 -155
  191. package/lib/motion/shotcraft/upstream/demos/ui-entrance/row-embed/RowEmbed.tsx +175 -175
  192. package/lib/motion/shotcraft/upstream/demos/ui-entrance/value-stagger-gradient/ValueStaggerGradient.tsx +153 -153
  193. package/lib/motion/shotcraft/upstream/demos/ui-entrance/wall-reveal-moves/BentoLightUp.tsx +146 -146
  194. package/lib/motion/shotcraft/upstream/demos/ui-entrance/wall-reveal-moves/WireframeDrawOn.tsx +226 -226
  195. package/lib/motion/shotcraft/upstream/gallery/api/library.json +1 -1
  196. package/lib/motion/shotcraft/upstream/references/aesthetic-rules.md +193 -193
  197. package/lib/motion/shotcraft/upstream/references/final-review.md +121 -121
  198. package/lib/motion/shotcraft/upstream/references/guided-free-creation.md +199 -199
  199. package/lib/motion/shotcraft/upstream/references/jianying-export.md +183 -183
  200. package/lib/motion/shotcraft/upstream/references/music-beat-sync.md +257 -257
  201. package/lib/motion/shotcraft/upstream/references/pipeline.md +382 -382
  202. package/lib/motion/shotcraft/upstream/references/publish-upload-post.md +111 -111
  203. package/lib/motion/shotcraft/upstream/references/sequences/promo-energy-arc.md +50 -50
  204. package/lib/motion/shotcraft/upstream/references/sfx-sourcing.md +95 -95
  205. package/lib/motion/shotcraft/upstream/references/shots/camera/basic-3d-scene.md +41 -41
  206. package/lib/motion/shotcraft/upstream/references/shots/camera/crash-zoom-punch.md +52 -52
  207. package/lib/motion/shotcraft/upstream/references/shots/camera/cursor-flyover.md +39 -39
  208. package/lib/motion/shotcraft/upstream/references/shots/camera/depth-layer-moves.md +39 -39
  209. package/lib/motion/shotcraft/upstream/references/shots/camera/graze-face-tour.md +57 -57
  210. package/lib/motion/shotcraft/upstream/references/shots/camera/overhead-camera-moves.md +51 -51
  211. package/lib/motion/shotcraft/upstream/references/shots/camera/space-camera-moves.md +46 -46
  212. package/lib/motion/shotcraft/upstream/references/shots/camera/steep-tilt-glide.md +52 -52
  213. package/lib/motion/shotcraft/upstream/references/shots/camera/tension-camera-moves.md +43 -43
  214. package/lib/motion/shotcraft/upstream/references/shots/camera/terminal-3d.md +42 -42
  215. package/lib/motion/shotcraft/upstream/references/shots/data/before-after-slider-scrub.md +48 -48
  216. package/lib/motion/shotcraft/upstream/references/shots/data/chart-live-moves.md +52 -52
  217. package/lib/motion/shotcraft/upstream/references/shots/data/gauge-readout-moves.md +45 -45
  218. package/lib/motion/shotcraft/upstream/references/shots/data/scroll-brake-moves.md +46 -46
  219. package/lib/motion/shotcraft/upstream/references/shots/data/timeline-travel.md +44 -44
  220. package/lib/motion/shotcraft/upstream/references/shots/effects/assemble-then-type-flyin.md +63 -63
  221. package/lib/motion/shotcraft/upstream/references/shots/effects/aurora-bloom-bg-flip.md +54 -54
  222. package/lib/motion/shotcraft/upstream/references/shots/effects/brand-frame-snap.md +58 -58
  223. package/lib/motion/shotcraft/upstream/references/shots/effects/fui-hud-moves.md +35 -35
  224. package/lib/motion/shotcraft/upstream/references/shots/effects/glow-flyline-moves.md +53 -53
  225. package/lib/motion/shotcraft/upstream/references/shots/effects/icon-performance-moves.md +42 -42
  226. package/lib/motion/shotcraft/upstream/references/shots/effects/impact-feedback.md +50 -50
  227. package/lib/motion/shotcraft/upstream/references/shots/effects/light-play-moves.md +46 -46
  228. package/lib/motion/shotcraft/upstream/references/shots/effects/radial-ripple-phone-chips.md +56 -56
  229. package/lib/motion/shotcraft/upstream/references/shots/effects/riso-print-hits.md +49 -49
  230. package/lib/motion/shotcraft/upstream/references/shots/effects/scan-bracket-sweep.md +50 -50
  231. package/lib/motion/shotcraft/upstream/references/shots/effects/scanline-annotate-focus.md +53 -53
  232. package/lib/motion/shotcraft/upstream/references/shots/effects/scanline-assemble-flyin.md +53 -53
  233. package/lib/motion/shotcraft/upstream/references/shots/effects/slam-entrance-moves.md +55 -55
  234. package/lib/motion/shotcraft/upstream/references/shots/effects/spotlight-sweep-moves.md +52 -52
  235. package/lib/motion/shotcraft/upstream/references/shots/interaction/autolayout-gap-dial.md +60 -60
  236. package/lib/motion/shotcraft/upstream/references/shots/interaction/canvas-materialize-moves.md +57 -57
  237. package/lib/motion/shotcraft/upstream/references/shots/interaction/chip-grid-single-select-blackout.md +51 -51
  238. package/lib/motion/shotcraft/upstream/references/shots/interaction/collab-cursor-moves.md +41 -41
  239. package/lib/motion/shotcraft/upstream/references/shots/interaction/glass-pill-dictation-typing.md +53 -53
  240. package/lib/motion/shotcraft/upstream/references/shots/interaction/hashtag-to-pill-materialize.md +60 -60
  241. package/lib/motion/shotcraft/upstream/references/shots/interaction/picker-carousel-feature-cycle.md +55 -55
  242. package/lib/motion/shotcraft/upstream/references/shots/interaction/segmented-thumb-hero.md +57 -57
  243. package/lib/motion/shotcraft/upstream/references/shots/interaction/theme-switch-moves.md +47 -47
  244. package/lib/motion/shotcraft/upstream/references/shots/interaction/type-and-filter.md +37 -37
  245. package/lib/motion/shotcraft/upstream/references/shots/interaction/voice-waveform-live.md +54 -54
  246. package/lib/motion/shotcraft/upstream/references/shots/opening/brand-ink-open.md +36 -36
  247. package/lib/motion/shotcraft/upstream/references/shots/opening/crane-rise-reveal.md +56 -56
  248. package/lib/motion/shotcraft/upstream/references/shots/opening/dataviz-landscape-open.md +56 -56
  249. package/lib/motion/shotcraft/upstream/references/shots/opening/fracture.md +40 -40
  250. package/lib/motion/shotcraft/upstream/references/shots/opening/icon-field-colorize.md +60 -60
  251. package/lib/motion/shotcraft/upstream/references/shots/opening/letterspace-materialize.md +57 -57
  252. package/lib/motion/shotcraft/upstream/references/shots/opening/magician-card-flourish.md +73 -73
  253. package/lib/motion/shotcraft/upstream/references/shots/opening/orbit-ring-title-open.md +87 -87
  254. package/lib/motion/shotcraft/upstream/references/shots/opening/spotlight-hero-card.md +55 -55
  255. package/lib/motion/shotcraft/upstream/references/shots/opening/stroke-segment-build.md +50 -50
  256. package/lib/motion/shotcraft/upstream/references/shots/opening/text-as-mask.md +59 -59
  257. package/lib/motion/shotcraft/upstream/references/shots/outro/grain-dissolve.md +38 -38
  258. package/lib/motion/shotcraft/upstream/references/shots/outro/logo-shrink-wordmark-lockup.md +38 -38
  259. package/lib/motion/shotcraft/upstream/references/shots/outro/neon-triple-marquee.md +59 -59
  260. package/lib/motion/shotcraft/upstream/references/shots/outro/outro-group-photo-launch.md +39 -39
  261. package/lib/motion/shotcraft/upstream/references/shots/outro/ui-strip-away-outro.md +61 -61
  262. package/lib/motion/shotcraft/upstream/references/shots/outro/ui-to-brand-morph.md +52 -52
  263. package/lib/motion/shotcraft/upstream/references/shots/rhythm/beat-cut-moves.md +50 -50
  264. package/lib/motion/shotcraft/upstream/references/shots/rhythm/beat-step-list-theme-cycle.md +66 -66
  265. package/lib/motion/shotcraft/upstream/references/shots/rhythm/panel-grid-moves.md +43 -43
  266. package/lib/motion/shotcraft/upstream/references/shots/rhythm/quad-split-parallel-scenes.md +38 -38
  267. package/lib/motion/shotcraft/upstream/references/shots/rhythm/rhythm-interrupt-moves.md +38 -38
  268. package/lib/motion/shotcraft/upstream/references/shots/rhythm/sakuga-timing-shift.md +56 -56
  269. package/lib/motion/shotcraft/upstream/references/shots/rhythm/speed-ramp-freeze.md +44 -44
  270. package/lib/motion/shotcraft/upstream/references/shots/rhythm/trailer-grammar-moves.md +38 -38
  271. package/lib/motion/shotcraft/upstream/references/shots/transition/bottom-push-stack-wipe.md +63 -63
  272. package/lib/motion/shotcraft/upstream/references/shots/transition/card-flip-reveal.md +57 -57
  273. package/lib/motion/shotcraft/upstream/references/shots/transition/card-flock-tumble.md +63 -63
  274. package/lib/motion/shotcraft/upstream/references/shots/transition/circle-match-iris.md +58 -58
  275. package/lib/motion/shotcraft/upstream/references/shots/transition/gradient-transition.md +45 -45
  276. package/lib/motion/shotcraft/upstream/references/shots/transition/line-carry-transition.md +55 -55
  277. package/lib/motion/shotcraft/upstream/references/shots/transition/mosaic-reframe.md +49 -49
  278. package/lib/motion/shotcraft/upstream/references/shots/transition/page-turn-transitions.md +42 -42
  279. package/lib/motion/shotcraft/upstream/references/shots/transition/print-texture-transitions.md +42 -42
  280. package/lib/motion/shotcraft/upstream/references/shots/transition/shot-transitions.md +45 -45
  281. package/lib/motion/shotcraft/upstream/references/shots/transition/tear-streak-transitions.md +48 -48
  282. package/lib/motion/shotcraft/upstream/references/shots/transition/transition-hidden-cut.md +55 -55
  283. package/lib/motion/shotcraft/upstream/references/shots/transition/transition-travel.md +43 -43
  284. package/lib/motion/shotcraft/upstream/references/shots/transition/white-flash-logo-simplify-cut.md +41 -41
  285. package/lib/motion/shotcraft/upstream/references/shots/transition/wipe-transitions.md +42 -42
  286. package/lib/motion/shotcraft/upstream/references/shots/typography/blur-slide.md +59 -59
  287. package/lib/motion/shotcraft/upstream/references/shots/typography/brace-expand.md +55 -55
  288. package/lib/motion/shotcraft/upstream/references/shots/typography/countdown-arc-scatter.md +65 -65
  289. package/lib/motion/shotcraft/upstream/references/shots/typography/document-typewriter-reveal.md +42 -42
  290. package/lib/motion/shotcraft/upstream/references/shots/typography/flying-words.md +64 -64
  291. package/lib/motion/shotcraft/upstream/references/shots/typography/glitch-cycle.md +64 -64
  292. package/lib/motion/shotcraft/upstream/references/shots/typography/gradient-word-sweep.md +54 -54
  293. package/lib/motion/shotcraft/upstream/references/shots/typography/lead-word-zoom-assemble.md +91 -91
  294. package/lib/motion/shotcraft/upstream/references/shots/typography/marker-underline-title.md +51 -51
  295. package/lib/motion/shotcraft/upstream/references/shots/typography/outline-word-fill.md +66 -66
  296. package/lib/motion/shotcraft/upstream/references/shots/typography/paper-title-card.md +38 -38
  297. package/lib/motion/shotcraft/upstream/references/shots/typography/pill-chip-slot-cycle-handled.md +66 -66
  298. package/lib/motion/shotcraft/upstream/references/shots/typography/pill-slot-cycle.md +58 -58
  299. package/lib/motion/shotcraft/upstream/references/shots/typography/text-column-converge.md +61 -61
  300. package/lib/motion/shotcraft/upstream/references/shots/typography/title-demote-to-label.md +64 -64
  301. package/lib/motion/shotcraft/upstream/references/shots/typography/type-assembly-moves.md +45 -45
  302. package/lib/motion/shotcraft/upstream/references/shots/typography/type-entrance-moves.md +41 -41
  303. package/lib/motion/shotcraft/upstream/references/shots/typography/type-rhythm-sync.md +51 -51
  304. package/lib/motion/shotcraft/upstream/references/shots/typography/typewriter-moves.md +42 -42
  305. package/lib/motion/shotcraft/upstream/references/shots/typography/vertical-word-roll-blur-cycle.md +63 -63
  306. package/lib/motion/shotcraft/upstream/references/shots/typography/word-relay-filmstrip.md +57 -57
  307. package/lib/motion/shotcraft/upstream/references/shots/typography/word-relay-geometry.md +75 -75
  308. package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/avatar-bracket-carousel.md +59 -59
  309. package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/bezier-source-converge-merge.md +67 -67
  310. package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/card-stack.md +60 -60
  311. package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/carousel-3d.md +66 -66
  312. package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/cloner-depth-echo.md +40 -40
  313. package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/deck-deal-flyin.md +45 -45
  314. package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/doc-park-left-pill-deal.md +62 -62
  315. package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/draw-svg-trace.md +55 -55
  316. package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/element-body-moves.md +37 -37
  317. package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/integration-hub-map.md +55 -55
  318. package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/list-reveal.md +50 -50
  319. package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/list-stack-press.md +43 -43
  320. package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/neon-frame-forerun.md +55 -55
  321. package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/neon-frame-orbit-drop.md +51 -51
  322. package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/page-waterfall-wall.md +51 -51
  323. package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/product-card-progressive-assemble.md +56 -56
  324. package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/radial-wave.md +49 -49
  325. package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/research-card-stack-scroll.md +57 -57
  326. package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/row-embed.md +36 -36
  327. package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/value-stagger-gradient.md +64 -64
  328. package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/wall-reveal-moves.md +47 -47
  329. package/lib/motion/shotcraft/upstream/references/sound-design.md +170 -170
  330. package/lib/motion/shotcraft/upstream/references/workbench.md +157 -157
  331. package/lib/motion/shotcraft/upstream/template/TEMPLATE.md +103 -103
  332. package/lib/motion/shotcraft/upstream/template/THEMES.md +44 -44
  333. package/lib/motion/shotcraft/upstream/template/package.json +20 -20
  334. package/lib/motion/shotcraft/upstream/template/remotion.config.ts +6 -6
  335. package/lib/motion/shotcraft/upstream/template/src/Root.tsx +17 -17
  336. package/lib/motion/shotcraft/upstream/template/src/aifl/Caption.tsx +65 -65
  337. package/lib/motion/shotcraft/upstream/template/src/aifl/DigitRoll.tsx +48 -48
  338. package/lib/motion/shotcraft/upstream/template/src/aifl/FlashCut.tsx +24 -24
  339. package/lib/motion/shotcraft/upstream/template/src/aifl/Main.tsx +192 -192
  340. package/lib/motion/shotcraft/upstream/template/src/aifl/PaperTitleCard.tsx +111 -111
  341. package/lib/motion/shotcraft/upstream/template/src/aifl/live/PageCam.tsx +150 -150
  342. package/lib/motion/shotcraft/upstream/template/src/aifl/live/SceneDetail.tsx +158 -158
  343. package/lib/motion/shotcraft/upstream/template/src/aifl/live/SceneFlyIn.tsx +468 -468
  344. package/lib/motion/shotcraft/upstream/template/src/aifl/live/SceneOpen.tsx +552 -552
  345. package/lib/motion/shotcraft/upstream/template/src/aifl/live/SceneOutroLive.tsx +411 -411
  346. package/lib/motion/shotcraft/upstream/template/src/aifl/live/ScenePapers.tsx +200 -200
  347. package/lib/motion/shotcraft/upstream/template/src/aifl/live/SceneWbr.tsx +324 -324
  348. package/lib/motion/shotcraft/upstream/template/src/aifl/live-layout.json +388 -388
  349. package/lib/motion/shotcraft/upstream/template/src/index.ts +4 -4
  350. package/lib/motion/shotcraft/upstream/template/src/themes/ThemedFilm.tsx +7 -7
  351. package/lib/motion/shotcraft/upstream/template/src/themes/manifest.tsx +68 -68
  352. package/lib/motion/shotcraft/upstream/template/src/themes/palette-assets.json +1 -1
  353. package/lib/motion/shotcraft/upstream/template/src/themes/palette-assets.ts +24 -24
  354. package/lib/motion/shotcraft/upstream/template/src/themes/palettes.json +74 -74
  355. package/lib/motion/shotcraft/upstream/template/src/themes/visual-theme.tsx +91 -91
  356. package/lib/motion/shotcraft/upstream/template/src/workbench.ts +213 -213
  357. package/lib/motion/shotcraft/upstream/template/tsconfig.json +15 -15
  358. package/lib/motion/style-frames.js +47 -12
  359. package/lib/motion-project.js +22 -3
  360. package/lib/motion-render-worker.js +31 -9
  361. package/lib/routing/router.js +12 -1
  362. package/lib/style-kernel.js +16 -6
  363. package/lib/video-trace-pool.js +128 -128
  364. package/package.json +1 -1
  365. package/LICENSE +0 -4175
@@ -1,291 +1,291 @@
1
- // 素材捕获:把真实文件搬进工程,并量出场景需要的元数据(尺寸、色板、摘要)。
2
- //
3
- // 为什么不是"画布快照":快照经过一次光栅化,颜色和像素都被画布缩放改过。
4
- // 场景要对齐品牌色、要对齐文案排版,就必须拿到原始字节。
5
- //
6
- // 落地形态:<工程>/assets/<名字>.<ext> + <工程>/assets/manifest.json。
7
- // 目录同时是 Remotion 的 publicDir,场景里用 staticFile('xxx.png') 取。
8
-
9
- import { createHash, randomUUID } from 'node:crypto'
10
- import { execFile } from 'node:child_process'
11
- import { tmpdir } from 'node:os'
12
- import { promisify } from 'node:util'
13
- import { open as openFile } from 'node:fs/promises'
14
- import { chmodSync, closeSync, copyFileSync, existsSync, mkdirSync, openSync, readSync, readFileSync, readdirSync, statSync, unlinkSync, writeFileSync } from 'node:fs'
15
- import { basename, extname, join } from 'node:path'
16
- import { ensureFfmpeg } from '../ffmpeg-manager.js'
17
- import { parseProjectJson } from './json.js'
18
-
19
- const exec = promisify(execFile)
20
- const IMAGE_EXT = new Set(['.png', '.jpg', '.jpeg', '.webp', '.gif', '.avif'])
21
- // 矢量素材(官方 logo / 图标)单独一类:不经 ffprobe 量尺寸,场景里 <Img> 照样能画,
22
- // 而且缩放到 4K 也不糊——品牌资产优先要这个,而不是截一张 PNG。
23
- const VECTOR_EXT = new Set(['.svg'])
24
- export const AUDIO_EXT = new Set(['.mp3', '.wav', '.m4a', '.aac', '.ogg', '.flac'])
25
- const VIDEO_EXT = new Set(['.mp4', '.webm', '.mov', '.mkv'])
26
- const MAX_BYTES = 24 * 1024 * 1024
27
- const MAX_VIDEO_BYTES = 256 * 1024 * 1024
28
- export const MEDIA_EXT = new Set([...IMAGE_EXT, ...AUDIO_EXT, ...VIDEO_EXT, ...VECTOR_EXT])
29
-
30
- async function run(args, options = {}) {
31
- const binary = await ensureFfmpeg({ signal: options.signal })
32
- return exec(binary, args, { windowsHide: true, timeout: 60000, maxBuffer: 8 * 1024 * 1024, ...options })
33
- }
34
-
35
- export function assetsDirectory(projectDir) {
36
- return join(projectDir, 'assets')
37
- }
38
-
39
- export function readAssets(projectDir) {
40
- const file = join(assetsDirectory(projectDir), 'manifest.json')
41
- if (!existsSync(file)) return { version: 1, items: [] }
42
- try {
43
- const raw = parseProjectJson(readFileSync(file, 'utf8'))
44
- return { version: 1, items: Array.isArray(raw.items) ? raw.items : [] }
45
- } catch {
46
- return { version: 1, items: [] }
47
- }
48
- }
49
-
50
- export function writeAssets(projectDir, manifest) {
51
- const dir = assetsDirectory(projectDir)
52
- mkdirSync(dir, { recursive: true })
53
- writeFileSync(join(dir, 'manifest.json'), JSON.stringify({ version: 1, items: manifest.items }, null, 2))
54
- }
55
-
56
- // 素材一变,bundle 就必须重建——所以指纹要参与 bundleKey。
57
- export function assetsHash(projectDir) {
58
- const { items } = readAssets(projectDir)
59
- const hash = createHash('sha256')
60
- for (const item of items) hash.update(JSON.stringify([item.id, item.file, item.sha256, item.width, item.height]))
61
- // motion_sfx / motion_audio 落进 assets/sfx 的文件不写 manifest.json,但一样是 public 资源。
62
- // 只认 manifest 会让 bundle 复用旧 public(缺 sfx/),渲染时 404 —— 所以目录本身也要进指纹。
63
- const dir = assetsDirectory(projectDir)
64
- if (existsSync(dir)) {
65
- const walk = (rel) => {
66
- for (const entry of readdirSync(join(dir, rel), { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
67
- const next = rel ? join(rel, entry.name) : entry.name
68
- if (entry.isDirectory()) walk(next)
69
- else {
70
- if (!entry.isFile()) throw new Error('素材目录不支持符号链接:' + next)
71
- const file = join(dir, next), stat = statSync(file)
72
- hash.update(JSON.stringify([next.replace(/\\/g, '/'), stat.size]))
73
- const fd = openSync(file, 'r'), buffer = Buffer.allocUnsafe(256 * 1024)
74
- try { let count; while ((count = readSync(fd, buffer, 0, buffer.length, null)) > 0) hash.update(buffer.subarray(0, count)) } finally { closeSync(fd) }
75
- }
76
- }
77
- }
78
- walk('')
79
- }
80
- return hash.digest('hex').slice(0, 16)
81
- }
82
-
83
- export function listAssets(projectDir) {
84
- const { items } = readAssets(projectDir)
85
- return items.map((item) => ({ ...item, path: join(assetsDirectory(projectDir), item.file) }))
86
- }
87
-
88
- // ffmpeg 不做 ffprobe 的活,但 -i 的 stderr 里第一段视频流就有尺寸。
89
- export async function probeImage(file) {
90
- const { stderr } = await run(['-hide_banner', '-i', file, '-f', 'null', '-'], { encoding: 'utf8' })
91
- const match = /Stream #\d+:\d+.*?(\d{2,5})x(\d{2,5})/.exec(stderr)
92
- if (!match) throw new Error('读不出图片尺寸:' + basename(file))
93
- return { width: Number(match[1]), height: Number(match[2]) }
94
- }
95
-
96
- // 音频只关心时长与峰值:时长给时间轴算 durationInFrames,峰值给"钉帧"用。
97
- export async function probeAudio(file) {
98
- const { stderr } = await run(['-hide_banner', '-i', file, '-f', 'null', '-'], { encoding: 'utf8' })
99
- const duration = /Duration:\s*(\d+):(\d+):(\d+(?:\.\d+)?)/.exec(stderr)
100
- if (!duration) throw new Error('读不出音频时长:' + basename(file))
101
- const seconds = Number(duration[1]) * 3600 + Number(duration[2]) * 60 + Number(duration[3])
102
- const peak = await run(['-hide_banner', '-i', file, '-af', 'volumedetect', '-f', 'null', '-'], { encoding: 'utf8' })
103
- const maxVolume = /max_volume:\s*(-?\d+(?:\.\d+)?)/.exec(peak.stderr || '')
104
- return { seconds, samplePeakDb: maxVolume ? Number(maxVolume[1]) : null }
105
- }
106
-
107
- function rgb(r, g, b) {
108
- return '#' + [r, g, b].map((value) => Math.max(0, Math.min(255, Math.round(value))).toString(16).padStart(2, '0')).join('')
109
- }
110
-
111
- function rgbAt(buffer, offset = 0) {
112
- return rgb(buffer[offset], buffer[offset + 1], buffer[offset + 2])
113
- }
114
-
115
- // 两种读数:平均色(scale=1:1)给"底色",主色统计(缩到 48×48 再按频次聚类)给"色板"。
116
- // 不用 palettegen:它会把没用满的槽位填成纯绿,色板里混进假颜色。
117
- export async function colorsOf(file, maxColors = 8) {
118
- const average = await run(['-hide_banner', '-loglevel', 'error', '-i', file, '-vf', 'scale=1:1:flags=area', '-frames:v', '1', '-f', 'rawvideo', '-pix_fmt', 'rgb24', '-'], { encoding: 'buffer' })
119
- const sampled = await run(['-hide_banner', '-loglevel', 'error', '-i', file, '-vf', 'scale=48:48:flags=area', '-frames:v', '1', '-f', 'rawvideo', '-pix_fmt', 'rgb24', '-'], { encoding: 'buffer' })
120
- const buckets = new Map()
121
- for (let offset = 0; offset + 2 < sampled.stdout.length; offset += 3) {
122
- const r = sampled.stdout[offset], g = sampled.stdout[offset + 1], b = sampled.stdout[offset + 2]
123
- const key = ((r >> 4) << 8) | ((g >> 4) << 4) | (b >> 4)
124
- const bucket = buckets.get(key) || { count: 0, r: 0, g: 0, b: 0 }
125
- bucket.count++
126
- bucket.r += r
127
- bucket.g += g
128
- bucket.b += b
129
- buckets.set(key, bucket)
130
- }
131
- const colors = [...buckets.values()]
132
- .sort((a, b) => b.count - a.count)
133
- .slice(0, maxColors)
134
- .map((bucket) => rgb(bucket.r / bucket.count, bucket.g / bucket.count, bucket.b / bucket.count))
135
- return { average: average.stdout.length >= 3 ? rgbAt(average.stdout) : null, colors }
136
- }
137
-
138
- function slug(name) {
139
- return String(name || 'asset')
140
- .replace(/\.[a-z0-9]+$/i, '')
141
- .replace(/[^\w\u4e00-\u9fa5-]+/g, '-')
142
- .replace(/^-+|-+$/g, '')
143
- .slice(0, 40) || 'asset'
144
- }
145
-
146
- function uniqueName(projectDir, base, ext) {
147
- const dir = assetsDirectory(projectDir)
148
- const taken = new Set(existsSync(dir) ? readdirSync(dir) : [])
149
- let index = 1, name = base + ext
150
- while (taken.has(name)) name = base + '-' + (++index) + ext
151
- return name
152
- }
153
-
154
- async function describe(file, item) {
155
- const ext = extname(file).toLowerCase()
156
- if (VECTOR_EXT.has(ext)) return { ...item, kind: 'vector' }
157
- if (AUDIO_EXT.has(ext)) {
158
- const { seconds, samplePeakDb } = await probeAudio(file)
159
- return { ...item, kind: 'audio', seconds, samplePeakDb }
160
- }
161
- if (VIDEO_EXT.has(ext)) {
162
- const { width, height } = await probeImage(file)
163
- const { seconds } = await probeAudio(file)
164
- return { ...item, kind: 'video', width, height, seconds }
165
- }
166
- const { width, height } = await probeImage(file)
167
- const { average, colors } = await colorsOf(file)
168
- return { ...item, kind: 'image', width, height, average, colors }
169
- }
170
-
171
- export async function captureAsset(projectDir, sourceFile, options = {}) {
172
- const ext = extname(sourceFile).toLowerCase()
173
- if (!MEDIA_EXT.has(ext)) throw new Error('只支持图片 png/jpg/webp/gif/avif、音频 mp3/wav/m4a/aac/ogg/flac、视频 mp4/webm/mov/mkv:' + basename(sourceFile))
174
- const bytes = statSync(sourceFile).size
175
- const limit = VIDEO_EXT.has(ext) ? MAX_VIDEO_BYTES : MAX_BYTES
176
- if (bytes > limit) throw new Error('素材超过 ' + Math.round(limit / 1024 / 1024) + 'MB,请先压缩')
177
- const dir = assetsDirectory(projectDir)
178
- mkdirSync(dir, { recursive: true })
179
- // 内容去重:同一个文件(同 sha256)只落一份。音效被反复引用时,
180
- // 重复拷贝会让 assets/ 里堆一串 a-2.mp3、a-3.mp3,打包也跟着变大。
181
- const sha256 = createHash('sha256').update(readFileSync(sourceFile)).digest('hex')
182
- const existing = readAssets(projectDir).items.find((item) => item.sha256 === sha256 && existsSync(join(dir, item.file)))
183
- if (existing) return existing
184
- const file = uniqueName(projectDir, options.name ? slug(options.name) : slug(basename(sourceFile)), ext)
185
- const target = join(dir, file)
186
- if (existsSync(target)) chmodSync(target, 0o644)
187
- copyFileSync(sourceFile, target)
188
- // 源文件可能带只读属性(截图被复制过来时常见),落地后一律清掉:
189
- // 否则下一次渲染把素材复制进 bundle/public 会因为目标已存在且只读而 EPERM。
190
- chmodSync(target, 0o644)
191
- const entry = await describe(target, {
192
- id: file.replace(/\.[a-z0-9]+$/i, ''), file, bytes, sha256,
193
- source: options.source || basename(sourceFile),
194
- ...(options.license ? { license: String(options.license).slice(0, 160) } : {}),
195
- ...(options.caption ? { caption: String(options.caption).slice(0, 400) } : {}),
196
- at: new Date().toISOString(),
197
- })
198
- const manifest = readAssets(projectDir)
199
- manifest.items.push(entry)
200
- writeAssets(projectDir, manifest)
201
- return entry
202
- }
203
-
204
- export async function cropAsset(projectDir, sourceFile, options = {}) {
205
- const dir = assetsDirectory(projectDir)
206
- mkdirSync(dir, { recursive: true })
207
- const x = Math.max(0, Math.round(Number(options.x) || 0)), y = Math.max(0, Math.round(Number(options.y) || 0))
208
- const w = Math.round(Number(options.w) || 0), h = Math.round(Number(options.h) || 0)
209
- if (!w || !h) throw new Error('裁切需要 w 与 h')
210
- const file = uniqueName(projectDir, options.name ? slug(options.name) : 'crop', '.png')
211
- const target = join(dir, file)
212
- await run(['-hide_banner', '-loglevel', 'error', '-y', '-i', sourceFile, '-vf', 'crop=' + w + ':' + h + ':' + x + ':' + y, target])
213
- const entry = await describe(target, {
214
- id: file.replace(/\.png$/, ''), file, bytes: statSync(target).size,
215
- sha256: createHash('sha256').update(readFileSync(target)).digest('hex'),
216
- source: basename(sourceFile), crop: { x, y, w, h },
217
- ...(options.caption ? { caption: String(options.caption).slice(0, 400) } : {}),
218
- at: new Date().toISOString(),
219
- })
220
- const manifest = readAssets(projectDir)
221
- manifest.items.push(entry)
222
- writeAssets(projectDir, manifest)
223
- return entry
224
- }
225
-
226
- const MIME_EXT = new Map([
227
- ['image/png', '.png'], ['image/jpeg', '.jpg'], ['image/webp', '.webp'], ['image/gif', '.gif'],
228
- ['image/avif', '.avif'], ['image/svg+xml', '.svg'], ['video/mp4', '.mp4'], ['video/webm', '.webm'],
229
- ['audio/mpeg', '.mp3'], ['audio/wav', '.wav'], ['audio/mp4', '.m4a'], ['audio/ogg', '.ogg'],
230
- ])
231
-
232
- function extensionForAsset(contentType, sourceUrl) {
233
- const byType = MIME_EXT.get(String(contentType || '').toLowerCase())
234
- if (byType) return byType
235
- try {
236
- const ext = extname(new URL(sourceUrl).pathname).toLowerCase()
237
- return MEDIA_EXT.has(ext) ? ext : null
238
- } catch {
239
- return null
240
- }
241
- }
242
-
243
- /**
244
- * 把网址上的原始字节下载进 assets/。
245
- *
246
- * 有它是因为"官方 logo"这件事不能靠截图也不能靠重画:品牌规范里的 SVG 才是唯一正确的像素,
247
- * 截屏会带背景、会掉精度、比例还可能被浏览器改过。下载后走与 capture 相同的落盘与量测路径,
248
- * 只是多记一个 source URL —— 交付时要能把"这张图哪来的"说清楚。
249
- */
250
- export async function fetchAsset(projectDir, sourceUrl, options = {}) {
251
- let url
252
- try {
253
- url = new URL(String(sourceUrl))
254
- } catch {
255
- throw new Error('不是一个合法的地址:' + sourceUrl)
256
- }
257
- if (!['http:', 'https:'].includes(url.protocol)) throw new Error('只支持 http(s) 地址')
258
- const controller = new AbortController(), upstream = options.signal
259
- const abort = () => controller.abort(upstream?.reason)
260
- if (upstream?.aborted) abort()
261
- else upstream?.addEventListener('abort', abort, { once: true })
262
- const timer = setTimeout(() => controller.abort(new Error('素材下载超时')), 60000)
263
- let temp
264
- try {
265
- const response = await fetch(url.href, { redirect: 'follow', headers: { 'user-agent': 'dsh-drawai/asset-fetch' }, signal: controller.signal })
266
- if (!response.ok) throw new Error('下载失败 HTTP ' + response.status + ':' + url.href)
267
- const contentType = (response.headers.get('content-type') || '').split(';')[0].trim().toLowerCase()
268
- const ext = extensionForAsset(contentType, url.href)
269
- if (!ext) { await response.body?.cancel(); throw new Error('这个地址不是图片/音视频/svg(content-type: ' + (contentType || '未知') + '):' + url.href) }
270
- const limit = VIDEO_EXT.has(ext) ? MAX_VIDEO_BYTES : MAX_BYTES
271
- const tooLarge = () => new Error('素材超过 ' + Math.round(limit / 1024 / 1024) + 'MB,请先压缩')
272
- if (Number(response.headers.get('content-length')) > limit) { await response.body?.cancel(); throw tooLarge() }
273
- temp = join(tmpdir(), 'drawai-fetch-' + randomUUID() + ext)
274
- const handle = await openFile(temp, 'wx')
275
- try {
276
- let bytes = 0
277
- if (!response.body) throw new Error('下载响应没有内容')
278
- for await (const chunk of response.body) {
279
- bytes += chunk.length
280
- if (bytes > limit) throw tooLarge()
281
- await handle.writeFile(chunk)
282
- }
283
- } finally { await handle.close() }
284
- const baseName = options.name || basename(url.pathname).replace(/\.[a-z0-9]+$/i, '') || 'asset'
285
- return await captureAsset(projectDir, temp, { name: baseName, caption: options.caption, source: url.href })
286
- } finally {
287
- clearTimeout(timer)
288
- upstream?.removeEventListener('abort', abort)
289
- if (temp) try { unlinkSync(temp) } catch {}
290
- }
291
- }
1
+ // 素材捕获:把真实文件搬进工程,并量出场景需要的元数据(尺寸、色板、摘要)。
2
+ //
3
+ // 为什么不是"画布快照":快照经过一次光栅化,颜色和像素都被画布缩放改过。
4
+ // 场景要对齐品牌色、要对齐文案排版,就必须拿到原始字节。
5
+ //
6
+ // 落地形态:<工程>/assets/<名字>.<ext> + <工程>/assets/manifest.json。
7
+ // 目录同时是 Remotion 的 publicDir,场景里用 staticFile('xxx.png') 取。
8
+
9
+ import { createHash, randomUUID } from 'node:crypto'
10
+ import { execFile } from 'node:child_process'
11
+ import { tmpdir } from 'node:os'
12
+ import { promisify } from 'node:util'
13
+ import { open as openFile } from 'node:fs/promises'
14
+ import { chmodSync, closeSync, copyFileSync, existsSync, mkdirSync, openSync, readSync, readFileSync, readdirSync, statSync, unlinkSync, writeFileSync } from 'node:fs'
15
+ import { basename, extname, join } from 'node:path'
16
+ import { ensureFfmpeg } from '../ffmpeg-manager.js'
17
+ import { parseProjectJson } from './json.js'
18
+
19
+ const exec = promisify(execFile)
20
+ const IMAGE_EXT = new Set(['.png', '.jpg', '.jpeg', '.webp', '.gif', '.avif'])
21
+ // 矢量素材(官方 logo / 图标)单独一类:不经 ffprobe 量尺寸,场景里 <Img> 照样能画,
22
+ // 而且缩放到 4K 也不糊——品牌资产优先要这个,而不是截一张 PNG。
23
+ const VECTOR_EXT = new Set(['.svg'])
24
+ export const AUDIO_EXT = new Set(['.mp3', '.wav', '.m4a', '.aac', '.ogg', '.flac'])
25
+ const VIDEO_EXT = new Set(['.mp4', '.webm', '.mov', '.mkv'])
26
+ const MAX_BYTES = 24 * 1024 * 1024
27
+ const MAX_VIDEO_BYTES = 256 * 1024 * 1024
28
+ export const MEDIA_EXT = new Set([...IMAGE_EXT, ...AUDIO_EXT, ...VIDEO_EXT, ...VECTOR_EXT])
29
+
30
+ async function run(args, options = {}) {
31
+ const binary = await ensureFfmpeg({ signal: options.signal })
32
+ return exec(binary, args, { windowsHide: true, timeout: 60000, maxBuffer: 8 * 1024 * 1024, ...options })
33
+ }
34
+
35
+ export function assetsDirectory(projectDir) {
36
+ return join(projectDir, 'assets')
37
+ }
38
+
39
+ export function readAssets(projectDir) {
40
+ const file = join(assetsDirectory(projectDir), 'manifest.json')
41
+ if (!existsSync(file)) return { version: 1, items: [] }
42
+ try {
43
+ const raw = parseProjectJson(readFileSync(file, 'utf8'))
44
+ return { version: 1, items: Array.isArray(raw.items) ? raw.items : [] }
45
+ } catch {
46
+ return { version: 1, items: [] }
47
+ }
48
+ }
49
+
50
+ export function writeAssets(projectDir, manifest) {
51
+ const dir = assetsDirectory(projectDir)
52
+ mkdirSync(dir, { recursive: true })
53
+ writeFileSync(join(dir, 'manifest.json'), JSON.stringify({ version: 1, items: manifest.items }, null, 2))
54
+ }
55
+
56
+ // 素材一变,bundle 就必须重建——所以指纹要参与 bundleKey。
57
+ export function assetsHash(projectDir) {
58
+ const { items } = readAssets(projectDir)
59
+ const hash = createHash('sha256')
60
+ for (const item of items) hash.update(JSON.stringify([item.id, item.file, item.sha256, item.width, item.height]))
61
+ // motion_sfx / motion_audio 落进 assets/sfx 的文件不写 manifest.json,但一样是 public 资源。
62
+ // 只认 manifest 会让 bundle 复用旧 public(缺 sfx/),渲染时 404 —— 所以目录本身也要进指纹。
63
+ const dir = assetsDirectory(projectDir)
64
+ if (existsSync(dir)) {
65
+ const walk = (rel) => {
66
+ for (const entry of readdirSync(join(dir, rel), { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
67
+ const next = rel ? join(rel, entry.name) : entry.name
68
+ if (entry.isDirectory()) walk(next)
69
+ else {
70
+ if (!entry.isFile()) throw new Error('素材目录不支持符号链接:' + next)
71
+ const file = join(dir, next), stat = statSync(file)
72
+ hash.update(JSON.stringify([next.replace(/\\/g, '/'), stat.size]))
73
+ const fd = openSync(file, 'r'), buffer = Buffer.allocUnsafe(256 * 1024)
74
+ try { let count; while ((count = readSync(fd, buffer, 0, buffer.length, null)) > 0) hash.update(buffer.subarray(0, count)) } finally { closeSync(fd) }
75
+ }
76
+ }
77
+ }
78
+ walk('')
79
+ }
80
+ return hash.digest('hex').slice(0, 16)
81
+ }
82
+
83
+ export function listAssets(projectDir) {
84
+ const { items } = readAssets(projectDir)
85
+ return items.map((item) => ({ ...item, path: join(assetsDirectory(projectDir), item.file) }))
86
+ }
87
+
88
+ // ffmpeg 不做 ffprobe 的活,但 -i 的 stderr 里第一段视频流就有尺寸。
89
+ export async function probeImage(file) {
90
+ const { stderr } = await run(['-hide_banner', '-i', file, '-f', 'null', '-'], { encoding: 'utf8' })
91
+ const match = /Stream #\d+:\d+.*?(\d{2,5})x(\d{2,5})/.exec(stderr)
92
+ if (!match) throw new Error('读不出图片尺寸:' + basename(file))
93
+ return { width: Number(match[1]), height: Number(match[2]) }
94
+ }
95
+
96
+ // 音频只关心时长与峰值:时长给时间轴算 durationInFrames,峰值给"钉帧"用。
97
+ export async function probeAudio(file) {
98
+ const { stderr } = await run(['-hide_banner', '-i', file, '-f', 'null', '-'], { encoding: 'utf8' })
99
+ const duration = /Duration:\s*(\d+):(\d+):(\d+(?:\.\d+)?)/.exec(stderr)
100
+ if (!duration) throw new Error('读不出音频时长:' + basename(file))
101
+ const seconds = Number(duration[1]) * 3600 + Number(duration[2]) * 60 + Number(duration[3])
102
+ const peak = await run(['-hide_banner', '-i', file, '-af', 'volumedetect', '-f', 'null', '-'], { encoding: 'utf8' })
103
+ const maxVolume = /max_volume:\s*(-?\d+(?:\.\d+)?)/.exec(peak.stderr || '')
104
+ return { seconds, samplePeakDb: maxVolume ? Number(maxVolume[1]) : null }
105
+ }
106
+
107
+ function rgb(r, g, b) {
108
+ return '#' + [r, g, b].map((value) => Math.max(0, Math.min(255, Math.round(value))).toString(16).padStart(2, '0')).join('')
109
+ }
110
+
111
+ function rgbAt(buffer, offset = 0) {
112
+ return rgb(buffer[offset], buffer[offset + 1], buffer[offset + 2])
113
+ }
114
+
115
+ // 两种读数:平均色(scale=1:1)给"底色",主色统计(缩到 48×48 再按频次聚类)给"色板"。
116
+ // 不用 palettegen:它会把没用满的槽位填成纯绿,色板里混进假颜色。
117
+ export async function colorsOf(file, maxColors = 8) {
118
+ const average = await run(['-hide_banner', '-loglevel', 'error', '-i', file, '-vf', 'scale=1:1:flags=area', '-frames:v', '1', '-f', 'rawvideo', '-pix_fmt', 'rgb24', '-'], { encoding: 'buffer' })
119
+ const sampled = await run(['-hide_banner', '-loglevel', 'error', '-i', file, '-vf', 'scale=48:48:flags=area', '-frames:v', '1', '-f', 'rawvideo', '-pix_fmt', 'rgb24', '-'], { encoding: 'buffer' })
120
+ const buckets = new Map()
121
+ for (let offset = 0; offset + 2 < sampled.stdout.length; offset += 3) {
122
+ const r = sampled.stdout[offset], g = sampled.stdout[offset + 1], b = sampled.stdout[offset + 2]
123
+ const key = ((r >> 4) << 8) | ((g >> 4) << 4) | (b >> 4)
124
+ const bucket = buckets.get(key) || { count: 0, r: 0, g: 0, b: 0 }
125
+ bucket.count++
126
+ bucket.r += r
127
+ bucket.g += g
128
+ bucket.b += b
129
+ buckets.set(key, bucket)
130
+ }
131
+ const colors = [...buckets.values()]
132
+ .sort((a, b) => b.count - a.count)
133
+ .slice(0, maxColors)
134
+ .map((bucket) => rgb(bucket.r / bucket.count, bucket.g / bucket.count, bucket.b / bucket.count))
135
+ return { average: average.stdout.length >= 3 ? rgbAt(average.stdout) : null, colors }
136
+ }
137
+
138
+ function slug(name) {
139
+ return String(name || 'asset')
140
+ .replace(/\.[a-z0-9]+$/i, '')
141
+ .replace(/[^\w\u4e00-\u9fa5-]+/g, '-')
142
+ .replace(/^-+|-+$/g, '')
143
+ .slice(0, 40) || 'asset'
144
+ }
145
+
146
+ function uniqueName(projectDir, base, ext) {
147
+ const dir = assetsDirectory(projectDir)
148
+ const taken = new Set(existsSync(dir) ? readdirSync(dir) : [])
149
+ let index = 1, name = base + ext
150
+ while (taken.has(name)) name = base + '-' + (++index) + ext
151
+ return name
152
+ }
153
+
154
+ async function describe(file, item) {
155
+ const ext = extname(file).toLowerCase()
156
+ if (VECTOR_EXT.has(ext)) return { ...item, kind: 'vector' }
157
+ if (AUDIO_EXT.has(ext)) {
158
+ const { seconds, samplePeakDb } = await probeAudio(file)
159
+ return { ...item, kind: 'audio', seconds, samplePeakDb }
160
+ }
161
+ if (VIDEO_EXT.has(ext)) {
162
+ const { width, height } = await probeImage(file)
163
+ const { seconds } = await probeAudio(file)
164
+ return { ...item, kind: 'video', width, height, seconds }
165
+ }
166
+ const { width, height } = await probeImage(file)
167
+ const { average, colors } = await colorsOf(file)
168
+ return { ...item, kind: 'image', width, height, average, colors }
169
+ }
170
+
171
+ export async function captureAsset(projectDir, sourceFile, options = {}) {
172
+ const ext = extname(sourceFile).toLowerCase()
173
+ if (!MEDIA_EXT.has(ext)) throw new Error('只支持图片 png/jpg/webp/gif/avif、音频 mp3/wav/m4a/aac/ogg/flac、视频 mp4/webm/mov/mkv:' + basename(sourceFile))
174
+ const bytes = statSync(sourceFile).size
175
+ const limit = VIDEO_EXT.has(ext) ? MAX_VIDEO_BYTES : MAX_BYTES
176
+ if (bytes > limit) throw new Error('素材超过 ' + Math.round(limit / 1024 / 1024) + 'MB,请先压缩')
177
+ const dir = assetsDirectory(projectDir)
178
+ mkdirSync(dir, { recursive: true })
179
+ // 内容去重:同一个文件(同 sha256)只落一份。音效被反复引用时,
180
+ // 重复拷贝会让 assets/ 里堆一串 a-2.mp3、a-3.mp3,打包也跟着变大。
181
+ const sha256 = createHash('sha256').update(readFileSync(sourceFile)).digest('hex')
182
+ const existing = readAssets(projectDir).items.find((item) => item.sha256 === sha256 && existsSync(join(dir, item.file)))
183
+ if (existing) return existing
184
+ const file = uniqueName(projectDir, options.name ? slug(options.name) : slug(basename(sourceFile)), ext)
185
+ const target = join(dir, file)
186
+ if (existsSync(target)) chmodSync(target, 0o644)
187
+ copyFileSync(sourceFile, target)
188
+ // 源文件可能带只读属性(截图被复制过来时常见),落地后一律清掉:
189
+ // 否则下一次渲染把素材复制进 bundle/public 会因为目标已存在且只读而 EPERM。
190
+ chmodSync(target, 0o644)
191
+ const entry = await describe(target, {
192
+ id: file.replace(/\.[a-z0-9]+$/i, ''), file, bytes, sha256,
193
+ source: options.source || basename(sourceFile),
194
+ ...(options.license ? { license: String(options.license).slice(0, 160) } : {}),
195
+ ...(options.caption ? { caption: String(options.caption).slice(0, 400) } : {}),
196
+ at: new Date().toISOString(),
197
+ })
198
+ const manifest = readAssets(projectDir)
199
+ manifest.items.push(entry)
200
+ writeAssets(projectDir, manifest)
201
+ return entry
202
+ }
203
+
204
+ export async function cropAsset(projectDir, sourceFile, options = {}) {
205
+ const dir = assetsDirectory(projectDir)
206
+ mkdirSync(dir, { recursive: true })
207
+ const x = Math.max(0, Math.round(Number(options.x) || 0)), y = Math.max(0, Math.round(Number(options.y) || 0))
208
+ const w = Math.round(Number(options.w) || 0), h = Math.round(Number(options.h) || 0)
209
+ if (!w || !h) throw new Error('裁切需要 w 与 h')
210
+ const file = uniqueName(projectDir, options.name ? slug(options.name) : 'crop', '.png')
211
+ const target = join(dir, file)
212
+ await run(['-hide_banner', '-loglevel', 'error', '-y', '-i', sourceFile, '-vf', 'crop=' + w + ':' + h + ':' + x + ':' + y, target])
213
+ const entry = await describe(target, {
214
+ id: file.replace(/\.png$/, ''), file, bytes: statSync(target).size,
215
+ sha256: createHash('sha256').update(readFileSync(target)).digest('hex'),
216
+ source: basename(sourceFile), crop: { x, y, w, h },
217
+ ...(options.caption ? { caption: String(options.caption).slice(0, 400) } : {}),
218
+ at: new Date().toISOString(),
219
+ })
220
+ const manifest = readAssets(projectDir)
221
+ manifest.items.push(entry)
222
+ writeAssets(projectDir, manifest)
223
+ return entry
224
+ }
225
+
226
+ const MIME_EXT = new Map([
227
+ ['image/png', '.png'], ['image/jpeg', '.jpg'], ['image/webp', '.webp'], ['image/gif', '.gif'],
228
+ ['image/avif', '.avif'], ['image/svg+xml', '.svg'], ['video/mp4', '.mp4'], ['video/webm', '.webm'],
229
+ ['audio/mpeg', '.mp3'], ['audio/wav', '.wav'], ['audio/mp4', '.m4a'], ['audio/ogg', '.ogg'],
230
+ ])
231
+
232
+ function extensionForAsset(contentType, sourceUrl) {
233
+ const byType = MIME_EXT.get(String(contentType || '').toLowerCase())
234
+ if (byType) return byType
235
+ try {
236
+ const ext = extname(new URL(sourceUrl).pathname).toLowerCase()
237
+ return MEDIA_EXT.has(ext) ? ext : null
238
+ } catch {
239
+ return null
240
+ }
241
+ }
242
+
243
+ /**
244
+ * 把网址上的原始字节下载进 assets/。
245
+ *
246
+ * 有它是因为"官方 logo"这件事不能靠截图也不能靠重画:品牌规范里的 SVG 才是唯一正确的像素,
247
+ * 截屏会带背景、会掉精度、比例还可能被浏览器改过。下载后走与 capture 相同的落盘与量测路径,
248
+ * 只是多记一个 source URL —— 交付时要能把"这张图哪来的"说清楚。
249
+ */
250
+ export async function fetchAsset(projectDir, sourceUrl, options = {}) {
251
+ let url
252
+ try {
253
+ url = new URL(String(sourceUrl))
254
+ } catch {
255
+ throw new Error('不是一个合法的地址:' + sourceUrl)
256
+ }
257
+ if (!['http:', 'https:'].includes(url.protocol)) throw new Error('只支持 http(s) 地址')
258
+ const controller = new AbortController(), upstream = options.signal
259
+ const abort = () => controller.abort(upstream?.reason)
260
+ if (upstream?.aborted) abort()
261
+ else upstream?.addEventListener('abort', abort, { once: true })
262
+ const timer = setTimeout(() => controller.abort(new Error('素材下载超时')), 60000)
263
+ let temp
264
+ try {
265
+ const response = await fetch(url.href, { redirect: 'follow', headers: { 'user-agent': 'dsh-drawai/asset-fetch' }, signal: controller.signal })
266
+ if (!response.ok) throw new Error('下载失败 HTTP ' + response.status + ':' + url.href)
267
+ const contentType = (response.headers.get('content-type') || '').split(';')[0].trim().toLowerCase()
268
+ const ext = extensionForAsset(contentType, url.href)
269
+ if (!ext) { await response.body?.cancel(); throw new Error('这个地址不是图片/音视频/svg(content-type: ' + (contentType || '未知') + '):' + url.href) }
270
+ const limit = VIDEO_EXT.has(ext) ? MAX_VIDEO_BYTES : MAX_BYTES
271
+ const tooLarge = () => new Error('素材超过 ' + Math.round(limit / 1024 / 1024) + 'MB,请先压缩')
272
+ if (Number(response.headers.get('content-length')) > limit) { await response.body?.cancel(); throw tooLarge() }
273
+ temp = join(tmpdir(), 'drawai-fetch-' + randomUUID() + ext)
274
+ const handle = await openFile(temp, 'wx')
275
+ try {
276
+ let bytes = 0
277
+ if (!response.body) throw new Error('下载响应没有内容')
278
+ for await (const chunk of response.body) {
279
+ bytes += chunk.length
280
+ if (bytes > limit) throw tooLarge()
281
+ await handle.writeFile(chunk)
282
+ }
283
+ } finally { await handle.close() }
284
+ const baseName = options.name || basename(url.pathname).replace(/\.[a-z0-9]+$/i, '') || 'asset'
285
+ return await captureAsset(projectDir, temp, { name: baseName, caption: options.caption, source: url.href })
286
+ } finally {
287
+ clearTimeout(timer)
288
+ upstream?.removeEventListener('abort', abort)
289
+ if (temp) try { unlinkSync(temp) } catch {}
290
+ }
291
+ }
@@ -0,0 +1,10 @@
1
+ // 由 tools/build-demo-assets-pack.mjs 生成,勿手改。
2
+ export const DEMO_ASSETS_LOCK = {
3
+ "version": "v1",
4
+ "file": "drawai-demo-assets-v1.tar.gz",
5
+ "bytes": 169794,
6
+ "sha256": "05a6fb947cb71bf51af309134d3d83a978dd5a6eb1ce1799779eb4346d519bce",
7
+ "files": 1,
8
+ "rawBytes": 549376,
9
+ "builtAt": "2026-11-08T16:48:01.972Z"
10
+ }
@@ -0,0 +1,28 @@
1
+ # vendored demo 依赖
2
+
3
+ `upstream/demos/*/index.html` 的 importmap 和 `<script src>` 指向 `/node_modules/*`。这些包不在
4
+ 视频运行时(`src/motion/runtime/package.json`)的依赖里,装到用户机器上就无从服务,于是
5
+ `mg_style action:"frames"` 直接报「依赖没装在本机」(issue #27)。
6
+
7
+ 这里按**原包内路径**放好被引用的构建产物,`src/motion/style-frames.js` 在服务 `/node_modules/*`
8
+ 时先查视频运行时、再回落到本目录,所以用户什么都不用装。
9
+
10
+ ```
11
+ modules/<包名>/<包内原路径>
12
+ ```
13
+
14
+ | 包 | 版本 | 文件 | 许可证 |
15
+ | --- | --- | --- | --- |
16
+ | d3 | 7.9.0 | `d3/dist/d3.min.js` | ISC(见 `d3/LICENSE`) |
17
+ | topojson-client | 3.1.0 | `topojson-client/dist/topojson-client.min.js` | ISC(见 `topojson-client/LICENSE`) |
18
+ | opentype.js | 1.3.4 | `opentype.js/dist/opentype.mjs` | MIT(见 `opentype.js/LICENSE`) |
19
+ | simplex-noise | 4.0.3 | `simplex-noise/dist/esm/simplex-noise.js` | MIT(见 `simplex-noise/LICENSE`) |
20
+
21
+ - 只用到的文件放进来了,没有整包拷(约 780 KB)。
22
+ - 目录叫 `modules` 而不是 `node_modules`:npm 打包会无条件跳过任何名为 `node_modules` 的目录,
23
+ 那样 vendored 文件进不了 tarball。`.gitignore` / `.npmignore` 里给了 `dist/` 的例外规则,
24
+ 否则这几个文件会被当成构建产物忽略。
25
+ - `opentype.js` 1.3.4 的 ESM 构建叫 `dist/opentype.module.js`,demo 按 `dist/opentype.mjs` 引用,
26
+ 所以这里用前者的内容放到后者的文件名下;升级时按同样规则替换。
27
+ - `three` 不在这里:视频运行时的依赖清单已包含 three 0.183.2,`/node_modules/three/*` 由运行时提供。
28
+ - 升级包时同步改本表版本号,并重跑 `node tools/check-package.mjs`(它校验这几个文件是否存在)。
@@ -0,0 +1,13 @@
1
+ Copyright 2010-2023 Mike Bostock
2
+
3
+ Permission to use, copy, modify, and/or distribute this software for any purpose
4
+ with or without fee is hereby granted, provided that the above copyright notice
5
+ and this permission notice appear in all copies.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
8
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
9
+ FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
10
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
11
+ OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
12
+ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
13
+ THIS SOFTWARE.