@higgsfield/video-editor-mcp 1.0.7 → 1.1.1-dev.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/THIRD_PARTY_NOTICES.md +24 -24
- package/dist/cli.mjs +134 -125
- package/dist/index.html +136 -133
- package/dist/launcher.mjs +90 -90
- package/dist/server.mjs +1553 -1389
- package/node_modules/@higgsfield/higgsedit/README.md +181 -1
- package/node_modules/@higgsfield/higgsedit/dist/capture-page.js +902 -314
- package/node_modules/@higgsfield/higgsedit/dist/chunk-2JYLVSVV.mjs +1594 -0
- package/node_modules/@higgsfield/higgsedit/dist/chunk-2WKNH7OL.mjs +16 -0
- package/node_modules/@higgsfield/higgsedit/dist/{chunk-OMKZ5BXM.mjs → chunk-5LTTL3Q3.mjs} +1 -1
- package/node_modules/@higgsfield/higgsedit/dist/chunk-7PYXSJS7.mjs +717 -0
- package/node_modules/@higgsfield/higgsedit/dist/{chunk-MLJAIZGP.mjs → chunk-BH2QF635.mjs} +33 -326
- package/node_modules/@higgsfield/higgsedit/dist/{chunk-WZR5PZM7.mjs → chunk-BV5COOQZ.mjs} +250 -161
- package/node_modules/@higgsfield/higgsedit/dist/{chunk-HTCYVKFX.mjs → chunk-C3GSB3XZ.mjs} +8 -7
- package/node_modules/@higgsfield/higgsedit/dist/{chunk-CYH2EUQV.mjs → chunk-DVN4LUXV.mjs} +13 -4
- package/node_modules/@higgsfield/higgsedit/dist/chunk-F4CMIG5G.mjs +36 -0
- package/node_modules/@higgsfield/higgsedit/dist/chunk-IEPPEDEU.mjs +33 -0
- package/node_modules/@higgsfield/higgsedit/dist/{chunk-UVAZ6I6U.mjs → chunk-KIWGVAPJ.mjs} +16 -12
- package/node_modules/@higgsfield/higgsedit/dist/{chunk-L6QDNSL3.mjs → chunk-L5LWBA6A.mjs} +5 -5
- package/node_modules/@higgsfield/higgsedit/dist/chunk-LMQR3DHT.mjs +37 -0
- package/node_modules/@higgsfield/higgsedit/dist/chunk-LTKMGZ6T.mjs +58 -0
- package/node_modules/@higgsfield/higgsedit/dist/{chunk-VI6IV6JP.mjs → chunk-MV2JTTBQ.mjs} +65 -47
- package/node_modules/@higgsfield/higgsedit/dist/{chunk-4D6AXU4B.mjs → chunk-NARTAWRN.mjs} +50 -30
- package/node_modules/@higgsfield/higgsedit/dist/{chunk-27NBQ5FA.mjs → chunk-NUXTKQNM.mjs} +33 -21
- package/node_modules/@higgsfield/higgsedit/dist/chunk-OCIZ36XD.mjs +40 -0
- package/node_modules/@higgsfield/higgsedit/dist/{chunk-MK6PL7N4.mjs → chunk-OF6RMSM2.mjs} +19 -17
- package/node_modules/@higgsfield/higgsedit/dist/{chunk-6BETMESC.mjs → chunk-P2BGHTRR.mjs} +2 -2
- package/node_modules/@higgsfield/higgsedit/dist/{chunk-EKFC4S4M.mjs → chunk-PAHMD6SU.mjs} +5 -0
- package/node_modules/@higgsfield/higgsedit/dist/{chunk-QVLAEV3U.mjs → chunk-PFQYUJQL.mjs} +3 -4
- package/node_modules/@higgsfield/higgsedit/dist/chunk-QJTILFBS.mjs +335 -0
- package/node_modules/@higgsfield/higgsedit/dist/chunk-QQ3GGKDN.mjs +21 -0
- package/node_modules/@higgsfield/higgsedit/dist/chunk-TH7TK67K.mjs +23 -0
- package/node_modules/@higgsfield/higgsedit/dist/{chunk-SIMO6I3D.mjs → chunk-UDL5AHKO.mjs} +163 -158
- package/node_modules/@higgsfield/higgsedit/dist/chunk-UMWOUQN3.mjs +60 -0
- package/node_modules/@higgsfield/higgsedit/dist/{chunk-3AIM3THH.mjs → chunk-VLM5EOWJ.mjs} +4 -4
- package/node_modules/@higgsfield/higgsedit/dist/chunk-WENFTPXQ.mjs +403 -0
- package/node_modules/@higgsfield/higgsedit/dist/chunk-YLVXQIPX.mjs +464 -0
- package/node_modules/@higgsfield/higgsedit/dist/editor/.vite/manifest.json +559 -506
- package/node_modules/@higgsfield/higgsedit/dist/editor/assets/agent-corner-bottom-q4hZltI-.svg +431 -0
- package/node_modules/@higgsfield/higgsedit/dist/editor/assets/agent-corner-top-BJYI-I24.svg +431 -0
- package/node_modules/@higgsfield/higgsedit/dist/editor/assets/confirmation-BTnwl3LS.css +1 -0
- package/node_modules/@higgsfield/higgsedit/dist/editor/assets/drag-Bt_t4HZO.css +1 -0
- package/node_modules/@higgsfield/higgsedit/dist/editor/assets/local-B3OTAV_A.css +1 -0
- package/node_modules/@higgsfield/higgsedit/dist/editor/assets/menubar-2Y1Y2XJt.css +1 -0
- package/node_modules/@higgsfield/higgsedit/dist/editor/assets/model-DdE4hCgf.css +1 -0
- package/node_modules/@higgsfield/higgsedit/dist/editor/assets/{IconText1-CzbCrP7v.css → notifications-B08L1jSe.css} +1 -1
- package/node_modules/@higgsfield/higgsedit/dist/editor/assets/{panel-g852pZf_.css → panel-BRN_AbBM.css} +1 -1
- package/node_modules/@higgsfield/higgsedit/dist/editor/assets/panel-CYELNqrv.css +1 -0
- package/node_modules/@higgsfield/higgsedit/dist/editor/assets/panel-DYLpT6oA.css +1 -0
- package/node_modules/@higgsfield/higgsedit/dist/editor/assets/panel-v84HaGTZ.css +1 -0
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/IconChainLink1-Bxb1Or0F.js +1 -0
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/{IconCheckmark2Medium-daaES9uF.js → IconCheckmark2Medium-hegaz9mQ.js} +1 -1
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/{IconChevronDownSmall-BhTBJk26.js → IconChevronDownSmall-DaruzJwU.js} +1 -1
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/IconClosedCaptioning-BPqcqKsD.js +1 -0
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/{IconCrossMedium-acfnCXOM.js → IconCrossMedium-BsPFJzc6.js} +1 -1
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/IconOpenaiFilled-D1fzthmi.js +1 -0
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/{IconPause-pTeVSvUe.js → IconPause-ClNg8pao.js} +1 -1
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/{IconPlayFilled-CwdgIuDG.js → IconPlayFilled-CHb-7GSx.js} +1 -1
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/IconSparklesSoftFilled-o7o33KRn.js +1 -0
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/ai-composer-B9h-eaki.js +567 -0
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/auto-layout-BO20Tg4B.js +1 -0
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/{button-CjG6GcgF.js → button-Cg0U0zpV.js} +1 -1
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/canvas-Bw4CVzfw.js +1 -0
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/capabilities-BCd4-zPF.js +1 -0
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/commands-QeSqAbCt.js +1 -0
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/components-tXMtIZNL.js +51 -0
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/confirmation-CO2PHR_z.js +1 -0
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/dom-6aoQn29n.js +1 -0
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/drag-Dw8b_vO2.js +1 -0
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/{editing-CjWbiCxv.js → editing-Cy-SaOyB.js} +1 -1
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/edits-CqLBqir0.js +1 -0
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/export-dialog-BjBskOe3.js +2 -0
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/factories-Bw_iQrUL.js +1 -0
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/id3-CSR0NCRI.js +2 -0
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/import-DP49i4ke.js +1 -0
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/import-error-CMz9sLPe.js +1 -0
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/ingest-DsmIc415.js +1 -0
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/jsx-runtime-B5yqYJvp.js +1 -0
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/{media-Bwjxu-0U.js → media-DQw6vU_T.js} +1 -1
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/menubar-D31srALX.js +1 -0
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/meters-D0nZVEJs.js +1 -0
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/mode-DEG6doKF.js +1 -0
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/model-Duf9fszJ.js +2 -0
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/model-DyAkLA69.js +1 -0
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/notifications-DqWZNIrK.js +9 -0
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/palette-HT83gWgL.js +1 -0
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/panel-B5Mvm6dg.js +7 -0
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/panel-BC90wyvd.js +1 -0
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/panel-Bw_ZJ2JI.js +3 -0
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/panel-DNS060Nt.js +3 -0
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/panel-DSgxIrEq.js +2 -0
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/panel-DgBttE5O.js +1 -0
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/panel-assets-_T2bHsHW.js +771 -0
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/panel-inspector-DfHvAt5X.js +2 -0
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/panel-nBZvHlkA.js +1 -0
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/panel-timeline-DsCpJVSt.js +327 -0
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/panel-viewport-CEhZwZ67.js +280 -0
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/projects-2_tW9Do7.js +1 -0
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/react-dom-1uB7Vbbb.js +1 -0
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/schemas-CddYAOcj.js +64 -0
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/session-BpFeKAI2.js +1 -0
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/src-BLG3tYBa.js +2 -0
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/styles-e2n2A_JN.js +284 -0
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/supercomputer-CuV0zYKd.js +24 -0
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/tabs-XeAgs9cs.js +1 -0
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/toggle-DbNEaSIl.js +1 -0
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/transport-C8Ll3qFl.js +69 -0
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/transport-bar-Dv1LPFbu.js +1 -0
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/useOpenInteractionType-HWSfSsp2.js +1 -0
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/useRenderElement-D4SOg3N_.js +1 -0
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/value-EB7YmAdS.js +1 -0
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/writeback-support-fj5rA9k3.js +1 -0
- package/node_modules/@higgsfield/higgsedit/dist/editor/editor-B-WAkLt7.js +372 -0
- package/node_modules/@higgsfield/higgsedit/dist/higgsedit.mjs +39 -24
- package/node_modules/@higgsfield/higgsedit/dist/jsx/hooks.d.ts +26 -0
- package/node_modules/@higgsfield/higgsedit/dist/jsx/index.d.ts +17 -0
- package/node_modules/@higgsfield/higgsedit/dist/jsx/inspect.d.ts +42 -0
- package/node_modules/@higgsfield/higgsedit/dist/jsx/jsx-runtime.d.ts +28 -0
- package/node_modules/@higgsfield/higgsedit/dist/jsx/svg.d.ts +9 -0
- package/node_modules/@higgsfield/higgsedit/dist/jsx/time.d.ts +6 -0
- package/node_modules/@higgsfield/higgsedit/dist/jsx/types.d.ts +420 -0
- package/node_modules/@higgsfield/higgsedit/package.json +28 -20
- package/package.json +4 -3
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/plugin.json +1 -1
- package/plugin/skills/video-editing/references/caption-titling.md +1 -1
- package/plugin/skills/video-editing/references/local-setup.md +3 -3
- package/node_modules/@higgsfield/higgsedit/dist/chunk-C6SASQHE.mjs +0 -424
- package/node_modules/@higgsfield/higgsedit/dist/chunk-FZ5U36SQ.mjs +0 -83
- package/node_modules/@higgsfield/higgsedit/dist/chunk-QLPCYN6K.mjs +0 -292
- package/node_modules/@higgsfield/higgsedit/dist/editor/assets/edit-drag-D2EV5ddd.css +0 -1
- package/node_modules/@higgsfield/higgsedit/dist/editor/assets/editor-CuRHT75g.css +0 -1
- package/node_modules/@higgsfield/higgsedit/dist/editor/assets/panel-DnuQD7ja.css +0 -1
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/FieldControl-B0fenhBJ.js +0 -2
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/IconChainLink1-Cwv1X7hi.js +0 -1
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/IconEyeOpen-BqTT9_VV.js +0 -1
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/IconEyeSlash-Whv58c1m.js +0 -1
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/IconPencil-Dr17k_7e.js +0 -1
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/IconPlay-BroH3ziH.js +0 -1
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/IconPlusSmall-43T9aE3d.js +0 -1
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/IconText1-BaP_p9DW.js +0 -1
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/IconTrashCan-DX2w2AIC.js +0 -1
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/ai-composer-C-l0eIdP.js +0 -560
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/captions-Cd-bDG3G.js +0 -1
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/clip-highlight-DpAvcA4x.js +0 -1
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/components-e3ZbUVns.js +0 -1
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/disposables-CwUPjcuA.js +0 -1
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/drag-HLBkto5o.js +0 -1
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/dropdown-BJ04G_X3.js +0 -1
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/edit-drag-Bg6LggKC.js +0 -1
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/edits-a2IixhrN.js +0 -1
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/export-dialog-4I4gqI5V.js +0 -2
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/face-tracker-DzVfjVOO.js +0 -1
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/fit-imported-media-CaCB0x0b.js +0 -1
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/generation-queue-DuL2F4-_.js +0 -1
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/import-error-CMARA0Hy.js +0 -1
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/jsx-runtime-B-hcVAMW.js +0 -1
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/menubar-Bs26OdwP.js +0 -1
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/meters-YEr9OQjq.js +0 -1
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/paints-DS2Msf91.js +0 -67
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/palette-CcS9IcAC.js +0 -1
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/panel-7M3uQokX.js +0 -1
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/panel-BBK1NyG-.js +0 -1
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/panel-CHZTs7By.js +0 -4
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/panel-DaSOa3MP.js +0 -2
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/panel-DlK3RYVo.js +0 -1
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/panel-Dst5OKWu.js +0 -2
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/panel-H_ZoDiKK.js +0 -2
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/panel-Rn9Z7Eek.js +0 -3
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/panel-assets-FAghGFG3.js +0 -678
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/panel-inspector-DEqULBNF.js +0 -260
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/panel-timeline-0GYNz1uc.js +0 -333
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/panel-viewport-DOWjJi5-.js +0 -317
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/projects-BBJc7h5_.js +0 -2
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/session-B9sZaFKC.js +0 -1
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/supercomputer-CX-LCL6Y.js +0 -24
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/tabs-CvKQ-eas.js +0 -1
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/toggle-CrzrwiDZ.js +0 -1
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/transport-BgRfk-Qq.js +0 -69
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/transport-bar-C9nqAfE2.js +0 -1
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/typography-C5-w9LOu-kD3uLKkf.js +0 -1
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/useOpenInteractionType-DbrQ6Ehu.js +0 -1
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/useRenderElement-bbxXdYq0.js +0 -1
- package/node_modules/@higgsfield/higgsedit/dist/editor/chunks/writeback-support-BZfNpkmB.js +0 -1
- package/node_modules/@higgsfield/higgsedit/dist/editor/editor-Csn2_T_e.js +0 -293
- package/node_modules/@higgsfield/higgsedit/dist/export-settings.mjs +0 -75
- package/node_modules/@higgsfield/higgsedit/dist/history.mjs +0 -295
- package/node_modules/@higgsfield/higgsedit/dist/media-upload.mjs +0 -381
- package/node_modules/@higgsfield/higgsedit/dist/proxy.mjs +0 -170
|
@@ -1,381 +0,0 @@
|
|
|
1
|
-
import { randomUUID } from "node:crypto";
|
|
2
|
-
import {
|
|
3
|
-
constants,
|
|
4
|
-
linkSync,
|
|
5
|
-
lstatSync,
|
|
6
|
-
mkdirSync,
|
|
7
|
-
mkdtempSync,
|
|
8
|
-
realpathSync,
|
|
9
|
-
readdirSync,
|
|
10
|
-
renameSync,
|
|
11
|
-
rmdirSync,
|
|
12
|
-
statSync
|
|
13
|
-
} from "node:fs";
|
|
14
|
-
import { open, rm } from "node:fs/promises";
|
|
15
|
-
import { extname, join } from "node:path";
|
|
16
|
-
const json = (res, status, body) => {
|
|
17
|
-
res.statusCode = status;
|
|
18
|
-
res.setHeader("content-type", "application/json");
|
|
19
|
-
res.end(JSON.stringify(body));
|
|
20
|
-
};
|
|
21
|
-
const MEDIA_UPLOAD_PART_BYTES = 2 << 20;
|
|
22
|
-
const MEDIA_UPLOAD_TEMP_PREFIX = ".higgsedit-upload-";
|
|
23
|
-
function isPrivateMediaUploadPath(path) {
|
|
24
|
-
return path.split(/[/\\]/).some((part) => part.toUpperCase().startsWith(MEDIA_UPLOAD_TEMP_PREFIX.toUpperCase()));
|
|
25
|
-
}
|
|
26
|
-
const defaults = {
|
|
27
|
-
maxBytes: 8 * 1024 ** 3,
|
|
28
|
-
idleMs: 6e4,
|
|
29
|
-
totalMs: 30 * 6e4,
|
|
30
|
-
maxConcurrent: 4
|
|
31
|
-
};
|
|
32
|
-
const envKeys = {
|
|
33
|
-
maxBytes: "HIGGSEDIT_MEDIA_MAX_BYTES",
|
|
34
|
-
idleMs: "HIGGSEDIT_MEDIA_IDLE_MS",
|
|
35
|
-
totalMs: "HIGGSEDIT_MEDIA_TOTAL_MS",
|
|
36
|
-
maxConcurrent: "HIGGSEDIT_MEDIA_MAX_CONCURRENT"
|
|
37
|
-
};
|
|
38
|
-
class UploadError extends Error {
|
|
39
|
-
constructor(message, status) {
|
|
40
|
-
super(message);
|
|
41
|
-
this.status = status;
|
|
42
|
-
}
|
|
43
|
-
status;
|
|
44
|
-
}
|
|
45
|
-
class MediaUploads {
|
|
46
|
-
active = /* @__PURE__ */ new Map();
|
|
47
|
-
root;
|
|
48
|
-
rootIdentity;
|
|
49
|
-
closed = false;
|
|
50
|
-
limits;
|
|
51
|
-
constructor(dir, limits = {}) {
|
|
52
|
-
this.root = realpathSync(dir);
|
|
53
|
-
this.rootIdentity = lstatSync(this.root);
|
|
54
|
-
this.limits = { ...defaults };
|
|
55
|
-
for (const key of Object.keys(defaults)) {
|
|
56
|
-
const value = limits[key] ?? (process.env[envKeys[key]] ? Number(process.env[envKeys[key]]) : defaults[key]);
|
|
57
|
-
if (!Number.isSafeInteger(value) || value <= 0)
|
|
58
|
-
throw new Error(`${envKeys[key]} must be a positive safe integer`);
|
|
59
|
-
this.limits[key] = value;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
get busy() {
|
|
63
|
-
return this.active.size > 0;
|
|
64
|
-
}
|
|
65
|
-
async close() {
|
|
66
|
-
this.closed = true;
|
|
67
|
-
await Promise.all(
|
|
68
|
-
[...this.active.values()].map((u) => this.dispose(u, new UploadError("editor closed", 503)))
|
|
69
|
-
);
|
|
70
|
-
}
|
|
71
|
-
checkMedia() {
|
|
72
|
-
const root = lstatSync(this.root);
|
|
73
|
-
if (root.isSymbolicLink() || root.dev !== this.rootIdentity.dev || root.ino !== this.rootIdentity.ino)
|
|
74
|
-
throw new UploadError("project directory changed during upload", 400);
|
|
75
|
-
const media = join(this.root, "media");
|
|
76
|
-
mkdirSync(media, { recursive: true });
|
|
77
|
-
const st = lstatSync(media);
|
|
78
|
-
if (!st.isDirectory() || st.isSymbolicLink() || realpathSync(media) !== media)
|
|
79
|
-
throw new UploadError("media must be a real project directory, not a symlink", 400);
|
|
80
|
-
return media;
|
|
81
|
-
}
|
|
82
|
-
async begin(name) {
|
|
83
|
-
if (!name || name.startsWith(".") || /[/\\]/.test(name) || [...name].some((c) => c.charCodeAt(0) < 32 || c.charCodeAt(0) === 127) || Buffer.byteLength(name) > 240)
|
|
84
|
-
throw new UploadError("invalid media filename", 400);
|
|
85
|
-
if (this.closed) throw new UploadError("editor closed", 503);
|
|
86
|
-
if (this.active.size >= this.limits.maxConcurrent)
|
|
87
|
-
throw new UploadError("too many active media uploads; try again after one finishes", 429);
|
|
88
|
-
const directory = lstatSync(this.checkMedia());
|
|
89
|
-
const temp = mkdtempSync(join(this.checkMedia(), MEDIA_UPLOAD_TEMP_PREFIX));
|
|
90
|
-
const u = {
|
|
91
|
-
id: randomUUID(),
|
|
92
|
-
name,
|
|
93
|
-
temp,
|
|
94
|
-
directory,
|
|
95
|
-
staging: lstatSync(temp),
|
|
96
|
-
bytes: 0,
|
|
97
|
-
busy: true,
|
|
98
|
-
abort: new AbortController()
|
|
99
|
-
};
|
|
100
|
-
this.active.set(u.id, u);
|
|
101
|
-
try {
|
|
102
|
-
u.file = await open(
|
|
103
|
-
join(temp, "body"),
|
|
104
|
-
constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY | constants.O_NOFOLLOW,
|
|
105
|
-
384
|
|
106
|
-
);
|
|
107
|
-
if (u.abort.signal.aborted) {
|
|
108
|
-
await u.file.close();
|
|
109
|
-
u.abort.signal.throwIfAborted();
|
|
110
|
-
}
|
|
111
|
-
u.total = setTimeout(() => {
|
|
112
|
-
void this.dispose(u, new UploadError("media upload exceeded its total time limit", 408));
|
|
113
|
-
}, this.limits.totalMs);
|
|
114
|
-
u.total.unref();
|
|
115
|
-
u.busy = false;
|
|
116
|
-
this.touch(u);
|
|
117
|
-
return u;
|
|
118
|
-
} catch (error) {
|
|
119
|
-
await this.dispose(u, error);
|
|
120
|
-
throw error;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
touch(u) {
|
|
124
|
-
clearTimeout(u.idle);
|
|
125
|
-
u.idle = setTimeout(() => {
|
|
126
|
-
void this.dispose(u, new UploadError("media upload stalled", 408));
|
|
127
|
-
}, this.limits.idleMs);
|
|
128
|
-
u.idle.unref();
|
|
129
|
-
}
|
|
130
|
-
dispose(u, reason = new UploadError("media upload cancelled", 499)) {
|
|
131
|
-
if (u.cleanup) return u.cleanup;
|
|
132
|
-
u.abort.abort(reason);
|
|
133
|
-
clearTimeout(u.idle);
|
|
134
|
-
clearTimeout(u.total);
|
|
135
|
-
u.cleanup = (async () => {
|
|
136
|
-
let failure;
|
|
137
|
-
try {
|
|
138
|
-
await u.file?.close();
|
|
139
|
-
} catch (error) {
|
|
140
|
-
failure = error;
|
|
141
|
-
}
|
|
142
|
-
try {
|
|
143
|
-
let current;
|
|
144
|
-
try {
|
|
145
|
-
current = lstatSync(u.temp);
|
|
146
|
-
} catch (error) {
|
|
147
|
-
if (error.code !== "ENOENT") throw error;
|
|
148
|
-
}
|
|
149
|
-
if (current?.isDirectory() && !current.isSymbolicLink() && current.dev === u.staging.dev && current.ino === u.staging.ino)
|
|
150
|
-
await rm(u.temp, { recursive: true, force: true });
|
|
151
|
-
} catch (error) {
|
|
152
|
-
failure ??= error;
|
|
153
|
-
}
|
|
154
|
-
this.active.delete(u.id);
|
|
155
|
-
if (failure) throw failure;
|
|
156
|
-
})();
|
|
157
|
-
void u.cleanup.catch(() => {
|
|
158
|
-
});
|
|
159
|
-
return u.cleanup;
|
|
160
|
-
}
|
|
161
|
-
declared(req, maximum) {
|
|
162
|
-
const length = req.headers["content-length"];
|
|
163
|
-
if (length !== void 0 && (!/^\d+$/.test(length) || !Number.isSafeInteger(Number(length)) || Number(length) > maximum)) {
|
|
164
|
-
throw new UploadError(`media upload exceeds the ${maximum} byte limit`, 413);
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
async append(u, req, maxPart) {
|
|
168
|
-
this.declared(req, Math.min(maxPart, this.limits.maxBytes - u.bytes));
|
|
169
|
-
let partBytes = 0;
|
|
170
|
-
const signal = u.abort.signal;
|
|
171
|
-
while (true) {
|
|
172
|
-
signal.throwIfAborted();
|
|
173
|
-
if (req.aborted || req.destroyed && !req.complete)
|
|
174
|
-
throw new UploadError("media upload disconnected", 499);
|
|
175
|
-
const bytes = req.read();
|
|
176
|
-
if (bytes === null) {
|
|
177
|
-
if (req.readableEnded) break;
|
|
178
|
-
await new Promise((resolve, reject) => {
|
|
179
|
-
const clean = () => {
|
|
180
|
-
req.off("readable", ready);
|
|
181
|
-
req.off("end", ready);
|
|
182
|
-
req.off("error", fail);
|
|
183
|
-
req.off("aborted", disconnected);
|
|
184
|
-
signal.removeEventListener("abort", aborted);
|
|
185
|
-
};
|
|
186
|
-
const ready = () => {
|
|
187
|
-
clean();
|
|
188
|
-
resolve();
|
|
189
|
-
};
|
|
190
|
-
const fail = (error) => {
|
|
191
|
-
clean();
|
|
192
|
-
reject(error);
|
|
193
|
-
};
|
|
194
|
-
const disconnected = () => fail(new UploadError("media upload disconnected", 499));
|
|
195
|
-
const aborted = () => fail(signal.reason);
|
|
196
|
-
req.once("readable", ready);
|
|
197
|
-
req.once("end", ready);
|
|
198
|
-
req.once("error", fail);
|
|
199
|
-
req.once("aborted", disconnected);
|
|
200
|
-
signal.addEventListener("abort", aborted, { once: true });
|
|
201
|
-
if (signal.aborted) aborted();
|
|
202
|
-
});
|
|
203
|
-
continue;
|
|
204
|
-
}
|
|
205
|
-
partBytes += bytes.byteLength;
|
|
206
|
-
if (partBytes > maxPart || u.bytes + bytes.byteLength > this.limits.maxBytes)
|
|
207
|
-
throw new UploadError(`media upload exceeds the ${this.limits.maxBytes} byte limit`, 413);
|
|
208
|
-
this.touch(u);
|
|
209
|
-
for (let offset = 0; offset < bytes.byteLength; ) {
|
|
210
|
-
signal.throwIfAborted();
|
|
211
|
-
const { bytesWritten } = await u.file.write(bytes, offset, bytes.byteLength - offset);
|
|
212
|
-
if (!bytesWritten) throw new Error("could not write media upload");
|
|
213
|
-
offset += bytesWritten;
|
|
214
|
-
}
|
|
215
|
-
u.bytes += bytes.byteLength;
|
|
216
|
-
this.touch(u);
|
|
217
|
-
}
|
|
218
|
-
signal.throwIfAborted();
|
|
219
|
-
}
|
|
220
|
-
async publish(u) {
|
|
221
|
-
await u.file.sync();
|
|
222
|
-
u.abort.signal.throwIfAborted();
|
|
223
|
-
const written = await u.file.stat();
|
|
224
|
-
u.abort.signal.throwIfAborted();
|
|
225
|
-
const media = this.checkMedia();
|
|
226
|
-
const directory = lstatSync(media);
|
|
227
|
-
const staging = lstatSync(u.temp);
|
|
228
|
-
const current = lstatSync(join(u.temp, "body"));
|
|
229
|
-
if (directory.dev !== u.directory.dev || directory.ino !== u.directory.ino || staging.isSymbolicLink() || staging.dev !== u.staging.dev || staging.ino !== u.staging.ino || !current.isFile() || current.isSymbolicLink() || current.dev !== written.dev || current.ino !== written.ino || current.size !== u.bytes)
|
|
230
|
-
throw new UploadError("media upload destination changed", 409);
|
|
231
|
-
const ext = extname(u.name);
|
|
232
|
-
const stem = u.name.slice(0, u.name.length - ext.length);
|
|
233
|
-
for (let suffix = 1; suffix <= 1e4; suffix++) {
|
|
234
|
-
const name = suffix === 1 ? u.name : `${stem}-${suffix}${ext}`;
|
|
235
|
-
try {
|
|
236
|
-
linkSync(join(u.temp, "body"), join(media, name));
|
|
237
|
-
} catch (error) {
|
|
238
|
-
const code = error.code;
|
|
239
|
-
if (code === "EEXIST") continue;
|
|
240
|
-
if (!["ENOTSUP", "EOPNOTSUPP", "EPERM", "EXDEV", "ENOSYS"].includes(code ?? ""))
|
|
241
|
-
throw error;
|
|
242
|
-
const reserved = mkdtempSync(join(media, "import-"));
|
|
243
|
-
try {
|
|
244
|
-
renameSync(join(u.temp, "body"), join(reserved, u.name));
|
|
245
|
-
} catch (failure) {
|
|
246
|
-
try {
|
|
247
|
-
rmdirSync(reserved);
|
|
248
|
-
} catch {
|
|
249
|
-
}
|
|
250
|
-
throw failure;
|
|
251
|
-
}
|
|
252
|
-
return { src: `./media/${reserved.slice(media.length + 1)}/${u.name}`, byteSize: u.bytes };
|
|
253
|
-
}
|
|
254
|
-
return { src: `./media/${name}`, byteSize: u.bytes };
|
|
255
|
-
}
|
|
256
|
-
throw new UploadError("too many media files with this name", 409);
|
|
257
|
-
}
|
|
258
|
-
async handle(req, res, url) {
|
|
259
|
-
if (isPrivateMediaUploadPath(decodeURIComponent(url.pathname))) {
|
|
260
|
-
json(res, 403, { error: "private upload" });
|
|
261
|
-
return true;
|
|
262
|
-
}
|
|
263
|
-
if (url.pathname === "/api/media/capabilities" && req.method === "GET") {
|
|
264
|
-
json(res, 200, {
|
|
265
|
-
streaming: true,
|
|
266
|
-
version: 1,
|
|
267
|
-
maxBytes: this.limits.maxBytes,
|
|
268
|
-
partBytes: MEDIA_UPLOAD_PART_BYTES,
|
|
269
|
-
maxConcurrent: this.limits.maxConcurrent
|
|
270
|
-
});
|
|
271
|
-
return true;
|
|
272
|
-
}
|
|
273
|
-
const direct = (url.pathname === "/api/media" || url.pathname === "/api/media/stream") && req.method === "POST";
|
|
274
|
-
const begin = url.pathname === "/api/media/uploads" && req.method === "POST";
|
|
275
|
-
const part = /^\/api\/media\/uploads\/([a-f0-9-]{36})$/.exec(url.pathname);
|
|
276
|
-
if (!direct && !begin && !part) return false;
|
|
277
|
-
let u;
|
|
278
|
-
let owns = false;
|
|
279
|
-
const disconnected = () => {
|
|
280
|
-
if (u && !res.writableFinished)
|
|
281
|
-
void this.dispose(u, new UploadError("media upload disconnected", 499));
|
|
282
|
-
};
|
|
283
|
-
res.once("close", disconnected);
|
|
284
|
-
try {
|
|
285
|
-
if (direct || begin) {
|
|
286
|
-
this.declared(req, direct ? this.limits.maxBytes : 0);
|
|
287
|
-
if (begin && req.headers["transfer-encoding"])
|
|
288
|
-
throw new UploadError("upload session creation takes no body", 400);
|
|
289
|
-
u = await this.begin(url.searchParams.get("name") ?? "");
|
|
290
|
-
if (res.destroyed) throw new UploadError("media upload disconnected", 499);
|
|
291
|
-
if (begin) {
|
|
292
|
-
json(res, 201, {
|
|
293
|
-
id: u.id,
|
|
294
|
-
maxBytes: this.limits.maxBytes,
|
|
295
|
-
partBytes: MEDIA_UPLOAD_PART_BYTES
|
|
296
|
-
});
|
|
297
|
-
return true;
|
|
298
|
-
}
|
|
299
|
-
} else {
|
|
300
|
-
u = this.active.get(part[1]);
|
|
301
|
-
if (req.method !== "PUT") {
|
|
302
|
-
this.declared(req, 0);
|
|
303
|
-
if (req.headers["transfer-encoding"])
|
|
304
|
-
throw new UploadError("upload finalization and cancellation take no body", 400);
|
|
305
|
-
}
|
|
306
|
-
if (!u) throw new UploadError("media upload not found or expired", 404);
|
|
307
|
-
if (req.method === "DELETE") {
|
|
308
|
-
await this.dispose(u);
|
|
309
|
-
json(res, 200, { ok: true });
|
|
310
|
-
return true;
|
|
311
|
-
}
|
|
312
|
-
if (u.busy) throw new UploadError("media upload already has a request in progress", 409);
|
|
313
|
-
if (req.method !== "PUT" && req.method !== "POST")
|
|
314
|
-
throw new UploadError("unsupported upload method", 405);
|
|
315
|
-
if (req.method === "PUT" && (!/^\d+$/.test(url.searchParams.get("offset") ?? "") || Number(url.searchParams.get("offset")) !== u.bytes))
|
|
316
|
-
throw new UploadError("media upload offset does not match", 409);
|
|
317
|
-
}
|
|
318
|
-
u.busy = true;
|
|
319
|
-
owns = true;
|
|
320
|
-
if (direct || req.method === "PUT")
|
|
321
|
-
await this.append(u, req, direct ? this.limits.maxBytes : MEDIA_UPLOAD_PART_BYTES);
|
|
322
|
-
if (direct || req.method === "POST") {
|
|
323
|
-
const result = await this.publish(u);
|
|
324
|
-
await this.dispose(u);
|
|
325
|
-
json(res, 200, result);
|
|
326
|
-
} else {
|
|
327
|
-
u.busy = false;
|
|
328
|
-
this.touch(u);
|
|
329
|
-
json(res, 200, { offset: u.bytes });
|
|
330
|
-
}
|
|
331
|
-
} catch (error) {
|
|
332
|
-
if (u && (owns || direct || begin)) await this.dispose(u, error);
|
|
333
|
-
if (!res.destroyed) {
|
|
334
|
-
if (!req.complete) {
|
|
335
|
-
res.setHeader("connection", "close");
|
|
336
|
-
res.once("finish", () => req.destroy());
|
|
337
|
-
}
|
|
338
|
-
json(res, error instanceof UploadError ? error.status : 500, {
|
|
339
|
-
error: error instanceof Error ? error.message : "media upload failed"
|
|
340
|
-
});
|
|
341
|
-
}
|
|
342
|
-
} finally {
|
|
343
|
-
res.off("close", disconnected);
|
|
344
|
-
}
|
|
345
|
-
return true;
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
function listMediaFiles(media) {
|
|
349
|
-
let entries;
|
|
350
|
-
try {
|
|
351
|
-
entries = readdirSync(media, { withFileTypes: true });
|
|
352
|
-
} catch (error) {
|
|
353
|
-
if (error.code === "ENOENT") return [];
|
|
354
|
-
throw error;
|
|
355
|
-
}
|
|
356
|
-
const files = [];
|
|
357
|
-
for (const entry of entries) {
|
|
358
|
-
if (entry.isFile() && !entry.name.startsWith("."))
|
|
359
|
-
files.push({
|
|
360
|
-
src: `./media/${entry.name}`,
|
|
361
|
-
byteSize: statSync(join(media, entry.name)).size
|
|
362
|
-
});
|
|
363
|
-
else if (entry.isDirectory() && /^import-[a-zA-Z0-9]{6}$/.test(entry.name)) {
|
|
364
|
-
for (const child of readdirSync(join(media, entry.name), { withFileTypes: true })) {
|
|
365
|
-
if (child.isFile() && !child.name.startsWith("."))
|
|
366
|
-
files.push({
|
|
367
|
-
src: `./media/${entry.name}/${child.name}`,
|
|
368
|
-
byteSize: statSync(join(media, entry.name, child.name)).size
|
|
369
|
-
});
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
return files.toSorted((a, b) => a.src.localeCompare(b.src));
|
|
374
|
-
}
|
|
375
|
-
export {
|
|
376
|
-
MEDIA_UPLOAD_PART_BYTES,
|
|
377
|
-
MEDIA_UPLOAD_TEMP_PREFIX,
|
|
378
|
-
MediaUploads,
|
|
379
|
-
isPrivateMediaUploadPath,
|
|
380
|
-
listMediaFiles
|
|
381
|
-
};
|
|
@@ -1,170 +0,0 @@
|
|
|
1
|
-
import { execFile } from "node:child_process";
|
|
2
|
-
import { createHash, randomUUID } from "node:crypto";
|
|
3
|
-
import { existsSync, mkdirSync, renameSync, rmSync, statSync } from "node:fs";
|
|
4
|
-
import { join } from "node:path";
|
|
5
|
-
import { promisify } from "node:util";
|
|
6
|
-
import { ffmpeg, requireTool } from "./chunk-EKFC4S4M.mjs";
|
|
7
|
-
const run = promisify(execFile);
|
|
8
|
-
const VIDEO = /\.(mp4|mov|m4v|mkv|webm)$/i;
|
|
9
|
-
const SKIP = /(^|\/)(node_modules|\.git|\.higgsedit-cache|\.higgsedit|renders|dist)(\/|$)/;
|
|
10
|
-
const LONG_SIDE = 1920;
|
|
11
|
-
const DECODABLE = /* @__PURE__ */ new Set(["h264", "hevc", "vp9", "av1"]);
|
|
12
|
-
const needsProxy = (p) => p.width * p.height > LONG_SIDE * 1088 || !DECODABLE.has(p.codec);
|
|
13
|
-
const even = (n) => Math.max(2, Math.round(n / 2) * 2);
|
|
14
|
-
const QUIET_MS = 400;
|
|
15
|
-
const TRACKED_LIMIT = 1024;
|
|
16
|
-
class Proxies {
|
|
17
|
-
constructor(dir, onReady, log = () => {
|
|
18
|
-
}) {
|
|
19
|
-
this.dir = dir;
|
|
20
|
-
this.onReady = onReady;
|
|
21
|
-
this.log = log;
|
|
22
|
-
this.root = join(dir, ".higgsedit-cache", "proxies");
|
|
23
|
-
}
|
|
24
|
-
dir;
|
|
25
|
-
onReady;
|
|
26
|
-
log;
|
|
27
|
-
root;
|
|
28
|
-
// Recent actual preview requests bound both pending work and cached no-proxy decisions.
|
|
29
|
-
candidates = /* @__PURE__ */ new Map();
|
|
30
|
-
active;
|
|
31
|
-
timer;
|
|
32
|
-
abort = new AbortController();
|
|
33
|
-
current;
|
|
34
|
-
stopped = false;
|
|
35
|
-
version(rel) {
|
|
36
|
-
try {
|
|
37
|
-
const st = statSync(join(this.dir, rel));
|
|
38
|
-
if (!st.isFile()) return void 0;
|
|
39
|
-
return { key: createHash("sha1").update(`${rel}\0${st.size}\0${st.mtimeMs}\0${st.ctimeMs}\0${st.ino}`).digest("hex").slice(0, 20), mtime: st.mtimeMs };
|
|
40
|
-
} catch {
|
|
41
|
-
return void 0;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
/** The finished proxy for a project-relative video path, if there is one. */
|
|
45
|
-
lookup(rel) {
|
|
46
|
-
const key = this.version(rel)?.key;
|
|
47
|
-
const file = key && join(this.root, `${key}.mp4`);
|
|
48
|
-
return file && existsSync(file) ? file : void 0;
|
|
49
|
-
}
|
|
50
|
-
/** Only /preview/ demand starts work; watcher events refresh previously requested footage. */
|
|
51
|
-
consider(rel, requested = false) {
|
|
52
|
-
if (this.stopped || !VIDEO.test(rel) || SKIP.test(rel)) return;
|
|
53
|
-
const prior = this.candidates.get(rel);
|
|
54
|
-
if (!requested && !prior) return;
|
|
55
|
-
const version = this.version(rel);
|
|
56
|
-
if (!version) {
|
|
57
|
-
this.candidates.delete(rel);
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
60
|
-
const now = Date.now();
|
|
61
|
-
const candidate = prior?.key === version.key ? prior : { ...version, readyAt: now + Math.min(QUIET_MS, Math.max(0, QUIET_MS - (now - version.mtime))), done: existsSync(join(this.root, `${version.key}.mp4`)) };
|
|
62
|
-
if (requested && candidate.retryAt !== void 0 && now >= candidate.retryAt) {
|
|
63
|
-
candidate.done = false;
|
|
64
|
-
candidate.retryAt = void 0;
|
|
65
|
-
}
|
|
66
|
-
if (requested) this.candidates.delete(rel);
|
|
67
|
-
this.candidates.set(rel, candidate);
|
|
68
|
-
while (this.candidates.size > TRACKED_LIMIT) {
|
|
69
|
-
for (const path of this.candidates.keys()) {
|
|
70
|
-
if (path !== this.active) {
|
|
71
|
-
this.candidates.delete(path);
|
|
72
|
-
break;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
this.schedule();
|
|
77
|
-
}
|
|
78
|
-
schedule() {
|
|
79
|
-
clearTimeout(this.timer);
|
|
80
|
-
this.timer = void 0;
|
|
81
|
-
if (this.stopped || this.active) return;
|
|
82
|
-
let next = Infinity;
|
|
83
|
-
for (const [rel, candidate] of this.candidates) {
|
|
84
|
-
if (candidate.done) continue;
|
|
85
|
-
if (candidate.readyAt > Date.now()) {
|
|
86
|
-
next = Math.min(next, candidate.readyAt);
|
|
87
|
-
continue;
|
|
88
|
-
}
|
|
89
|
-
const version = this.version(rel);
|
|
90
|
-
if (!version) {
|
|
91
|
-
this.candidates.delete(rel);
|
|
92
|
-
continue;
|
|
93
|
-
}
|
|
94
|
-
if (version.key !== candidate.key) {
|
|
95
|
-
Object.assign(candidate, version, { readyAt: Date.now() + QUIET_MS });
|
|
96
|
-
next = Math.min(next, candidate.readyAt);
|
|
97
|
-
continue;
|
|
98
|
-
}
|
|
99
|
-
this.active = rel;
|
|
100
|
-
void this.make(rel, candidate.key).then(() => {
|
|
101
|
-
if (this.candidates.get(rel)?.key === candidate.key) candidate.done = true;
|
|
102
|
-
}, (error) => {
|
|
103
|
-
if (this.stopped) return;
|
|
104
|
-
candidate.done = true;
|
|
105
|
-
candidate.retryAt = Date.now() + 5e3;
|
|
106
|
-
this.log(`proxy for ${rel} failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
107
|
-
}).finally(() => {
|
|
108
|
-
this.active = void 0;
|
|
109
|
-
this.consider(rel);
|
|
110
|
-
this.schedule();
|
|
111
|
-
});
|
|
112
|
-
return;
|
|
113
|
-
}
|
|
114
|
-
if (Number.isFinite(next)) this.timer = setTimeout(() => this.schedule(), Math.max(0, next - Date.now()));
|
|
115
|
-
}
|
|
116
|
-
async make(rel, key) {
|
|
117
|
-
const file = join(this.dir, rel);
|
|
118
|
-
if (this.lookup(rel)) return;
|
|
119
|
-
const { stdout } = await run(requireTool("ffprobe"), ["-v", "error", "-select_streams", "v:0", "-show_entries", "stream=width,height,codec_name,time_base", "-of", "json", file], { maxBuffer: 1 << 20, timeout: 3e4, signal: this.abort.signal });
|
|
120
|
-
if (this.stopped || this.version(rel)?.key !== key) return;
|
|
121
|
-
const data = JSON.parse(stdout);
|
|
122
|
-
const stream = data?.streams?.[0];
|
|
123
|
-
if (!stream || typeof stream.width !== "number" || stream.width <= 0 || typeof stream.height !== "number" || stream.height <= 0 || typeof stream.codec_name !== "string") throw new Error("invalid video stream metadata");
|
|
124
|
-
const facts = { width: stream.width, height: stream.height, codec: stream.codec_name };
|
|
125
|
-
if (!needsProxy(facts)) return;
|
|
126
|
-
const timescale = Number(String(stream.time_base).split("/")[1]) || 9e4;
|
|
127
|
-
const scale = LONG_SIDE / Math.max(facts.width, facts.height);
|
|
128
|
-
const [w, h] = scale < 1 ? [even(facts.width * scale), even(facts.height * scale)] : [even(facts.width), even(facts.height)];
|
|
129
|
-
mkdirSync(this.root, { recursive: true });
|
|
130
|
-
const out = join(this.root, `${key}.mp4`);
|
|
131
|
-
const part = join(this.root, `${key}.${randomUUID()}.part.mp4`);
|
|
132
|
-
const t0 = performance.now();
|
|
133
|
-
this.log(`making a preview proxy for ${rel} (${facts.width}\xD7${facts.height} \u2192 ${w}\xD7${h})\u2026`);
|
|
134
|
-
const args = ["-y", "-i", file, "-map", "0:v:0", "-map", "0:a:0?", "-vf", `scale=${w}:${h}`, "-c:v", "libx264", "-preset", "veryfast", "-crf", "20", "-bf", "0", "-g", "12", "-pix_fmt", "yuv420p"];
|
|
135
|
-
args.push("-fps_mode", "passthrough", "-video_track_timescale", String(timescale), "-c:a", "aac", "-b:a", "160k", "-movflags", "+faststart", part);
|
|
136
|
-
try {
|
|
137
|
-
await new Promise((resolve, reject) => {
|
|
138
|
-
const p = ffmpeg(args);
|
|
139
|
-
this.current = p;
|
|
140
|
-
let err = "";
|
|
141
|
-
p.stderr.on("data", (d) => {
|
|
142
|
-
err = (err + d.toString()).slice(-16384);
|
|
143
|
-
});
|
|
144
|
-
p.on("error", reject);
|
|
145
|
-
p.on("close", (code) => {
|
|
146
|
-
if (this.current === p) this.current = void 0;
|
|
147
|
-
if (code === 0) resolve();
|
|
148
|
-
else reject(new Error(err.trim() || `ffmpeg exited with ${code}`));
|
|
149
|
-
});
|
|
150
|
-
});
|
|
151
|
-
if (this.stopped || this.version(rel)?.key !== key) return;
|
|
152
|
-
renameSync(part, out);
|
|
153
|
-
this.log(`proxy for ${rel} ready in ${((performance.now() - t0) / 1e3).toFixed(1)} s`);
|
|
154
|
-
this.onReady(`./${rel}`);
|
|
155
|
-
} finally {
|
|
156
|
-
rmSync(part, { force: true });
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
stop() {
|
|
160
|
-
this.stopped = true;
|
|
161
|
-
clearTimeout(this.timer);
|
|
162
|
-
this.candidates.clear();
|
|
163
|
-
this.abort.abort();
|
|
164
|
-
this.current?.kill("SIGKILL");
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
export {
|
|
168
|
-
Proxies,
|
|
169
|
-
needsProxy
|
|
170
|
-
};
|