@hyperframes/studio-server 0.8.116 → 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 +43 -28
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -4890,7 +4890,7 @@ function registerWaveformRoutes(api, adapter) {
|
|
|
4890
4890
|
|
|
4891
4891
|
// src/routes/freezeFrame.ts
|
|
4892
4892
|
import { execFile } from "child_process";
|
|
4893
|
-
import {
|
|
4893
|
+
import { closeSync as closeSync3, openSync as openSync3, readFileSync as readFileSync11, rmSync as rmSync5, statSync as statSync8 } from "fs";
|
|
4894
4894
|
import { dirname as dirname5, join as join13, relative as relative4, sep as sep2 } from "path";
|
|
4895
4895
|
import { findFfBinary as findFfBinary3 } from "@hyperframes/parsers/ff-binaries";
|
|
4896
4896
|
import { replaceFileAtomically as replaceFileAtomically3 } from "@hyperframes/core/atomic-file";
|
|
@@ -4918,7 +4918,7 @@ function freezeFrameMediaTime(input) {
|
|
|
4918
4918
|
return input.mediaStart + sourceTimeAt(rate, Math.max(0, input.playhead - input.clipStart));
|
|
4919
4919
|
}
|
|
4920
4920
|
function freezeExtractArgs(src, mediaTime, output) {
|
|
4921
|
-
return ["-
|
|
4921
|
+
return ["-y", "-ss", String(round3(mediaTime)), "-i", src, "-frames:v", "1", output];
|
|
4922
4922
|
}
|
|
4923
4923
|
var randomStillToken = () => randomBytes(4).toString("hex");
|
|
4924
4924
|
function freezeStillFileName(clipId, playhead, token = randomStillToken()) {
|
|
@@ -5091,6 +5091,15 @@ function readExpected(absPath, expectedVersion) {
|
|
|
5091
5091
|
}
|
|
5092
5092
|
return fileContentVersion(content) === expectedVersion ? { content } : { error: "file conflict", status: 409 };
|
|
5093
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
|
+
}
|
|
5094
5103
|
async function extractStill(projectDir, absPath, source, playhead, tools) {
|
|
5095
5104
|
const fileDir = dirname5(absPath);
|
|
5096
5105
|
const mediaPath = pinWithinProject(projectDir, relative4(projectDir, join13(fileDir, source.src)));
|
|
@@ -5102,17 +5111,18 @@ async function extractStill(projectDir, absPath, source, playhead, tools) {
|
|
|
5102
5111
|
if (!imagePath || dirname5(imagePath) !== freezeDir) {
|
|
5103
5112
|
return { error: `forbidden freeze path: ${fileName}`, status: 403 };
|
|
5104
5113
|
}
|
|
5105
|
-
if (
|
|
5114
|
+
if (!claimStillNameBeforeExtracting(imagePath))
|
|
5106
5115
|
return { error: `freeze still already exists: ${fileName}`, status: 409 };
|
|
5107
5116
|
const extracted = await tools.extract(freezeExtractArgs(mediaPath, source.mediaTime, imagePath));
|
|
5108
|
-
|
|
5109
|
-
|
|
5110
|
-
|
|
5111
|
-
|
|
5112
|
-
};
|
|
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 };
|
|
5113
5122
|
}
|
|
5114
5123
|
const depth = relative4(projectDir, fileDir).split(sep2).filter(Boolean).length;
|
|
5115
|
-
|
|
5124
|
+
const stillPath = `${FREEZE_DIR.join("/")}/${fileName}`;
|
|
5125
|
+
return { imageSrc: `${"../".repeat(depth)}${stillPath}`, stillPath, imagePath };
|
|
5116
5126
|
}
|
|
5117
5127
|
function writeFolded(projectDir, absPath, path, before, after, options) {
|
|
5118
5128
|
if (readFileSync11(absPath, "utf-8") !== before) {
|
|
@@ -5146,17 +5156,21 @@ function registerFreezeFrameRoutes(api, adapter, extract = ffmpegExtractor, stil
|
|
|
5146
5156
|
stillToken
|
|
5147
5157
|
});
|
|
5148
5158
|
if ("error" in still) return c.json({ error: still.error }, still.status);
|
|
5149
|
-
const { imageSrc } = still;
|
|
5159
|
+
const { imageSrc, stillPath, imagePath } = still;
|
|
5150
5160
|
const folded = applyFreezeFrameToHtml(before, {
|
|
5151
5161
|
target: body.target,
|
|
5152
5162
|
playhead: body.playhead,
|
|
5153
5163
|
imageSrc
|
|
5154
5164
|
});
|
|
5155
|
-
|
|
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);
|
|
5156
5170
|
const written = writeFolded(project.dir, absPath, body.path, before, folded.html, {
|
|
5157
5171
|
token: body.transactionToken ?? c.req.header("X-Hyperframes-Write-Token")
|
|
5158
5172
|
});
|
|
5159
|
-
if ("error" in written) return
|
|
5173
|
+
if ("error" in written) return failAndRemoveStill(written.error, written.status);
|
|
5160
5174
|
const { version, writeToken, backupPath } = written;
|
|
5161
5175
|
return c.json({
|
|
5162
5176
|
ok: true,
|
|
@@ -5167,13 +5181,14 @@ function registerFreezeFrameRoutes(api, adapter, extract = ffmpegExtractor, stil
|
|
|
5167
5181
|
writeToken,
|
|
5168
5182
|
backupPath,
|
|
5169
5183
|
freezeId: folded.freezeId,
|
|
5170
|
-
imageSrc
|
|
5184
|
+
imageSrc,
|
|
5185
|
+
stillPath
|
|
5171
5186
|
});
|
|
5172
5187
|
});
|
|
5173
5188
|
}
|
|
5174
5189
|
|
|
5175
5190
|
// src/routes/loudness.ts
|
|
5176
|
-
import { existsSync as
|
|
5191
|
+
import { existsSync as existsSync8 } from "fs";
|
|
5177
5192
|
var defaultMeasure = (file, window) => measureAudio(requiredFfmpeg(), file, window);
|
|
5178
5193
|
var InvalidField = class extends Error {
|
|
5179
5194
|
};
|
|
@@ -5224,7 +5239,7 @@ function registerLoudnessRoutes(api, adapter, measure = defaultMeasure) {
|
|
|
5224
5239
|
} catch (error) {
|
|
5225
5240
|
return c.json({ error: error instanceof Error ? error.message : String(error) }, 400);
|
|
5226
5241
|
}
|
|
5227
|
-
if (!
|
|
5242
|
+
if (!existsSync8(file)) return c.json({ error: "file not found" }, 404);
|
|
5228
5243
|
try {
|
|
5229
5244
|
const measurement = await measure(file, request.window);
|
|
5230
5245
|
const plan = absoluteLoudnessPlan(
|
|
@@ -5239,7 +5254,7 @@ function registerLoudnessRoutes(api, adapter, measure = defaultMeasure) {
|
|
|
5239
5254
|
}
|
|
5240
5255
|
|
|
5241
5256
|
// src/routes/peaks.ts
|
|
5242
|
-
import { existsSync as
|
|
5257
|
+
import { existsSync as existsSync9, readFileSync as readFileSync12, statSync as statSync9 } from "fs";
|
|
5243
5258
|
import { join as join14, resolve as resolve6 } from "path";
|
|
5244
5259
|
import { isWithinProjectRoot as isWithinProjectRoot3 } from "@hyperframes/parsers/asset-resolution";
|
|
5245
5260
|
|
|
@@ -5357,7 +5372,7 @@ function decodeOnce(cachePath, mediaPath, decode) {
|
|
|
5357
5372
|
}
|
|
5358
5373
|
function readCachedBins(cacheDir, cachePath) {
|
|
5359
5374
|
try {
|
|
5360
|
-
if (!isWaveformCacheDirectory(cacheDir) || !
|
|
5375
|
+
if (!isWaveformCacheDirectory(cacheDir) || !existsSync9(cachePath)) return null;
|
|
5361
5376
|
const parsed = JSON.parse(readFileSync12(cachePath, "utf-8"));
|
|
5362
5377
|
return Array.isArray(parsed) && parsed.every((n) => typeof n === "number") ? parsed : null;
|
|
5363
5378
|
} catch {
|
|
@@ -5393,7 +5408,7 @@ function registerPeakRoutes(api, adapter, decode = decodePeakMap) {
|
|
|
5393
5408
|
}
|
|
5394
5409
|
|
|
5395
5410
|
// src/routes/fonts.ts
|
|
5396
|
-
import { closeSync as
|
|
5411
|
+
import { closeSync as closeSync4, constants, fstatSync as fstatSync3, openSync as openSync4, readSync } from "fs";
|
|
5397
5412
|
import {
|
|
5398
5413
|
collectFontFileEntries,
|
|
5399
5414
|
fontDirectories,
|
|
@@ -5500,7 +5515,7 @@ function registerFontRoutes(api) {
|
|
|
5500
5515
|
if (!located) return c.json({ error: "font not found" }, 404);
|
|
5501
5516
|
let fd;
|
|
5502
5517
|
try {
|
|
5503
|
-
fd =
|
|
5518
|
+
fd = openSync4(located.path, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
5504
5519
|
} catch {
|
|
5505
5520
|
return c.json({ error: "font file not accessible" }, 404);
|
|
5506
5521
|
}
|
|
@@ -5522,7 +5537,7 @@ function registerFontRoutes(api) {
|
|
|
5522
5537
|
} catch {
|
|
5523
5538
|
return c.json({ error: "failed to read font file" }, 500);
|
|
5524
5539
|
} finally {
|
|
5525
|
-
|
|
5540
|
+
closeSync4(fd);
|
|
5526
5541
|
}
|
|
5527
5542
|
});
|
|
5528
5543
|
}
|
|
@@ -5658,7 +5673,7 @@ function registerSelectionRoutes(api, adapter) {
|
|
|
5658
5673
|
|
|
5659
5674
|
// src/routes/media.ts
|
|
5660
5675
|
import { streamSSE as streamSSE2 } from "hono/streaming";
|
|
5661
|
-
import { existsSync as
|
|
5676
|
+
import { existsSync as existsSync10 } from "fs";
|
|
5662
5677
|
import { basename as basename2, dirname as dirname6, extname, join as join15, posix } from "path";
|
|
5663
5678
|
var VIDEO_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
5664
5679
|
".mp4",
|
|
@@ -5696,7 +5711,7 @@ function uniqueAssetPath(projectDir, assetPath, taken) {
|
|
|
5696
5711
|
const ext = extname(assetPath);
|
|
5697
5712
|
const withoutExt = assetPath.slice(0, -ext.length);
|
|
5698
5713
|
let candidate = assetPath;
|
|
5699
|
-
for (let index = 2; taken.has(candidate) ||
|
|
5714
|
+
for (let index = 2; taken.has(candidate) || existsSync10(join15(projectDir, candidate)); index++) {
|
|
5700
5715
|
candidate = `${withoutExt}-${index}${ext}`;
|
|
5701
5716
|
}
|
|
5702
5717
|
return candidate;
|
|
@@ -5757,7 +5772,7 @@ function registerMediaRoutes(api, adapter, options = {}) {
|
|
|
5757
5772
|
}
|
|
5758
5773
|
const filePath = resolveWithinProject(project.dir, assetPath);
|
|
5759
5774
|
if (!filePath) return c.json({ error: "forbidden" }, 403);
|
|
5760
|
-
if (!
|
|
5775
|
+
if (!existsSync10(filePath)) return c.json({ error: "media not found" }, 404);
|
|
5761
5776
|
return c.json({ path: assetPath, metadata: await readMediaMetadata(filePath) });
|
|
5762
5777
|
});
|
|
5763
5778
|
api.post(
|
|
@@ -5779,7 +5794,7 @@ function registerMediaRoutes(api, adapter, options = {}) {
|
|
|
5779
5794
|
}
|
|
5780
5795
|
const inputPath = resolveWithinProject(project.dir, inputAssetPath);
|
|
5781
5796
|
if (!inputPath) return c.json({ error: "forbidden" }, 403);
|
|
5782
|
-
if (!
|
|
5797
|
+
if (!existsSync10(inputPath)) return c.json({ error: "input media not found" }, 404);
|
|
5783
5798
|
const inputIsVideo = isVideoPath(inputAssetPath);
|
|
5784
5799
|
const inputIsImage = isImagePath(inputAssetPath);
|
|
5785
5800
|
if (!inputIsVideo && !inputIsImage) {
|
|
@@ -5873,12 +5888,12 @@ function registerMediaRoutes(api, adapter, options = {}) {
|
|
|
5873
5888
|
}
|
|
5874
5889
|
|
|
5875
5890
|
// src/routes/globalAssets.ts
|
|
5876
|
-
import { existsSync as
|
|
5891
|
+
import { existsSync as existsSync11, readFileSync as readFileSync13 } from "fs";
|
|
5877
5892
|
import { homedir } from "os";
|
|
5878
5893
|
import { join as join16 } from "path";
|
|
5879
5894
|
function readGlobalAssets(home = homedir()) {
|
|
5880
5895
|
const manifestPath = join16(home, ".media", "manifest.jsonl");
|
|
5881
|
-
if (!
|
|
5896
|
+
if (!existsSync11(manifestPath)) return [];
|
|
5882
5897
|
const out = [];
|
|
5883
5898
|
for (const line of readFileSync13(manifestPath, "utf8").split("\n")) {
|
|
5884
5899
|
if (!line.trim()) continue;
|
|
@@ -6132,7 +6147,7 @@ function numericAttribute(element, name) {
|
|
|
6132
6147
|
|
|
6133
6148
|
// src/helpers/previewDocumentStore.ts
|
|
6134
6149
|
import { createHash as createHash6 } from "crypto";
|
|
6135
|
-
import { readFileSync as readFileSync14, readdirSync as readdirSync5, rmSync as
|
|
6150
|
+
import { readFileSync as readFileSync14, readdirSync as readdirSync5, rmSync as rmSync6 } from "fs";
|
|
6136
6151
|
import { join as join17 } from "path";
|
|
6137
6152
|
import { replaceFileAtomically as replaceFileAtomically4 } from "@hyperframes/core/atomic-file";
|
|
6138
6153
|
function createPreviewDocumentStore(projectDir, salt) {
|
|
@@ -6153,7 +6168,7 @@ ${key}`).digest("hex").slice(0, 32)}.html`);
|
|
|
6153
6168
|
const file = fileFor(key);
|
|
6154
6169
|
replaceFileAtomically4(file, html, 420);
|
|
6155
6170
|
for (const name of readdirSync5(dir)) {
|
|
6156
|
-
if (join17(dir, name) !== file)
|
|
6171
|
+
if (join17(dir, name) !== file) rmSync6(join17(dir, name), { force: true });
|
|
6157
6172
|
}
|
|
6158
6173
|
} catch {
|
|
6159
6174
|
}
|