hyperframes 0.8.183 → 0.8.185
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/dist/{add-HW3ZGR36.js → add-ODTNPRGG.js} +14 -14
- package/dist/{agentPromptGenerator-VIIPZCJ3.js → agentPromptGenerator-KYO5MKTW.js} +1 -1
- package/dist/{animatedGifPrep-EPLN37ZN.js → animatedGifPrep-PEP5GWOJ.js} +10 -9
- package/dist/api.js +18 -17
- package/dist/{asset-TOASKL3Z.js → asset-EZG42XPA.js} +2 -2
- package/dist/{assetCataloger-PZN5HYG2.js → assetCataloger-OHAJFCA7.js} +10 -9
- package/dist/{audio-F5OIQVJO.js → audio-65R4T74I.js} +18 -16
- package/dist/{auth-YVQLRS3T.js → auth-VP2SIZFN.js} +1 -1
- package/dist/{autoUpdate-EG4ZFW4D.js → autoUpdate-RVSUX4SS.js} +2 -2
- package/dist/{backgroundChecks-WINNQVR2.js → backgroundChecks-LEHK74NS.js} +3 -3
- package/dist/backgroundChecksWorker.js +3 -3
- package/dist/{batchRender-PASZTMEE.js → batchRender-3X6CL7PK.js} +5 -5
- package/dist/{beats-KGIOEBLW.js → beats-66VPQLSC.js} +6 -6
- package/dist/{benchmark-ZNY2L7HN.js → benchmark-PBJVTOJP.js} +10 -10
- package/dist/{browser-REAO7RU4.js → browser-TPL5MWGC.js} +3 -3
- package/dist/{bundleWithLocalizedFonts-OAYLYC4W.js → bundleWithLocalizedFonts-67MLTHKM.js} +2 -2
- package/dist/{capture-WOSF276U.js → capture-F3GNMRZM.js} +14 -13
- package/dist/{capture-IK6GWQDD.js → capture-YYRE7EV3.js} +2 -2
- package/dist/{captureCompositionFrame-DDZNKYRE.js → captureCompositionFrame-PXV2ISHD.js} +14 -13
- package/dist/{catalog-AKTXS7OJ.js → catalog-6SDD3OHF.js} +14 -14
- package/dist/{catch-up-O3VZMPVE.js → catch-up-CWIVA5VG.js} +4 -4
- package/dist/{check-6E46NIWI.js → check-YQ6REK2S.js} +35 -34
- package/dist/{checkBrowser-UNZHQFNH.js → checkBrowser-5XZCBRXM.js} +32 -31
- package/dist/{chunk-TOHA5NAU.js → chunk-2TAS5O4P.js} +56 -11
- package/dist/{chunk-3Q47QC46.js → chunk-37CNYTQQ.js} +2 -2
- package/dist/chunk-3NPOFC43.js +3805 -0
- package/dist/{chunk-EEP5Y5IE.js → chunk-46HEFGXJ.js} +1 -1
- package/dist/{chunk-GAGMLVCV.js → chunk-4CTNDBE3.js} +2 -2
- package/dist/{chunk-CGK4XZ2J.js → chunk-55YELV7B.js} +1 -1
- package/dist/{chunk-TJB4GAV6.js → chunk-5GUKZRQN.js} +1 -1
- package/dist/{chunk-J4L5TPLS.js → chunk-5UDRPPN2.js} +48 -6
- package/dist/{chunk-SAQYJLI6.js → chunk-6ASIXMFP.js} +1 -1
- package/dist/{chunk-GPE3WWTO.js → chunk-6GPR3G3C.js} +14 -11
- package/dist/{chunk-MIZDJVPZ.js → chunk-6RIZY7OP.js} +1 -1
- package/dist/{chunk-5CS4T6C6.js → chunk-7L37AB57.js} +1 -1
- package/dist/{chunk-VH5KESLX.js → chunk-7PS6RVWD.js} +1 -1
- package/dist/{chunk-U5YVWG5W.js → chunk-7RAY4WZP.js} +3 -3
- package/dist/{chunk-COTLNID2.js → chunk-A6QCO474.js} +5 -5
- package/dist/{chunk-YMS5FOMC.js → chunk-B2A56S5C.js} +1 -1
- package/dist/{chunk-4FQKS7BJ.js → chunk-BH7TTIPN.js} +11 -13
- package/dist/{chunk-LBWRQ4XA.js → chunk-BRAPBUPQ.js} +1 -1
- package/dist/{chunk-IC2WYONA.js → chunk-CX3USX23.js} +3 -3
- package/dist/{chunk-ITSLKUQM.js → chunk-D42NU3DZ.js} +1 -1
- package/dist/{chunk-Q6UYKWBG.js → chunk-EMAWIZ54.js} +481 -36
- package/dist/{chunk-ZFO3A3QL.js → chunk-EMSDWBG3.js} +2 -2
- package/dist/{chunk-KRFAX5JK.js → chunk-ESBMUZXZ.js} +1 -1
- package/dist/{chunk-CAHTO62O.js → chunk-F3OPUWQX.js} +7 -7
- package/dist/{chunk-R6OV4J4K.js → chunk-FX6RUJUY.js} +3 -3
- package/dist/{chunk-F6WHPHLF.js → chunk-G2UANIDX.js} +1 -1
- package/dist/{chunk-DEORFAWP.js → chunk-H5SM5PJI.js} +1 -1
- package/dist/{chunk-5OGDBU4G.js → chunk-H6UY725W.js} +2 -2
- package/dist/{chunk-K2Z5DZBS.js → chunk-HDIRZGK4.js} +1 -1
- package/dist/{chunk-TXGZZKDF.js → chunk-HHSJJFVM.js} +2 -2
- package/dist/{chunk-VDJO4DJ7.js → chunk-HXUTX5GR.js} +3 -3
- package/dist/{chunk-73LTCCZI.js → chunk-I3IH5MV5.js} +19 -23
- package/dist/{chunk-3OLIGYWN.js → chunk-I45HL3VP.js} +3 -3
- package/dist/{chunk-TYXH3EKP.js → chunk-I5JRXMCV.js} +16 -1
- package/dist/{chunk-RZXUQR42.js → chunk-I7QMXZSB.js} +2 -2
- package/dist/{chunk-XOVHO3TT.js → chunk-IDFNKFPL.js} +7 -7
- package/dist/{chunk-QX4GGPMY.js → chunk-IDGLGXPA.js} +1 -1
- package/dist/{chunk-5MBLWFLM.js → chunk-IPDKZ5IN.js} +33 -6
- package/dist/{chunk-ANR6WOC3.js → chunk-ISTRDGIX.js} +1 -1
- package/dist/{chunk-4UFJWRHG.js → chunk-IVEFOUZI.js} +211 -83
- package/dist/chunk-IZCW2NKT.js +29 -0
- package/dist/{chunk-X3IA5RYH.js → chunk-J36JVBLR.js} +9 -2
- package/dist/{chunk-Z4R35NJS.js → chunk-J5XEZ24I.js} +3 -3
- package/dist/{chunk-OH56PXFH.js → chunk-K2ZKXTVL.js} +1 -1
- package/dist/{chunk-ATMDOUNN.js → chunk-K73RJB23.js} +2 -2
- package/dist/{chunk-NZ3BE2QE.js → chunk-KQ4JEJLF.js} +10 -10
- package/dist/{chunk-VFBAKPRC.js → chunk-KROXPHXX.js} +9 -3
- package/dist/{chunk-QGOYNPQT.js → chunk-LLFQA7XF.js} +48 -5
- package/dist/chunk-LLMSX6V6.js +17 -0
- package/dist/{chunk-XR5BSEUF.js → chunk-LYKSTRAY.js} +3 -3
- package/dist/{chunk-UNOSCGBG.js → chunk-OAG47RE6.js} +1 -1
- package/dist/{chunk-O7N3A7PE.js → chunk-PHMBMOXF.js} +1 -1
- package/dist/{chunk-BP4VNTZL.js → chunk-PMMQRJDE.js} +1 -1
- package/dist/{chunk-ADDHW2FS.js → chunk-Q447CX26.js} +82 -12
- package/dist/{chunk-WT4PYV2J.js → chunk-Q47FAYYK.js} +202 -55
- package/dist/{chunk-AKFADCMS.js → chunk-RTBSA3EA.js} +1 -1
- package/dist/{chunk-VAK7A2N6.js → chunk-RUEGYANA.js} +11 -11
- package/dist/{chunk-OYLO6TYK.js → chunk-RUOG4VB7.js} +1 -1
- package/dist/{chunk-EVAQPW6O.js → chunk-SKYPMHQC.js} +4 -4
- package/dist/{chunk-B2SZZFWW.js → chunk-SQVRXHOW.js} +3 -3
- package/dist/{chunk-CJYAJ3QM.js → chunk-TAX2ZJAP.js} +24 -16
- package/dist/{chunk-GTE5WZAM.js → chunk-TOI35GN2.js} +1 -1
- package/dist/{chunk-XJHKJXT4.js → chunk-UPR3MU6Z.js} +1 -1
- package/dist/{chunk-DLC2FICN.js → chunk-WQLKDL35.js} +2 -1
- package/dist/{chunk-3BA2GP4H.js → chunk-X6L6MD5B.js} +5 -5
- package/dist/{chunk-XAQDR5Y5.js → chunk-XGHHVYC7.js} +94 -32
- package/dist/{chunk-HLBODNGW.js → chunk-XWC7CNPY.js} +1 -1
- package/dist/{clean-WNEIVBF3.js → clean-7J6ZCCGV.js} +19 -18
- package/dist/cli.js +53 -53
- package/dist/{cloud-6GSSDCJL.js → cloud-7IJCJCDD.js} +4 -4
- package/dist/{cloudrun-4NEJRE4T.js → cloudrun-UNZRIIDB.js} +7 -7
- package/dist/commands/contrast-audit.browser.js +1 -2
- package/dist/commands/layout-audit.browser.js +196 -0
- package/dist/{compare-2JWQ5ABA.js → compare-Q7VW5JZT.js} +29 -28
- package/dist/{compiler-27KQD7KW.js → compiler-KPHYGFXL.js} +6 -6
- package/dist/{component-HCRET3MH.js → component-UUBREJKN.js} +3 -3
- package/dist/{compositions-HTSQ6SUJ.js → compositions-T4FTKIDP.js} +14 -13
- package/dist/{delete-GHQ5UYM7.js → delete-WZQF2IIA.js} +3 -3
- package/dist/{deploy-I3Z3NAVA.js → deploy-JU7A3XNC.js} +1 -1
- package/dist/{deterministicFonts-JFLSSKRB.js → deterministicFonts-EGAAE7QL.js} +3 -3
- package/dist/{dist-SENYPQGW.js → dist-LM7AVHOU.js} +3 -3
- package/dist/{dist-WRDLWRMS.js → dist-ULGTPSU4.js} +8 -4
- package/dist/docs/data-attributes.md +1 -1
- package/dist/{doctor-UGFVQZHI.js → doctor-VSNGRKXJ.js} +14 -13
- package/dist/{events-YS4OVX3I.js → events-CRW2DYUT.js} +3 -3
- package/dist/{events-BLJGWMNP.js → events-E63YBUL2.js} +3 -3
- package/dist/{feedback-AZ3SJ5HF.js → feedback-OSZWVR6N.js} +9 -9
- package/dist/{ffmpeg-2AECXG7V.js → ffmpeg-JLH3Z7G7.js} +10 -9
- package/dist/{figma-DRYIJQHV.js → figma-2BQNNR53.js} +3 -3
- package/dist/fontLocalizeCli.js +13 -12
- package/dist/{get-DDEAOFPB.js → get-P2XI53QW.js} +3 -3
- package/dist/{gpuPolicy-UDJT2MQK.js → gpuPolicy-NTKQJY75.js} +10 -9
- package/dist/{grade-compare-LJKK5NSY.js → grade-compare-OYASIHIB.js} +30 -29
- package/dist/{gsapParserAcorn-7NCK2LVT.js → gsapParserAcorn-SKODXC2N.js} +2 -2
- package/dist/{help-TQBGYOID.js → help-HK6K2N4G.js} +1 -1
- package/dist/{history-5TOOCF2Y.js → history-JMTYKLXW.js} +6 -6
- package/dist/hyperframe.manifest.json +1 -1
- package/dist/hyperframe.runtime.iife.js +65 -65
- package/dist/hyperframes-player.global.js +1 -1
- package/dist/{info-ENI5MP4W.js → info-AW7CIVRE.js} +9 -9
- package/dist/{init-JXPXWV3G.js → init-6KIDG4LB.js} +31 -30
- package/dist/{inspect-WZNYQ5AB.js → inspect-B7BLYMRZ.js} +30 -29
- package/dist/{installer-3SGRBLJW.js → installer-ZP2LQZUQ.js} +7 -7
- package/dist/{keyframes-HEOC4CT4.js → keyframes-EQ36A5JE.js} +7 -7
- package/dist/{lambda-2VHQ4AL2.js → lambda-E6VURLIS.js} +9 -9
- package/dist/{layout-7WEUNO6T.js → layout-XYC64QQQ.js} +30 -29
- package/dist/{lint-5YMAF3L7.js → lint-V5DJYWBX.js} +9 -9
- package/dist/{lintProject-C2ONSH2U.js → lintProject-JATQKR64.js} +3 -3
- package/dist/{list-BIE3X7CO.js → list-RUGVRTVD.js} +3 -3
- package/dist/{login-YDIGJWCG.js → login-ZTKGKXGS.js} +4 -4
- package/dist/{manager-D2UV6RDQ.js → manager-JUP7PM6N.js} +11 -10
- package/dist/{media-treatment-FSKHPMWA.js → media-treatment-VIUPK4OR.js} +16 -15
- package/dist/{models-COEK6LXG.js → models-LJS2HBOZ.js} +14 -13
- package/dist/{motionShot-HYZ2ZGJQ.js → motionShot-3UAXZ7SN.js} +17 -16
- package/dist/{normalize-audio-UXR6WQ7Q.js → normalize-audio-SBYI4YIH.js} +4 -4
- package/dist/{open-OFT2GIDG.js → open-S27TUD3A.js} +14 -14
- package/dist/{parakeetRunner-4NZ3QXS3.js → parakeetRunner-ETLMLJ4L.js} +15 -14
- package/dist/{pipeline-ZKE54JMG.js → pipeline-I2M22M24.js} +11 -10
- package/dist/{play-2VG77FT6.js → play-COJU6VJK.js} +22 -21
- package/dist/{preflight-QTWDIM5C.js → preflight-FZ24NCHH.js} +11 -10
- package/dist/{present-OONXQLJG.js → present-YAHKLC5D.js} +18 -17
- package/dist/{preview-6RZJFU76.js → preview-KQOEU5YE.js} +27 -26
- package/dist/{producer-6YM3UH3B.js → producer-DTNNG4T6.js} +1 -1
- package/dist/{projectConfig-YMW4P2X6.js → projectConfig-4A76VYUP.js} +9 -9
- package/dist/{publication-SEINUAPZ.js → publication-SQPMC2W3.js} +4 -4
- package/dist/{publish-KZBTBWFO.js → publish-QUEJBGGT.js} +13 -12
- package/dist/{remove-background-BIQNMIR7.js → remove-background-FP2ETAQH.js} +12 -11
- package/dist/{render-FIH37HJA.js → render-BIFIL4U6.js} +32 -31
- package/dist/render-GQFXJWLV.js +1504 -0
- package/dist/{render-LW76HOMJ.js → render-P664SMC7.js} +5 -5
- package/dist/{render-batch-POLEEYD4.js → render-batch-ZCPMYH6Z.js} +5 -5
- package/dist/renderSetupWorker.js +3 -3
- package/dist/{resolver-O2SED3XB.js → resolver-BUC2GFZQ.js} +6 -6
- package/dist/{sherpa-HTFFFTVQ.js → sherpa-HFT6XEP6.js} +14 -13
- package/dist/skills/hyperframes/SKILL.md +52 -174
- package/dist/skills/hyperframes/assets/frame-presets/biennale-yellow/FRAME.md +289 -0
- package/dist/skills/hyperframes/assets/frame-presets/biennale-yellow/caption-skin.html +220 -0
- package/dist/skills/hyperframes/assets/frame-presets/biennale-yellow/frame-showcase.html +1214 -0
- package/dist/skills/hyperframes/assets/frame-presets/blockframe/FRAME.md +275 -0
- package/dist/skills/hyperframes/assets/frame-presets/blockframe/caption-skin.html +223 -0
- package/dist/skills/hyperframes/assets/frame-presets/blockframe/frame-showcase.html +1347 -0
- package/dist/skills/hyperframes/assets/frame-presets/blue-professional/FRAME.md +307 -0
- package/dist/skills/hyperframes/assets/frame-presets/blue-professional/caption-skin.html +222 -0
- package/dist/skills/hyperframes/assets/frame-presets/blue-professional/frame-showcase.html +1372 -0
- package/dist/skills/hyperframes/assets/frame-presets/bold-poster/FRAME.md +275 -0
- package/dist/skills/hyperframes/assets/frame-presets/bold-poster/caption-skin.html +229 -0
- package/dist/skills/hyperframes/assets/frame-presets/bold-poster/frame-showcase.html +1140 -0
- package/dist/skills/hyperframes/assets/frame-presets/broadside/FRAME.md +284 -0
- package/dist/skills/hyperframes/assets/frame-presets/broadside/caption-skin.html +224 -0
- package/dist/skills/hyperframes/assets/frame-presets/broadside/frame-showcase.html +1166 -0
- package/dist/skills/hyperframes/assets/frame-presets/capsule/FRAME.md +284 -0
- package/dist/skills/hyperframes/assets/frame-presets/capsule/caption-skin.html +223 -0
- package/dist/skills/hyperframes/assets/frame-presets/capsule/frame-showcase.html +1465 -0
- package/dist/skills/hyperframes/assets/frame-presets/cartesian/FRAME.md +278 -0
- package/dist/skills/hyperframes/assets/frame-presets/cartesian/caption-skin.html +221 -0
- package/dist/skills/hyperframes/assets/frame-presets/cartesian/frame-showcase.html +1263 -0
- package/dist/skills/hyperframes/assets/frame-presets/cobalt-grid/FRAME.md +265 -0
- package/dist/skills/hyperframes/assets/frame-presets/cobalt-grid/caption-skin.html +237 -0
- package/dist/skills/hyperframes/assets/frame-presets/cobalt-grid/frame-showcase.html +1468 -0
- package/dist/skills/hyperframes/assets/frame-presets/code-editorial/FRAME.md +277 -0
- package/dist/skills/hyperframes/assets/frame-presets/code-editorial/caption-skin.html +223 -0
- package/dist/skills/hyperframes/assets/frame-presets/code-editorial/fonts/EBGaramond-400.woff2 +0 -0
- package/dist/skills/hyperframes/assets/frame-presets/code-editorial/fonts/EBGaramond-700.woff2 +0 -0
- package/dist/skills/hyperframes/assets/frame-presets/code-editorial/fonts/Inter-400.woff2 +0 -0
- package/dist/skills/hyperframes/assets/frame-presets/code-editorial/fonts/Inter-700.woff2 +0 -0
- package/dist/skills/hyperframes/assets/frame-presets/code-editorial/fonts/JetBrainsMono-400.woff2 +0 -0
- package/dist/skills/hyperframes/assets/frame-presets/code-editorial/fonts/JetBrainsMono-700.woff2 +0 -0
- package/dist/skills/hyperframes/assets/frame-presets/code-editorial/fonts/OFL-eb-garamond.txt +93 -0
- package/dist/skills/hyperframes/assets/frame-presets/code-editorial/fonts/OFL-inter.txt +93 -0
- package/dist/skills/hyperframes/assets/frame-presets/code-editorial/fonts/OFL-jetbrains-mono.txt +93 -0
- package/dist/skills/hyperframes/assets/frame-presets/code-editorial/frame-showcase.html +1438 -0
- package/dist/skills/hyperframes/assets/frame-presets/coral/FRAME.md +314 -0
- package/dist/skills/hyperframes/assets/frame-presets/coral/caption-skin.html +225 -0
- package/dist/skills/hyperframes/assets/frame-presets/coral/frame-showcase.html +1268 -0
- package/dist/skills/hyperframes/assets/frame-presets/creative-mode/FRAME.md +333 -0
- package/dist/skills/hyperframes/assets/frame-presets/creative-mode/caption-skin.html +226 -0
- package/dist/skills/hyperframes/assets/frame-presets/creative-mode/frame-showcase.html +1330 -0
- package/dist/skills/hyperframes/assets/frame-presets/daisy-days/FRAME.md +274 -0
- package/dist/skills/hyperframes/assets/frame-presets/daisy-days/caption-skin.html +229 -0
- package/dist/skills/hyperframes/assets/frame-presets/daisy-days/frame-showcase.html +1444 -0
- package/dist/skills/hyperframes/assets/frame-presets/editorial-forest/FRAME.md +265 -0
- package/dist/skills/hyperframes/assets/frame-presets/editorial-forest/caption-skin.html +222 -0
- package/dist/skills/hyperframes/assets/frame-presets/editorial-forest/frame-showcase.html +1273 -0
- package/dist/skills/hyperframes/references/captions.md +46 -0
- package/dist/skills/hyperframes/references/film.md +47 -0
- package/dist/skills/hyperframes/references/format.md +157 -0
- package/dist/skills/hyperframes/references/look.md +69 -0
- package/dist/skills/hyperframes/references/motion.md +94 -0
- package/dist/skills/hyperframes/references/overlays.md +39 -0
- package/dist/skills/hyperframes/references/plugin-installation.md +3 -3
- package/dist/skills/hyperframes/references/review.md +51 -0
- package/dist/skills/hyperframes/references/runtimes.md +43 -0
- package/dist/skills/hyperframes/references/skill-lifecycle.md +21 -13
- package/dist/skills/hyperframes/references/sound.md +43 -0
- package/dist/skills/hyperframes/references/spec.md +76 -0
- package/dist/skills/hyperframes/references/story.md +103 -0
- package/dist/skills/hyperframes/references/storyboard-format.md +30 -22
- package/dist/skills/hyperframes/references/storyboard.md +36 -0
- package/dist/skills/hyperframes/references/types/3d.md +25 -0
- package/dist/skills/hyperframes/references/types/character.md +20 -0
- package/dist/skills/hyperframes/references/types/data.md +19 -0
- package/dist/skills/hyperframes/references/types/explainer.md +34 -0
- package/dist/skills/hyperframes/references/types/launch.md +31 -0
- package/dist/skills/hyperframes/references/types/music.md +24 -0
- package/dist/skills/hyperframes/references/types/vertical.md +23 -0
- package/dist/skills/hyperframes/scripts/bake-basemap.mjs +324 -0
- package/dist/skills/hyperframes/scripts/lib/main-module.test.mjs +1 -6
- package/dist/skills/hyperframes/scripts/locate.mjs +228 -0
- package/dist/skills/hyperframes-cli/SKILL.md +76 -179
- package/dist/skills/hyperframes-cli/references/beats.md +1 -1
- package/dist/skills/hyperframes-cli/references/cloud.md +2 -2
- package/dist/skills/hyperframes-cli/references/cloudrun.md +1 -1
- package/dist/skills/hyperframes-cli/references/conventions.md +17 -0
- package/dist/skills/hyperframes-cli/references/feedback.md +14 -0
- package/dist/skills/hyperframes-cli/references/init-and-scaffold.md +11 -7
- package/dist/skills/hyperframes-cli/references/lambda.md +1 -1
- package/dist/skills/hyperframes-cli/references/lint-validate-inspect.md +19 -8
- package/dist/skills/hyperframes-cli/references/preview-render.md +8 -2
- package/dist/skills/hyperframes-cli/references/upgrade-info-misc.md +8 -0
- package/dist/skills/media-use/SKILL.md +47 -91
- package/dist/skills/media-use/audio/references/bgm.md +1 -1
- package/dist/skills/media-use/audio/references/captions/authoring.md +1 -2
- package/dist/skills/media-use/audio/references/captions/motion.md +5 -5
- package/dist/skills/media-use/audio/references/tts.md +0 -3
- package/dist/skills/media-use/audio/scripts/audio.mjs +5 -6
- package/dist/skills/media-use/audio/scripts/carve.mjs +560 -0
- package/dist/skills/media-use/audio/scripts/carve.test.mjs +254 -0
- package/dist/skills/media-use/audio/scripts/extract-audio-data.py +199 -0
- package/dist/skills/media-use/references/local.md +24 -0
- package/dist/skills/media-use/references/memory.md +4 -4
- package/dist/skills/media-use/references/mixing.md +71 -0
- package/dist/skills/media-use/references/operations.md +1 -1
- package/dist/skills/media-use/scripts/compatibility.test.mjs +1 -56
- package/dist/skills/media-use/scripts/lib/prefs-store.mjs +1 -1
- package/dist/skills/media-use/scripts/lib/recipe-store.mjs +1 -1
- package/dist/skills/media-use/scripts/prefs.mjs +2 -3
- package/dist/skills/media-use/scripts/recipe.mjs +1 -3
- package/dist/{skills-J6MBGS5O.js → skills-3WEVO3NU.js} +20 -13
- package/dist/{skillsUpdateCheck-ZAXNLBF3.js → skillsUpdateCheck-BFPWFOK5.js} +3 -3
- package/dist/{snapshot-TL7TTFS6.js → snapshot-WDQ6S7CJ.js} +458 -98
- package/dist/{src-Z5H5N5T4.js → src-NPR2JZOJ.js} +15 -11
- package/dist/{src-EL6HGIS4.js → src-RZ574OKP.js} +9 -8
- package/dist/{staticProjectServer-EO7X7RAY.js → staticProjectServer-FTGTEZI4.js} +18 -17
- package/dist/studio/assets/{hyperframes-player-hz_VqxBZ.js → hyperframes-player-CoOdf435.js} +1 -1
- package/dist/studio/assets/{index-CV_ojgp5.js → index-CR85xg2x.js} +1 -1
- package/dist/studio/assets/{index-S1ceEagc.js → index-C_6rTpaB.js} +5 -5
- package/dist/studio/index.html +1 -1
- package/dist/{studio-api-CUZDC5B3.js → studio-api-TAWB7B23.js} +12 -11
- package/dist/{studioServer-EZPRV43Y.js → studioServer-HRQGJTBU.js} +49 -48
- package/dist/{telemetry-LFUVHARP.js → telemetry-QZ6HJF4W.js} +3 -3
- package/dist/templates/_shared/AGENTS.md +14 -19
- package/dist/templates/_shared/CLAUDE.md +14 -19
- package/dist/templates/blank/index.html +10 -4
- package/dist/{timeline-GSH743JE.js → timeline-IKUEW42T.js} +21 -19
- package/dist/{tokens-3ZMPSH7B.js → tokens-AUGUEWHS.js} +2 -2
- package/dist/{transcribe-4SHNDPWW.js → transcribe-7734CG3N.js} +19 -18
- package/dist/{transcribe-TV7JBZQS.js → transcribe-FWU6EMEO.js} +13 -12
- package/dist/{updateCheck-W7FTFOQF.js → updateCheck-VIPQ3FPW.js} +2 -2
- package/dist/{upgrade-7YYYNAPF.js → upgrade-4VNDQTVO.js} +2 -2
- package/dist/{urlDownloader-PQSUS2CP.js → urlDownloader-6CXL4CL7.js} +9 -8
- package/dist/{validate-GCHAEVXM.js → validate-QWCMVWID.js} +24 -23
- package/dist/{video-KGN7NJS5.js → video-245Q5WN4.js} +10 -9
- package/dist/warmCheck.js +37 -36
- package/package.json +1 -1
- package/dist/chunk-GMNHAOMJ.js +0 -3805
- package/dist/render-65K3K6SD.js +0 -719
- package/dist/skills/hyperframes/references/brief-contract.md +0 -108
- package/dist/skills/hyperframes/references/brief-format.md +0 -72
- package/dist/skills/hyperframes/references/capability-menu.md +0 -47
- package/dist/skills/hyperframes/references/frame-worker-core.md +0 -82
- package/dist/skills/hyperframes/references/intent-interview.md +0 -73
- package/dist/skills/hyperframes/references/pitch-round.md +0 -40
- package/dist/skills/hyperframes/references/production-loop.md +0 -26
- package/dist/skills/hyperframes/references/review-loop.md +0 -37
- package/dist/skills/hyperframes/references/route-briefs.md +0 -11
- package/dist/skills/hyperframes/references/routes/embedded-captions.md +0 -11
- package/dist/skills/hyperframes/references/routes/faceless-explainer.md +0 -12
- package/dist/skills/hyperframes/references/routes/general-video.md +0 -12
- package/dist/skills/hyperframes/references/routes/motion-graphics.md +0 -11
- package/dist/skills/hyperframes/references/routes/music-to-video.md +0 -12
- package/dist/skills/hyperframes/references/routes/pr-to-video.md +0 -28
- package/dist/skills/hyperframes/references/routes/product-launch-video.md +0 -13
- package/dist/skills/hyperframes/references/routes/remotion-to-hyperframes.md +0 -9
- package/dist/skills/hyperframes/references/routes/slideshow.md +0 -11
- package/dist/skills/hyperframes/references/routes/talking-head-recut.md +0 -11
- package/dist/skills/hyperframes/references/script-format.md +0 -52
- package/dist/skills/hyperframes/references/subagent-dispatch.md +0 -35
- package/dist/skills/hyperframes/references/workflow-catalog.md +0 -10
- package/dist/skills/hyperframes/scripts/lib/frame-packets-core.mjs +0 -176
- package/dist/skills/media-use/audio/scripts/lib/host-audio.mjs +0 -28
- package/dist/skills/media-use/audio/scripts/lib/host-audio.test.mjs +0 -106
|
@@ -0,0 +1,1504 @@
|
|
|
1
|
+
import { createRequire as __hf_createRequire } from "node:module";
|
|
2
|
+
import { fileURLToPath as __hf_fileURLToPath } from "node:url";
|
|
3
|
+
import { dirname as __hf_dirname } from "node:path";
|
|
4
|
+
var require = __hf_createRequire(import.meta.url);
|
|
5
|
+
var __filename = __hf_fileURLToPath(import.meta.url);
|
|
6
|
+
var __dirname = __hf_dirname(__filename);
|
|
7
|
+
import {
|
|
8
|
+
parseEnumFlag,
|
|
9
|
+
parseIntFlag,
|
|
10
|
+
parseNumericFlag
|
|
11
|
+
} from "./chunk-DGECWPTL.js";
|
|
12
|
+
import {
|
|
13
|
+
colorStatus
|
|
14
|
+
} from "./chunk-U7CZBDMV.js";
|
|
15
|
+
import {
|
|
16
|
+
DEFAULT_MAX_WAIT_MS,
|
|
17
|
+
DEFAULT_POLL_INTERVAL_MS,
|
|
18
|
+
PollTimeoutError,
|
|
19
|
+
createCloudClient,
|
|
20
|
+
pollUntilTerminal,
|
|
21
|
+
reportApiError
|
|
22
|
+
} from "./chunk-J36JVBLR.js";
|
|
23
|
+
import {
|
|
24
|
+
uploadAssetViaDirectUpload,
|
|
25
|
+
uploadZipViaDirectUpload
|
|
26
|
+
} from "./chunk-5UDRPPN2.js";
|
|
27
|
+
import "./chunk-5UBZPG74.js";
|
|
28
|
+
import {
|
|
29
|
+
TEXT_FILE_EXTS,
|
|
30
|
+
assertUnchanged,
|
|
31
|
+
readChecked,
|
|
32
|
+
readStaged,
|
|
33
|
+
rewriteCssUrls,
|
|
34
|
+
rewriteHtmlAttributes,
|
|
35
|
+
rewriteStyleBlocks,
|
|
36
|
+
stagePublishFiles,
|
|
37
|
+
stagedSize,
|
|
38
|
+
zipPublishFileMap
|
|
39
|
+
} from "./chunk-XGHHVYC7.js";
|
|
40
|
+
import "./chunk-JQYJXUJW.js";
|
|
41
|
+
import {
|
|
42
|
+
resolveCloudBaseUrl
|
|
43
|
+
} from "./chunk-HRPWRZGF.js";
|
|
44
|
+
import "./chunk-7KVVNF7B.js";
|
|
45
|
+
import {
|
|
46
|
+
reportVariableIssues,
|
|
47
|
+
resolveVariablesArg,
|
|
48
|
+
validateVariablesAgainstProject
|
|
49
|
+
} from "./chunk-37CNYTQQ.js";
|
|
50
|
+
import {
|
|
51
|
+
downloadToFile
|
|
52
|
+
} from "./chunk-CZUAX3BV.js";
|
|
53
|
+
import {
|
|
54
|
+
findFFmpeg,
|
|
55
|
+
findFFprobe,
|
|
56
|
+
getFFmpegInstallHint
|
|
57
|
+
} from "./chunk-K2ZKXTVL.js";
|
|
58
|
+
import {
|
|
59
|
+
resolveProject
|
|
60
|
+
} from "./chunk-5GUKZRQN.js";
|
|
61
|
+
import {
|
|
62
|
+
errorBox,
|
|
63
|
+
formatBytes,
|
|
64
|
+
formatDuration
|
|
65
|
+
} from "./chunk-K2FLNJVB.js";
|
|
66
|
+
import "./chunk-442JNXRM.js";
|
|
67
|
+
import "./chunk-EOVYQ3IG.js";
|
|
68
|
+
import "./chunk-WQLKDL35.js";
|
|
69
|
+
import "./chunk-H5SM5PJI.js";
|
|
70
|
+
import "./chunk-VWG7OQKU.js";
|
|
71
|
+
import {
|
|
72
|
+
defineCommand
|
|
73
|
+
} from "./chunk-S3S4TGEH.js";
|
|
74
|
+
import "./chunk-54HJ63Q2.js";
|
|
75
|
+
import {
|
|
76
|
+
failCommand
|
|
77
|
+
} from "./chunk-JVBRJMOP.js";
|
|
78
|
+
import {
|
|
79
|
+
c
|
|
80
|
+
} from "./chunk-ZTZA4UWZ.js";
|
|
81
|
+
import "./chunk-I3IH5MV5.js";
|
|
82
|
+
import "./chunk-IZCW2NKT.js";
|
|
83
|
+
import {
|
|
84
|
+
parseAnimatedGifMetadata
|
|
85
|
+
} from "./chunk-KROXPHXX.js";
|
|
86
|
+
import "./chunk-RQVDTPGD.js";
|
|
87
|
+
import "./chunk-Q47FAYYK.js";
|
|
88
|
+
import "./chunk-3NPOFC43.js";
|
|
89
|
+
import "./chunk-VC76R6W2.js";
|
|
90
|
+
import "./chunk-UJKQWGFF.js";
|
|
91
|
+
import "./chunk-B2A56S5C.js";
|
|
92
|
+
import "./chunk-OFQWTKPW.js";
|
|
93
|
+
import "./chunk-DPVXBVLL.js";
|
|
94
|
+
import "./chunk-3GXQPCJC.js";
|
|
95
|
+
import "./chunk-634HSJ3J.js";
|
|
96
|
+
import "./chunk-FXMKHHPO.js";
|
|
97
|
+
import "./chunk-DYAPHNV6.js";
|
|
98
|
+
import "./chunk-T6AGUTZJ.js";
|
|
99
|
+
import "./chunk-XT6MXC7A.js";
|
|
100
|
+
import "./chunk-4UMPIPCQ.js";
|
|
101
|
+
import "./chunk-Q447CX26.js";
|
|
102
|
+
import "./chunk-VAS6HQX3.js";
|
|
103
|
+
import {
|
|
104
|
+
cleanAssetUrl,
|
|
105
|
+
isRemoteOrInlineUrl,
|
|
106
|
+
resolveLocalAssetCandidates
|
|
107
|
+
} from "./chunk-6BNTGNGD.js";
|
|
108
|
+
import "./chunk-RUOG4VB7.js";
|
|
109
|
+
import "./chunk-MMQ52NIZ.js";
|
|
110
|
+
import {
|
|
111
|
+
readAuthoredDurationSeconds,
|
|
112
|
+
readMediaOffsetSeconds,
|
|
113
|
+
readPlaybackRate,
|
|
114
|
+
rewriteAssetPath
|
|
115
|
+
} from "./chunk-BSQ7XANM.js";
|
|
116
|
+
import {
|
|
117
|
+
parseHTML
|
|
118
|
+
} from "./chunk-SDYDNHEE.js";
|
|
119
|
+
import "./chunk-IXLUTE6N.js";
|
|
120
|
+
import "./chunk-I5JRXMCV.js";
|
|
121
|
+
import "./chunk-W2LGJSEN.js";
|
|
122
|
+
import {
|
|
123
|
+
withMeta
|
|
124
|
+
} from "./chunk-ISTRDGIX.js";
|
|
125
|
+
import "./chunk-K4WIOZFP.js";
|
|
126
|
+
import "./chunk-XUXGIA7H.js";
|
|
127
|
+
import "./chunk-WERLJGIC.js";
|
|
128
|
+
import "./chunk-ISN5GRYI.js";
|
|
129
|
+
import "./chunk-I7QMXZSB.js";
|
|
130
|
+
import {
|
|
131
|
+
normalizeErrorMessage
|
|
132
|
+
} from "./chunk-KNU5COPB.js";
|
|
133
|
+
import "./chunk-KGPLIH7B.js";
|
|
134
|
+
import "./chunk-M2WBOQ2Q.js";
|
|
135
|
+
|
|
136
|
+
// src/cloud/detectAspectRatio.ts
|
|
137
|
+
import { readFileSync } from "fs";
|
|
138
|
+
var RATIO_TOLERANCE = 0.05;
|
|
139
|
+
var SUPPORTED_RATIOS = [
|
|
140
|
+
{ value: "16:9", ratio: 16 / 9 },
|
|
141
|
+
{ value: "9:16", ratio: 9 / 16 },
|
|
142
|
+
{ value: "1:1", ratio: 1 }
|
|
143
|
+
];
|
|
144
|
+
var ROOT_COMPOSITION_DIV_RE = /<div\b[^>]*?\bdata-composition-id\s*=\s*(?:"[^"]*"|'[^']*'|[^\s>]+)[^>]*>/i;
|
|
145
|
+
var DATA_WIDTH_RE = /\bdata-width\s*=\s*(?:"(\d+(?:\.\d+)?)"|'(\d+(?:\.\d+)?)'|(\d+(?:\.\d+)?))(?=\s|>|\/)/i;
|
|
146
|
+
var DATA_HEIGHT_RE = /\bdata-height\s*=\s*(?:"(\d+(?:\.\d+)?)"|'(\d+(?:\.\d+)?)'|(\d+(?:\.\d+)?))(?=\s|>|\/)/i;
|
|
147
|
+
function extractAttributeNumber(tag, re) {
|
|
148
|
+
const match = tag.match(re);
|
|
149
|
+
if (!match) return null;
|
|
150
|
+
const raw = match[1] ?? match[2] ?? match[3];
|
|
151
|
+
if (raw === void 0) return null;
|
|
152
|
+
const value = Number(raw);
|
|
153
|
+
return Number.isFinite(value) ? value : null;
|
|
154
|
+
}
|
|
155
|
+
function detectAspectRatioFromHtml(entryHtmlPath) {
|
|
156
|
+
let html;
|
|
157
|
+
try {
|
|
158
|
+
html = readFileSync(entryHtmlPath, "utf-8");
|
|
159
|
+
} catch (err) {
|
|
160
|
+
return { kind: "read-error", error: normalizeErrorMessage(err) };
|
|
161
|
+
}
|
|
162
|
+
return detectAspectRatioFromHtmlString(html);
|
|
163
|
+
}
|
|
164
|
+
function detectAspectRatioFromHtmlString(html) {
|
|
165
|
+
const tagMatch = html.match(ROOT_COMPOSITION_DIV_RE);
|
|
166
|
+
if (!tagMatch) return { kind: "no-root-div" };
|
|
167
|
+
const openTag = tagMatch[0];
|
|
168
|
+
const width = extractAttributeNumber(openTag, DATA_WIDTH_RE);
|
|
169
|
+
const height = extractAttributeNumber(openTag, DATA_HEIGHT_RE);
|
|
170
|
+
if (width === null || height === null) return { kind: "no-dims" };
|
|
171
|
+
if (width <= 0 || height <= 0) return { kind: "invalid-dims", width, height };
|
|
172
|
+
const ratio = width / height;
|
|
173
|
+
for (const candidate of SUPPORTED_RATIOS) {
|
|
174
|
+
if (Math.abs(ratio - candidate.ratio) <= RATIO_TOLERANCE) {
|
|
175
|
+
return { kind: "matched", aspectRatio: candidate.value, width, height };
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return { kind: "no-match", width, height, ratio };
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// src/cloud/offload.ts
|
|
182
|
+
import { execFile } from "child_process";
|
|
183
|
+
import { createHash } from "crypto";
|
|
184
|
+
import {
|
|
185
|
+
copyFileSync,
|
|
186
|
+
createReadStream,
|
|
187
|
+
mkdirSync,
|
|
188
|
+
mkdtempSync,
|
|
189
|
+
readFileSync as readFileSync2,
|
|
190
|
+
renameSync,
|
|
191
|
+
rmSync,
|
|
192
|
+
writeFileSync
|
|
193
|
+
} from "fs";
|
|
194
|
+
import { homedir, tmpdir } from "os";
|
|
195
|
+
import { basename as basename2, dirname, extname, join, posix, relative, resolve } from "path";
|
|
196
|
+
import { promisify } from "util";
|
|
197
|
+
|
|
198
|
+
// src/cloud/offloadTrim.ts
|
|
199
|
+
import { basename } from "path";
|
|
200
|
+
var TRIM_MARGIN_SECONDS = 0.5;
|
|
201
|
+
var RETIMING_SCRIPT_RE = /data-(?:playback-start|media-start|start|duration|end|playback-rate)|\b(?:dataset|setAttribute|currentTime|playbackRate)\b|\.(?:src|loop)\b/;
|
|
202
|
+
function mediaSources(el) {
|
|
203
|
+
const own = el.getAttribute("src");
|
|
204
|
+
const nested = [...el.querySelectorAll("source[src]")].map((s) => s.getAttribute("src"));
|
|
205
|
+
return [...own ? [own] : [], ...nested];
|
|
206
|
+
}
|
|
207
|
+
function hasTimedAncestor(el) {
|
|
208
|
+
for (let p = el.parentElement; p; p = p.parentElement) {
|
|
209
|
+
const get = (name) => p.getAttribute(name);
|
|
210
|
+
if (readMediaOffsetSeconds(get) > 0) return true;
|
|
211
|
+
if (p.hasAttribute("data-composition-id") && p.parentElement?.closest("[data-composition-id]")) {
|
|
212
|
+
return true;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
217
|
+
function playedWindow(el) {
|
|
218
|
+
if (el.hasAttribute("loop") || el.hasAttribute("data-automation") || el.hasAttribute("data-var-src") || hasTimedAncestor(el)) {
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
const get = (name) => el.getAttribute(name);
|
|
222
|
+
const start = readMediaOffsetSeconds(get);
|
|
223
|
+
const duration = readAuthoredDurationSeconds(get, Number(get("data-start") ?? 0));
|
|
224
|
+
if (duration === null || !(duration > 0)) return null;
|
|
225
|
+
return { start, end: start + duration * readPlaybackRate(get) };
|
|
226
|
+
}
|
|
227
|
+
function mergeWindows(windows, gridPerSecond) {
|
|
228
|
+
const down = (t) => Math.floor(t * gridPerSecond) / gridPerSecond;
|
|
229
|
+
const up = (t) => Math.ceil(t * gridPerSecond) / gridPerSecond;
|
|
230
|
+
const merged = [];
|
|
231
|
+
for (const w of [...windows].sort((a, b) => a.start - b.start)) {
|
|
232
|
+
const start = down(Math.max(0, w.start - TRIM_MARGIN_SECONDS));
|
|
233
|
+
const end = up(w.end + TRIM_MARGIN_SECONDS);
|
|
234
|
+
const last = merged.at(-1);
|
|
235
|
+
if (last && start <= last.end) last.end = Math.max(last.end, end);
|
|
236
|
+
else merged.push({ start, end });
|
|
237
|
+
}
|
|
238
|
+
return merged;
|
|
239
|
+
}
|
|
240
|
+
function shiftMediaStart(el, segments) {
|
|
241
|
+
const get = (name) => el.getAttribute(name);
|
|
242
|
+
const start = readMediaOffsetSeconds(get);
|
|
243
|
+
let offset = 0;
|
|
244
|
+
for (const segment of segments) {
|
|
245
|
+
if (start >= segment.start && start < segment.end) {
|
|
246
|
+
const attr = Number.parseFloat(get("data-playback-start") ?? "") >= 0 ? "data-playback-start" : "data-media-start";
|
|
247
|
+
el.setAttribute(attr, String(offset + (start - segment.start)));
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
offset += segment.end - segment.start;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
function planTrim(input) {
|
|
254
|
+
if (input.mentionedElsewhere || RETIMING_SCRIPT_RE.test(input.scriptCode)) return null;
|
|
255
|
+
const { document } = parseHTML(input.entryHtml);
|
|
256
|
+
const uses = [];
|
|
257
|
+
let sources = 0;
|
|
258
|
+
for (const el of document.querySelectorAll("video, audio")) {
|
|
259
|
+
const matching = mediaSources(el).filter((src) => input.resolveKey(src) === input.path);
|
|
260
|
+
if (matching.length === 0) continue;
|
|
261
|
+
if (matching.some((src) => src.includes("#"))) return null;
|
|
262
|
+
uses.push(el);
|
|
263
|
+
sources += matching.length;
|
|
264
|
+
}
|
|
265
|
+
if (uses.length === 0 || input.entryHtml.split(basename(input.path)).length - 1 !== sources) {
|
|
266
|
+
return null;
|
|
267
|
+
}
|
|
268
|
+
const windows = uses.map(playedWindow);
|
|
269
|
+
if (windows.some((w) => w === null)) return null;
|
|
270
|
+
const segments = mergeWindows(windows, input.gridPerSecond);
|
|
271
|
+
for (const el of uses) shiftMediaStart(el, segments);
|
|
272
|
+
return { segments, html: document.toString() };
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// src/cloud/offload.ts
|
|
276
|
+
var execFileAsync = promisify(execFile);
|
|
277
|
+
var OFFLOAD_MIN_BYTES = 5 * 1024 * 1024;
|
|
278
|
+
var MiB = 1024 * 1024;
|
|
279
|
+
var AUDIO_MAX_BYTES = 15e8;
|
|
280
|
+
var ACCEPTED = {
|
|
281
|
+
".mp4": { contentType: "video/mp4", maxBytes: 200 * MiB },
|
|
282
|
+
".m4v": { contentType: "video/mp4", maxBytes: 200 * MiB },
|
|
283
|
+
".mov": { contentType: "video/quicktime", maxBytes: 200 * MiB },
|
|
284
|
+
".webm": { contentType: "video/webm", maxBytes: 200 * MiB },
|
|
285
|
+
".png": { contentType: "image/png", maxBytes: 200 * MiB },
|
|
286
|
+
".jpg": { contentType: "image/jpeg", maxBytes: 200 * MiB },
|
|
287
|
+
".jpeg": { contentType: "image/jpeg", maxBytes: 200 * MiB },
|
|
288
|
+
".webp": { contentType: "image/webp", maxBytes: 200 * MiB },
|
|
289
|
+
".wav": { contentType: "audio/wav", maxBytes: AUDIO_MAX_BYTES },
|
|
290
|
+
".mp3": { contentType: "audio/mpeg", maxBytes: AUDIO_MAX_BYTES }
|
|
291
|
+
};
|
|
292
|
+
var VIDEO_EXTS = /* @__PURE__ */ new Set([
|
|
293
|
+
".mp4",
|
|
294
|
+
".m4v",
|
|
295
|
+
".mov",
|
|
296
|
+
".webm",
|
|
297
|
+
".mkv",
|
|
298
|
+
".avi",
|
|
299
|
+
".mpg",
|
|
300
|
+
".mpeg",
|
|
301
|
+
".ts",
|
|
302
|
+
".m2ts",
|
|
303
|
+
".mts",
|
|
304
|
+
".flv",
|
|
305
|
+
".wmv",
|
|
306
|
+
".ogv",
|
|
307
|
+
".3gp"
|
|
308
|
+
]);
|
|
309
|
+
var AUDIO_EXTS = /* @__PURE__ */ new Set([
|
|
310
|
+
".wav",
|
|
311
|
+
".mp3",
|
|
312
|
+
".m4a",
|
|
313
|
+
".aac",
|
|
314
|
+
".ogg",
|
|
315
|
+
".oga",
|
|
316
|
+
".opus",
|
|
317
|
+
".flac",
|
|
318
|
+
".aif",
|
|
319
|
+
".aiff",
|
|
320
|
+
".wma"
|
|
321
|
+
]);
|
|
322
|
+
var CONVERTIBLE_IMAGE_EXTS = /* @__PURE__ */ new Set([".gif", ".avif", ".bmp", ".tif", ".tiff"]);
|
|
323
|
+
var REENCODE_PRESETS = {
|
|
324
|
+
video: {
|
|
325
|
+
ext: ".mp4",
|
|
326
|
+
contentType: "video/mp4",
|
|
327
|
+
args: ["-c:v", "libx264", "-preset", "slow", "-crf", "18", "-pix_fmt", "yuv420p"],
|
|
328
|
+
audio: ["-c:a", "aac", "-b:a", "320k", "-movflags", "+faststart"]
|
|
329
|
+
},
|
|
330
|
+
hdrVideo: {
|
|
331
|
+
ext: ".mp4",
|
|
332
|
+
contentType: "video/mp4",
|
|
333
|
+
args: ["-c:v", "libx265", "-preset", "slow", "-crf", "18", "-pix_fmt", "yuv420p10le"],
|
|
334
|
+
audio: ["-tag:v", "hvc1", "-c:a", "aac", "-b:a", "320k", "-movflags", "+faststart"]
|
|
335
|
+
},
|
|
336
|
+
alphaVideo: {
|
|
337
|
+
ext: ".webm",
|
|
338
|
+
contentType: "video/webm",
|
|
339
|
+
args: ["-c:v", "libvpx-vp9", "-crf", "18", "-b:v", "0", "-pix_fmt", "yuva420p"],
|
|
340
|
+
audio: ["-c:a", "libopus", "-b:a", "256k"]
|
|
341
|
+
},
|
|
342
|
+
audio: {
|
|
343
|
+
ext: ".wav",
|
|
344
|
+
contentType: "audio/wav",
|
|
345
|
+
args: ["-vn"],
|
|
346
|
+
audio: ["-c:a", "pcm_s16le"]
|
|
347
|
+
},
|
|
348
|
+
image: {
|
|
349
|
+
ext: ".png",
|
|
350
|
+
contentType: "image/png",
|
|
351
|
+
args: ["-frames:v", "1"],
|
|
352
|
+
audio: []
|
|
353
|
+
}
|
|
354
|
+
};
|
|
355
|
+
function defaultOffloadCachePath() {
|
|
356
|
+
return join(homedir(), ".cache", "hyperframes", "cloud-assets.json");
|
|
357
|
+
}
|
|
358
|
+
function readCache(path) {
|
|
359
|
+
try {
|
|
360
|
+
return JSON.parse(readFileSync2(path, "utf-8")) ?? {};
|
|
361
|
+
} catch {
|
|
362
|
+
return {};
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
function writeCache(path, cache) {
|
|
366
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
367
|
+
const tmp = `${path}.${process.pid}.tmp`;
|
|
368
|
+
writeFileSync(tmp, JSON.stringify(cache, null, 2));
|
|
369
|
+
renameSync(tmp, path);
|
|
370
|
+
}
|
|
371
|
+
async function stillServed(entry, fetchImpl) {
|
|
372
|
+
try {
|
|
373
|
+
const res = await fetchImpl(entry.url, { method: "HEAD" });
|
|
374
|
+
return res.ok && Number(res.headers.get("content-length")) === entry.size_bytes;
|
|
375
|
+
} catch {
|
|
376
|
+
return false;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
function mediaKind(path) {
|
|
380
|
+
const ext = extname(path).toLowerCase();
|
|
381
|
+
if (VIDEO_EXTS.has(ext)) return "video";
|
|
382
|
+
if (AUDIO_EXTS.has(ext)) return "audio";
|
|
383
|
+
if (ACCEPTED[ext] || CONVERTIBLE_IMAGE_EXTS.has(ext)) return "image";
|
|
384
|
+
return null;
|
|
385
|
+
}
|
|
386
|
+
async function probeVideo(path) {
|
|
387
|
+
const ffprobe = findFFprobe();
|
|
388
|
+
if (!ffprobe)
|
|
389
|
+
throw new Error(`Re-encoding needs ffprobe. Install ffmpeg: ${getFFmpegInstallHint()}`);
|
|
390
|
+
const { stdout } = await execFileAsync(ffprobe, [
|
|
391
|
+
"-v",
|
|
392
|
+
"error",
|
|
393
|
+
"-select_streams",
|
|
394
|
+
"v:0",
|
|
395
|
+
"-show_entries",
|
|
396
|
+
"stream=codec_name,pix_fmt,color_primaries,color_transfer,color_space:stream_tags",
|
|
397
|
+
"-of",
|
|
398
|
+
"json",
|
|
399
|
+
"--",
|
|
400
|
+
path
|
|
401
|
+
]);
|
|
402
|
+
const stream = JSON.parse(stdout).streams?.[0] ?? {};
|
|
403
|
+
const tags = stream.tags ?? {};
|
|
404
|
+
const alphaTag = Object.entries(tags).some(
|
|
405
|
+
([k, v]) => k.toLowerCase() === "alpha_mode" && v === "1"
|
|
406
|
+
);
|
|
407
|
+
const transfer = stream.color_transfer;
|
|
408
|
+
return {
|
|
409
|
+
// VP9 alpha keeps pix_fmt yuv420p and flags the plane only in the ALPHA_MODE tag.
|
|
410
|
+
alpha: alphaTag || /^(yuva|rgba|argb|bgra|abgr|gbrap|ya)/.test(String(stream.pix_fmt ?? "")),
|
|
411
|
+
hdr: transfer === "smpte2084" || transfer === "arib-std-b67",
|
|
412
|
+
codec: stream.codec_name,
|
|
413
|
+
color: {
|
|
414
|
+
primaries: stream.color_primaries,
|
|
415
|
+
transfer,
|
|
416
|
+
space: stream.color_space
|
|
417
|
+
}
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
function alphaDecoderArgs(probe) {
|
|
421
|
+
if (!probe.alpha) return [];
|
|
422
|
+
if (probe.codec === "vp9") return ["-c:v", "libvpx-vp9"];
|
|
423
|
+
if (probe.codec === "vp8") return ["-c:v", "libvpx"];
|
|
424
|
+
return [];
|
|
425
|
+
}
|
|
426
|
+
function hdrColorArgs({ color }) {
|
|
427
|
+
return [
|
|
428
|
+
...color.primaries ? ["-color_primaries", color.primaries] : [],
|
|
429
|
+
...color.transfer ? ["-color_trc", color.transfer] : [],
|
|
430
|
+
...color.space ? ["-colorspace", color.space] : []
|
|
431
|
+
];
|
|
432
|
+
}
|
|
433
|
+
async function hasAudioStream(ffprobe, path) {
|
|
434
|
+
const { stdout } = await execFileAsync(ffprobe, [
|
|
435
|
+
"-v",
|
|
436
|
+
"error",
|
|
437
|
+
"-select_streams",
|
|
438
|
+
"a",
|
|
439
|
+
"-show_entries",
|
|
440
|
+
"stream=index",
|
|
441
|
+
"-of",
|
|
442
|
+
"csv=p=0",
|
|
443
|
+
"--",
|
|
444
|
+
path
|
|
445
|
+
]);
|
|
446
|
+
return stdout.trim() !== "";
|
|
447
|
+
}
|
|
448
|
+
var seconds = (t) => (Math.floor(t * 1e6) / 1e6).toFixed(6);
|
|
449
|
+
async function segmentArgs(sourcePath, decoder, segments, audioOnly) {
|
|
450
|
+
if (!segments?.length) return [...decoder, "-i", sourcePath];
|
|
451
|
+
const inputs = segments.flatMap((s) => [
|
|
452
|
+
...decoder,
|
|
453
|
+
"-ss",
|
|
454
|
+
seconds(s.start),
|
|
455
|
+
"-t",
|
|
456
|
+
seconds(s.end - s.start),
|
|
457
|
+
"-i",
|
|
458
|
+
sourcePath
|
|
459
|
+
]);
|
|
460
|
+
if (segments.length === 1) return inputs;
|
|
461
|
+
const audio = audioOnly || await hasAudioStream(findFFprobe(), sourcePath);
|
|
462
|
+
return [...inputs, ...concatArgs(segments.length, !audioOnly, audio)];
|
|
463
|
+
}
|
|
464
|
+
function concatArgs(count, video, audio) {
|
|
465
|
+
const pad = (i) => (video ? `[${i}:v]` : "") + (audio ? `[${i}:a]` : "");
|
|
466
|
+
const pads = Array.from({ length: count }, (_, i) => pad(i)).join("");
|
|
467
|
+
const flags = `n=${count}:v=${video ? 1 : 0}:a=${audio ? 1 : 0}`;
|
|
468
|
+
return [
|
|
469
|
+
"-filter_complex",
|
|
470
|
+
`${pads}concat=${flags}${video ? "[v]" : ""}${audio ? "[a]" : ""}`,
|
|
471
|
+
...video ? ["-map", "[v]"] : [],
|
|
472
|
+
...audio ? ["-map", "[a]"] : []
|
|
473
|
+
];
|
|
474
|
+
}
|
|
475
|
+
async function ffmpegReencode(sourcePath, presetName, segments) {
|
|
476
|
+
const ffmpeg = findFFmpeg();
|
|
477
|
+
if (!ffmpeg) throw new Error(`Re-encoding needs ffmpeg. Install it: ${getFFmpegInstallHint()}`);
|
|
478
|
+
const preset = REENCODE_PRESETS[presetName];
|
|
479
|
+
const probe = preset.contentType.startsWith("video/") ? await probeVideo(sourcePath) : void 0;
|
|
480
|
+
const decoder = probe ? alphaDecoderArgs(probe) : [];
|
|
481
|
+
const color = presetName === "hdrVideo" && probe ? hdrColorArgs(probe) : [];
|
|
482
|
+
const workDir = mkdtempSync(join(tmpdir(), "hf-offload-"));
|
|
483
|
+
const outPath = join(workDir, `${basename2(sourcePath, extname(sourcePath))}${preset.ext}`);
|
|
484
|
+
try {
|
|
485
|
+
await execFileAsync(
|
|
486
|
+
ffmpeg,
|
|
487
|
+
[
|
|
488
|
+
"-v",
|
|
489
|
+
"error",
|
|
490
|
+
"-y",
|
|
491
|
+
...await segmentArgs(sourcePath, decoder, segments, presetName === "audio"),
|
|
492
|
+
...preset.args,
|
|
493
|
+
...color,
|
|
494
|
+
...preset.audio,
|
|
495
|
+
outPath
|
|
496
|
+
],
|
|
497
|
+
{ maxBuffer: 16 * MiB }
|
|
498
|
+
);
|
|
499
|
+
return {
|
|
500
|
+
bytes: readChecked(outPath, ({ size }) => assertFitsAsset(sourcePath, outPath, size)),
|
|
501
|
+
filename: basename2(outPath),
|
|
502
|
+
contentType: preset.contentType
|
|
503
|
+
};
|
|
504
|
+
} finally {
|
|
505
|
+
rmSync(workDir, { recursive: true, force: true });
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
async function choosePreset(sourcePath, kind) {
|
|
509
|
+
if (kind === "audio") return "audio";
|
|
510
|
+
if (kind === "image") return await isAnimatedImage(sourcePath) ? null : "image";
|
|
511
|
+
const probe = await probeVideo(sourcePath);
|
|
512
|
+
return probe.alpha ? "alphaVideo" : probe.hdr ? "hdrVideo" : "video";
|
|
513
|
+
}
|
|
514
|
+
async function isAnimatedImage(path) {
|
|
515
|
+
if (extname(path).toLowerCase() === ".gif") {
|
|
516
|
+
return parseAnimatedGifMetadata(readFileSync2(path))?.animated ?? true;
|
|
517
|
+
}
|
|
518
|
+
const ffprobe = findFFprobe();
|
|
519
|
+
if (!ffprobe) return true;
|
|
520
|
+
try {
|
|
521
|
+
const { stdout } = await execFileAsync(ffprobe, [
|
|
522
|
+
"-v",
|
|
523
|
+
"error",
|
|
524
|
+
"-show_entries",
|
|
525
|
+
"stream=nb_frames",
|
|
526
|
+
"-of",
|
|
527
|
+
"csv=p=0",
|
|
528
|
+
"--",
|
|
529
|
+
path
|
|
530
|
+
]);
|
|
531
|
+
return stdout.split("\n").some((line) => Number(line) > 1);
|
|
532
|
+
} catch {
|
|
533
|
+
return true;
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
async function trimGrid(sourcePath, kind) {
|
|
537
|
+
if (kind === "audio") return 1e3;
|
|
538
|
+
if (kind !== "video") return null;
|
|
539
|
+
const ffprobe = findFFprobe();
|
|
540
|
+
if (!ffprobe) return null;
|
|
541
|
+
const { stdout } = await execFileAsync(ffprobe, [
|
|
542
|
+
"-v",
|
|
543
|
+
"error",
|
|
544
|
+
"-select_streams",
|
|
545
|
+
"v:0",
|
|
546
|
+
"-show_entries",
|
|
547
|
+
"stream=r_frame_rate,avg_frame_rate",
|
|
548
|
+
"-of",
|
|
549
|
+
"csv=p=0",
|
|
550
|
+
"--",
|
|
551
|
+
sourcePath
|
|
552
|
+
]);
|
|
553
|
+
const rate = (fraction) => {
|
|
554
|
+
const [num, den] = (fraction ?? "").split("/").map(Number);
|
|
555
|
+
return num && den ? num / den : NaN;
|
|
556
|
+
};
|
|
557
|
+
const [real, average] = stdout.trim().split(",");
|
|
558
|
+
const fps = rate(real);
|
|
559
|
+
return Number.isFinite(fps) && Math.abs(fps - rate(average)) < 0.01 ? fps : null;
|
|
560
|
+
}
|
|
561
|
+
var ffmpegReencoder = { choose: choosePreset, trimGrid, run: ffmpegReencode };
|
|
562
|
+
async function stagedSha256(file) {
|
|
563
|
+
if (Buffer.isBuffer(file)) return createHash("sha256").update(file).digest("hex");
|
|
564
|
+
const hash = createHash("sha256");
|
|
565
|
+
for await (const chunk of createReadStream(file.absPath)) hash.update(chunk);
|
|
566
|
+
assertUnchanged(file);
|
|
567
|
+
return hash.digest("hex");
|
|
568
|
+
}
|
|
569
|
+
function snapshotPath(path, file, tempFiles) {
|
|
570
|
+
return Buffer.isBuffer(file) ? stagedCopy(path, file, tempFiles) : file.absPath;
|
|
571
|
+
}
|
|
572
|
+
function stagedCopy(path, file, tempFiles) {
|
|
573
|
+
const dir = mkdtempSync(join(tmpdir(), "hf-offload-src-"));
|
|
574
|
+
tempFiles.push(dir);
|
|
575
|
+
const copy = join(dir, basename2(path));
|
|
576
|
+
if (Buffer.isBuffer(file)) writeFileSync(copy, file);
|
|
577
|
+
else {
|
|
578
|
+
copyFileSync(file.absPath, copy);
|
|
579
|
+
assertUnchanged(file);
|
|
580
|
+
}
|
|
581
|
+
return copy;
|
|
582
|
+
}
|
|
583
|
+
function textOf(path, file) {
|
|
584
|
+
return TEXT_FILE_EXTS.test(path) && Buffer.isBuffer(file) ? file.toString("utf-8") : void 0;
|
|
585
|
+
}
|
|
586
|
+
function scriptCode(fileMap) {
|
|
587
|
+
const parts = [];
|
|
588
|
+
for (const [path, content] of fileMap) {
|
|
589
|
+
const text = textOf(path, content);
|
|
590
|
+
if (text === void 0) continue;
|
|
591
|
+
if (/\.html?$/i.test(path)) {
|
|
592
|
+
for (const script of parseHTML(text).document.querySelectorAll("script:not([src])")) {
|
|
593
|
+
parts.push(script.textContent ?? "");
|
|
594
|
+
}
|
|
595
|
+
} else if (/\.(m?js|cjs|json)$/i.test(path)) parts.push(text);
|
|
596
|
+
}
|
|
597
|
+
return parts.join("\n");
|
|
598
|
+
}
|
|
599
|
+
function assertFitsAsset(path, filename, size) {
|
|
600
|
+
const maxBytes = ACCEPTED[extname(filename).toLowerCase()]?.maxBytes ?? 0;
|
|
601
|
+
if (size > maxBytes) {
|
|
602
|
+
throw new Error(
|
|
603
|
+
`${path} is ${formatBytes(size)} after re-encoding at visually lossless quality, over the ${formatBytes(maxBytes)} a HeyGen asset can hold.`
|
|
604
|
+
);
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
async function planFile(ctx, path, file) {
|
|
608
|
+
const kind = mediaKind(path);
|
|
609
|
+
const size = stagedSize(file);
|
|
610
|
+
if (!kind || size < OFFLOAD_MIN_BYTES) return null;
|
|
611
|
+
const sha256 = await stagedSha256(file);
|
|
612
|
+
const keyFor = (variant2) => `${ctx.opts.apiBaseUrl} ${sha256} ${variant2}`;
|
|
613
|
+
const accepted = ACCEPTED[extname(path).toLowerCase()];
|
|
614
|
+
if (accepted && size <= accepted.maxBytes) {
|
|
615
|
+
return { key: keyFor("as-is"), sourcePath: "", contentType: accepted.contentType };
|
|
616
|
+
}
|
|
617
|
+
const sourcePath = snapshotPath(path, file, ctx.tempFiles);
|
|
618
|
+
const preset = await ctx.reencoder.choose(sourcePath, kind);
|
|
619
|
+
if (!preset) return null;
|
|
620
|
+
const segments = kind === "image" ? null : await planFileTrim(ctx, path, sourcePath, kind);
|
|
621
|
+
const variant = JSON.stringify([REENCODE_PRESETS[preset], segments]);
|
|
622
|
+
return { key: keyFor(variant), preset, sourcePath, segments: segments ?? void 0 };
|
|
623
|
+
}
|
|
624
|
+
async function planFileTrim(ctx, path, sourcePath, kind) {
|
|
625
|
+
const entryPath = ctx.opts.entryPath ?? "index.html";
|
|
626
|
+
const entryHtml = textOf(entryPath, ctx.fileMap.get(entryPath));
|
|
627
|
+
const gridPerSecond = entryHtml ? await ctx.reencoder.trimGrid?.(sourcePath, kind) : null;
|
|
628
|
+
if (!entryHtml || !gridPerSecond) return null;
|
|
629
|
+
const name = basename2(path);
|
|
630
|
+
const plan = planTrim({
|
|
631
|
+
entryHtml,
|
|
632
|
+
path,
|
|
633
|
+
gridPerSecond,
|
|
634
|
+
resolveKey: (raw) => htmlSrcKey(ctx.absDir, entryPath, raw, ctx.fileMap),
|
|
635
|
+
mentionedElsewhere: [...ctx.fileMap].some(
|
|
636
|
+
([other, content]) => other !== entryPath && other !== path && !!textOf(other, content)?.includes(name)
|
|
637
|
+
),
|
|
638
|
+
scriptCode: scriptCode(ctx.fileMap)
|
|
639
|
+
});
|
|
640
|
+
if (!plan) return null;
|
|
641
|
+
ctx.fileMap.set(entryPath, Buffer.from(plan.html, "utf-8"));
|
|
642
|
+
return plan.segments;
|
|
643
|
+
}
|
|
644
|
+
async function offloadFile(ctx, path, file) {
|
|
645
|
+
const plan = await planFile(ctx, path, file);
|
|
646
|
+
if (!plan) return null;
|
|
647
|
+
const item = {
|
|
648
|
+
path,
|
|
649
|
+
size_bytes: stagedSize(file),
|
|
650
|
+
action: plan.preset ? "reencode" : "upload",
|
|
651
|
+
...plan.segments && {
|
|
652
|
+
trimmed_to_seconds: plan.segments.reduce((sum, s) => sum + s.end - s.start, 0)
|
|
653
|
+
}
|
|
654
|
+
};
|
|
655
|
+
const hit = ctx.cache[plan.key];
|
|
656
|
+
if (hit && (!ctx.opts.upload || await stillServed(hit, ctx.fetchImpl))) {
|
|
657
|
+
return { ...item, action: "cached", url: hit.url };
|
|
658
|
+
}
|
|
659
|
+
if (!ctx.opts.upload) return item;
|
|
660
|
+
return { ...item, url: await uploadPlanned(ctx, ctx.opts.upload, path, file, plan) };
|
|
661
|
+
}
|
|
662
|
+
async function uploadPlanned(ctx, upload, path, staged, plan) {
|
|
663
|
+
const filename = basename2(path);
|
|
664
|
+
const file = plan.preset ? await ctx.reencoder.run(plan.sourcePath, plan.preset, plan.segments) : Buffer.isBuffer(staged) ? { bytes: staged, filename, contentType: plan.contentType } : { path: staged.absPath, size: staged.size, filename, contentType: plan.contentType };
|
|
665
|
+
if (plan.preset && !Buffer.isBuffer(staged)) assertUnchanged(staged);
|
|
666
|
+
const size = "bytes" in file ? file.bytes.byteLength : file.size;
|
|
667
|
+
assertFitsAsset(path, file.filename, size);
|
|
668
|
+
const asset = await upload(file);
|
|
669
|
+
if (!Buffer.isBuffer(staged)) assertUnchanged(staged);
|
|
670
|
+
ctx.cache[plan.key] = {
|
|
671
|
+
asset_id: asset.asset_id,
|
|
672
|
+
url: asset.url,
|
|
673
|
+
size_bytes: size
|
|
674
|
+
};
|
|
675
|
+
writeCache(ctx.cachePath, ctx.cache);
|
|
676
|
+
return asset.url;
|
|
677
|
+
}
|
|
678
|
+
var GIF_PREFIX = "_animated_gif";
|
|
679
|
+
async function prepareEntryGifs(ctx, fileMap) {
|
|
680
|
+
const entryPath = ctx.opts.entryPath ?? "index.html";
|
|
681
|
+
const html = textOf(entryPath, fileMap.get(entryPath));
|
|
682
|
+
if (!html || !/\.gif\b/i.test(html)) return;
|
|
683
|
+
const sourceAssets = entryGifSources(ctx, entryPath, html);
|
|
684
|
+
if (!sourceAssets) return;
|
|
685
|
+
const prep = ctx.opts.gifPrep ?? (await import("./src-NPR2JZOJ.js")).prepareAnimatedGifInputs;
|
|
686
|
+
const outputDir = mkdtempSync(join(tmpdir(), "hf-offload-gif-"));
|
|
687
|
+
ctx.tempFiles.push(outputDir);
|
|
688
|
+
const result = await prep(html, {
|
|
689
|
+
projectDir: ctx.absDir,
|
|
690
|
+
downloadDir: outputDir,
|
|
691
|
+
outputDir,
|
|
692
|
+
outputSrcPrefix: posix.relative(posix.dirname(entryPath), GIF_PREFIX),
|
|
693
|
+
cacheDir: join(homedir(), ".cache", "hyperframes", "animated-gif"),
|
|
694
|
+
sourceAssets
|
|
695
|
+
});
|
|
696
|
+
if (result.preparedGifs.length === 0) return;
|
|
697
|
+
fileMap.set(entryPath, Buffer.from(result.html, "utf-8"));
|
|
698
|
+
for (const gif of result.preparedGifs) {
|
|
699
|
+
fileMap.set(`${GIF_PREFIX}/${basename2(gif.outputPath)}`, readFileSync2(gif.outputPath));
|
|
700
|
+
const source = htmlSrcKey(ctx.absDir, entryPath, gif.sourceSrc.trim(), fileMap);
|
|
701
|
+
if (source) ctx.preparedGifSources.push(source);
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
function entryGifSources(ctx, entryPath, html) {
|
|
705
|
+
const sourceAssets = /* @__PURE__ */ new Map();
|
|
706
|
+
let heavy = false;
|
|
707
|
+
for (const img of parseHTML(html).document.querySelectorAll("img[src]")) {
|
|
708
|
+
const src = img.getAttribute("src").trim();
|
|
709
|
+
const key = htmlSrcKey(ctx.absDir, entryPath, src, ctx.fileMap);
|
|
710
|
+
if (!key?.toLowerCase().endsWith(".gif")) continue;
|
|
711
|
+
const file = ctx.fileMap.get(key);
|
|
712
|
+
sourceAssets.set(src, stagedCopy(key, file, ctx.tempFiles));
|
|
713
|
+
heavy ||= stagedSize(file) >= OFFLOAD_MIN_BYTES;
|
|
714
|
+
}
|
|
715
|
+
return heavy ? sourceAssets : null;
|
|
716
|
+
}
|
|
717
|
+
async function offloadHeavyMedia(projectDir, fileMap, opts) {
|
|
718
|
+
const cachePath = opts.cachePath ?? defaultOffloadCachePath();
|
|
719
|
+
const ctx = {
|
|
720
|
+
absDir: resolve(projectDir),
|
|
721
|
+
opts,
|
|
722
|
+
cachePath,
|
|
723
|
+
cache: readCache(cachePath),
|
|
724
|
+
fetchImpl: opts.fetchImpl ?? fetch,
|
|
725
|
+
reencoder: opts.reencoder ?? ffmpegReencoder,
|
|
726
|
+
tempFiles: [],
|
|
727
|
+
preparedGifSources: [],
|
|
728
|
+
fileMap
|
|
729
|
+
};
|
|
730
|
+
const urls = /* @__PURE__ */ new Map();
|
|
731
|
+
const report = [];
|
|
732
|
+
try {
|
|
733
|
+
await prepareEntryGifs(ctx, fileMap);
|
|
734
|
+
const referenced = referencedFiles(ctx.absDir, fileMap);
|
|
735
|
+
for (const [path, file] of [...fileMap].sort(([a], [b]) => a.localeCompare(b))) {
|
|
736
|
+
if (!referenced.has(path)) continue;
|
|
737
|
+
const item = await offloadFile(ctx, path, file);
|
|
738
|
+
if (!item) continue;
|
|
739
|
+
urls.set(path, item.url ?? `https://assets.invalid/${report.length}`);
|
|
740
|
+
report.push(item);
|
|
741
|
+
opts.onFile?.(item);
|
|
742
|
+
}
|
|
743
|
+
} finally {
|
|
744
|
+
for (const dir of ctx.tempFiles) rmSync(dir, { recursive: true, force: true });
|
|
745
|
+
}
|
|
746
|
+
if (urls.size > 0) rewriteReferences(ctx.absDir, fileMap, (key) => urls.get(key) ?? null);
|
|
747
|
+
dropReplacedFiles(fileMap, report, ctx.preparedGifSources, [...urls.values()]);
|
|
748
|
+
return report;
|
|
749
|
+
}
|
|
750
|
+
function dropReplacedFiles(fileMap, report, preparedGifSources, assetUrls) {
|
|
751
|
+
const withoutUrls = (text) => assetUrls.reduce((acc, url) => acc.replaceAll(url, ""), text);
|
|
752
|
+
const code = withoutUrls(scriptCode(fileMap));
|
|
753
|
+
for (const item of report) {
|
|
754
|
+
if (isStillMentioned(fileMap, item.path, code, withoutUrls)) item.kept_in_archive = true;
|
|
755
|
+
else fileMap.delete(item.path);
|
|
756
|
+
}
|
|
757
|
+
for (const gif of preparedGifSources) {
|
|
758
|
+
if (!isStillMentioned(fileMap, gif, code, withoutUrls)) fileMap.delete(gif);
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
function isStillMentioned(fileMap, path, code, withoutUrls) {
|
|
762
|
+
const ext = extname(path);
|
|
763
|
+
if (code.includes(basename2(path, ext)) && code.includes(ext.slice(1))) return true;
|
|
764
|
+
const name = basename2(path);
|
|
765
|
+
for (const [entryPath, content] of fileMap) {
|
|
766
|
+
const contentText = textOf(entryPath, content);
|
|
767
|
+
if (entryPath === path || !contentText?.includes(name)) continue;
|
|
768
|
+
if (withoutUrls(contentText).includes(name)) return true;
|
|
769
|
+
}
|
|
770
|
+
return false;
|
|
771
|
+
}
|
|
772
|
+
function htmlSrcKey(absDir, entryPath, raw, fileMap) {
|
|
773
|
+
const clean = cleanAssetUrl(raw);
|
|
774
|
+
if (!clean || isRemoteOrInlineUrl(clean)) return null;
|
|
775
|
+
const rootRelative = clean.startsWith("/") ? clean : rewriteAssetPath(entryPath, clean, (p) => fileMap.has(p));
|
|
776
|
+
for (const candidate of resolveLocalAssetCandidates(absDir, rootRelative)) {
|
|
777
|
+
const key = relative(absDir, candidate).replaceAll("\\", "/");
|
|
778
|
+
if (fileMap.has(key)) return key;
|
|
779
|
+
}
|
|
780
|
+
return null;
|
|
781
|
+
}
|
|
782
|
+
function referencedFiles(absDir, fileMap) {
|
|
783
|
+
const keys = /* @__PURE__ */ new Set();
|
|
784
|
+
rewriteReferences(absDir, fileMap, (key) => {
|
|
785
|
+
if (fileMap.has(key)) keys.add(key);
|
|
786
|
+
return null;
|
|
787
|
+
});
|
|
788
|
+
return keys;
|
|
789
|
+
}
|
|
790
|
+
function rewriteReferences(absDir, fileMap, urlForKey) {
|
|
791
|
+
const urlFor = (rootRelative) => {
|
|
792
|
+
for (const candidate of resolveLocalAssetCandidates(absDir, rootRelative)) {
|
|
793
|
+
const url = urlForKey(relative(absDir, candidate).replaceAll("\\", "/"));
|
|
794
|
+
if (url) return url;
|
|
795
|
+
}
|
|
796
|
+
return null;
|
|
797
|
+
};
|
|
798
|
+
for (const [entryPath, content] of [...fileMap]) {
|
|
799
|
+
const rewrite = rewriterFor(absDir, entryPath, urlFor, fileMap);
|
|
800
|
+
const text = rewrite ? textOf(entryPath, content) : void 0;
|
|
801
|
+
const next = rewrite && text !== void 0 ? rewrite(text) : null;
|
|
802
|
+
if (next !== null) fileMap.set(entryPath, Buffer.from(next, "utf-8"));
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
function rewriterFor(absDir, entryPath, urlFor, fileMap) {
|
|
806
|
+
if (/\.html?$/i.test(entryPath)) {
|
|
807
|
+
return (text) => rewriteHtmlEntry(absDir, entryPath, text, urlFor, fileMap);
|
|
808
|
+
}
|
|
809
|
+
if (/\.css$/i.test(entryPath)) return (text) => rewriteCssEntry(absDir, entryPath, text, urlFor);
|
|
810
|
+
if (/\.(m?js|cjs|json)$/i.test(entryPath)) return (text) => rewriteStringLiterals(text, urlFor);
|
|
811
|
+
return null;
|
|
812
|
+
}
|
|
813
|
+
function localTarget(raw, toRootRelative, urlFor) {
|
|
814
|
+
const clean = cleanAssetUrl(raw);
|
|
815
|
+
if (!clean || isRemoteOrInlineUrl(clean)) return null;
|
|
816
|
+
const url = urlFor(clean.startsWith("/") ? clean : toRootRelative(clean));
|
|
817
|
+
return url && url + raw.trim().slice(clean.length);
|
|
818
|
+
}
|
|
819
|
+
function rewriteCssEntry(absDir, entryPath, css, urlFor) {
|
|
820
|
+
const result = rewriteCssUrls(
|
|
821
|
+
css,
|
|
822
|
+
resolve(absDir, dirname(entryPath)),
|
|
823
|
+
entryPath,
|
|
824
|
+
(raw) => localTarget(raw, (clean) => posix.join(posix.dirname(entryPath), clean), urlFor)
|
|
825
|
+
);
|
|
826
|
+
return result.modified ? result.css : null;
|
|
827
|
+
}
|
|
828
|
+
function rewriteHtmlEntry(absDir, entryPath, html, urlFor, fileMap) {
|
|
829
|
+
const referrerAbsDir = resolve(absDir, dirname(entryPath));
|
|
830
|
+
const resolveTarget = (raw) => localTarget(raw, (clean) => rewriteAssetPath(entryPath, clean, (p) => fileMap.has(p)), urlFor);
|
|
831
|
+
const { document } = parseHTML(html);
|
|
832
|
+
const attrs = rewriteHtmlAttributes(document, referrerAbsDir, entryPath, resolveTarget, {
|
|
833
|
+
attrs: ["src", "poster", "href"]
|
|
834
|
+
});
|
|
835
|
+
const srcsets = rewriteSrcsets(document, resolveTarget);
|
|
836
|
+
const styles = rewriteStyleBlocks(document, referrerAbsDir, entryPath, resolveTarget);
|
|
837
|
+
let scripts = false;
|
|
838
|
+
for (const script of document.querySelectorAll("script")) {
|
|
839
|
+
if (script.hasAttribute("src")) continue;
|
|
840
|
+
const next = rewriteStringLiterals(script.textContent ?? "", urlFor);
|
|
841
|
+
if (next === null) continue;
|
|
842
|
+
script.textContent = next;
|
|
843
|
+
scripts = true;
|
|
844
|
+
}
|
|
845
|
+
return attrs || srcsets || styles || scripts ? document.toString() : null;
|
|
846
|
+
}
|
|
847
|
+
var STRING_LITERAL_RE = /(["'`])([^"'`\\\n$]+)\1/g;
|
|
848
|
+
function rewriteStringLiterals(code, urlFor) {
|
|
849
|
+
let modified = false;
|
|
850
|
+
const next = code.replace(STRING_LITERAL_RE, (full, quote, value) => {
|
|
851
|
+
const url = localTarget(value, (clean) => clean, urlFor);
|
|
852
|
+
if (!url) return full;
|
|
853
|
+
modified = true;
|
|
854
|
+
return `${quote}${url}${quote}`;
|
|
855
|
+
});
|
|
856
|
+
return modified ? next : null;
|
|
857
|
+
}
|
|
858
|
+
function rewriteSrcsets(document, resolveTarget) {
|
|
859
|
+
let modified = false;
|
|
860
|
+
for (const el of document.querySelectorAll("[srcset]")) {
|
|
861
|
+
const srcset = el.getAttribute("srcset") ?? "";
|
|
862
|
+
if (srcset.includes("data:")) continue;
|
|
863
|
+
let changed = false;
|
|
864
|
+
const next = srcset.split(",").map((part) => {
|
|
865
|
+
const [url, ...descriptors] = part.trim().split(/\s+/);
|
|
866
|
+
const target = url ? resolveTarget(url, "") : null;
|
|
867
|
+
if (!target) return part.trim();
|
|
868
|
+
changed = true;
|
|
869
|
+
return [target, ...descriptors].join(" ");
|
|
870
|
+
}).join(", ");
|
|
871
|
+
if (changed) {
|
|
872
|
+
el.setAttribute("srcset", next);
|
|
873
|
+
modified = true;
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
return modified;
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
// src/commands/cloud/render.ts
|
|
880
|
+
import { isAbsolute, relative as relative2, resolve as resolvePath } from "path";
|
|
881
|
+
import { existsSync } from "fs";
|
|
882
|
+
var VALID_QUALITY = ["draft", "standard", "high"];
|
|
883
|
+
var VALID_FORMAT = ["mp4", "webm", "mov"];
|
|
884
|
+
var VALID_RESOLUTION = ["1080p", "4k"];
|
|
885
|
+
var VALID_ASPECT_RATIO = ["16:9", "9:16", "1:1"];
|
|
886
|
+
var DIRECT_UPLOAD_LIMIT_BYTES = 200 * 1024 * 1024;
|
|
887
|
+
var ARCHIVE_DIAGNOSTIC_THRESHOLD_BYTES = 150 * 1024 * 1024;
|
|
888
|
+
var LARGEST_FILE_COUNT = 10;
|
|
889
|
+
var FORMAT_EXT = { mp4: ".mp4", webm: ".webm", mov: ".mov" };
|
|
890
|
+
var examples = [
|
|
891
|
+
["Render the current directory in the cloud", "hyperframes cloud render"],
|
|
892
|
+
["Inspect archive size without uploading", "hyperframes cloud render --dry-run"],
|
|
893
|
+
[
|
|
894
|
+
"Pick a specific composition + output path",
|
|
895
|
+
"hyperframes cloud render . --composition compositions/intro.html -o ./renders/intro.mp4"
|
|
896
|
+
],
|
|
897
|
+
["Higher quality, 60fps", "hyperframes cloud render --quality high --fps 60"],
|
|
898
|
+
[
|
|
899
|
+
"Submit and exit; webhook fires when the render terminates",
|
|
900
|
+
"hyperframes cloud render --callback-url https://example.com/hook --no-wait"
|
|
901
|
+
],
|
|
902
|
+
[
|
|
903
|
+
"Override variables (parametrized render)",
|
|
904
|
+
`hyperframes cloud render --variables '{"title":"Q4 Recap","theme":"dark"}'`
|
|
905
|
+
],
|
|
906
|
+
["Re-render an already-uploaded zip", "hyperframes cloud render --asset-id asst_abc123"]
|
|
907
|
+
];
|
|
908
|
+
var render_default = defineCommand({
|
|
909
|
+
meta: { name: "render", description: "Render a HyperFrames composition in the cloud" },
|
|
910
|
+
args: {
|
|
911
|
+
dir: { type: "positional", required: false, description: "Project directory (default: .)" },
|
|
912
|
+
fps: { type: "string", description: "Frames per second (1-240). Default: 30." },
|
|
913
|
+
quality: { type: "string", description: "draft | standard | high (default: standard)" },
|
|
914
|
+
format: { type: "string", description: "mp4 | webm | mov (default: mp4)" },
|
|
915
|
+
resolution: {
|
|
916
|
+
type: "string",
|
|
917
|
+
description: "Resolution tier: 1080p | 4k (default: 1080p; 4k is billed at 1.5x)"
|
|
918
|
+
},
|
|
919
|
+
"aspect-ratio": {
|
|
920
|
+
type: "string",
|
|
921
|
+
description: "Aspect ratio: 16:9 | 9:16 | 1:1 (default: 16:9)"
|
|
922
|
+
},
|
|
923
|
+
composition: {
|
|
924
|
+
type: "string",
|
|
925
|
+
alias: "c",
|
|
926
|
+
description: "Entry HTML file inside the zip (default: index.html)"
|
|
927
|
+
},
|
|
928
|
+
variables: {
|
|
929
|
+
type: "string",
|
|
930
|
+
description: `Inline JSON object overriding data-composition-variables. Example: --variables '{"title":"X"}'`
|
|
931
|
+
},
|
|
932
|
+
"variables-file": {
|
|
933
|
+
type: "string",
|
|
934
|
+
description: "Path to a JSON file with variable values (alternative to --variables)"
|
|
935
|
+
},
|
|
936
|
+
"strict-variables": {
|
|
937
|
+
type: "boolean",
|
|
938
|
+
description: "Fail when --variables keys are undeclared or have the wrong type",
|
|
939
|
+
default: false
|
|
940
|
+
},
|
|
941
|
+
title: {
|
|
942
|
+
type: "string",
|
|
943
|
+
description: "Free-text label echoed back in detail responses"
|
|
944
|
+
},
|
|
945
|
+
"callback-url": {
|
|
946
|
+
type: "string",
|
|
947
|
+
description: "HTTPS webhook fired when the render terminates. Fires regardless of whether the CLI is still polling \u2014 combine with --no-wait for true fire-and-forget."
|
|
948
|
+
},
|
|
949
|
+
"callback-id": {
|
|
950
|
+
type: "string",
|
|
951
|
+
description: "Opaque tracking ID echoed in webhook payloads"
|
|
952
|
+
},
|
|
953
|
+
"asset-id": {
|
|
954
|
+
type: "string",
|
|
955
|
+
description: "Skip zip+upload and submit an already-uploaded composition. Mutually exclusive with --url and the project dir."
|
|
956
|
+
},
|
|
957
|
+
url: {
|
|
958
|
+
type: "string",
|
|
959
|
+
description: "Public HTTPS URL of a composition zip. Mutually exclusive with --asset-id and the project dir."
|
|
960
|
+
},
|
|
961
|
+
// Citty parses `--no-FOO` as `--FOO=false`. A flag literally named
|
|
962
|
+
// "no-wait" gets routed as `args.wait=false`, leaving
|
|
963
|
+
// `args["no-wait"]` undefined and the early-return for
|
|
964
|
+
// fire-and-forget mode unreachable. Named the arg `wait` so the
|
|
965
|
+
// user-facing `--no-wait` flag works via citty's negation; the
|
|
966
|
+
// run() body checks `if (!args.wait)`.
|
|
967
|
+
wait: {
|
|
968
|
+
type: "boolean",
|
|
969
|
+
description: "Poll until completion and download the video (default: true). Pass `--no-wait` for fire-and-forget \u2014 submits and exits with the render_id.",
|
|
970
|
+
default: true
|
|
971
|
+
},
|
|
972
|
+
output: {
|
|
973
|
+
type: "string",
|
|
974
|
+
alias: "o",
|
|
975
|
+
description: "Destination path for the downloaded video (default: renders/<render_id>.<ext>)"
|
|
976
|
+
},
|
|
977
|
+
"poll-interval": {
|
|
978
|
+
type: "string",
|
|
979
|
+
description: `Poll cadence in seconds (default: ${DEFAULT_POLL_INTERVAL_MS / 1e3})`
|
|
980
|
+
},
|
|
981
|
+
"max-wait": {
|
|
982
|
+
type: "string",
|
|
983
|
+
description: `Max poll duration in minutes (default: ${DEFAULT_MAX_WAIT_MS / 6e4})`
|
|
984
|
+
},
|
|
985
|
+
json: {
|
|
986
|
+
type: "boolean",
|
|
987
|
+
description: "Emit machine-readable JSON instead of human-friendly progress",
|
|
988
|
+
default: false
|
|
989
|
+
},
|
|
990
|
+
"dry-run": {
|
|
991
|
+
type: "boolean",
|
|
992
|
+
description: "Build and inspect the project zip without authenticating or uploading",
|
|
993
|
+
default: false
|
|
994
|
+
},
|
|
995
|
+
"idempotency-key": {
|
|
996
|
+
type: "string",
|
|
997
|
+
description: "Optional Idempotency-Key for safe retries (1-255 chars from [A-Za-z0-9_:.-])"
|
|
998
|
+
}
|
|
999
|
+
},
|
|
1000
|
+
// fallow-ignore-next-line complexity
|
|
1001
|
+
async run({ args }) {
|
|
1002
|
+
const asJson = Boolean(args.json);
|
|
1003
|
+
const fps = parseIntFlag(args.fps, { flag: "--fps", min: 1, max: 240 });
|
|
1004
|
+
const quality = parseEnumFlag(args.quality, VALID_QUALITY, { flag: "--quality" });
|
|
1005
|
+
const format = parseEnumFlag(args.format, VALID_FORMAT, { flag: "--format" });
|
|
1006
|
+
const resolution = parseEnumFlag(args.resolution, VALID_RESOLUTION, {
|
|
1007
|
+
flag: "--resolution"
|
|
1008
|
+
});
|
|
1009
|
+
const explicitAspectRatio = parseEnumFlag(args["aspect-ratio"], VALID_ASPECT_RATIO, {
|
|
1010
|
+
flag: "--aspect-ratio"
|
|
1011
|
+
});
|
|
1012
|
+
const pollIntervalMs = parsePollIntervalMs(args["poll-interval"]);
|
|
1013
|
+
const maxWaitMs = parseMaxWaitMs(args["max-wait"]);
|
|
1014
|
+
validateIdempotencyKey(args["idempotency-key"]);
|
|
1015
|
+
const project = resolveProjectInput({
|
|
1016
|
+
dir: args.dir,
|
|
1017
|
+
assetId: args["asset-id"],
|
|
1018
|
+
url: args.url
|
|
1019
|
+
});
|
|
1020
|
+
validateDryRunSource(project, args["dry-run"] ?? false);
|
|
1021
|
+
const aspectRatio = resolveAspectRatioForSubmit(
|
|
1022
|
+
project,
|
|
1023
|
+
args.composition,
|
|
1024
|
+
explicitAspectRatio,
|
|
1025
|
+
asJson
|
|
1026
|
+
);
|
|
1027
|
+
if (args["dry-run"]) {
|
|
1028
|
+
if (project.kind !== "dir") throw new Error("Dry-run project must be a local directory");
|
|
1029
|
+
reportDryRun(await prepareLocalArchive(project, args.composition, asJson), asJson);
|
|
1030
|
+
return;
|
|
1031
|
+
}
|
|
1032
|
+
const variables = resolveVariablesAndValidateIfLocal(
|
|
1033
|
+
args.variables,
|
|
1034
|
+
args["variables-file"],
|
|
1035
|
+
args["strict-variables"] ?? false,
|
|
1036
|
+
project
|
|
1037
|
+
);
|
|
1038
|
+
const client = await createCloudClient();
|
|
1039
|
+
const preparedArchive = project.kind === "dir" ? await prepareLocalArchive(
|
|
1040
|
+
project,
|
|
1041
|
+
args.composition,
|
|
1042
|
+
asJson,
|
|
1043
|
+
(file) => uploadAssetViaDirectUpload({ client, ...file })
|
|
1044
|
+
) : void 0;
|
|
1045
|
+
const upload = await maybeUploadProject(
|
|
1046
|
+
client,
|
|
1047
|
+
project,
|
|
1048
|
+
preparedArchive,
|
|
1049
|
+
asJson,
|
|
1050
|
+
args["idempotency-key"]
|
|
1051
|
+
);
|
|
1052
|
+
const submitted = await submitRender(client, {
|
|
1053
|
+
projectInput: upload.projectInput,
|
|
1054
|
+
fps,
|
|
1055
|
+
quality,
|
|
1056
|
+
format,
|
|
1057
|
+
resolution,
|
|
1058
|
+
aspectRatio,
|
|
1059
|
+
composition: args.composition,
|
|
1060
|
+
variables,
|
|
1061
|
+
title: args.title,
|
|
1062
|
+
callbackUrl: args["callback-url"],
|
|
1063
|
+
callbackId: args["callback-id"],
|
|
1064
|
+
idempotencyKey: args["idempotency-key"]
|
|
1065
|
+
});
|
|
1066
|
+
const renderId = submitted.render_id;
|
|
1067
|
+
if (!args.wait) {
|
|
1068
|
+
if (asJson) {
|
|
1069
|
+
console.log(
|
|
1070
|
+
JSON.stringify(
|
|
1071
|
+
withMeta({ render: { render_id: renderId, status: "queued" } }),
|
|
1072
|
+
null,
|
|
1073
|
+
2
|
|
1074
|
+
)
|
|
1075
|
+
);
|
|
1076
|
+
} else {
|
|
1077
|
+
console.log("");
|
|
1078
|
+
console.log(`${c.success("\u2713")} Submitted ${c.accent(renderId)}`);
|
|
1079
|
+
console.log(c.dim(` Poll with: hyperframes cloud get ${renderId}`));
|
|
1080
|
+
}
|
|
1081
|
+
return;
|
|
1082
|
+
}
|
|
1083
|
+
if (!asJson) {
|
|
1084
|
+
console.log("");
|
|
1085
|
+
console.log(c.dim(` Polling ${renderId} every ${pollIntervalMs / 1e3}s \u2026`));
|
|
1086
|
+
}
|
|
1087
|
+
const detail = await pollWithProgress(client, renderId, asJson, {
|
|
1088
|
+
intervalMs: pollIntervalMs,
|
|
1089
|
+
maxWaitMs
|
|
1090
|
+
});
|
|
1091
|
+
if (detail.status === "failed") {
|
|
1092
|
+
handleFailedRender(detail, asJson);
|
|
1093
|
+
}
|
|
1094
|
+
if (!detail.video_url) {
|
|
1095
|
+
errorBox(
|
|
1096
|
+
"Render completed but returned no video_url",
|
|
1097
|
+
`render_id: ${renderId}. Try \`hyperframes cloud get ${renderId}\` to inspect raw fields.`
|
|
1098
|
+
);
|
|
1099
|
+
failCommand();
|
|
1100
|
+
}
|
|
1101
|
+
const outputPath = resolveOutputPath(args.output, renderId, detail.format);
|
|
1102
|
+
const downloadResult = await streamVideo(detail.video_url, outputPath, asJson);
|
|
1103
|
+
if (asJson) {
|
|
1104
|
+
console.log(
|
|
1105
|
+
JSON.stringify(
|
|
1106
|
+
withMeta({
|
|
1107
|
+
render: detail,
|
|
1108
|
+
output_path: outputPath,
|
|
1109
|
+
bytes_written: downloadResult.bytes
|
|
1110
|
+
}),
|
|
1111
|
+
null,
|
|
1112
|
+
2
|
|
1113
|
+
)
|
|
1114
|
+
);
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
});
|
|
1118
|
+
function parsePollIntervalMs(raw) {
|
|
1119
|
+
const n = parseNumericFlag(raw, { flag: "--poll-interval", min: 1 });
|
|
1120
|
+
return n === void 0 ? DEFAULT_POLL_INTERVAL_MS : Math.round(n * 1e3);
|
|
1121
|
+
}
|
|
1122
|
+
function parseMaxWaitMs(raw) {
|
|
1123
|
+
const n = parseNumericFlag(raw, { flag: "--max-wait", min: 1e-4 });
|
|
1124
|
+
return n === void 0 ? DEFAULT_MAX_WAIT_MS : Math.round(n * 6e4);
|
|
1125
|
+
}
|
|
1126
|
+
var IDEMPOTENCY_KEY_RE = /^[A-Za-z0-9_:.-]{1,255}$/;
|
|
1127
|
+
function validateIdempotencyKey(key) {
|
|
1128
|
+
if (key === void 0) return;
|
|
1129
|
+
if (!IDEMPOTENCY_KEY_RE.test(key)) {
|
|
1130
|
+
errorBox("Invalid --idempotency-key", `Got "${key}". Must be 1-255 chars from [A-Za-z0-9_:.-]`);
|
|
1131
|
+
failCommand();
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
function validateDryRunSource(source, dryRun) {
|
|
1135
|
+
if (!dryRun || source.kind === "dir") return;
|
|
1136
|
+
errorBox(
|
|
1137
|
+
"Invalid --dry-run input",
|
|
1138
|
+
"--dry-run inspects a local project directory and cannot be combined with --asset-id or --url."
|
|
1139
|
+
);
|
|
1140
|
+
failCommand();
|
|
1141
|
+
}
|
|
1142
|
+
function resolveProjectInput(opts) {
|
|
1143
|
+
const explicit = {
|
|
1144
|
+
dir: opts.dir !== void 0 && opts.dir !== "",
|
|
1145
|
+
assetId: opts.assetId !== void 0 && opts.assetId !== "",
|
|
1146
|
+
url: opts.url !== void 0 && opts.url !== ""
|
|
1147
|
+
};
|
|
1148
|
+
const count = Number(explicit.dir) + Number(explicit.assetId) + Number(explicit.url);
|
|
1149
|
+
if (count > 1) {
|
|
1150
|
+
errorBox("Conflicting inputs", "Pass only one of: project dir, --asset-id, --url.");
|
|
1151
|
+
failCommand();
|
|
1152
|
+
}
|
|
1153
|
+
if (explicit.assetId) return { kind: "asset_id", assetId: opts.assetId };
|
|
1154
|
+
if (explicit.url) return { kind: "url", url: opts.url };
|
|
1155
|
+
return { kind: "dir", dir: opts.dir ?? "." };
|
|
1156
|
+
}
|
|
1157
|
+
function resolveAspectRatioForSubmit(project, compositionArg, explicit, asJson) {
|
|
1158
|
+
if (project.kind !== "dir") {
|
|
1159
|
+
if (!explicit) {
|
|
1160
|
+
const reason = project.kind === "asset_id" ? "--asset-id" : "--url";
|
|
1161
|
+
logDetection(asJson, `Auto-detect skipped (project is ${reason})`);
|
|
1162
|
+
}
|
|
1163
|
+
return explicit;
|
|
1164
|
+
}
|
|
1165
|
+
const dir = project.dir ?? ".";
|
|
1166
|
+
const entryRelative = compositionArg ?? "index.html";
|
|
1167
|
+
const entryPath = resolvePath(dir, entryRelative);
|
|
1168
|
+
if (!existsSync(entryPath)) {
|
|
1169
|
+
errorBox(
|
|
1170
|
+
"Composition not found",
|
|
1171
|
+
`Entry file "${entryRelative}" does not exist in ${dir}.`,
|
|
1172
|
+
"Pass --composition with a path that exists inside the project, or omit it to use index.html."
|
|
1173
|
+
);
|
|
1174
|
+
failCommand();
|
|
1175
|
+
}
|
|
1176
|
+
const detection = detectAspectRatioFromHtml(entryPath);
|
|
1177
|
+
if (explicit) {
|
|
1178
|
+
const conflictDetail = detection.kind === "matched" && detection.aspectRatio !== explicit ? `${detection.width}\xD7${detection.height} \u2192 ${detection.aspectRatio}` : detection.kind === "no-match" ? `${detection.width}\xD7${detection.height}, ratio ${detection.ratio.toFixed(2)} \u2014 not a supported ratio` : void 0;
|
|
1179
|
+
if (conflictDetail) {
|
|
1180
|
+
errorBox(
|
|
1181
|
+
"Aspect ratio mismatch",
|
|
1182
|
+
`--aspect-ratio ${explicit} doesn't match the composition (${conflictDetail}).`,
|
|
1183
|
+
"The renderer matches the composition's authored aspect ratio \u2014 it can't reshape it. Drop --aspect-ratio (it's auto-detected) or re-author the composition at the target ratio."
|
|
1184
|
+
);
|
|
1185
|
+
failCommand();
|
|
1186
|
+
}
|
|
1187
|
+
return explicit;
|
|
1188
|
+
}
|
|
1189
|
+
logDetection(asJson, summarizeDetection(detection, entryRelative));
|
|
1190
|
+
return detection.kind === "matched" ? detection.aspectRatio : void 0;
|
|
1191
|
+
}
|
|
1192
|
+
var ASPECT_FALLBACK_HINT = "server will default aspect_ratio to 16:9. Pass --aspect-ratio to override.";
|
|
1193
|
+
function logDetection(asJson, message) {
|
|
1194
|
+
if (asJson) return;
|
|
1195
|
+
const suffix = message.startsWith("Detected aspect ratio") ? "" : `; ${ASPECT_FALLBACK_HINT}`;
|
|
1196
|
+
console.log(c.dim(` ${message}${suffix}`));
|
|
1197
|
+
}
|
|
1198
|
+
function summarizeDetection(detection, entryRelative) {
|
|
1199
|
+
switch (detection.kind) {
|
|
1200
|
+
case "matched":
|
|
1201
|
+
return `Detected aspect ratio: ${detection.aspectRatio} (from ${entryRelative} dims ${detection.width}\xD7${detection.height})`;
|
|
1202
|
+
case "no-root-div":
|
|
1203
|
+
return `No <div data-composition-id> found in ${entryRelative}`;
|
|
1204
|
+
case "no-dims":
|
|
1205
|
+
return `${entryRelative} root composition has no data-width / data-height`;
|
|
1206
|
+
case "invalid-dims":
|
|
1207
|
+
return `${entryRelative} root has invalid dims (${detection.width}\xD7${detection.height})`;
|
|
1208
|
+
case "no-match":
|
|
1209
|
+
return `${entryRelative} dims ${detection.width}\xD7${detection.height} (ratio ${detection.ratio.toFixed(2)}) don't match 16:9, 9:16, or 1:1`;
|
|
1210
|
+
case "read-error":
|
|
1211
|
+
return `Couldn't read ${entryRelative} for aspect-ratio detection (${detection.error})`;
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
function resolveVariablesAndValidateIfLocal(inline, filePath, strict, source) {
|
|
1215
|
+
const variables = resolveVariablesArg(inline, filePath);
|
|
1216
|
+
if (!variables || Object.keys(variables).length === 0) return variables;
|
|
1217
|
+
if (source.kind !== "dir") return variables;
|
|
1218
|
+
const { indexPath } = resolveProject(source.dir);
|
|
1219
|
+
const issues = validateVariablesAgainstProject(indexPath, variables);
|
|
1220
|
+
reportVariableIssues(issues, { strict, quiet: false });
|
|
1221
|
+
return variables;
|
|
1222
|
+
}
|
|
1223
|
+
function summarizeLargestFiles(fileMap) {
|
|
1224
|
+
return [...fileMap.entries()].map(([path, content]) => ({ path, size_bytes: stagedSize(content) })).sort((a, b) => b.size_bytes - a.size_bytes || a.path.localeCompare(b.path)).slice(0, LARGEST_FILE_COUNT);
|
|
1225
|
+
}
|
|
1226
|
+
async function prepareLocalArchive(source, composition, asJson, upload) {
|
|
1227
|
+
const project = resolveProject(source.dir);
|
|
1228
|
+
if (!asJson) {
|
|
1229
|
+
console.log("");
|
|
1230
|
+
console.log(`${c.accent("\u25C6")} Zipping ${c.accent(project.name)}`);
|
|
1231
|
+
}
|
|
1232
|
+
const { fileMap, entryPath } = stageProjectFiles(project, composition);
|
|
1233
|
+
const offloaded = await offloadMedia(project, fileMap, entryPath, asJson, upload);
|
|
1234
|
+
const onDiskBytes = [...fileMap.values()].filter((file) => !Buffer.isBuffer(file)).reduce((sum, file) => sum + stagedSize(file), 0);
|
|
1235
|
+
const archive = onDiskBytes > DIRECT_UPLOAD_LIMIT_BYTES ? void 0 : zipPublishFileMap(new Map([...fileMap].map(([path, file]) => [path, readStaged(file)])));
|
|
1236
|
+
const sizeBytes = archive?.buffer.byteLength ?? onDiskBytes;
|
|
1237
|
+
if (!asJson) {
|
|
1238
|
+
console.log(c.dim(` ${fileMap.size} files \xB7 ${formatBytes(sizeBytes)}`));
|
|
1239
|
+
}
|
|
1240
|
+
const largestFiles = summarizeLargestFiles(fileMap);
|
|
1241
|
+
const prepared = {
|
|
1242
|
+
project,
|
|
1243
|
+
archive,
|
|
1244
|
+
sizeBytes,
|
|
1245
|
+
fileCount: fileMap.size,
|
|
1246
|
+
largestFiles,
|
|
1247
|
+
offloaded
|
|
1248
|
+
};
|
|
1249
|
+
if (upload) assertArchiveFits(sizeBytes, largestFiles);
|
|
1250
|
+
if (!asJson && sizeBytes >= ARCHIVE_DIAGNOSTIC_THRESHOLD_BYTES) {
|
|
1251
|
+
reportLargestFiles(prepared);
|
|
1252
|
+
}
|
|
1253
|
+
return prepared;
|
|
1254
|
+
}
|
|
1255
|
+
function stageProjectFiles(project, composition) {
|
|
1256
|
+
try {
|
|
1257
|
+
const fileMap = stagePublishFiles(project.dir, OFFLOAD_MIN_BYTES);
|
|
1258
|
+
const entryPath = relative2(
|
|
1259
|
+
project.dir,
|
|
1260
|
+
resolvePath(project.dir, composition ?? "index.html")
|
|
1261
|
+
).replaceAll("\\", "/");
|
|
1262
|
+
if (!fileMap.has(entryPath)) {
|
|
1263
|
+
throw new Error(
|
|
1264
|
+
`Composition "${composition ?? "index.html"}" is excluded from the archive. Check .hyperframesignore.`
|
|
1265
|
+
);
|
|
1266
|
+
}
|
|
1267
|
+
return { fileMap, entryPath };
|
|
1268
|
+
} catch (err) {
|
|
1269
|
+
const msg = normalizeErrorMessage(err);
|
|
1270
|
+
errorBox("Zip failed", msg, "Check the project and .hyperframesignore for missing files.");
|
|
1271
|
+
failCommand();
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
async function offloadMedia(project, fileMap, entryPath, asJson, upload) {
|
|
1275
|
+
let offloaded;
|
|
1276
|
+
try {
|
|
1277
|
+
offloaded = await offloadHeavyMedia(project.dir, fileMap, {
|
|
1278
|
+
apiBaseUrl: resolveCloudBaseUrl(),
|
|
1279
|
+
upload,
|
|
1280
|
+
entryPath,
|
|
1281
|
+
onFile: asJson ? void 0 : (item) => console.log(c.dim(` ${describeOffload(item, !upload)}`))
|
|
1282
|
+
});
|
|
1283
|
+
} catch (err) {
|
|
1284
|
+
reportApiError("Media upload failed", err);
|
|
1285
|
+
}
|
|
1286
|
+
if (!asJson) {
|
|
1287
|
+
for (const item of offloaded.filter((entry) => entry.kept_in_archive)) {
|
|
1288
|
+
console.log(
|
|
1289
|
+
c.dim(
|
|
1290
|
+
` ${item.path} also stays in the zip: a script builds its path in a way that cannot be rewritten`
|
|
1291
|
+
)
|
|
1292
|
+
);
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
return offloaded;
|
|
1296
|
+
}
|
|
1297
|
+
function assertArchiveFits(sizeBytes, largestFiles) {
|
|
1298
|
+
if (sizeBytes <= DIRECT_UPLOAD_LIMIT_BYTES) return;
|
|
1299
|
+
errorBox(
|
|
1300
|
+
"Project too large for cloud render",
|
|
1301
|
+
`The zip is ${formatBytes(sizeBytes)} after moving media to HeyGen assets, over the ${formatBytes(DIRECT_UPLOAD_LIMIT_BYTES)} upload limit. Largest files left in it:
|
|
1302
|
+
${largestFiles.map((file) => ` ${formatBytes(file.size_bytes).padStart(9)} ${file.path}`).join("\n")}`,
|
|
1303
|
+
"Exclude files the render does not need with .hyperframesignore."
|
|
1304
|
+
);
|
|
1305
|
+
failCommand();
|
|
1306
|
+
}
|
|
1307
|
+
function describeOffload(item, dryRun) {
|
|
1308
|
+
const verb = item.action === "cached" ? "already uploaded" : item.action === "reencode" ? dryRun ? "would re-encode and upload" : "re-encoded and uploaded" : dryRun ? "would upload" : "uploaded";
|
|
1309
|
+
return `${formatBytes(item.size_bytes).padStart(9)} ${item.path} \xB7 ${verb}`;
|
|
1310
|
+
}
|
|
1311
|
+
function reportLargestFiles(prepared) {
|
|
1312
|
+
console.log(c.dim(" Largest included files:"));
|
|
1313
|
+
for (const file of prepared.largestFiles) {
|
|
1314
|
+
console.log(c.dim(` ${formatBytes(file.size_bytes).padStart(9)} ${file.path}`));
|
|
1315
|
+
}
|
|
1316
|
+
console.log(c.dim(" Exclude verified-unneeded files with .hyperframesignore."));
|
|
1317
|
+
}
|
|
1318
|
+
function reportDryRun(prepared, asJson) {
|
|
1319
|
+
const { sizeBytes } = prepared;
|
|
1320
|
+
if (asJson) {
|
|
1321
|
+
console.log(
|
|
1322
|
+
JSON.stringify(
|
|
1323
|
+
withMeta({
|
|
1324
|
+
archive: {
|
|
1325
|
+
project: prepared.project.name,
|
|
1326
|
+
file_count: prepared.fileCount,
|
|
1327
|
+
size_bytes: sizeBytes,
|
|
1328
|
+
upload_limit_bytes: DIRECT_UPLOAD_LIMIT_BYTES,
|
|
1329
|
+
exceeds_upload_limit: sizeBytes > DIRECT_UPLOAD_LIMIT_BYTES,
|
|
1330
|
+
largest_files: prepared.largestFiles,
|
|
1331
|
+
offloaded_media: prepared.offloaded
|
|
1332
|
+
}
|
|
1333
|
+
}),
|
|
1334
|
+
null,
|
|
1335
|
+
2
|
|
1336
|
+
)
|
|
1337
|
+
);
|
|
1338
|
+
return;
|
|
1339
|
+
}
|
|
1340
|
+
if (sizeBytes < ARCHIVE_DIAGNOSTIC_THRESHOLD_BYTES) reportLargestFiles(prepared);
|
|
1341
|
+
console.log("");
|
|
1342
|
+
console.log(`${c.success("\u2713")} Dry run complete \u2014 nothing uploaded`);
|
|
1343
|
+
if (sizeBytes > DIRECT_UPLOAD_LIMIT_BYTES) {
|
|
1344
|
+
console.log(c.warn(` Archive exceeds the 200 MB direct-upload limit.`));
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
async function maybeUploadProject(client, source, preparedArchive, asJson, idempotencyKey) {
|
|
1348
|
+
if (source.kind === "asset_id") {
|
|
1349
|
+
return { projectInput: { type: "asset_id", asset_id: source.assetId } };
|
|
1350
|
+
}
|
|
1351
|
+
if (source.kind === "url") {
|
|
1352
|
+
return { projectInput: { type: "url", url: source.url } };
|
|
1353
|
+
}
|
|
1354
|
+
if (!preparedArchive) throw new Error("Local project archive was not prepared");
|
|
1355
|
+
const { project, archive } = preparedArchive;
|
|
1356
|
+
if (!archive) throw new Error("Local project archive is over the upload limit");
|
|
1357
|
+
if (!asJson) {
|
|
1358
|
+
console.log("");
|
|
1359
|
+
console.log(`${c.accent("\u25C6")} Uploading (direct-to-S3)`);
|
|
1360
|
+
}
|
|
1361
|
+
const uploadStart = Date.now();
|
|
1362
|
+
let uploaded;
|
|
1363
|
+
try {
|
|
1364
|
+
uploaded = await uploadZipViaDirectUpload({
|
|
1365
|
+
client,
|
|
1366
|
+
bytes: archive.buffer,
|
|
1367
|
+
filename: `${project.name}.zip`,
|
|
1368
|
+
idempotencyKey,
|
|
1369
|
+
onProgress: !asJson ? (ev) => {
|
|
1370
|
+
if (ev.phase === "initialize") {
|
|
1371
|
+
console.log(c.dim(` initializing\u2026`));
|
|
1372
|
+
} else if (ev.phase === "upload" && ev.percent === 0) {
|
|
1373
|
+
console.log(c.dim(` uploading to S3\u2026`));
|
|
1374
|
+
} else if (ev.phase === "complete") {
|
|
1375
|
+
console.log(c.dim(` finalizing\u2026`));
|
|
1376
|
+
}
|
|
1377
|
+
} : void 0
|
|
1378
|
+
});
|
|
1379
|
+
} catch (err) {
|
|
1380
|
+
reportApiError("Upload failed", err);
|
|
1381
|
+
}
|
|
1382
|
+
if (!asJson) {
|
|
1383
|
+
console.log(
|
|
1384
|
+
c.dim(
|
|
1385
|
+
` asset_id: ${c.accent(uploaded.asset_id)} \xB7 ${formatDuration(Date.now() - uploadStart)}`
|
|
1386
|
+
)
|
|
1387
|
+
);
|
|
1388
|
+
}
|
|
1389
|
+
return { projectInput: { type: "asset_id", asset_id: uploaded.asset_id } };
|
|
1390
|
+
}
|
|
1391
|
+
async function submitRender(client, opts) {
|
|
1392
|
+
const body = buildRenderBody(opts);
|
|
1393
|
+
try {
|
|
1394
|
+
return await client.createRender({ body, idempotencyKey: opts.idempotencyKey });
|
|
1395
|
+
} catch (err) {
|
|
1396
|
+
reportApiError("Submit failed", err);
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
function buildRenderBody(opts) {
|
|
1400
|
+
const body = { project: opts.projectInput };
|
|
1401
|
+
if (opts.fps !== void 0) body.fps = opts.fps;
|
|
1402
|
+
if (opts.quality !== void 0) body.quality = opts.quality;
|
|
1403
|
+
if (opts.format !== void 0) body.format = opts.format;
|
|
1404
|
+
if (opts.resolution !== void 0) body.resolution = opts.resolution;
|
|
1405
|
+
if (opts.aspectRatio !== void 0) body.aspect_ratio = opts.aspectRatio;
|
|
1406
|
+
if (opts.composition !== void 0) body.composition = opts.composition;
|
|
1407
|
+
if (opts.variables !== void 0) body.variables = opts.variables;
|
|
1408
|
+
if (opts.title !== void 0) body.title = opts.title;
|
|
1409
|
+
if (opts.callbackUrl !== void 0) body.callback_url = opts.callbackUrl;
|
|
1410
|
+
if (opts.callbackId !== void 0) body.callback_id = opts.callbackId;
|
|
1411
|
+
return body;
|
|
1412
|
+
}
|
|
1413
|
+
async function pollWithProgress(client, renderId, asJson, poll) {
|
|
1414
|
+
const interactive = !asJson && process.stdout.isTTY === true;
|
|
1415
|
+
let lastStatus = "";
|
|
1416
|
+
try {
|
|
1417
|
+
return await pollUntilTerminal(client, renderId, {
|
|
1418
|
+
intervalMs: poll.intervalMs,
|
|
1419
|
+
maxWaitMs: poll.maxWaitMs,
|
|
1420
|
+
// fallow-ignore-next-line complexity
|
|
1421
|
+
onTick: (detail, elapsedMs) => {
|
|
1422
|
+
if (asJson) return;
|
|
1423
|
+
if (interactive) {
|
|
1424
|
+
if (detail.status === lastStatus) {
|
|
1425
|
+
process.stdout.write(`\r\x1B[2K ${formatTickLine(detail, elapsedMs)}`);
|
|
1426
|
+
} else {
|
|
1427
|
+
if (lastStatus) process.stdout.write("\n");
|
|
1428
|
+
process.stdout.write(` ${formatTickLine(detail, elapsedMs)}`);
|
|
1429
|
+
lastStatus = detail.status;
|
|
1430
|
+
}
|
|
1431
|
+
} else if (detail.status !== lastStatus) {
|
|
1432
|
+
console.log(` ${formatTickLine(detail, elapsedMs)}`);
|
|
1433
|
+
lastStatus = detail.status;
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
});
|
|
1437
|
+
} catch (err) {
|
|
1438
|
+
if (!asJson && lastStatus && interactive) process.stdout.write("\n");
|
|
1439
|
+
if (err instanceof PollTimeoutError) {
|
|
1440
|
+
errorBox(
|
|
1441
|
+
"Poll timed out",
|
|
1442
|
+
err.message,
|
|
1443
|
+
`The render may still complete. Resume with: hyperframes cloud get ${renderId}`
|
|
1444
|
+
);
|
|
1445
|
+
failCommand();
|
|
1446
|
+
}
|
|
1447
|
+
return reportApiError("API error during poll", err, {
|
|
1448
|
+
suggestion: `The render may still be running. Resume with: hyperframes cloud get ${renderId}`
|
|
1449
|
+
});
|
|
1450
|
+
} finally {
|
|
1451
|
+
if (!asJson && lastStatus && interactive) process.stdout.write("\n");
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1454
|
+
function formatTickLine(detail, elapsedMs) {
|
|
1455
|
+
const status = colorStatus(detail.status);
|
|
1456
|
+
return `${status} ${c.dim(formatDuration(elapsedMs))}`;
|
|
1457
|
+
}
|
|
1458
|
+
function handleFailedRender(detail, asJson) {
|
|
1459
|
+
if (asJson) {
|
|
1460
|
+
console.log(JSON.stringify(withMeta({ render: detail }), null, 2));
|
|
1461
|
+
failCommand();
|
|
1462
|
+
}
|
|
1463
|
+
errorBox(
|
|
1464
|
+
"Render failed",
|
|
1465
|
+
detail.failure_message ?? "(no failure_message returned)",
|
|
1466
|
+
`Inspect: hyperframes cloud get ${detail.render_id}`
|
|
1467
|
+
);
|
|
1468
|
+
failCommand();
|
|
1469
|
+
}
|
|
1470
|
+
function resolveOutputPath(output, renderId, format) {
|
|
1471
|
+
if (output) {
|
|
1472
|
+
return isAbsolute(output) ? output : resolvePath(process.cwd(), output);
|
|
1473
|
+
}
|
|
1474
|
+
const ext = FORMAT_EXT[format] ?? `.${format}`;
|
|
1475
|
+
return resolvePath(process.cwd(), "renders", `${renderId}${ext}`);
|
|
1476
|
+
}
|
|
1477
|
+
async function streamVideo(url, destPath, asJson) {
|
|
1478
|
+
if (!asJson) {
|
|
1479
|
+
console.log("");
|
|
1480
|
+
console.log(`${c.accent("\u25C6")} Downloading to ${c.accent(destPath)}`);
|
|
1481
|
+
}
|
|
1482
|
+
try {
|
|
1483
|
+
const result = await downloadToFile(url, destPath);
|
|
1484
|
+
if (!asJson) {
|
|
1485
|
+
console.log(c.dim(` ${formatBytes(result.bytes)} written`));
|
|
1486
|
+
}
|
|
1487
|
+
return { bytes: result.bytes };
|
|
1488
|
+
} catch (err) {
|
|
1489
|
+
const message = normalizeErrorMessage(err);
|
|
1490
|
+
errorBox(
|
|
1491
|
+
"Download failed",
|
|
1492
|
+
message,
|
|
1493
|
+
"The presigned URL is short-lived; re-fetch with `hyperframes cloud get`."
|
|
1494
|
+
);
|
|
1495
|
+
failCommand();
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
export {
|
|
1499
|
+
assertArchiveFits,
|
|
1500
|
+
render_default as default,
|
|
1501
|
+
examples,
|
|
1502
|
+
resolveAspectRatioForSubmit,
|
|
1503
|
+
validateDryRunSource
|
|
1504
|
+
};
|