@hyperframes/studio-server 0.8.30 → 0.8.31
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 +95 -52
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -364,12 +364,12 @@ import {
|
|
|
364
364
|
writeSync,
|
|
365
365
|
mkdirSync as mkdirSync3,
|
|
366
366
|
unlinkSync as unlinkSync2,
|
|
367
|
-
rmSync as
|
|
367
|
+
rmSync as rmSync3,
|
|
368
368
|
statSync as statSync2,
|
|
369
|
-
renameSync,
|
|
369
|
+
renameSync as renameSync2,
|
|
370
370
|
readdirSync as readdirSync4
|
|
371
371
|
} from "fs";
|
|
372
|
-
import { resolve as resolve3, dirname as
|
|
372
|
+
import { resolve as resolve3, dirname as dirname3, join as join6 } from "path";
|
|
373
373
|
|
|
374
374
|
// src/helpers/mime.ts
|
|
375
375
|
var MIME_TYPES = {
|
|
@@ -419,8 +419,17 @@ function isAudioFile(name) {
|
|
|
419
419
|
|
|
420
420
|
// src/helpers/waveform.ts
|
|
421
421
|
import { spawn } from "child_process";
|
|
422
|
-
import {
|
|
423
|
-
|
|
422
|
+
import {
|
|
423
|
+
existsSync as existsSync2,
|
|
424
|
+
writeFileSync,
|
|
425
|
+
mkdirSync,
|
|
426
|
+
lstatSync as lstatSync2,
|
|
427
|
+
mkdtempSync,
|
|
428
|
+
renameSync,
|
|
429
|
+
rmSync,
|
|
430
|
+
statSync
|
|
431
|
+
} from "fs";
|
|
432
|
+
import { dirname, join as join3 } from "path";
|
|
424
433
|
import { findFfBinary } from "@hyperframes/parsers/ff-binaries";
|
|
425
434
|
var SAMPLE_RATE = 4e3;
|
|
426
435
|
var PEAK_COUNT = 4e3;
|
|
@@ -492,15 +501,39 @@ async function generateWaveformCache(projectDir, assetPath) {
|
|
|
492
501
|
const stats = statSync(audioPath);
|
|
493
502
|
const cacheDir = join3(projectDir, ".waveform-cache");
|
|
494
503
|
const cachePath = join3(cacheDir, buildWaveformCacheKey(assetPath, stats));
|
|
495
|
-
if (existsSync2(cachePath)) return;
|
|
504
|
+
if (isWaveformCacheDirectory(cacheDir) && existsSync2(cachePath)) return;
|
|
496
505
|
const peaks = await decodeAudioPeaks(audioPath);
|
|
497
|
-
|
|
498
|
-
|
|
506
|
+
writeWaveformCache(cachePath, peaks);
|
|
507
|
+
}
|
|
508
|
+
function isWaveformCacheDirectory(cacheDir) {
|
|
509
|
+
return lstatSync2(cacheDir, { throwIfNoEntry: false })?.isDirectory() ?? false;
|
|
510
|
+
}
|
|
511
|
+
function writeWaveformCache(cachePath, peaks) {
|
|
512
|
+
const cacheDir = dirname(cachePath);
|
|
513
|
+
try {
|
|
514
|
+
mkdirSync(cacheDir, { mode: 448 });
|
|
515
|
+
} catch (error) {
|
|
516
|
+
if (!(error instanceof Error && "code" in error && error.code === "EEXIST")) throw error;
|
|
517
|
+
}
|
|
518
|
+
if (!isWaveformCacheDirectory(cacheDir)) {
|
|
519
|
+
throw new Error("Waveform cache must be a directory, not a symlink");
|
|
520
|
+
}
|
|
521
|
+
const stagingDir = mkdtempSync(join3(cacheDir, ".waveform-"));
|
|
522
|
+
try {
|
|
523
|
+
const stagingPath = join3(stagingDir, "peaks.json");
|
|
524
|
+
writeFileSync(stagingPath, JSON.stringify(peaks), { flag: "wx" });
|
|
525
|
+
renameSync(stagingPath, cachePath);
|
|
526
|
+
} finally {
|
|
527
|
+
try {
|
|
528
|
+
rmSync(stagingDir, { recursive: true, force: true });
|
|
529
|
+
} catch {
|
|
530
|
+
}
|
|
531
|
+
}
|
|
499
532
|
}
|
|
500
533
|
|
|
501
534
|
// src/helpers/mediaValidation.ts
|
|
502
535
|
import { spawnSync } from "child_process";
|
|
503
|
-
import { mkdtempSync, rmSync, writeFileSync as writeFileSync2 } from "fs";
|
|
536
|
+
import { mkdtempSync as mkdtempSync2, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
504
537
|
import { tmpdir } from "os";
|
|
505
538
|
import { basename, join as join4 } from "path";
|
|
506
539
|
var VIDEO_EXT = /\.(mp4|webm|mov|mkv|avi|m4v|mxf|mts|m2ts|ts)$/i;
|
|
@@ -539,13 +572,13 @@ function validateUploadedMedia(filePath, runner = spawnSync) {
|
|
|
539
572
|
}
|
|
540
573
|
}
|
|
541
574
|
function validateUploadedMediaBuffer(fileName, buffer, runner = spawnSync) {
|
|
542
|
-
const tempDir =
|
|
575
|
+
const tempDir = mkdtempSync2(join4(tmpdir(), "hyperframes-upload-"));
|
|
543
576
|
const tempPath = join4(tempDir, basename(fileName));
|
|
544
577
|
try {
|
|
545
578
|
writeFileSync2(tempPath, buffer);
|
|
546
579
|
return validateUploadedMedia(tempPath, runner);
|
|
547
580
|
} finally {
|
|
548
|
-
|
|
581
|
+
rmSync2(tempDir, { recursive: true, force: true });
|
|
549
582
|
}
|
|
550
583
|
}
|
|
551
584
|
|
|
@@ -687,7 +720,7 @@ import { parseHTML as parseHTML2 } from "linkedom";
|
|
|
687
720
|
// src/helpers/compositionInsertion.ts
|
|
688
721
|
import { existsSync as existsSync3, readFileSync as readFileSync4, realpathSync } from "fs";
|
|
689
722
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
690
|
-
import { dirname, relative as relative3, resolve as resolve2, sep as sep2 } from "path";
|
|
723
|
+
import { dirname as dirname2, relative as relative3, resolve as resolve2, sep as sep2 } from "path";
|
|
691
724
|
import { parseHTML } from "linkedom";
|
|
692
725
|
var CompositionInsertionError = class extends Error {
|
|
693
726
|
constructor(message, status) {
|
|
@@ -742,7 +775,7 @@ function canonicalDependency(projectDir, ownerAbs, sourcePath) {
|
|
|
742
775
|
validateSourcePath(sourcePath);
|
|
743
776
|
return canonicalProjectPath(
|
|
744
777
|
projectDir,
|
|
745
|
-
resolveWithinProject(projectDir, relative3(projectDir, resolve2(
|
|
778
|
+
resolveWithinProject(projectDir, relative3(projectDir, resolve2(dirname2(ownerAbs), sourcePath)))
|
|
746
779
|
);
|
|
747
780
|
}
|
|
748
781
|
function validateDependencyGraph(projectDir, targetAbs, sourceAbs) {
|
|
@@ -815,7 +848,7 @@ function uniqueHostId(root, base) {
|
|
|
815
848
|
return `${base}_${suffix}`;
|
|
816
849
|
}
|
|
817
850
|
function relativeSourcePath(targetAbs, sourceAbs) {
|
|
818
|
-
return relative3(
|
|
851
|
+
return relative3(dirname2(targetAbs), sourceAbs).split(sep2).join("/");
|
|
819
852
|
}
|
|
820
853
|
function insertCompositionIntoSource(input) {
|
|
821
854
|
const targetAbs = canonicalProjectFile(input.projectDir, input.targetPath);
|
|
@@ -1153,7 +1186,7 @@ async function parseMutationBody(c) {
|
|
|
1153
1186
|
return { target: body.target, body };
|
|
1154
1187
|
}
|
|
1155
1188
|
function ensureDir(filePath) {
|
|
1156
|
-
const dir =
|
|
1189
|
+
const dir = dirname3(filePath);
|
|
1157
1190
|
if (!existsSync4(dir)) mkdirSync3(dir, { recursive: true });
|
|
1158
1191
|
}
|
|
1159
1192
|
function generateCopyPath(projectDir, originalPath) {
|
|
@@ -2457,7 +2490,7 @@ function registerFileRoutes(api, adapter) {
|
|
|
2457
2490
|
const backup = snapshotBeforeWrite(res.project.dir, res.absPath);
|
|
2458
2491
|
if (backup.error) console.warn(`Failed to create backup for ${res.filePath}: ${backup.error}`);
|
|
2459
2492
|
if (stat.isDirectory()) {
|
|
2460
|
-
|
|
2493
|
+
rmSync3(res.absPath, { recursive: true });
|
|
2461
2494
|
} else {
|
|
2462
2495
|
unlinkSync2(res.absPath);
|
|
2463
2496
|
}
|
|
@@ -2953,7 +2986,7 @@ function registerFileRoutes(api, adapter) {
|
|
|
2953
2986
|
return c.json({ error: "already exists" }, 409);
|
|
2954
2987
|
}
|
|
2955
2988
|
ensureDir(newAbs);
|
|
2956
|
-
|
|
2989
|
+
renameSync2(res.absPath, newAbs);
|
|
2957
2990
|
const updatedFiles = updateReferences(res.project.dir, res.filePath, body.newPath);
|
|
2958
2991
|
return c.json({ ok: true, path: body.newPath, updatedReferences: updatedFiles });
|
|
2959
2992
|
});
|
|
@@ -3073,7 +3106,8 @@ function registerFileRoutes(api, adapter) {
|
|
|
3073
3106
|
}
|
|
3074
3107
|
|
|
3075
3108
|
// src/routes/preview.ts
|
|
3076
|
-
import { existsSync as existsSync6, readFileSync as readFileSync8, statSync as statSync3 } from "fs";
|
|
3109
|
+
import { createReadStream, existsSync as existsSync6, readFileSync as readFileSync8, statSync as statSync3 } from "fs";
|
|
3110
|
+
import { Readable } from "stream";
|
|
3077
3111
|
import { join as join8, resolve as resolve4 } from "path";
|
|
3078
3112
|
import { createHash as createHash3 } from "crypto";
|
|
3079
3113
|
import { injectScriptsIntoHtml, stripEmbeddedRuntimeScripts as stripEmbeddedRuntimeScripts2 } from "@hyperframes/core/compiler";
|
|
@@ -3714,29 +3748,39 @@ ${runtimeTag}`;
|
|
|
3714
3748
|
}
|
|
3715
3749
|
servedContentType = PROXY_VARIANT_CONFIG[proxyVariant].contentType;
|
|
3716
3750
|
}
|
|
3717
|
-
const
|
|
3718
|
-
const totalSize =
|
|
3751
|
+
const textBuffer = isText ? Buffer.from(readFileSync8(file, "utf-8"), "utf-8") : null;
|
|
3752
|
+
const totalSize = textBuffer ? textBuffer.length : statSync3(servedPath).size;
|
|
3753
|
+
const bodyFor = (start, end) => textBuffer ? new Uint8Array(textBuffer.subarray(start, end + 1)) : (
|
|
3754
|
+
// Node's web stream type and the DOM one do not overlap for tsc on
|
|
3755
|
+
// every platform's lib set; the double cast is the documented bridge.
|
|
3756
|
+
Readable.toWeb(
|
|
3757
|
+
createReadStream(servedPath, { start, end })
|
|
3758
|
+
)
|
|
3759
|
+
);
|
|
3719
3760
|
const rangeHeader = c.req.header("Range");
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
headers: {
|
|
3730
|
-
...cacheHeaders,
|
|
3731
|
-
"Content-Type": servedContentType,
|
|
3732
|
-
"Content-Range": `bytes ${start}-${safeEnd}/${totalSize}`,
|
|
3733
|
-
"Accept-Ranges": "bytes",
|
|
3734
|
-
"Content-Length": String(chunkSize)
|
|
3735
|
-
}
|
|
3761
|
+
const match = rangeHeader ? /bytes=(\d+)-(\d*)/.exec(rangeHeader) : null;
|
|
3762
|
+
if (match) {
|
|
3763
|
+
const start = parseInt(match[1], 10);
|
|
3764
|
+
const end = match[2] ? parseInt(match[2], 10) : totalSize - 1;
|
|
3765
|
+
const safeEnd = Math.min(end, totalSize - 1);
|
|
3766
|
+
if (start > safeEnd) {
|
|
3767
|
+
return new Response(null, {
|
|
3768
|
+
status: 416,
|
|
3769
|
+
headers: { ...cacheHeaders, "Content-Range": `bytes */${totalSize}` }
|
|
3736
3770
|
});
|
|
3737
3771
|
}
|
|
3772
|
+
return new Response(bodyFor(start, safeEnd), {
|
|
3773
|
+
status: 206,
|
|
3774
|
+
headers: {
|
|
3775
|
+
...cacheHeaders,
|
|
3776
|
+
"Content-Type": servedContentType,
|
|
3777
|
+
"Content-Range": `bytes ${start}-${safeEnd}/${totalSize}`,
|
|
3778
|
+
"Accept-Ranges": "bytes",
|
|
3779
|
+
"Content-Length": String(safeEnd - start + 1)
|
|
3780
|
+
}
|
|
3781
|
+
});
|
|
3738
3782
|
}
|
|
3739
|
-
return new Response(
|
|
3783
|
+
return new Response(totalSize > 0 ? bodyFor(0, totalSize - 1) : null, {
|
|
3740
3784
|
headers: {
|
|
3741
3785
|
...cacheHeaders,
|
|
3742
3786
|
"Content-Type": servedContentType,
|
|
@@ -4041,8 +4085,8 @@ import {
|
|
|
4041
4085
|
mkdirSync as mkdirSync5,
|
|
4042
4086
|
readFileSync as readFileSync11,
|
|
4043
4087
|
readdirSync as readdirSync6,
|
|
4044
|
-
renameSync as
|
|
4045
|
-
rmSync as
|
|
4088
|
+
renameSync as renameSync3,
|
|
4089
|
+
rmSync as rmSync4,
|
|
4046
4090
|
statSync as statSync5,
|
|
4047
4091
|
unlinkSync as unlinkSync4,
|
|
4048
4092
|
writeFileSync as writeFileSync6
|
|
@@ -4176,7 +4220,7 @@ function pruneThumbnailCache(cacheDir, protectedPaths, now = Date.now()) {
|
|
|
4176
4220
|
const retained = [];
|
|
4177
4221
|
for (const file of files) {
|
|
4178
4222
|
if (!protectedPaths.has(file.path) && now - file.mtimeMs > THUMBNAIL_CACHE_MAX_AGE_MS) {
|
|
4179
|
-
|
|
4223
|
+
rmSync4(file.path, { force: true });
|
|
4180
4224
|
} else {
|
|
4181
4225
|
retained.push(file);
|
|
4182
4226
|
}
|
|
@@ -4196,9 +4240,9 @@ function writeThumbnailAtomically(path, buffer) {
|
|
|
4196
4240
|
const temporaryPath = `${path}.${process.pid}.${randomUUID3()}.tmp`;
|
|
4197
4241
|
try {
|
|
4198
4242
|
writeFileSync6(temporaryPath, buffer, { flag: "wx" });
|
|
4199
|
-
|
|
4243
|
+
renameSync3(temporaryPath, path);
|
|
4200
4244
|
} finally {
|
|
4201
|
-
|
|
4245
|
+
rmSync4(temporaryPath, { force: true });
|
|
4202
4246
|
}
|
|
4203
4247
|
}
|
|
4204
4248
|
function registerThumbnailRoutes(api, adapter) {
|
|
@@ -4334,7 +4378,7 @@ function registerThumbnailRoutes(api, adapter) {
|
|
|
4334
4378
|
}
|
|
4335
4379
|
|
|
4336
4380
|
// src/routes/waveform.ts
|
|
4337
|
-
import { existsSync as existsSync9, readFileSync as readFileSync12,
|
|
4381
|
+
import { existsSync as existsSync9, readFileSync as readFileSync12, statSync as statSync6 } from "fs";
|
|
4338
4382
|
import { join as join12 } from "path";
|
|
4339
4383
|
function registerWaveformRoutes(api, adapter) {
|
|
4340
4384
|
api.get("/projects/:id/waveform/*", async (c) => {
|
|
@@ -4348,12 +4392,12 @@ function registerWaveformRoutes(api, adapter) {
|
|
|
4348
4392
|
if (!stats) return c.json({ error: "file not found" }, 404);
|
|
4349
4393
|
const cacheDir = join12(project.dir, ".waveform-cache");
|
|
4350
4394
|
const cachePath = join12(cacheDir, buildWaveformCacheKey(assetPath, stats));
|
|
4351
|
-
|
|
4352
|
-
|
|
4395
|
+
try {
|
|
4396
|
+
if (isWaveformCacheDirectory(cacheDir) && existsSync9(cachePath)) {
|
|
4353
4397
|
const peaks2 = JSON.parse(readFileSync12(cachePath, "utf-8"));
|
|
4354
4398
|
return c.json({ peaks: peaks2 });
|
|
4355
|
-
} catch {
|
|
4356
4399
|
}
|
|
4400
|
+
} catch {
|
|
4357
4401
|
}
|
|
4358
4402
|
let peaks;
|
|
4359
4403
|
try {
|
|
@@ -4362,8 +4406,7 @@ function registerWaveformRoutes(api, adapter) {
|
|
|
4362
4406
|
return c.json({ error: "failed to decode audio" }, 500);
|
|
4363
4407
|
}
|
|
4364
4408
|
try {
|
|
4365
|
-
|
|
4366
|
-
writeFileSync7(cachePath, JSON.stringify(peaks));
|
|
4409
|
+
writeWaveformCache(cachePath, peaks);
|
|
4367
4410
|
} catch {
|
|
4368
4411
|
}
|
|
4369
4412
|
return c.json({ peaks });
|
|
@@ -4634,8 +4677,8 @@ function registerSelectionRoutes(api, adapter) {
|
|
|
4634
4677
|
|
|
4635
4678
|
// src/routes/media.ts
|
|
4636
4679
|
import { streamSSE as streamSSE2 } from "hono/streaming";
|
|
4637
|
-
import { existsSync as existsSync10, mkdirSync as
|
|
4638
|
-
import { basename as basename2, dirname as
|
|
4680
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync6 } from "fs";
|
|
4681
|
+
import { basename as basename2, dirname as dirname4, extname as extname2, join as join13 } from "path";
|
|
4639
4682
|
var VIDEO_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
4640
4683
|
".mp4",
|
|
4641
4684
|
".mov",
|
|
@@ -4779,8 +4822,8 @@ function registerMediaRoutes(api, adapter, options = {}) {
|
|
|
4779
4822
|
return c.json({ error: "forbidden" }, 403);
|
|
4780
4823
|
}
|
|
4781
4824
|
}
|
|
4782
|
-
|
|
4783
|
-
if (backgroundOutputPath)
|
|
4825
|
+
mkdirSync6(dirname4(outputPath), { recursive: true });
|
|
4826
|
+
if (backgroundOutputPath) mkdirSync6(dirname4(backgroundOutputPath), { recursive: true });
|
|
4784
4827
|
const jobId = makeJobId(project.id, mediaJobs);
|
|
4785
4828
|
const state = adapter.startBackgroundRemoval({
|
|
4786
4829
|
project,
|