@hyperframes/studio-server 0.8.115 → 0.8.117
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/index.js +61 -50
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -591,6 +591,7 @@ import {
|
|
|
591
591
|
addMotionPathToScript,
|
|
592
592
|
removeArcPathFromScript,
|
|
593
593
|
addAnimationWithKeyframesToScript,
|
|
594
|
+
replaceTweenWithKeyframesInScript,
|
|
594
595
|
splitAnimationsInScript,
|
|
595
596
|
splitIntoPropertyGroupsFromScript,
|
|
596
597
|
shiftPositionsInScript,
|
|
@@ -1462,6 +1463,21 @@ function bakeVisibilityOnDelete(document, anim) {
|
|
|
1462
1463
|
} catch {
|
|
1463
1464
|
}
|
|
1464
1465
|
}
|
|
1466
|
+
function replaceInPlaceUnlessSetOrUnknown(scriptText, body) {
|
|
1467
|
+
const edit = { ...body, easeEach: resolveReplacementEaseEach(scriptText, body) };
|
|
1468
|
+
const inPlace = replaceTweenWithKeyframesInScript(scriptText, body.animationId, edit);
|
|
1469
|
+
if (inPlace !== null) return inPlace;
|
|
1470
|
+
const script = removeAnimationFromScript(scriptText, body.animationId);
|
|
1471
|
+
return addAnimationWithKeyframesToScript(
|
|
1472
|
+
script,
|
|
1473
|
+
body.targetSelector,
|
|
1474
|
+
body.position,
|
|
1475
|
+
body.duration,
|
|
1476
|
+
body.keyframes,
|
|
1477
|
+
body.ease,
|
|
1478
|
+
edit.easeEach
|
|
1479
|
+
).script;
|
|
1480
|
+
}
|
|
1465
1481
|
function resolveReplacementEaseEach(scriptText, request) {
|
|
1466
1482
|
if (request.easeEach !== void 0) return request.easeEach;
|
|
1467
1483
|
const original = parseGsapScriptAcorn(scriptText).animations.find(
|
|
@@ -1868,17 +1884,7 @@ function executeGsapMutationAcorn(body, block, respond) {
|
|
|
1868
1884
|
if (keyframesWritePosition(body.keyframes) || keyframesWriteRotation(body.keyframes)) {
|
|
1869
1885
|
stripStudioEditsFromTarget(block.document, body.targetSelector);
|
|
1870
1886
|
}
|
|
1871
|
-
|
|
1872
|
-
const added = addAnimationWithKeyframesToScript(
|
|
1873
|
-
script,
|
|
1874
|
-
body.targetSelector,
|
|
1875
|
-
body.position,
|
|
1876
|
-
body.duration,
|
|
1877
|
-
body.keyframes,
|
|
1878
|
-
body.ease,
|
|
1879
|
-
resolveReplacementEaseEach(block.scriptText, body)
|
|
1880
|
-
);
|
|
1881
|
-
return added.script;
|
|
1887
|
+
return replaceInPlaceUnlessSetOrUnknown(block.scriptText, body);
|
|
1882
1888
|
}
|
|
1883
1889
|
case "split-animations": {
|
|
1884
1890
|
if (typeof body.originalId !== "string" || !body.originalId || typeof body.newId !== "string" || !body.newId || typeof body.splitTime !== "number" || !Number.isFinite(body.splitTime) || typeof body.elementStart !== "number" || !Number.isFinite(body.elementStart) || typeof body.elementDuration !== "number" || !Number.isFinite(body.elementDuration) || body.elementDuration <= 0) {
|
|
@@ -2200,17 +2206,7 @@ async function executeGsapMutationRecast(body, block, respond) {
|
|
|
2200
2206
|
if (keyframesWritePosition(body.keyframes) || keyframesWriteRotation(body.keyframes)) {
|
|
2201
2207
|
stripStudioEditsFromTarget(block.document, body.targetSelector);
|
|
2202
2208
|
}
|
|
2203
|
-
|
|
2204
|
-
const added = addAnimationWithKeyframesToScript2(
|
|
2205
|
-
script,
|
|
2206
|
-
body.targetSelector,
|
|
2207
|
-
body.position,
|
|
2208
|
-
body.duration,
|
|
2209
|
-
body.keyframes,
|
|
2210
|
-
body.ease,
|
|
2211
|
-
resolveReplacementEaseEach(block.scriptText, body)
|
|
2212
|
-
);
|
|
2213
|
-
return added.script;
|
|
2209
|
+
return replaceInPlaceUnlessSetOrUnknown(block.scriptText, body);
|
|
2214
2210
|
}
|
|
2215
2211
|
case "split-animations": {
|
|
2216
2212
|
if (typeof body.originalId !== "string" || !body.originalId || typeof body.newId !== "string" || !body.newId || typeof body.splitTime !== "number" || !Number.isFinite(body.splitTime) || typeof body.elementStart !== "number" || !Number.isFinite(body.elementStart) || typeof body.elementDuration !== "number" || !Number.isFinite(body.elementDuration) || body.elementDuration <= 0) {
|
|
@@ -4894,7 +4890,7 @@ function registerWaveformRoutes(api, adapter) {
|
|
|
4894
4890
|
|
|
4895
4891
|
// src/routes/freezeFrame.ts
|
|
4896
4892
|
import { execFile } from "child_process";
|
|
4897
|
-
import {
|
|
4893
|
+
import { closeSync as closeSync3, openSync as openSync3, readFileSync as readFileSync11, rmSync as rmSync5, statSync as statSync8 } from "fs";
|
|
4898
4894
|
import { dirname as dirname5, join as join13, relative as relative4, sep as sep2 } from "path";
|
|
4899
4895
|
import { findFfBinary as findFfBinary3 } from "@hyperframes/parsers/ff-binaries";
|
|
4900
4896
|
import { replaceFileAtomically as replaceFileAtomically3 } from "@hyperframes/core/atomic-file";
|
|
@@ -4922,7 +4918,7 @@ function freezeFrameMediaTime(input) {
|
|
|
4922
4918
|
return input.mediaStart + sourceTimeAt(rate, Math.max(0, input.playhead - input.clipStart));
|
|
4923
4919
|
}
|
|
4924
4920
|
function freezeExtractArgs(src, mediaTime, output) {
|
|
4925
|
-
return ["-
|
|
4921
|
+
return ["-y", "-ss", String(round3(mediaTime)), "-i", src, "-frames:v", "1", output];
|
|
4926
4922
|
}
|
|
4927
4923
|
var randomStillToken = () => randomBytes(4).toString("hex");
|
|
4928
4924
|
function freezeStillFileName(clipId, playhead, token = randomStillToken()) {
|
|
@@ -5095,6 +5091,15 @@ function readExpected(absPath, expectedVersion) {
|
|
|
5095
5091
|
}
|
|
5096
5092
|
return fileContentVersion(content) === expectedVersion ? { content } : { error: "file conflict", status: 409 };
|
|
5097
5093
|
}
|
|
5094
|
+
function claimStillNameBeforeExtracting(path) {
|
|
5095
|
+
try {
|
|
5096
|
+
closeSync3(openSync3(path, "wx"));
|
|
5097
|
+
return true;
|
|
5098
|
+
} catch (error) {
|
|
5099
|
+
if (error.code === "EEXIST") return false;
|
|
5100
|
+
throw error;
|
|
5101
|
+
}
|
|
5102
|
+
}
|
|
5098
5103
|
async function extractStill(projectDir, absPath, source, playhead, tools) {
|
|
5099
5104
|
const fileDir = dirname5(absPath);
|
|
5100
5105
|
const mediaPath = pinWithinProject(projectDir, relative4(projectDir, join13(fileDir, source.src)));
|
|
@@ -5106,17 +5111,18 @@ async function extractStill(projectDir, absPath, source, playhead, tools) {
|
|
|
5106
5111
|
if (!imagePath || dirname5(imagePath) !== freezeDir) {
|
|
5107
5112
|
return { error: `forbidden freeze path: ${fileName}`, status: 403 };
|
|
5108
5113
|
}
|
|
5109
|
-
if (
|
|
5114
|
+
if (!claimStillNameBeforeExtracting(imagePath))
|
|
5110
5115
|
return { error: `freeze still already exists: ${fileName}`, status: 409 };
|
|
5111
5116
|
const extracted = await tools.extract(freezeExtractArgs(mediaPath, source.mediaTime, imagePath));
|
|
5112
|
-
|
|
5113
|
-
|
|
5114
|
-
|
|
5115
|
-
|
|
5116
|
-
};
|
|
5117
|
+
const partialOrNoFrame = !extracted.ok || statSync8(imagePath).size === 0;
|
|
5118
|
+
if (partialOrNoFrame) {
|
|
5119
|
+
rmSync5(imagePath, { force: true });
|
|
5120
|
+
const reason = extracted.ok ? "no frame at this time" : extracted.error ?? "ffmpeg failed";
|
|
5121
|
+
return { error: `Could not extract the frame: ${reason}`, status: 500 };
|
|
5117
5122
|
}
|
|
5118
5123
|
const depth = relative4(projectDir, fileDir).split(sep2).filter(Boolean).length;
|
|
5119
|
-
|
|
5124
|
+
const stillPath = `${FREEZE_DIR.join("/")}/${fileName}`;
|
|
5125
|
+
return { imageSrc: `${"../".repeat(depth)}${stillPath}`, stillPath, imagePath };
|
|
5120
5126
|
}
|
|
5121
5127
|
function writeFolded(projectDir, absPath, path, before, after, options) {
|
|
5122
5128
|
if (readFileSync11(absPath, "utf-8") !== before) {
|
|
@@ -5150,17 +5156,21 @@ function registerFreezeFrameRoutes(api, adapter, extract = ffmpegExtractor, stil
|
|
|
5150
5156
|
stillToken
|
|
5151
5157
|
});
|
|
5152
5158
|
if ("error" in still) return c.json({ error: still.error }, still.status);
|
|
5153
|
-
const { imageSrc } = still;
|
|
5159
|
+
const { imageSrc, stillPath, imagePath } = still;
|
|
5154
5160
|
const folded = applyFreezeFrameToHtml(before, {
|
|
5155
5161
|
target: body.target,
|
|
5156
5162
|
playhead: body.playhead,
|
|
5157
5163
|
imageSrc
|
|
5158
5164
|
});
|
|
5159
|
-
|
|
5165
|
+
const failAndRemoveStill = (error, status) => {
|
|
5166
|
+
rmSync5(imagePath, { force: true });
|
|
5167
|
+
return c.json({ error }, status);
|
|
5168
|
+
};
|
|
5169
|
+
if (!folded) return failAndRemoveStill("Freeze target was not found in the file", 400);
|
|
5160
5170
|
const written = writeFolded(project.dir, absPath, body.path, before, folded.html, {
|
|
5161
5171
|
token: body.transactionToken ?? c.req.header("X-Hyperframes-Write-Token")
|
|
5162
5172
|
});
|
|
5163
|
-
if ("error" in written) return
|
|
5173
|
+
if ("error" in written) return failAndRemoveStill(written.error, written.status);
|
|
5164
5174
|
const { version, writeToken, backupPath } = written;
|
|
5165
5175
|
return c.json({
|
|
5166
5176
|
ok: true,
|
|
@@ -5171,13 +5181,14 @@ function registerFreezeFrameRoutes(api, adapter, extract = ffmpegExtractor, stil
|
|
|
5171
5181
|
writeToken,
|
|
5172
5182
|
backupPath,
|
|
5173
5183
|
freezeId: folded.freezeId,
|
|
5174
|
-
imageSrc
|
|
5184
|
+
imageSrc,
|
|
5185
|
+
stillPath
|
|
5175
5186
|
});
|
|
5176
5187
|
});
|
|
5177
5188
|
}
|
|
5178
5189
|
|
|
5179
5190
|
// src/routes/loudness.ts
|
|
5180
|
-
import { existsSync as
|
|
5191
|
+
import { existsSync as existsSync8 } from "fs";
|
|
5181
5192
|
var defaultMeasure = (file, window) => measureAudio(requiredFfmpeg(), file, window);
|
|
5182
5193
|
var InvalidField = class extends Error {
|
|
5183
5194
|
};
|
|
@@ -5228,7 +5239,7 @@ function registerLoudnessRoutes(api, adapter, measure = defaultMeasure) {
|
|
|
5228
5239
|
} catch (error) {
|
|
5229
5240
|
return c.json({ error: error instanceof Error ? error.message : String(error) }, 400);
|
|
5230
5241
|
}
|
|
5231
|
-
if (!
|
|
5242
|
+
if (!existsSync8(file)) return c.json({ error: "file not found" }, 404);
|
|
5232
5243
|
try {
|
|
5233
5244
|
const measurement = await measure(file, request.window);
|
|
5234
5245
|
const plan = absoluteLoudnessPlan(
|
|
@@ -5243,7 +5254,7 @@ function registerLoudnessRoutes(api, adapter, measure = defaultMeasure) {
|
|
|
5243
5254
|
}
|
|
5244
5255
|
|
|
5245
5256
|
// src/routes/peaks.ts
|
|
5246
|
-
import { existsSync as
|
|
5257
|
+
import { existsSync as existsSync9, readFileSync as readFileSync12, statSync as statSync9 } from "fs";
|
|
5247
5258
|
import { join as join14, resolve as resolve6 } from "path";
|
|
5248
5259
|
import { isWithinProjectRoot as isWithinProjectRoot3 } from "@hyperframes/parsers/asset-resolution";
|
|
5249
5260
|
|
|
@@ -5361,7 +5372,7 @@ function decodeOnce(cachePath, mediaPath, decode) {
|
|
|
5361
5372
|
}
|
|
5362
5373
|
function readCachedBins(cacheDir, cachePath) {
|
|
5363
5374
|
try {
|
|
5364
|
-
if (!isWaveformCacheDirectory(cacheDir) || !
|
|
5375
|
+
if (!isWaveformCacheDirectory(cacheDir) || !existsSync9(cachePath)) return null;
|
|
5365
5376
|
const parsed = JSON.parse(readFileSync12(cachePath, "utf-8"));
|
|
5366
5377
|
return Array.isArray(parsed) && parsed.every((n) => typeof n === "number") ? parsed : null;
|
|
5367
5378
|
} catch {
|
|
@@ -5397,7 +5408,7 @@ function registerPeakRoutes(api, adapter, decode = decodePeakMap) {
|
|
|
5397
5408
|
}
|
|
5398
5409
|
|
|
5399
5410
|
// src/routes/fonts.ts
|
|
5400
|
-
import { closeSync as
|
|
5411
|
+
import { closeSync as closeSync4, constants, fstatSync as fstatSync3, openSync as openSync4, readSync } from "fs";
|
|
5401
5412
|
import {
|
|
5402
5413
|
collectFontFileEntries,
|
|
5403
5414
|
fontDirectories,
|
|
@@ -5504,7 +5515,7 @@ function registerFontRoutes(api) {
|
|
|
5504
5515
|
if (!located) return c.json({ error: "font not found" }, 404);
|
|
5505
5516
|
let fd;
|
|
5506
5517
|
try {
|
|
5507
|
-
fd =
|
|
5518
|
+
fd = openSync4(located.path, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
5508
5519
|
} catch {
|
|
5509
5520
|
return c.json({ error: "font file not accessible" }, 404);
|
|
5510
5521
|
}
|
|
@@ -5526,7 +5537,7 @@ function registerFontRoutes(api) {
|
|
|
5526
5537
|
} catch {
|
|
5527
5538
|
return c.json({ error: "failed to read font file" }, 500);
|
|
5528
5539
|
} finally {
|
|
5529
|
-
|
|
5540
|
+
closeSync4(fd);
|
|
5530
5541
|
}
|
|
5531
5542
|
});
|
|
5532
5543
|
}
|
|
@@ -5662,7 +5673,7 @@ function registerSelectionRoutes(api, adapter) {
|
|
|
5662
5673
|
|
|
5663
5674
|
// src/routes/media.ts
|
|
5664
5675
|
import { streamSSE as streamSSE2 } from "hono/streaming";
|
|
5665
|
-
import { existsSync as
|
|
5676
|
+
import { existsSync as existsSync10 } from "fs";
|
|
5666
5677
|
import { basename as basename2, dirname as dirname6, extname, join as join15, posix } from "path";
|
|
5667
5678
|
var VIDEO_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
5668
5679
|
".mp4",
|
|
@@ -5700,7 +5711,7 @@ function uniqueAssetPath(projectDir, assetPath, taken) {
|
|
|
5700
5711
|
const ext = extname(assetPath);
|
|
5701
5712
|
const withoutExt = assetPath.slice(0, -ext.length);
|
|
5702
5713
|
let candidate = assetPath;
|
|
5703
|
-
for (let index = 2; taken.has(candidate) ||
|
|
5714
|
+
for (let index = 2; taken.has(candidate) || existsSync10(join15(projectDir, candidate)); index++) {
|
|
5704
5715
|
candidate = `${withoutExt}-${index}${ext}`;
|
|
5705
5716
|
}
|
|
5706
5717
|
return candidate;
|
|
@@ -5761,7 +5772,7 @@ function registerMediaRoutes(api, adapter, options = {}) {
|
|
|
5761
5772
|
}
|
|
5762
5773
|
const filePath = resolveWithinProject(project.dir, assetPath);
|
|
5763
5774
|
if (!filePath) return c.json({ error: "forbidden" }, 403);
|
|
5764
|
-
if (!
|
|
5775
|
+
if (!existsSync10(filePath)) return c.json({ error: "media not found" }, 404);
|
|
5765
5776
|
return c.json({ path: assetPath, metadata: await readMediaMetadata(filePath) });
|
|
5766
5777
|
});
|
|
5767
5778
|
api.post(
|
|
@@ -5783,7 +5794,7 @@ function registerMediaRoutes(api, adapter, options = {}) {
|
|
|
5783
5794
|
}
|
|
5784
5795
|
const inputPath = resolveWithinProject(project.dir, inputAssetPath);
|
|
5785
5796
|
if (!inputPath) return c.json({ error: "forbidden" }, 403);
|
|
5786
|
-
if (!
|
|
5797
|
+
if (!existsSync10(inputPath)) return c.json({ error: "input media not found" }, 404);
|
|
5787
5798
|
const inputIsVideo = isVideoPath(inputAssetPath);
|
|
5788
5799
|
const inputIsImage = isImagePath(inputAssetPath);
|
|
5789
5800
|
if (!inputIsVideo && !inputIsImage) {
|
|
@@ -5877,12 +5888,12 @@ function registerMediaRoutes(api, adapter, options = {}) {
|
|
|
5877
5888
|
}
|
|
5878
5889
|
|
|
5879
5890
|
// src/routes/globalAssets.ts
|
|
5880
|
-
import { existsSync as
|
|
5891
|
+
import { existsSync as existsSync11, readFileSync as readFileSync13 } from "fs";
|
|
5881
5892
|
import { homedir } from "os";
|
|
5882
5893
|
import { join as join16 } from "path";
|
|
5883
5894
|
function readGlobalAssets(home = homedir()) {
|
|
5884
5895
|
const manifestPath = join16(home, ".media", "manifest.jsonl");
|
|
5885
|
-
if (!
|
|
5896
|
+
if (!existsSync11(manifestPath)) return [];
|
|
5886
5897
|
const out = [];
|
|
5887
5898
|
for (const line of readFileSync13(manifestPath, "utf8").split("\n")) {
|
|
5888
5899
|
if (!line.trim()) continue;
|
|
@@ -6136,7 +6147,7 @@ function numericAttribute(element, name) {
|
|
|
6136
6147
|
|
|
6137
6148
|
// src/helpers/previewDocumentStore.ts
|
|
6138
6149
|
import { createHash as createHash6 } from "crypto";
|
|
6139
|
-
import { readFileSync as readFileSync14, readdirSync as readdirSync5, rmSync as
|
|
6150
|
+
import { readFileSync as readFileSync14, readdirSync as readdirSync5, rmSync as rmSync6 } from "fs";
|
|
6140
6151
|
import { join as join17 } from "path";
|
|
6141
6152
|
import { replaceFileAtomically as replaceFileAtomically4 } from "@hyperframes/core/atomic-file";
|
|
6142
6153
|
function createPreviewDocumentStore(projectDir, salt) {
|
|
@@ -6157,7 +6168,7 @@ ${key}`).digest("hex").slice(0, 32)}.html`);
|
|
|
6157
6168
|
const file = fileFor(key);
|
|
6158
6169
|
replaceFileAtomically4(file, html, 420);
|
|
6159
6170
|
for (const name of readdirSync5(dir)) {
|
|
6160
|
-
if (join17(dir, name) !== file)
|
|
6171
|
+
if (join17(dir, name) !== file) rmSync6(join17(dir, name), { force: true });
|
|
6161
6172
|
}
|
|
6162
6173
|
} catch {
|
|
6163
6174
|
}
|