@hyperframes/studio-server 0.8.64 → 0.8.65
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.d.ts +9 -2
- package/dist/index.js +82 -19
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -41,8 +41,15 @@ declare function createStudioApi(adapter: StudioApiAdapter): Hono;
|
|
|
41
41
|
declare function affectsProjectSignature(projectDir: string, changedPath: string): boolean;
|
|
42
42
|
/**
|
|
43
43
|
* Creates a stable preview cache-busting signature for project source plus Studio manifests.
|
|
44
|
+
* `excluding` (project-relative paths) leaves those files out.
|
|
44
45
|
*/
|
|
45
|
-
declare function createProjectSignature(projectDir: string): string;
|
|
46
|
+
declare function createProjectSignature(projectDir: string, excluding?: ReadonlySet<string>): string;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Compositions whose rendered frames a write at `changedPath` can change, or `null` for all
|
|
50
|
+
* of them: the root, assets and any file the root does not mount reach every composition.
|
|
51
|
+
*/
|
|
52
|
+
declare function compositionsAffectedBy(projectDir: string, changedPath: string): string[] | null;
|
|
46
53
|
|
|
47
54
|
/** Recursively walk a directory and return relative file paths. */
|
|
48
55
|
declare function walkDir(dir: string, prefix?: string): string[];
|
|
@@ -160,4 +167,4 @@ type BackgroundRemovalRender = (options: {
|
|
|
160
167
|
}>;
|
|
161
168
|
declare function createBackgroundRemovalJob(opts: BackgroundRemovalJobOptions, render: BackgroundRemovalRender): MediaProcessingJobState;
|
|
162
169
|
|
|
163
|
-
export { type AppliedFileMutation, type BackgroundRemovalRender, type DuplicateElementResult, type FileMutationInput, type FileWriteReceipt, MIME_TYPES, MediaProcessingJobState, SourceMutationTarget, StudioApiAdapter, type ThumbnailOutputDimensions, affectsProjectSignature, applyFileMutations, buildSubCompositionHtml, consumeFileWriteReceipt, createBackgroundRemovalJob, createProjectSignature, createStudioApi, duplicateElementInHtml, fileContentVersion, getMimeType, identifyFileWrite, thumbnailDeviceScaleFactor, walkDir };
|
|
170
|
+
export { type AppliedFileMutation, type BackgroundRemovalRender, type DuplicateElementResult, type FileMutationInput, type FileWriteReceipt, MIME_TYPES, MediaProcessingJobState, SourceMutationTarget, StudioApiAdapter, type ThumbnailOutputDimensions, affectsProjectSignature, applyFileMutations, buildSubCompositionHtml, compositionsAffectedBy, consumeFileWriteReceipt, createBackgroundRemovalJob, createProjectSignature, createStudioApi, duplicateElementInHtml, fileContentVersion, getMimeType, identifyFileWrite, thumbnailDeviceScaleFactor, walkDir };
|
package/dist/index.js
CHANGED
|
@@ -217,14 +217,18 @@ async function resolveProjectAndSignature(adapter, projectId) {
|
|
|
217
217
|
if (!project) return null;
|
|
218
218
|
return { project, signature: resolveProjectSignature(adapter, project.dir) };
|
|
219
219
|
}
|
|
220
|
-
function createProjectSignature(projectDir) {
|
|
220
|
+
function createProjectSignature(projectDir, excluding = /* @__PURE__ */ new Set()) {
|
|
221
221
|
const normalizedProjectDir = resolve(projectDir);
|
|
222
|
-
const
|
|
223
|
-
collectProjectSignatureFiles(normalizedProjectDir, normalizedProjectDir,
|
|
224
|
-
collectProjectSignatureManifestFiles(normalizedProjectDir,
|
|
222
|
+
const collected = [];
|
|
223
|
+
collectProjectSignatureFiles(normalizedProjectDir, normalizedProjectDir, collected);
|
|
224
|
+
collectProjectSignatureManifestFiles(normalizedProjectDir, collected);
|
|
225
|
+
const files = collected.filter(
|
|
226
|
+
(entry) => !excluding.has(relative(normalizedProjectDir, entry.file).split(sep).join("/"))
|
|
227
|
+
);
|
|
225
228
|
files.sort((a, b) => a.file.localeCompare(b.file));
|
|
226
229
|
const fingerprint = createProjectFingerprint(normalizedProjectDir, files);
|
|
227
|
-
const
|
|
230
|
+
const cacheKey = excluding.size ? `${normalizedProjectDir}\0${createHash("sha256").update([...excluding].sort().join("\0")).digest("hex")}` : normalizedProjectDir;
|
|
231
|
+
const cached = projectSignatureCache.get(cacheKey);
|
|
228
232
|
if (cached?.fingerprint === fingerprint) return cached.signature;
|
|
229
233
|
const hash = createHash("sha256");
|
|
230
234
|
for (const entry of files) {
|
|
@@ -245,7 +249,7 @@ function createProjectSignature(projectDir) {
|
|
|
245
249
|
hash.update("\0");
|
|
246
250
|
}
|
|
247
251
|
const signature = hash.digest("hex").slice(0, 24);
|
|
248
|
-
projectSignatureCache.set(
|
|
252
|
+
projectSignatureCache.set(cacheKey, { fingerprint, signature });
|
|
249
253
|
return signature;
|
|
250
254
|
}
|
|
251
255
|
|
|
@@ -4154,7 +4158,7 @@ function registerRenderRoutes(api, adapter) {
|
|
|
4154
4158
|
import {
|
|
4155
4159
|
existsSync as existsSync7,
|
|
4156
4160
|
mkdirSync as mkdirSync5,
|
|
4157
|
-
readFileSync as
|
|
4161
|
+
readFileSync as readFileSync12,
|
|
4158
4162
|
readdirSync as readdirSync6,
|
|
4159
4163
|
renameSync as renameSync3,
|
|
4160
4164
|
rmSync as rmSync4,
|
|
@@ -4165,6 +4169,63 @@ import {
|
|
|
4165
4169
|
import { join as join11 } from "path";
|
|
4166
4170
|
import { createHash as createHash4, randomUUID as randomUUID4 } from "crypto";
|
|
4167
4171
|
|
|
4172
|
+
// src/helpers/compositionInputs.ts
|
|
4173
|
+
import { readFileSync as readFileSync11 } from "fs";
|
|
4174
|
+
var ROOT_COMPOSITION = "index.html";
|
|
4175
|
+
var COMPOSITION_SRC = /\bdata-composition-src\s*=\s*(["'])(.*?)\1/g;
|
|
4176
|
+
function projectReader(projectDir) {
|
|
4177
|
+
const sources = /* @__PURE__ */ new Map();
|
|
4178
|
+
return (compPath) => {
|
|
4179
|
+
if (!sources.has(compPath)) {
|
|
4180
|
+
const file = resolveWithinProject(projectDir, compPath);
|
|
4181
|
+
let source = null;
|
|
4182
|
+
try {
|
|
4183
|
+
source = file ? readFileSync11(file, "utf-8") : null;
|
|
4184
|
+
} catch {
|
|
4185
|
+
}
|
|
4186
|
+
sources.set(compPath, source);
|
|
4187
|
+
}
|
|
4188
|
+
return sources.get(compPath) ?? null;
|
|
4189
|
+
};
|
|
4190
|
+
}
|
|
4191
|
+
function normalizeSource(src) {
|
|
4192
|
+
if (/^[a-z][a-z0-9+.-]*:/i.test(src) || src.startsWith("//")) return null;
|
|
4193
|
+
const path = src.split(/[?#]/)[0]?.replace(/^\.?\//, "") ?? "";
|
|
4194
|
+
return path || null;
|
|
4195
|
+
}
|
|
4196
|
+
function closureOf(read, compPath) {
|
|
4197
|
+
const closure = /* @__PURE__ */ new Set();
|
|
4198
|
+
const visit = (path) => {
|
|
4199
|
+
if (closure.has(path)) return;
|
|
4200
|
+
closure.add(path);
|
|
4201
|
+
for (const match of (read(path) ?? "").matchAll(COMPOSITION_SRC)) {
|
|
4202
|
+
const mounted = normalizeSource(match[2] ?? "");
|
|
4203
|
+
if (mounted) visit(mounted);
|
|
4204
|
+
}
|
|
4205
|
+
};
|
|
4206
|
+
visit(compPath);
|
|
4207
|
+
return closure;
|
|
4208
|
+
}
|
|
4209
|
+
var inputSignatures = /* @__PURE__ */ new Map();
|
|
4210
|
+
function compositionInputSignature(projectDir, compPath, projectSignature) {
|
|
4211
|
+
const key = `${projectDir}\0${compPath}`;
|
|
4212
|
+
const known = inputSignatures.get(key);
|
|
4213
|
+
if (known?.projectSignature === projectSignature) return known.inputSignature;
|
|
4214
|
+
const read = projectReader(projectDir);
|
|
4215
|
+
const inputs = closureOf(read, normalizeSource(compPath) ?? compPath).add(ROOT_COMPOSITION);
|
|
4216
|
+
const siblings = [...closureOf(read, ROOT_COMPOSITION)].filter((path) => !inputs.has(path));
|
|
4217
|
+
const inputSignature = createProjectSignature(projectDir, new Set(siblings));
|
|
4218
|
+
inputSignatures.set(key, { projectSignature, inputSignature });
|
|
4219
|
+
return inputSignature;
|
|
4220
|
+
}
|
|
4221
|
+
function compositionsAffectedBy(projectDir, changedPath) {
|
|
4222
|
+
const changed = changedPath.replace(/\\/g, "/");
|
|
4223
|
+
const read = projectReader(projectDir);
|
|
4224
|
+
const mounted = closureOf(read, ROOT_COMPOSITION);
|
|
4225
|
+
if (changed === ROOT_COMPOSITION || !mounted.has(changed)) return null;
|
|
4226
|
+
return [...mounted].filter((path) => closureOf(read, path).has(changed));
|
|
4227
|
+
}
|
|
4228
|
+
|
|
4168
4229
|
// src/routes/thumbnailGenerationCoordinator.ts
|
|
4169
4230
|
var ThumbnailGenerationCoordinator = class {
|
|
4170
4231
|
constructor(concurrency = 1) {
|
|
@@ -4327,6 +4388,7 @@ function registerThumbnailRoutes(api, adapter) {
|
|
|
4327
4388
|
c.req.path.replace(`/projects/${project.id}/thumbnail/`, "").split("?")[0] ?? ""
|
|
4328
4389
|
);
|
|
4329
4390
|
if (compPath && !compPath.includes(".")) compPath += ".html";
|
|
4391
|
+
const inputSignature = compositionInputSignature(project.dir, compPath, projectSignature);
|
|
4330
4392
|
const url = new URL(c.req.url, `http://${c.req.header("host") || "localhost"}`);
|
|
4331
4393
|
const rawSeekTime = url.searchParams.get("t");
|
|
4332
4394
|
const parsedSeekTime = rawSeekTime == null ? Number.NaN : parseFloat(rawSeekTime);
|
|
@@ -4347,7 +4409,7 @@ function registerThumbnailRoutes(api, adapter) {
|
|
|
4347
4409
|
let sourceKey = "";
|
|
4348
4410
|
const htmlFile = join11(project.dir, compPath);
|
|
4349
4411
|
if (existsSync7(htmlFile)) {
|
|
4350
|
-
const html =
|
|
4412
|
+
const html = readFileSync12(htmlFile, "utf-8");
|
|
4351
4413
|
sourceKey = `_${createHash4("sha1").update(html).digest("hex").slice(0, 16)}`;
|
|
4352
4414
|
sourceMtime = Math.round(statSync7(htmlFile).mtimeMs);
|
|
4353
4415
|
if (!vpWidth) {
|
|
@@ -4360,14 +4422,14 @@ function registerThumbnailRoutes(api, adapter) {
|
|
|
4360
4422
|
const manualEditsFile = join11(project.dir, STUDIO_MANUAL_EDITS_PATH);
|
|
4361
4423
|
let manualEditsKey = "";
|
|
4362
4424
|
if (existsSync7(manualEditsFile)) {
|
|
4363
|
-
const manualEditsContent =
|
|
4425
|
+
const manualEditsContent = readFileSync12(manualEditsFile, "utf-8");
|
|
4364
4426
|
manualEditsKey = `_${createHash4("sha1").update(manualEditsContent).digest("hex").slice(0, 16)}`;
|
|
4365
4427
|
sourceMtime = Math.max(sourceMtime, Math.round(statSync7(manualEditsFile).mtimeMs));
|
|
4366
4428
|
}
|
|
4367
4429
|
const motionFile = join11(project.dir, STUDIO_MOTION_PATH);
|
|
4368
4430
|
let motionKey = "";
|
|
4369
4431
|
if (existsSync7(motionFile)) {
|
|
4370
|
-
const motionContent =
|
|
4432
|
+
const motionContent = readFileSync12(motionFile, "utf-8");
|
|
4371
4433
|
motionKey = `_${createHash4("sha1").update(motionContent).digest("hex").slice(0, 16)}`;
|
|
4372
4434
|
sourceMtime = Math.max(sourceMtime, Math.round(statSync7(motionFile).mtimeMs));
|
|
4373
4435
|
}
|
|
@@ -4375,11 +4437,11 @@ function registerThumbnailRoutes(api, adapter) {
|
|
|
4375
4437
|
const cacheDir = join11(project.dir, ".thumbnails");
|
|
4376
4438
|
const selectorKey = selector ? `_${selector.replace(/[^a-zA-Z0-9_-]+/g, "_").slice(0, 80)}_${selectorIndex ?? 0}` : "";
|
|
4377
4439
|
const urlVersionKey = urlVersion ? `_${urlVersion.replace(/[^a-zA-Z0-9_-]+/g, "_").slice(0, 32)}` : "";
|
|
4378
|
-
const
|
|
4440
|
+
const inputSignatureKey = `_${createHash4("sha1").update(inputSignature).digest("hex").slice(0, 16)}`;
|
|
4379
4441
|
const outputScale = outputMode === "source" ? 1 : Math.min(1, THUMBNAIL_MAX_OUTPUT_WIDTH / compW, THUMBNAIL_MAX_OUTPUT_HEIGHT / compH);
|
|
4380
4442
|
const outputWidth = Math.max(1, Math.round(compW * outputScale));
|
|
4381
4443
|
const outputHeight = Math.max(1, Math.round(compH * outputScale));
|
|
4382
|
-
const cacheKey = `${THUMBNAIL_CACHE_VERSION}${urlVersionKey}${
|
|
4444
|
+
const cacheKey = `${THUMBNAIL_CACHE_VERSION}${urlVersionKey}${inputSignatureKey}${manualEditsKey}${motionKey}${sourceKey}_${format}_${outputMode}_${compPath.replace(/\//g, "_")}_${compW}x${compH}_${outputWidth}x${outputHeight}_${sourceMtime}_${seekTime.toFixed(2)}${selectorKey}.${format === "png" ? "png" : "jpg"}`;
|
|
4383
4445
|
const cachePath = join11(cacheDir, cacheKey);
|
|
4384
4446
|
if (!prunedCacheDirs.has(cacheDir)) {
|
|
4385
4447
|
prunedCacheDirs.add(cacheDir);
|
|
@@ -4389,7 +4451,7 @@ function registerThumbnailRoutes(api, adapter) {
|
|
|
4389
4451
|
);
|
|
4390
4452
|
}
|
|
4391
4453
|
if (existsSync7(cachePath)) {
|
|
4392
|
-
return new Response(new Uint8Array(
|
|
4454
|
+
return new Response(new Uint8Array(readFileSync12(cachePath)), {
|
|
4393
4455
|
headers: { "Content-Type": contentType, "Cache-Control": "no-cache" }
|
|
4394
4456
|
});
|
|
4395
4457
|
}
|
|
@@ -4414,8 +4476,8 @@ function registerThumbnailRoutes(api, adapter) {
|
|
|
4414
4476
|
});
|
|
4415
4477
|
if (!generated) return null;
|
|
4416
4478
|
const afterGeneration = await resolveProjectAndSignature(adapter, project.id);
|
|
4417
|
-
const
|
|
4418
|
-
if (
|
|
4479
|
+
const inputsUnchanged = (signature) => compositionInputSignature(project.dir, compPath, signature) === inputSignature;
|
|
4480
|
+
if (afterGeneration?.project.dir !== project.dir || !inputsUnchanged(afterGeneration.signature) || !inputsUnchanged(createProjectSignature(project.dir))) {
|
|
4419
4481
|
return generated;
|
|
4420
4482
|
}
|
|
4421
4483
|
if (!existsSync7(cacheDir)) mkdirSync5(cacheDir, { recursive: true });
|
|
@@ -4444,7 +4506,7 @@ function registerThumbnailRoutes(api, adapter) {
|
|
|
4444
4506
|
}
|
|
4445
4507
|
|
|
4446
4508
|
// src/routes/waveform.ts
|
|
4447
|
-
import { existsSync as existsSync8, readFileSync as
|
|
4509
|
+
import { existsSync as existsSync8, readFileSync as readFileSync13, statSync as statSync8 } from "fs";
|
|
4448
4510
|
import { join as join12 } from "path";
|
|
4449
4511
|
function registerWaveformRoutes(api, adapter) {
|
|
4450
4512
|
api.get("/projects/:id/waveform/*", async (c) => {
|
|
@@ -4460,7 +4522,7 @@ function registerWaveformRoutes(api, adapter) {
|
|
|
4460
4522
|
const cachePath = join12(cacheDir, buildWaveformCacheKey(assetPath, stats));
|
|
4461
4523
|
try {
|
|
4462
4524
|
if (isWaveformCacheDirectory(cacheDir) && existsSync8(cachePath)) {
|
|
4463
|
-
const peaks2 = JSON.parse(
|
|
4525
|
+
const peaks2 = JSON.parse(readFileSync13(cachePath, "utf-8"));
|
|
4464
4526
|
return c.json({ peaks: peaks2 });
|
|
4465
4527
|
}
|
|
4466
4528
|
} catch {
|
|
@@ -4946,14 +5008,14 @@ function registerMediaRoutes(api, adapter, options = {}) {
|
|
|
4946
5008
|
}
|
|
4947
5009
|
|
|
4948
5010
|
// src/routes/globalAssets.ts
|
|
4949
|
-
import { existsSync as existsSync10, readFileSync as
|
|
5011
|
+
import { existsSync as existsSync10, readFileSync as readFileSync14 } from "fs";
|
|
4950
5012
|
import { homedir } from "os";
|
|
4951
5013
|
import { join as join14 } from "path";
|
|
4952
5014
|
function readGlobalAssets(home = homedir()) {
|
|
4953
5015
|
const manifestPath = join14(home, ".media", "manifest.jsonl");
|
|
4954
5016
|
if (!existsSync10(manifestPath)) return [];
|
|
4955
5017
|
const out = [];
|
|
4956
|
-
for (const line of
|
|
5018
|
+
for (const line of readFileSync14(manifestPath, "utf8").split("\n")) {
|
|
4957
5019
|
if (!line.trim()) continue;
|
|
4958
5020
|
try {
|
|
4959
5021
|
const rec = JSON.parse(line);
|
|
@@ -5113,6 +5175,7 @@ export {
|
|
|
5113
5175
|
affectsProjectSignature,
|
|
5114
5176
|
applyFileMutations,
|
|
5115
5177
|
buildSubCompositionHtml,
|
|
5178
|
+
compositionsAffectedBy,
|
|
5116
5179
|
consumeFileWriteReceipt,
|
|
5117
5180
|
createBackgroundRemovalJob,
|
|
5118
5181
|
createProjectSignature,
|