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.
- package/CHANGELOG.md +21 -0
- package/README.en.md +26 -65
- package/README.md +25 -67
- package/docs/USAGE.en.md +93 -0
- package/docs/USAGE.md +97 -0
- package/docs/VALIDATION.md +22 -0
- package/lib/client.js +90 -0
- package/lib/index.d.ts +3 -0
- package/lib/index.js +5 -1
- package/lib/media-store.d.ts +25 -0
- package/lib/media-store.js +174 -0
- package/lib/media-templates.d.ts +52 -0
- package/lib/media-templates.js +91 -0
- package/lib/media-workbench.d.ts +42 -0
- package/lib/media-workbench.js +476 -0
- package/package.json +18 -5
- package/scripts/build-client.mjs +4 -0
- package/skills/remotion-best-practices/SKILL.md +54 -54
- package/skills/remotion-best-practices/agents/openai.yaml +7 -7
- package/skills/remotion-best-practices/assets/remotion-icon.svg +4 -4
- package/skills/remotion-best-practices/remotion-captions/REFERENCE.md +36 -36
- package/skills/remotion-best-practices/remotion-captions/agents/openai.yaml +7 -7
- package/skills/remotion-best-practices/remotion-captions/assets/remotion-icon.svg +4 -4
- package/skills/remotion-best-practices/remotion-captions/display-captions.md +190 -190
- package/skills/remotion-best-practices/remotion-captions/import-srt-captions.md +73 -73
- package/skills/remotion-best-practices/remotion-captions/transcribe-captions.md +70 -70
- package/skills/remotion-best-practices/remotion-create/REFERENCE.md +85 -85
- package/skills/remotion-best-practices/remotion-create/agents/openai.yaml +7 -7
- package/skills/remotion-best-practices/remotion-create/assets/remotion-icon.svg +4 -4
- package/skills/remotion-best-practices/remotion-create/tailwind.md +11 -11
- package/skills/remotion-best-practices/remotion-create/video-layout.md +9 -9
- package/skills/remotion-best-practices/remotion-docs/REFERENCE.md +46 -46
- package/skills/remotion-best-practices/remotion-docs/agents/openai.yaml +7 -7
- package/skills/remotion-best-practices/remotion-docs/assets/remotion-icon.svg +4 -4
- package/skills/remotion-best-practices/remotion-interactivity/REFERENCE.md +315 -315
- package/skills/remotion-best-practices/remotion-interactivity/agents/openai.yaml +7 -7
- package/skills/remotion-best-practices/remotion-interactivity/assets/remotion-icon.svg +4 -4
- package/skills/remotion-best-practices/remotion-maps/REFERENCE.md +36 -36
- package/skills/remotion-best-practices/remotion-maps/agents/openai.yaml +7 -7
- package/skills/remotion-best-practices/remotion-maps/assets/remotion-icon.svg +4 -4
- package/skills/remotion-best-practices/remotion-maps/techniques/cesium/TECHNIQUE.md +89 -89
- package/skills/remotion-best-practices/remotion-maps/techniques/cesium/assets/CesiumFlythrough.tsx +284 -284
- package/skills/remotion-best-practices/remotion-maps/techniques/cesium/assets/cesium-path.json +299 -299
- package/skills/remotion-best-practices/remotion-maps/techniques/cesium/assets/city-path.json +12 -12
- package/skills/remotion-best-practices/remotion-maps/techniques/cesium/assets/example-Root.tsx +39 -39
- package/skills/remotion-best-practices/remotion-maps/techniques/cesium/assets/flight-path.ts +35 -35
- package/skills/remotion-best-practices/remotion-maps/techniques/cesium/assets/sample-river.geojson +215 -215
- package/skills/remotion-best-practices/remotion-maps/techniques/cesium/references/3d-data-sources.md +43 -43
- package/skills/remotion-best-practices/remotion-maps/techniques/cesium/references/3d-flyover-architecture.md +128 -128
- package/skills/remotion-best-practices/remotion-maps/techniques/cesium/references/3d-troubleshooting.md +57 -57
- package/skills/remotion-best-practices/remotion-maps/techniques/cesium/scripts/prep-cesium-path.mjs +161 -161
- package/skills/remotion-best-practices/remotion-maps/techniques/mapbox/TECHNIQUE.md +449 -449
- package/skills/remotion-best-practices/remotion-maps/techniques/mapbox/references/render-stability.md +64 -64
- package/skills/remotion-best-practices/remotion-maps/techniques/maplibre/TECHNIQUE.md +484 -484
- package/skills/remotion-best-practices/remotion-maps/techniques/maplibre/references/render-stability.md +64 -64
- package/skills/remotion-best-practices/remotion-maps/techniques/maptiler/TECHNIQUE.md +80 -80
- package/skills/remotion-best-practices/remotion-maps/techniques/maptiler/assets/CountryLabel.tsx +70 -70
- package/skills/remotion-best-practices/remotion-maps/techniques/maptiler/assets/MapTilerVectorElement.ts +80 -80
- package/skills/remotion-best-practices/remotion-maps/techniques/maptiler/assets/RiverReveal.tsx +363 -363
- package/skills/remotion-best-practices/remotion-maps/techniques/maptiler/assets/example-Root.tsx +22 -22
- package/skills/remotion-best-practices/remotion-maps/techniques/maptiler/assets/sample-data/country-meta.json +7535 -7535
- package/skills/remotion-best-practices/remotion-maps/techniques/maptiler/assets/sample-data/yarlung-flow.json +605 -605
- package/skills/remotion-best-practices/remotion-maps/techniques/maptiler/assets/tokens.ts +34 -34
- package/skills/remotion-best-practices/remotion-maps/techniques/maptiler/references/map-data-sources.md +91 -91
- package/skills/remotion-best-practices/remotion-maps/techniques/maptiler/references/map-explainer-architecture.md +129 -129
- package/skills/remotion-best-practices/remotion-maps/techniques/maptiler/references/map-geo-prep.md +85 -85
- package/skills/remotion-best-practices/remotion-maps/techniques/maptiler/references/render-stability.md +64 -64
- package/skills/remotion-best-practices/remotion-maps/techniques/maptiler/scripts/prep-geo.mjs +202 -202
- package/skills/remotion-best-practices/remotion-maps/techniques/static-map/TECHNIQUE.md +37 -37
- package/skills/remotion-best-practices/remotion-markup/3d.md +86 -86
- package/skills/remotion-best-practices/remotion-markup/REFERENCE.md +358 -358
- package/skills/remotion-best-practices/remotion-markup/agents/openai.yaml +7 -7
- package/skills/remotion-best-practices/remotion-markup/assets/remotion-icon.svg +4 -4
- package/skills/remotion-best-practices/remotion-markup/audio-visualization.md +198 -198
- package/skills/remotion-best-practices/remotion-markup/audio.md +169 -169
- package/skills/remotion-best-practices/remotion-markup/calculate-metadata.md +136 -136
- package/skills/remotion-best-practices/remotion-markup/compositions.md +123 -123
- package/skills/remotion-best-practices/remotion-markup/connected-compositions.md +79 -79
- package/skills/remotion-best-practices/remotion-markup/cropping.md +34 -34
- package/skills/remotion-best-practices/remotion-markup/effects.md +251 -251
- package/skills/remotion-best-practices/remotion-markup/embedding-videos.md +171 -171
- package/skills/remotion-best-practices/remotion-markup/ffmpeg.md +38 -38
- package/skills/remotion-best-practices/remotion-markup/gifs.md +187 -187
- package/skills/remotion-best-practices/remotion-markup/google-fonts.md +76 -76
- package/skills/remotion-best-practices/remotion-markup/html-in-canvas.md +128 -128
- package/skills/remotion-best-practices/remotion-markup/images.md +73 -73
- package/skills/remotion-best-practices/remotion-markup/light-leaks.md +106 -106
- package/skills/remotion-best-practices/remotion-markup/local-fonts.md +69 -69
- package/skills/remotion-best-practices/remotion-markup/lottie.md +73 -73
- package/skills/remotion-best-practices/remotion-markup/measuring-dom-nodes.md +38 -38
- package/skills/remotion-best-practices/remotion-markup/measuring-text.md +146 -146
- package/skills/remotion-best-practices/remotion-markup/motion-blur.md +60 -60
- package/skills/remotion-best-practices/remotion-markup/multi-scene-video.md +81 -81
- package/skills/remotion-best-practices/remotion-markup/parameters.md +111 -111
- package/skills/remotion-best-practices/remotion-markup/remotion-maps/REFERENCE.md +36 -36
- package/skills/remotion-best-practices/remotion-markup/remotion-maps/agents/openai.yaml +7 -7
- package/skills/remotion-best-practices/remotion-markup/remotion-maps/assets/remotion-icon.svg +4 -4
- package/skills/remotion-best-practices/remotion-markup/remotion-maps/techniques/cesium/TECHNIQUE.md +89 -89
- package/skills/remotion-best-practices/remotion-markup/remotion-maps/techniques/cesium/assets/CesiumFlythrough.tsx +284 -284
- package/skills/remotion-best-practices/remotion-markup/remotion-maps/techniques/cesium/assets/cesium-path.json +299 -299
- package/skills/remotion-best-practices/remotion-markup/remotion-maps/techniques/cesium/assets/city-path.json +12 -12
- package/skills/remotion-best-practices/remotion-markup/remotion-maps/techniques/cesium/assets/example-Root.tsx +39 -39
- package/skills/remotion-best-practices/remotion-markup/remotion-maps/techniques/cesium/assets/flight-path.ts +35 -35
- package/skills/remotion-best-practices/remotion-markup/remotion-maps/techniques/cesium/assets/sample-river.geojson +215 -215
- package/skills/remotion-best-practices/remotion-markup/remotion-maps/techniques/cesium/references/3d-data-sources.md +43 -43
- package/skills/remotion-best-practices/remotion-markup/remotion-maps/techniques/cesium/references/3d-flyover-architecture.md +128 -128
- package/skills/remotion-best-practices/remotion-markup/remotion-maps/techniques/cesium/references/3d-troubleshooting.md +57 -57
- package/skills/remotion-best-practices/remotion-markup/remotion-maps/techniques/cesium/scripts/prep-cesium-path.mjs +161 -161
- package/skills/remotion-best-practices/remotion-markup/remotion-maps/techniques/mapbox/TECHNIQUE.md +449 -449
- package/skills/remotion-best-practices/remotion-markup/remotion-maps/techniques/mapbox/references/render-stability.md +64 -64
- package/skills/remotion-best-practices/remotion-markup/remotion-maps/techniques/maplibre/TECHNIQUE.md +484 -484
- package/skills/remotion-best-practices/remotion-markup/remotion-maps/techniques/maplibre/references/render-stability.md +64 -64
- package/skills/remotion-best-practices/remotion-markup/remotion-maps/techniques/maptiler/TECHNIQUE.md +80 -80
- package/skills/remotion-best-practices/remotion-markup/remotion-maps/techniques/maptiler/assets/CountryLabel.tsx +70 -70
- package/skills/remotion-best-practices/remotion-markup/remotion-maps/techniques/maptiler/assets/MapTilerVectorElement.ts +80 -80
- package/skills/remotion-best-practices/remotion-markup/remotion-maps/techniques/maptiler/assets/RiverReveal.tsx +363 -363
- package/skills/remotion-best-practices/remotion-markup/remotion-maps/techniques/maptiler/assets/example-Root.tsx +22 -22
- package/skills/remotion-best-practices/remotion-markup/remotion-maps/techniques/maptiler/assets/sample-data/country-meta.json +7535 -7535
- package/skills/remotion-best-practices/remotion-markup/remotion-maps/techniques/maptiler/assets/sample-data/yarlung-flow.json +605 -605
- package/skills/remotion-best-practices/remotion-markup/remotion-maps/techniques/maptiler/assets/tokens.ts +34 -34
- package/skills/remotion-best-practices/remotion-markup/remotion-maps/techniques/maptiler/references/map-data-sources.md +91 -91
- package/skills/remotion-best-practices/remotion-markup/remotion-maps/techniques/maptiler/references/map-explainer-architecture.md +129 -129
- package/skills/remotion-best-practices/remotion-markup/remotion-maps/techniques/maptiler/references/map-geo-prep.md +85 -85
- package/skills/remotion-best-practices/remotion-markup/remotion-maps/techniques/maptiler/references/render-stability.md +64 -64
- package/skills/remotion-best-practices/remotion-markup/remotion-maps/techniques/maptiler/scripts/prep-geo.mjs +202 -202
- package/skills/remotion-best-practices/remotion-markup/remotion-maps/techniques/static-map/TECHNIQUE.md +37 -37
- package/skills/remotion-best-practices/remotion-markup/sequencing.md +169 -169
- package/skills/remotion-best-practices/remotion-markup/sfx.md +51 -51
- package/skills/remotion-best-practices/remotion-markup/silence-detection.md +71 -71
- package/skills/remotion-best-practices/remotion-markup/text-highlights.md +56 -56
- package/skills/remotion-best-practices/remotion-markup/timing.md +111 -111
- package/skills/remotion-best-practices/remotion-markup/transitions.md +239 -239
- package/skills/remotion-best-practices/remotion-markup/video-editing.md +90 -90
- package/skills/remotion-best-practices/remotion-markup/voiceover.md +99 -99
- package/skills/remotion-best-practices/remotion-multimedia/REFERENCE.md +20 -20
- package/skills/remotion-best-practices/remotion-multimedia/agents/openai.yaml +7 -7
- package/skills/remotion-best-practices/remotion-multimedia/assets/remotion-icon.svg +4 -4
- package/skills/remotion-best-practices/remotion-multimedia/get-audio-duration.md +56 -56
- package/skills/remotion-best-practices/remotion-multimedia/get-video-dimensions.md +66 -66
- package/skills/remotion-best-practices/remotion-multimedia/get-video-duration.md +58 -58
- package/skills/remotion-best-practices/remotion-render/REFERENCE.md +35 -35
- package/skills/remotion-best-practices/remotion-render/agents/openai.yaml +7 -7
- package/skills/remotion-best-practices/remotion-render/assets/remotion-icon.svg +4 -4
- package/skills/remotion-best-practices/remotion-render/transparent-videos.md +106 -106
- package/skills/remotion-best-practices/remotion-saas/REFERENCE.md +33 -33
- package/skills/remotion-best-practices/remotion-saas/agents/openai.yaml +7 -7
- package/skills/remotion-best-practices/remotion-saas/assets/remotion-icon.svg +4 -4
- package/skills/remotion-best-practices/remotion-saas/framework.md +19 -19
- package/skills/remotion-best-practices/remotion-saas/player.md +33 -33
- package/skills/remotion-best-practices/remotion-saas/rendering.md +69 -69
- package/skills/remotion-best-practices/remotion-studio/REFERENCE.md +24 -24
- package/skills/remotion-best-practices/remotion-studio/agents/openai.yaml +7 -7
- package/skills/remotion-best-practices/remotion-studio/assets/remotion-icon.svg +4 -4
- package/skills/remotion-best-practices/remotion-upgrade/REFERENCE.md +31 -31
- package/skills/remotion-best-practices/remotion-upgrade/agents/openai.yaml +7 -7
- package/skills/remotion-best-practices/remotion-upgrade/assets/remotion-icon.svg +4 -4
- package/skills/remotion-captions/SKILL.md +31 -31
- package/skills/remotion-captions/agents/openai.yaml +7 -7
- package/skills/remotion-captions/assets/remotion-icon.svg +4 -4
- package/skills/remotion-captions/display-captions.md +190 -190
- package/skills/remotion-captions/import-srt-captions.md +73 -73
- package/skills/remotion-captions/transcribe-captions.md +70 -70
- package/skills/remotion-create/SKILL.md +80 -80
- package/skills/remotion-create/agents/openai.yaml +7 -7
- package/skills/remotion-create/assets/remotion-icon.svg +4 -4
- package/skills/remotion-create/tailwind.md +11 -11
- package/skills/remotion-create/video-layout.md +9 -9
- package/skills/remotion-docs/SKILL.md +41 -41
- package/skills/remotion-docs/agents/openai.yaml +7 -7
- package/skills/remotion-docs/assets/remotion-icon.svg +4 -4
- package/skills/remotion-interactivity/SKILL.md +310 -310
- package/skills/remotion-interactivity/agents/openai.yaml +7 -7
- package/skills/remotion-interactivity/assets/remotion-icon.svg +4 -4
- package/skills/remotion-maps/SKILL.md +31 -31
- package/skills/remotion-maps/agents/openai.yaml +7 -7
- package/skills/remotion-maps/assets/remotion-icon.svg +4 -4
- package/skills/remotion-maps/techniques/cesium/TECHNIQUE.md +89 -89
- package/skills/remotion-maps/techniques/cesium/assets/CesiumFlythrough.tsx +284 -284
- package/skills/remotion-maps/techniques/cesium/assets/cesium-path.json +299 -299
- package/skills/remotion-maps/techniques/cesium/assets/city-path.json +12 -12
- package/skills/remotion-maps/techniques/cesium/assets/example-Root.tsx +39 -39
- package/skills/remotion-maps/techniques/cesium/assets/flight-path.ts +35 -35
- package/skills/remotion-maps/techniques/cesium/assets/sample-river.geojson +215 -215
- package/skills/remotion-maps/techniques/cesium/references/3d-data-sources.md +43 -43
- package/skills/remotion-maps/techniques/cesium/references/3d-flyover-architecture.md +128 -128
- package/skills/remotion-maps/techniques/cesium/references/3d-troubleshooting.md +57 -57
- package/skills/remotion-maps/techniques/cesium/scripts/prep-cesium-path.mjs +161 -161
- package/skills/remotion-maps/techniques/mapbox/TECHNIQUE.md +449 -449
- package/skills/remotion-maps/techniques/mapbox/references/render-stability.md +64 -64
- package/skills/remotion-maps/techniques/maplibre/TECHNIQUE.md +484 -484
- package/skills/remotion-maps/techniques/maplibre/references/render-stability.md +64 -64
- package/skills/remotion-maps/techniques/maptiler/TECHNIQUE.md +80 -80
- package/skills/remotion-maps/techniques/maptiler/assets/CountryLabel.tsx +70 -70
- package/skills/remotion-maps/techniques/maptiler/assets/MapTilerVectorElement.ts +80 -80
- package/skills/remotion-maps/techniques/maptiler/assets/RiverReveal.tsx +363 -363
- package/skills/remotion-maps/techniques/maptiler/assets/example-Root.tsx +22 -22
- package/skills/remotion-maps/techniques/maptiler/assets/sample-data/country-meta.json +7535 -7535
- package/skills/remotion-maps/techniques/maptiler/assets/sample-data/yarlung-flow.json +605 -605
- package/skills/remotion-maps/techniques/maptiler/assets/tokens.ts +34 -34
- package/skills/remotion-maps/techniques/maptiler/references/map-data-sources.md +91 -91
- package/skills/remotion-maps/techniques/maptiler/references/map-explainer-architecture.md +129 -129
- package/skills/remotion-maps/techniques/maptiler/references/map-geo-prep.md +85 -85
- package/skills/remotion-maps/techniques/maptiler/references/render-stability.md +64 -64
- package/skills/remotion-maps/techniques/maptiler/scripts/prep-geo.mjs +202 -202
- package/skills/remotion-maps/techniques/static-map/TECHNIQUE.md +37 -37
- package/skills/remotion-markup/3d.md +86 -86
- package/skills/remotion-markup/SKILL.md +353 -353
- package/skills/remotion-markup/agents/openai.yaml +7 -7
- package/skills/remotion-markup/assets/remotion-icon.svg +4 -4
- package/skills/remotion-markup/audio-visualization.md +198 -198
- package/skills/remotion-markup/audio.md +169 -169
- package/skills/remotion-markup/calculate-metadata.md +136 -136
- package/skills/remotion-markup/compositions.md +123 -123
- package/skills/remotion-markup/connected-compositions.md +79 -79
- package/skills/remotion-markup/cropping.md +34 -34
- package/skills/remotion-markup/effects.md +251 -251
- package/skills/remotion-markup/embedding-videos.md +171 -171
- package/skills/remotion-markup/ffmpeg.md +38 -38
- package/skills/remotion-markup/gifs.md +187 -187
- package/skills/remotion-markup/google-fonts.md +76 -76
- package/skills/remotion-markup/html-in-canvas.md +128 -128
- package/skills/remotion-markup/images.md +73 -73
- package/skills/remotion-markup/light-leaks.md +106 -106
- package/skills/remotion-markup/local-fonts.md +69 -69
- package/skills/remotion-markup/lottie.md +73 -73
- package/skills/remotion-markup/measuring-dom-nodes.md +38 -38
- package/skills/remotion-markup/measuring-text.md +146 -146
- package/skills/remotion-markup/motion-blur.md +60 -60
- package/skills/remotion-markup/multi-scene-video.md +81 -81
- package/skills/remotion-markup/parameters.md +111 -111
- package/skills/remotion-markup/remotion-maps/REFERENCE.md +36 -36
- package/skills/remotion-markup/remotion-maps/agents/openai.yaml +7 -7
- package/skills/remotion-markup/remotion-maps/assets/remotion-icon.svg +4 -4
- package/skills/remotion-markup/remotion-maps/techniques/cesium/TECHNIQUE.md +89 -89
- package/skills/remotion-markup/remotion-maps/techniques/cesium/assets/CesiumFlythrough.tsx +284 -284
- package/skills/remotion-markup/remotion-maps/techniques/cesium/assets/cesium-path.json +299 -299
- package/skills/remotion-markup/remotion-maps/techniques/cesium/assets/city-path.json +12 -12
- package/skills/remotion-markup/remotion-maps/techniques/cesium/assets/example-Root.tsx +39 -39
- package/skills/remotion-markup/remotion-maps/techniques/cesium/assets/flight-path.ts +35 -35
- package/skills/remotion-markup/remotion-maps/techniques/cesium/assets/sample-river.geojson +215 -215
- package/skills/remotion-markup/remotion-maps/techniques/cesium/references/3d-data-sources.md +43 -43
- package/skills/remotion-markup/remotion-maps/techniques/cesium/references/3d-flyover-architecture.md +128 -128
- package/skills/remotion-markup/remotion-maps/techniques/cesium/references/3d-troubleshooting.md +57 -57
- package/skills/remotion-markup/remotion-maps/techniques/cesium/scripts/prep-cesium-path.mjs +161 -161
- package/skills/remotion-markup/remotion-maps/techniques/mapbox/TECHNIQUE.md +449 -449
- package/skills/remotion-markup/remotion-maps/techniques/mapbox/references/render-stability.md +64 -64
- package/skills/remotion-markup/remotion-maps/techniques/maplibre/TECHNIQUE.md +484 -484
- package/skills/remotion-markup/remotion-maps/techniques/maplibre/references/render-stability.md +64 -64
- package/skills/remotion-markup/remotion-maps/techniques/maptiler/TECHNIQUE.md +80 -80
- package/skills/remotion-markup/remotion-maps/techniques/maptiler/assets/CountryLabel.tsx +70 -70
- package/skills/remotion-markup/remotion-maps/techniques/maptiler/assets/MapTilerVectorElement.ts +80 -80
- package/skills/remotion-markup/remotion-maps/techniques/maptiler/assets/RiverReveal.tsx +363 -363
- package/skills/remotion-markup/remotion-maps/techniques/maptiler/assets/example-Root.tsx +22 -22
- package/skills/remotion-markup/remotion-maps/techniques/maptiler/assets/sample-data/country-meta.json +7535 -7535
- package/skills/remotion-markup/remotion-maps/techniques/maptiler/assets/sample-data/yarlung-flow.json +605 -605
- package/skills/remotion-markup/remotion-maps/techniques/maptiler/assets/tokens.ts +34 -34
- package/skills/remotion-markup/remotion-maps/techniques/maptiler/references/map-data-sources.md +91 -91
- package/skills/remotion-markup/remotion-maps/techniques/maptiler/references/map-explainer-architecture.md +129 -129
- package/skills/remotion-markup/remotion-maps/techniques/maptiler/references/map-geo-prep.md +85 -85
- package/skills/remotion-markup/remotion-maps/techniques/maptiler/references/render-stability.md +64 -64
- package/skills/remotion-markup/remotion-maps/techniques/maptiler/scripts/prep-geo.mjs +202 -202
- package/skills/remotion-markup/remotion-maps/techniques/static-map/TECHNIQUE.md +37 -37
- package/skills/remotion-markup/sequencing.md +169 -169
- package/skills/remotion-markup/sfx.md +51 -51
- package/skills/remotion-markup/silence-detection.md +71 -71
- package/skills/remotion-markup/text-highlights.md +56 -56
- package/skills/remotion-markup/timing.md +111 -111
- package/skills/remotion-markup/transitions.md +239 -239
- package/skills/remotion-markup/video-editing.md +90 -90
- package/skills/remotion-markup/voiceover.md +99 -99
- package/skills/remotion-multimedia/SKILL.md +15 -15
- package/skills/remotion-multimedia/agents/openai.yaml +7 -7
- package/skills/remotion-multimedia/assets/remotion-icon.svg +4 -4
- package/skills/remotion-multimedia/get-audio-duration.md +56 -56
- package/skills/remotion-multimedia/get-video-dimensions.md +66 -66
- package/skills/remotion-multimedia/get-video-duration.md +58 -58
- package/skills/remotion-render/SKILL.md +30 -30
- package/skills/remotion-render/agents/openai.yaml +7 -7
- package/skills/remotion-render/assets/remotion-icon.svg +4 -4
- package/skills/remotion-render/transparent-videos.md +106 -106
- package/skills/remotion-saas/SKILL.md +28 -28
- package/skills/remotion-saas/agents/openai.yaml +7 -7
- package/skills/remotion-saas/assets/remotion-icon.svg +4 -4
- package/skills/remotion-saas/framework.md +19 -19
- package/skills/remotion-saas/player.md +33 -33
- package/skills/remotion-saas/rendering.md +69 -69
- package/skills/remotion-studio/SKILL.md +19 -19
- package/skills/remotion-studio/agents/openai.yaml +7 -7
- package/skills/remotion-studio/assets/remotion-icon.svg +4 -4
- package/skills/remotion-upgrade/SKILL.md +26 -26
- package/skills/remotion-upgrade/agents/openai.yaml +7 -7
- package/skills/remotion-upgrade/assets/remotion-icon.svg +4 -4
- package/templates/DESIGN.md +26 -0
package/lib/index.js
CHANGED
|
@@ -5,6 +5,10 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { fileURLToPath } from 'node:url';
|
|
7
7
|
import { createSkillBundle } from './skill-bundle.js';
|
|
8
|
+
import { installMediaWorkbench } from './media-workbench.js';
|
|
9
|
+
export { MediaWorkbench } from './media-workbench.js';
|
|
10
|
+
export { MediaStore } from './media-store.js';
|
|
11
|
+
export { MEDIA_TEMPLATES, MEDIA_VERSIONS } from './media-templates.js';
|
|
8
12
|
export { parseSkillFile } from './skill-bundle.js';
|
|
9
13
|
export const name = 'remotion-skills';
|
|
10
14
|
export const inject = ['skills', 'tools'];
|
|
@@ -35,4 +39,4 @@ const bundle = createSkillBundle({
|
|
|
35
39
|
/** Re-read and validate disk resources; runtime state is checked by the health tool. */
|
|
36
40
|
export function checkBundledSkills() { return bundle.checkFiles(); }
|
|
37
41
|
/** Register this package's skills and its read-only health tool. */
|
|
38
|
-
export function apply(ctx) { bundle.apply(ctx); }
|
|
42
|
+
export function apply(ctx) { bundle.apply(ctx); installMediaWorkbench(ctx, 'remotion'); }
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type MediaEngine, type MediaProject, type MediaAsset } from './media-templates.js';
|
|
2
|
+
export declare function mediaId(value: unknown): string;
|
|
3
|
+
export declare class MediaConflict extends Error {
|
|
4
|
+
status: number;
|
|
5
|
+
}
|
|
6
|
+
export declare class MediaStore {
|
|
7
|
+
readonly root: string;
|
|
8
|
+
constructor(engine: MediaEngine, root?: string);
|
|
9
|
+
init(): Promise<void>;
|
|
10
|
+
path(id: string): Promise<string>;
|
|
11
|
+
checkPath(path: string): Promise<void>;
|
|
12
|
+
get(id: string): Promise<MediaProject>;
|
|
13
|
+
list(): Promise<MediaProject[]>;
|
|
14
|
+
private write;
|
|
15
|
+
create(value: unknown): Promise<MediaProject>;
|
|
16
|
+
edit(id: string, revision: unknown, fn: (p: MediaProject) => Promise<void> | void, bump?: boolean): Promise<MediaProject>;
|
|
17
|
+
update(id: string, revision: unknown, value: unknown): Promise<MediaProject>;
|
|
18
|
+
assetBytes(id: string, asset: MediaAsset): Promise<Buffer>;
|
|
19
|
+
upload(id: string, revision: unknown, name: string, bytes: Buffer): Promise<MediaProject>;
|
|
20
|
+
removeAsset(id: string, revision: unknown, assetId: string): Promise<MediaProject>;
|
|
21
|
+
snapshot(id: string, revision: unknown): Promise<{
|
|
22
|
+
project: MediaProject;
|
|
23
|
+
assets: Map<string, Buffer>;
|
|
24
|
+
}>;
|
|
25
|
+
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { mkdir, readFile, writeFile, rename, readdir, stat, lstat, unlink, realpath } from 'node:fs/promises';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
import { join, resolve, relative, isAbsolute, extname } from 'node:path';
|
|
5
|
+
import lockfile from 'proper-lockfile';
|
|
6
|
+
import { validateFields } from './media-templates.js';
|
|
7
|
+
export function mediaId(value) {
|
|
8
|
+
if (typeof value !== 'string' || !/^[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}$/.test(value))
|
|
9
|
+
throw new Error('项目或任务编号无效。');
|
|
10
|
+
return value;
|
|
11
|
+
}
|
|
12
|
+
export class MediaConflict extends Error {
|
|
13
|
+
status = 409;
|
|
14
|
+
}
|
|
15
|
+
export class MediaStore {
|
|
16
|
+
root;
|
|
17
|
+
constructor(engine, root) { this.root = resolve(root ?? join(process.env.DSH_HOME || join(homedir(), '.dsh'), 'data', 'dsh-' + engine)); }
|
|
18
|
+
async init() { await mkdir(join(this.root, 'projects'), { recursive: true, mode: 0o700 }); }
|
|
19
|
+
async path(id) {
|
|
20
|
+
await this.init();
|
|
21
|
+
const dir = join(this.root, 'projects', mediaId(id));
|
|
22
|
+
if ((await lstat(dir)).isSymbolicLink())
|
|
23
|
+
throw new Error('项目目录不能是符号链接。');
|
|
24
|
+
await this.checkPath(dir);
|
|
25
|
+
return dir;
|
|
26
|
+
}
|
|
27
|
+
async checkPath(path) {
|
|
28
|
+
const base = await realpath(this.root), actual = await realpath(path), rel = relative(base, actual);
|
|
29
|
+
if (rel === '..' || rel.startsWith('..' + (process.platform === 'win32' ? '\\' : '/')) || isAbsolute(rel))
|
|
30
|
+
throw new Error('文件不在本插件项目目录内。');
|
|
31
|
+
}
|
|
32
|
+
async get(id) {
|
|
33
|
+
const file = join(await this.path(id), 'project.json');
|
|
34
|
+
await this.checkPath(file);
|
|
35
|
+
if ((await stat(file)).size > 65536)
|
|
36
|
+
throw new Error('工程记录过大,请保留文件后检查。');
|
|
37
|
+
const p = JSON.parse(await readFile(file, 'utf8'));
|
|
38
|
+
validateFields(p);
|
|
39
|
+
if (p.id !== id || !Number.isInteger(p.revision) || p.revision < 1 || !Array.isArray(p.assets) || p.assets.length > 12 || !Array.isArray(p.renders))
|
|
40
|
+
throw new Error('工程记录损坏;原文件已保留。');
|
|
41
|
+
for (const a of p.assets) {
|
|
42
|
+
mediaId(a.id);
|
|
43
|
+
if (!new RegExp('^' + a.id + '\\.(png|jpg|mp3|wav|mp4)$').test(a.file))
|
|
44
|
+
throw new Error('素材记录无效。');
|
|
45
|
+
}
|
|
46
|
+
for (const r of p.renders)
|
|
47
|
+
mediaId(r.id);
|
|
48
|
+
return p;
|
|
49
|
+
}
|
|
50
|
+
async list() {
|
|
51
|
+
await this.init();
|
|
52
|
+
const result = [];
|
|
53
|
+
for (const id of await readdir(join(this.root, 'projects'))) {
|
|
54
|
+
if (/^[0-9a-f-]{36}$/.test(id))
|
|
55
|
+
result.push(await this.get(id));
|
|
56
|
+
}
|
|
57
|
+
return result.sort((a, b) => b.updated.localeCompare(a.updated));
|
|
58
|
+
}
|
|
59
|
+
async write(p) {
|
|
60
|
+
const dir = await this.path(p.id), temp = join(dir, randomUUID() + '.tmp');
|
|
61
|
+
try {
|
|
62
|
+
await writeFile(temp, JSON.stringify(p, null, 2), { flag: 'wx', mode: 0o600 });
|
|
63
|
+
await rename(temp, join(dir, 'project.json'));
|
|
64
|
+
}
|
|
65
|
+
finally {
|
|
66
|
+
await unlink(temp).catch(() => { });
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
async create(value) {
|
|
70
|
+
await this.init();
|
|
71
|
+
const unlock = await lockfile.lock(join(this.root, 'projects'), { retries: { retries: 8, minTimeout: 30, maxTimeout: 60 } });
|
|
72
|
+
try {
|
|
73
|
+
if ((await this.list()).length >= 30)
|
|
74
|
+
throw new Error('工作台最多保存 30 个项目,请先备份现有工程。');
|
|
75
|
+
const p = { ...validateFields(value), id: randomUUID(), revision: 1, updated: new Date().toISOString(), assets: [], renders: [] };
|
|
76
|
+
await mkdir(join(this.root, 'projects', p.id), { mode: 0o700 });
|
|
77
|
+
await this.write(p);
|
|
78
|
+
return p;
|
|
79
|
+
}
|
|
80
|
+
finally {
|
|
81
|
+
await unlock();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
async edit(id, revision, fn, bump = true) {
|
|
85
|
+
const dir = await this.path(id);
|
|
86
|
+
const unlock = await lockfile.lock(dir, { retries: { retries: 8, minTimeout: 30, maxTimeout: 60 } });
|
|
87
|
+
try {
|
|
88
|
+
const p = await this.get(id);
|
|
89
|
+
if (revision !== undefined && revision !== p.revision)
|
|
90
|
+
throw new MediaConflict('工程已在另一窗口更新。你的输入仍保留,请重新打开最新版本后比较。');
|
|
91
|
+
await fn(p);
|
|
92
|
+
if (bump)
|
|
93
|
+
p.revision++;
|
|
94
|
+
p.updated = new Date().toISOString();
|
|
95
|
+
await this.write(p);
|
|
96
|
+
return p;
|
|
97
|
+
}
|
|
98
|
+
finally {
|
|
99
|
+
await unlock();
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
async update(id, revision, value) { return this.edit(id, revision, p => { Object.assign(p, validateFields(value)); }); }
|
|
103
|
+
async assetBytes(id, asset) {
|
|
104
|
+
const file = join(await this.path(id), 'assets', asset.file);
|
|
105
|
+
await this.checkPath(file);
|
|
106
|
+
if ((await stat(file)).size !== asset.bytes || asset.bytes > 20 * 1024 * 1024)
|
|
107
|
+
throw new Error('素材已改变,请重新上传并检查。');
|
|
108
|
+
return readFile(file);
|
|
109
|
+
}
|
|
110
|
+
async upload(id, revision, name, bytes) {
|
|
111
|
+
if (!bytes.length || bytes.length > 20 * 1024 * 1024)
|
|
112
|
+
throw new Error('单个素材需为 1 字节–20 MB。');
|
|
113
|
+
const ext = extname(name).toLowerCase(), isPng = bytes.subarray(0, 8).equals(Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]));
|
|
114
|
+
const isJpg = bytes[0] === 255 && bytes[1] === 216 && bytes[2] === 255;
|
|
115
|
+
let kind, suffix;
|
|
116
|
+
if (ext === '.png' && isPng) {
|
|
117
|
+
kind = 'image';
|
|
118
|
+
suffix = '.png';
|
|
119
|
+
}
|
|
120
|
+
else if (['.jpg', '.jpeg'].includes(ext) && isJpg) {
|
|
121
|
+
kind = 'image';
|
|
122
|
+
suffix = '.jpg';
|
|
123
|
+
}
|
|
124
|
+
else if (ext === '.wav' && bytes.toString('ascii', 0, 4) === 'RIFF' && bytes.toString('ascii', 8, 12) === 'WAVE') {
|
|
125
|
+
kind = 'audio';
|
|
126
|
+
suffix = '.wav';
|
|
127
|
+
}
|
|
128
|
+
else if (ext === '.mp3' && (bytes.toString('ascii', 0, 3) === 'ID3' || bytes[0] === 255 && (bytes[1] & 224) === 224)) {
|
|
129
|
+
kind = 'audio';
|
|
130
|
+
suffix = '.mp3';
|
|
131
|
+
}
|
|
132
|
+
else if (ext === '.mp4' && bytes.toString('ascii', 4, 8) === 'ftyp') {
|
|
133
|
+
kind = 'video';
|
|
134
|
+
suffix = '.mp4';
|
|
135
|
+
}
|
|
136
|
+
else
|
|
137
|
+
throw new Error('仅支持内容与扩展名一致的 PNG、JPEG、MP3、WAV 或 MP4。');
|
|
138
|
+
return this.edit(id, revision, async (p) => {
|
|
139
|
+
if (p.assets.length >= 12 || p.assets.reduce((n, a) => n + a.bytes, 0) + bytes.length > 40 * 1024 * 1024)
|
|
140
|
+
throw new Error('每个项目最多 12 个素材、合计 40 MB。');
|
|
141
|
+
if (kind !== 'image' && p.assets.some(a => a.kind === kind))
|
|
142
|
+
throw new Error(kind === 'audio' ? '已有背景音频,请先移除再替换。' : '已有背景视频,请先移除再替换。');
|
|
143
|
+
const assetId = randomUUID(), dir = join(await this.path(id), 'assets');
|
|
144
|
+
await mkdir(dir, { recursive: true });
|
|
145
|
+
const asset = { id: assetId, name: name.replace(/[\x00-\x1f/\\]/g, '_').slice(0, 100), file: assetId + suffix, kind, bytes: bytes.length };
|
|
146
|
+
await writeFile(join(dir, asset.file), bytes, { flag: 'wx', mode: 0o600 });
|
|
147
|
+
p.assets.push(asset);
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
async removeAsset(id, revision, assetId) {
|
|
151
|
+
let file;
|
|
152
|
+
const p = await this.edit(id, revision, async (p) => { const a = p.assets.find(a => a.id === mediaId(assetId)); if (!a)
|
|
153
|
+
throw new Error('素材不存在。'); file = join(await this.path(id), 'assets', a.file); p.assets = p.assets.filter(a => a.id !== assetId); });
|
|
154
|
+
// Unlink only this managed upload, never a source file or a resolved link target.
|
|
155
|
+
if (file)
|
|
156
|
+
await unlink(file).catch(() => { });
|
|
157
|
+
return p;
|
|
158
|
+
}
|
|
159
|
+
async snapshot(id, revision) {
|
|
160
|
+
const dir = await this.path(id), unlock = await lockfile.lock(dir, { retries: { retries: 8, minTimeout: 30, maxTimeout: 60 } });
|
|
161
|
+
try {
|
|
162
|
+
const project = await this.get(id);
|
|
163
|
+
if (project.revision !== revision)
|
|
164
|
+
throw new MediaConflict('项目已更新,请保存并重新预览。');
|
|
165
|
+
const assets = new Map();
|
|
166
|
+
for (const a of project.assets)
|
|
167
|
+
assets.set(a.file, await this.assetBytes(id, a));
|
|
168
|
+
return { project, assets };
|
|
169
|
+
}
|
|
170
|
+
finally {
|
|
171
|
+
await unlock();
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/** Original managed templates. Visual identity: templates/DESIGN.md. */
|
|
2
|
+
export type MediaEngine = 'hyperframes' | 'remotion';
|
|
3
|
+
export interface MediaAsset {
|
|
4
|
+
id: string;
|
|
5
|
+
name: string;
|
|
6
|
+
file: string;
|
|
7
|
+
kind: 'image' | 'audio' | 'video';
|
|
8
|
+
bytes: number;
|
|
9
|
+
}
|
|
10
|
+
export interface MediaFields {
|
|
11
|
+
template: string;
|
|
12
|
+
title: string;
|
|
13
|
+
body: string;
|
|
14
|
+
format: string;
|
|
15
|
+
duration: number;
|
|
16
|
+
}
|
|
17
|
+
export interface MediaProject extends MediaFields {
|
|
18
|
+
id: string;
|
|
19
|
+
revision: number;
|
|
20
|
+
updated: string;
|
|
21
|
+
assets: MediaAsset[];
|
|
22
|
+
renders: Array<{
|
|
23
|
+
id: string;
|
|
24
|
+
revision: number;
|
|
25
|
+
bytes: number;
|
|
26
|
+
created: string;
|
|
27
|
+
}>;
|
|
28
|
+
}
|
|
29
|
+
export declare const MEDIA_VERSIONS: {
|
|
30
|
+
readonly hyperframes: "0.8.114";
|
|
31
|
+
readonly remotion: "4.0.532";
|
|
32
|
+
readonly gsap: "3.14.2";
|
|
33
|
+
};
|
|
34
|
+
export declare const MEDIA_TEMPLATES: readonly [{
|
|
35
|
+
readonly id: "title";
|
|
36
|
+
readonly zh: "标题卡";
|
|
37
|
+
readonly en: "Title card";
|
|
38
|
+
readonly description: "一句标题、一段介绍,适合开场与通知。";
|
|
39
|
+
}, {
|
|
40
|
+
readonly id: "product";
|
|
41
|
+
readonly zh: "产品介绍";
|
|
42
|
+
readonly en: "Product card";
|
|
43
|
+
readonly description: "深色画布与一张主图,适合产品亮点。";
|
|
44
|
+
}, {
|
|
45
|
+
readonly id: "slideshow";
|
|
46
|
+
readonly zh: "图文轮播";
|
|
47
|
+
readonly en: "Slideshow";
|
|
48
|
+
readonly description: "上传的图片依次展示,正文每行对应一页。";
|
|
49
|
+
}];
|
|
50
|
+
export declare const MEDIA_FORMATS: Record<string, [number, number]>;
|
|
51
|
+
export declare function validateFields(value: unknown): MediaFields;
|
|
52
|
+
export declare function projectSources(p: MediaProject, engine: MediaEngine): Record<string, string>;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
export const MEDIA_VERSIONS = { hyperframes: '0.8.114', remotion: '4.0.532', gsap: '3.14.2' };
|
|
2
|
+
export const MEDIA_TEMPLATES = [
|
|
3
|
+
{ id: 'title', zh: '标题卡', en: 'Title card', description: '一句标题、一段介绍,适合开场与通知。' },
|
|
4
|
+
{ id: 'product', zh: '产品介绍', en: 'Product card', description: '深色画布与一张主图,适合产品亮点。' },
|
|
5
|
+
{ id: 'slideshow', zh: '图文轮播', en: 'Slideshow', description: '上传的图片依次展示,正文每行对应一页。' },
|
|
6
|
+
];
|
|
7
|
+
export const MEDIA_FORMATS = { landscape: [1280, 720], portrait: [720, 1280], square: [720, 720] };
|
|
8
|
+
export function validateFields(value) {
|
|
9
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
10
|
+
throw new Error('请填写项目内容。');
|
|
11
|
+
const v = value;
|
|
12
|
+
if (!MEDIA_TEMPLATES.some(row => row.id === v.template))
|
|
13
|
+
throw new Error('请选择支持的模板。');
|
|
14
|
+
if (typeof v.title !== 'string' || !v.title.trim() || v.title.length > 72)
|
|
15
|
+
throw new Error('标题需为 1–72 个字符。');
|
|
16
|
+
if (typeof v.body !== 'string' || v.body.length > 360)
|
|
17
|
+
throw new Error('正文最多 360 个字符。');
|
|
18
|
+
if (typeof v.format !== 'string' || !Object.hasOwn(MEDIA_FORMATS, v.format))
|
|
19
|
+
throw new Error('请选择支持的画幅。');
|
|
20
|
+
if (typeof v.duration !== 'number' || !Number.isInteger(v.duration) || v.duration < 3 || v.duration > 30)
|
|
21
|
+
throw new Error('时长需为 3–30 整秒。');
|
|
22
|
+
return { template: v.template, title: v.title.trim(), body: v.body.trim(), format: v.format, duration: v.duration };
|
|
23
|
+
}
|
|
24
|
+
const escapeHtml = (text) => text.replace(/[&<>"']/g, char => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[char]));
|
|
25
|
+
export function projectSources(p, engine) {
|
|
26
|
+
const [width, height] = MEDIA_FORMATS[p.format];
|
|
27
|
+
const images = p.assets.filter(a => a.kind === 'image'), audio = p.assets.find(a => a.kind === 'audio'), video = p.assets.find(a => a.kind === 'video');
|
|
28
|
+
const count = p.template === 'slideshow' ? Math.max(images.length, 1) : 1;
|
|
29
|
+
if (p.duration < count * 2)
|
|
30
|
+
throw new Error(`轮播 ${count} 张图片至少需要 ${count * 2} 秒,请增加时长。`);
|
|
31
|
+
const dark = p.template === 'product', portrait = width <= height;
|
|
32
|
+
const copyWidth = (width - (portrait ? 96 : 128)) * (!portrait && images.length ? .51 : 1);
|
|
33
|
+
const copyHeight = (height - (portrait ? 120 : 112)) * (portrait && images.length ? .55 : 1) - 36;
|
|
34
|
+
// Conservatively fit full-width CJK characters and explicit newlines in the safe area.
|
|
35
|
+
const linesAt = (text, size) => text.split('\n').reduce((n, line) => n + Math.max(1, Math.ceil([...line].length / Math.max(1, Math.floor(copyWidth / size)))), 0);
|
|
36
|
+
let titleSize = Math.round((portrait ? 52 : 68) * (p.title.length > 40 ? .72 : p.title.length > 24 ? .85 : 1));
|
|
37
|
+
let bodySize = p.body.length > 200 ? 21 : 27;
|
|
38
|
+
const bodies = count > 1 ? p.body.split('\n').slice(0, count) : [p.body];
|
|
39
|
+
const fits = () => 42 + linesAt(p.title, titleSize) * titleSize * 1.15 + 22 + Math.max(...bodies.map(body => linesAt(body, bodySize) * bodySize * 1.6)) <= copyHeight;
|
|
40
|
+
while (!fits() && (bodySize > 16 || titleSize > 28)) {
|
|
41
|
+
if (bodySize > 16)
|
|
42
|
+
bodySize--;
|
|
43
|
+
else
|
|
44
|
+
titleSize--;
|
|
45
|
+
}
|
|
46
|
+
if (!fits())
|
|
47
|
+
throw new Error('文字超过此画幅的可读空间。请缩短正文或分成轮播页后再导出。');
|
|
48
|
+
if (count > 1 && p.body.split('\n').length > count)
|
|
49
|
+
throw new Error('正文行数多于轮播图片数,请增加图片或合并文字。');
|
|
50
|
+
const css = `@font-face{font-family:"Microsoft YaHei";src:local("Microsoft YaHei")}*{box-sizing:border-box}body{margin:0;font-family:system-ui,"Microsoft YaHei",sans-serif} #root{position:relative;width:${width}px;height:${height}px;overflow:hidden;background:${dark ? '#142D39' : '#FFF8ED'};color:${dark ? '#FFF8ED' : '#142D39'}} .scene{position:absolute;inset:0;padding:${portrait ? '60px 48px' : '56px 64px'};display:flex;${portrait ? 'flex-direction:column;' : ''}align-items:center;justify-content:center;gap:36px} .copy{flex:1;min-width:0;width:100%} .eyebrow{color:#F26445;font-size:18px;letter-spacing:4px;font-weight:700;margin-bottom:18px} h1{font-size:${titleSize}px;line-height:1.15;margin:0;overflow-wrap:anywhere} p{font-size:${bodySize}px;line-height:1.6;margin:22px 0 0;white-space:pre-line;overflow-wrap:anywhere} .visual{${portrait ? 'height:40%;width:100%;' : 'width:45%;height:80%;'}flex-shrink:0;object-fit:contain;border-radius:18px} .accent{position:absolute;left:0;top:0;width:14px;height:100%;background:#F26445} .page{position:absolute;right:40px;bottom:22px;font-size:18px;opacity:.65}`;
|
|
51
|
+
const design = `# Design\nOriginal DSH managed templates.\n\nPaper #FFF8ED; ink #142D39; coral #F26445; mint #79CDB4. Local system-ui / Microsoft YaHei. Safe margins, wrapped text, deterministic entrances. No remote fonts, random or infinite animation.\n`;
|
|
52
|
+
const info = { ...p, width, height, fps: 24, titleSize, bodySize, dark, portrait };
|
|
53
|
+
const sources = {
|
|
54
|
+
'project.json': JSON.stringify(p, null, 2), 'DESIGN.md': design,
|
|
55
|
+
'README.md': `# ${p.title.replace(/[\r\n]/g, ' ')}\n\nManaged ${engine} project, revision ${p.revision}.\n\nRun npm install, then npm run preview or npm run render. Media files are local copies in public/assets. Generated template code is MIT. Renderer dependencies keep their own licenses; Remotion: https://www.remotion.dev/license .\n`,
|
|
56
|
+
};
|
|
57
|
+
if (engine === 'hyperframes') {
|
|
58
|
+
sources['meta.json'] = JSON.stringify({ id: p.id, name: p.title, createdAt: p.updated }, null, 2);
|
|
59
|
+
sources['hyperframes.json'] = JSON.stringify({ $schema: 'https://hyperframes.heygen.com/schema/hyperframes.json', paths: { blocks: 'compositions', components: 'compositions/components', assets: 'public/assets' }, media: { autoProxy: true } }, null, 2);
|
|
60
|
+
let clips = '';
|
|
61
|
+
for (let i = 0; i < count; i++) {
|
|
62
|
+
const start = i * p.duration / count, duration = p.duration / count;
|
|
63
|
+
const body = count > 1 ? (p.body.split('\n')[i] ?? '') : p.body;
|
|
64
|
+
const scene = 'scene-' + i;
|
|
65
|
+
clips += `<div id="${scene}" class="clip" data-composition-id="${scene}" data-composition-src="compositions/${scene}.html" data-start="${start}" data-duration="${duration}" data-track-index="1" style="position:absolute;inset:0;width:100%;height:100%"></div>`;
|
|
66
|
+
sources['compositions/' + scene + '.html'] = `<!doctype html><html lang="zh"><head><meta charset="utf-8"><style>${css}#root{background:transparent}</style></head><body><div id="root" data-composition-id="${scene}" data-start="0" data-duration="${duration}" data-width="${width}" data-height="${height}"><div class="scene"><div class="copy"><div class="eyebrow">${escapeHtml(p.template.toUpperCase())}</div><h1 id="title-${i}">${escapeHtml(p.title)}</h1><p id="body-${i}">${escapeHtml(body)}</p></div>${images.length ? `<img class="visual" src="public/assets/${images[i % images.length].file}" alt="">` : ''}<div class="page">${i + 1} / ${count}</div></div><div class="accent" data-layout-ignore></div></div><script src="vendor/gsap.min.js"></script><script>window.__timelines=window.__timelines||{};const tl=gsap.timeline({paused:true});tl.to({},{duration:${duration}},0);tl.from('#title-${i}',{y:24,opacity:0,duration:.45},.08);tl.from('#body-${i}',{y:16,opacity:0,duration:.4},.2);window.__timelines['${scene}']=tl;</script></body></html>`;
|
|
67
|
+
}
|
|
68
|
+
const media = `${video ? `<video id="bg-video" class="clip" style="position:absolute;inset:0;width:100%;height:100%;opacity:.22;object-fit:cover" src="public/assets/${video.file}" muted playsinline data-start="0" data-duration="${p.duration}" data-track-index="0"></video>` : ''}${audio ? `<audio id="music" class="clip" src="public/assets/${audio.file}" data-start="0" data-duration="${p.duration}" data-track-index="2" data-volume="0.4"></audio>` : ''}`;
|
|
69
|
+
sources['index.html'] = `<!doctype html><html lang="zh"><head><meta charset="utf-8"><style>${css}</style></head><body><div id="root" data-composition-id="managed-video" data-start="0" data-duration="${p.duration}" data-width="${width}" data-height="${height}" data-fps="24">${media}${clips}</div><script src="vendor/gsap.min.js"></script><script>window.__timelines=window.__timelines||{};const tl=gsap.timeline({paused:true});tl.to({},{duration:${p.duration}},0);window.__timelines['managed-video']=tl;</script></body></html>`;
|
|
70
|
+
sources['setup.mjs'] = `import{mkdirSync,copyFileSync}from'node:fs';mkdirSync('vendor',{recursive:true});copyFileSync('node_modules/gsap/dist/gsap.min.js','vendor/gsap.min.js');\n`;
|
|
71
|
+
sources['package.json'] = JSON.stringify({ private: true, type: 'module', dependencies: { hyperframes: MEDIA_VERSIONS.hyperframes, gsap: MEDIA_VERSIONS.gsap }, scripts: { prepare: 'node setup.mjs', preview: 'hyperframes preview --no-open --foreground', render: 'hyperframes browser ensure && hyperframes render --workers 1 --no-best-effort --strict --output final.mp4 --fps 24' } }, null, 2);
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
sources['data.json'] = JSON.stringify(info);
|
|
75
|
+
sources['src/index.jsx'] = `import React from 'react';
|
|
76
|
+
import {registerRoot,Composition,AbsoluteFill,Img,Audio,OffthreadVideo,staticFile,useCurrentFrame,interpolate} from 'remotion';
|
|
77
|
+
import data from '../data.json';
|
|
78
|
+
function Video(){const frame=useCurrentFrame();const photos=data.assets.filter(a=>a.kind==='image'),music=data.assets.find(a=>a.kind==='audio'),video=data.assets.find(a=>a.kind==='video');const count=data.template==='slideshow'?Math.max(photos.length,1):1;const length=data.duration*24/count;const page=Math.min(count-1,Math.floor(frame/length));const local=frame-page*length;const opacity=interpolate(local,[0,12,length-10,length],[0,1,1,0],{extrapolateLeft:'clamp',extrapolateRight:'clamp'});const body=count>1?(data.body.split('\\n')[page]||''):data.body;
|
|
79
|
+
return <AbsoluteFill style={{background:data.dark?'#142D39':'#FFF8ED',color:data.dark?'#FFF8ED':'#142D39',fontFamily:'system-ui,Microsoft YaHei,sans-serif'}}>
|
|
80
|
+
{video&&<OffthreadVideo muted src={staticFile('assets/'+video.file)} style={{position:'absolute',width:'100%',height:'100%',objectFit:'cover',opacity:.22}}/>}
|
|
81
|
+
<div style={{position:'absolute',inset:0,padding:data.portrait?'60px 48px':'56px 64px',display:'flex',flexDirection:data.portrait?'column':'row',alignItems:'center',justifyContent:'center',gap:36,opacity}}>
|
|
82
|
+
<div style={{flex:1,minWidth:0,width:'100%'}}><div style={{color:'#F26445',fontSize:18,letterSpacing:4,fontWeight:700,marginBottom:18}}>{data.template.toUpperCase()}</div><h1 style={{fontSize:data.titleSize,lineHeight:1.15,margin:0,overflowWrap:'anywhere'}}>{data.title}</h1><p style={{fontSize:data.bodySize,lineHeight:1.6,marginTop:22,whiteSpace:'pre-line',overflowWrap:'anywhere'}}>{body}</p></div>
|
|
83
|
+
{photos.length>0&&<Img src={staticFile('assets/'+photos[page%photos.length].file)} style={{width:data.portrait?'100%':'45%',height:data.portrait?'40%':'80%',flexShrink:0,objectFit:'contain',borderRadius:18}}/>}</div>
|
|
84
|
+
<div style={{position:'absolute',left:0,top:0,width:14,height:'100%',background:'#F26445'}}/><div style={{position:'absolute',right:40,bottom:22,fontSize:18,opacity:.65}}>{page+1} / {count}</div>{music&&<Audio src={staticFile('assets/'+music.file)} volume={.4}/>}</AbsoluteFill>}
|
|
85
|
+
const Root=()=> <Composition id="ManagedVideo" component={Video} durationInFrames={data.duration*24} fps={24} width={data.width} height={data.height}/>;
|
|
86
|
+
registerRoot(Root);
|
|
87
|
+
`;
|
|
88
|
+
sources['package.json'] = JSON.stringify({ private: true, dependencies: { '@remotion/cli': MEDIA_VERSIONS.remotion, remotion: MEDIA_VERSIONS.remotion, react: '18.3.1', 'react-dom': '18.3.1' }, scripts: { preview: 'remotion studio src/index.jsx --no-open', render: 'remotion render src/index.jsx ManagedVideo final.mp4 --concurrency 1 --codec h264' } }, null, 2);
|
|
89
|
+
}
|
|
90
|
+
return sources;
|
|
91
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { MediaStore } from './media-store.js';
|
|
2
|
+
import { type MediaEngine } from './media-templates.js';
|
|
3
|
+
interface MediaJob {
|
|
4
|
+
id: string;
|
|
5
|
+
kind: 'prepare' | 'preview' | 'render';
|
|
6
|
+
projectId?: string;
|
|
7
|
+
revision?: number;
|
|
8
|
+
state: 'running' | 'done' | 'failed' | 'cancelled';
|
|
9
|
+
stage: string;
|
|
10
|
+
lines: string[];
|
|
11
|
+
url?: string;
|
|
12
|
+
output?: string;
|
|
13
|
+
}
|
|
14
|
+
export declare class MediaWorkbench {
|
|
15
|
+
readonly engine: MediaEngine;
|
|
16
|
+
readonly store: MediaStore;
|
|
17
|
+
readonly route: string;
|
|
18
|
+
readonly runtime: string;
|
|
19
|
+
private jobs;
|
|
20
|
+
private disposed;
|
|
21
|
+
constructor(engine: MediaEngine, root?: string);
|
|
22
|
+
private browserFile;
|
|
23
|
+
environment(): Promise<{
|
|
24
|
+
ready: boolean;
|
|
25
|
+
version: string;
|
|
26
|
+
node: string;
|
|
27
|
+
hint: string;
|
|
28
|
+
}>;
|
|
29
|
+
job(id: string): MediaJob;
|
|
30
|
+
private log;
|
|
31
|
+
private stopChild;
|
|
32
|
+
private run;
|
|
33
|
+
private cli;
|
|
34
|
+
start(kind: MediaJob['kind'], id?: string, revision?: unknown): Promise<MediaJob>;
|
|
35
|
+
private execute;
|
|
36
|
+
cancel(id: string): MediaJob;
|
|
37
|
+
dispose(): void;
|
|
38
|
+
private guard;
|
|
39
|
+
fetch(request: Request): Promise<Response>;
|
|
40
|
+
}
|
|
41
|
+
export declare function installMediaWorkbench(ctx: any, engine: MediaEngine): MediaWorkbench;
|
|
42
|
+
export {};
|