@hyperframes/studio-server 0.8.118 → 0.8.119
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 +60 -20
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -4891,10 +4891,14 @@ function registerWaveformRoutes(api, adapter) {
|
|
|
4891
4891
|
|
|
4892
4892
|
// src/routes/freezeFrame.ts
|
|
4893
4893
|
import { execFile } from "child_process";
|
|
4894
|
-
import {
|
|
4895
|
-
import { dirname as dirname5, join as join13, relative as relative4, sep as sep2 } from "path";
|
|
4894
|
+
import { readFileSync as readFileSync11, rmSync as rmSync5, statSync as statSync8 } from "fs";
|
|
4895
|
+
import { basename as basename2, dirname as dirname5, join as join13, relative as relative4, sep as sep2 } from "path";
|
|
4896
4896
|
import { findFfBinary as findFfBinary3 } from "@hyperframes/parsers/ff-binaries";
|
|
4897
|
-
import {
|
|
4897
|
+
import {
|
|
4898
|
+
atomicTempPath,
|
|
4899
|
+
createFileAtomically as createFileAtomically2,
|
|
4900
|
+
replaceFileAtomically as replaceFileAtomically3
|
|
4901
|
+
} from "@hyperframes/core/atomic-file";
|
|
4898
4902
|
|
|
4899
4903
|
// src/helpers/freezeFrame.ts
|
|
4900
4904
|
import { createHash as createHash5, randomBytes } from "crypto";
|
|
@@ -4919,7 +4923,22 @@ function freezeFrameMediaTime(input) {
|
|
|
4919
4923
|
return input.mediaStart + sourceTimeAt(rate, Math.max(0, input.playhead - input.clipStart));
|
|
4920
4924
|
}
|
|
4921
4925
|
function freezeExtractArgs(src, mediaTime, output) {
|
|
4922
|
-
return [
|
|
4926
|
+
return [
|
|
4927
|
+
"-y",
|
|
4928
|
+
"-ss",
|
|
4929
|
+
String(round3(mediaTime)),
|
|
4930
|
+
"-i",
|
|
4931
|
+
src,
|
|
4932
|
+
"-frames:v",
|
|
4933
|
+
"1",
|
|
4934
|
+
"-c:v",
|
|
4935
|
+
"png",
|
|
4936
|
+
"-f",
|
|
4937
|
+
"image2",
|
|
4938
|
+
"-update",
|
|
4939
|
+
"1",
|
|
4940
|
+
output
|
|
4941
|
+
];
|
|
4923
4942
|
}
|
|
4924
4943
|
var randomStillToken = () => randomBytes(4).toString("hex");
|
|
4925
4944
|
function freezeStillFileName(clipId, playhead, token = randomStillToken()) {
|
|
@@ -5092,15 +5111,38 @@ function readExpected(absPath, expectedVersion) {
|
|
|
5092
5111
|
}
|
|
5093
5112
|
return fileContentVersion(content) === expectedVersion ? { content } : { error: "file conflict", status: 409 };
|
|
5094
5113
|
}
|
|
5095
|
-
function
|
|
5114
|
+
function readFrame(path) {
|
|
5115
|
+
try {
|
|
5116
|
+
return readFileSync11(path);
|
|
5117
|
+
} catch (error) {
|
|
5118
|
+
if (error.code === "ENOENT") return null;
|
|
5119
|
+
throw error;
|
|
5120
|
+
}
|
|
5121
|
+
}
|
|
5122
|
+
function publishStill(path, frame) {
|
|
5096
5123
|
try {
|
|
5097
|
-
|
|
5124
|
+
createFileAtomically2(path, frame);
|
|
5098
5125
|
return true;
|
|
5099
5126
|
} catch (error) {
|
|
5100
5127
|
if (error.code === "EEXIST") return false;
|
|
5101
5128
|
throw error;
|
|
5102
5129
|
}
|
|
5103
5130
|
}
|
|
5131
|
+
async function extractThenPublishWhole(extract, mediaPath, mediaTime, imagePath) {
|
|
5132
|
+
const unseenByHistory = atomicTempPath(imagePath);
|
|
5133
|
+
try {
|
|
5134
|
+
const extracted = await extract(freezeExtractArgs(mediaPath, mediaTime, unseenByHistory));
|
|
5135
|
+
const frame = extracted.ok ? readFrame(unseenByHistory) : null;
|
|
5136
|
+
if (!frame?.length) {
|
|
5137
|
+
const reason = extracted.ok ? "no frame at this time" : extracted.error ?? "ffmpeg failed";
|
|
5138
|
+
return { error: `Could not extract the frame: ${reason}`, status: 500 };
|
|
5139
|
+
}
|
|
5140
|
+
if (publishStill(imagePath, frame)) return null;
|
|
5141
|
+
return { error: `freeze still already exists: ${basename2(imagePath)}`, status: 409 };
|
|
5142
|
+
} finally {
|
|
5143
|
+
rmSync5(unseenByHistory, { force: true });
|
|
5144
|
+
}
|
|
5145
|
+
}
|
|
5104
5146
|
async function extractStill(projectDir, absPath, source, playhead, tools) {
|
|
5105
5147
|
const fileDir = dirname5(absPath);
|
|
5106
5148
|
const mediaPath = pinWithinProject(projectDir, relative4(projectDir, join13(fileDir, source.src)));
|
|
@@ -5112,15 +5154,13 @@ async function extractStill(projectDir, absPath, source, playhead, tools) {
|
|
|
5112
5154
|
if (!imagePath || dirname5(imagePath) !== freezeDir) {
|
|
5113
5155
|
return { error: `forbidden freeze path: ${fileName}`, status: 403 };
|
|
5114
5156
|
}
|
|
5115
|
-
|
|
5116
|
-
|
|
5117
|
-
|
|
5118
|
-
|
|
5119
|
-
|
|
5120
|
-
|
|
5121
|
-
|
|
5122
|
-
return { error: `Could not extract the frame: ${reason}`, status: 500 };
|
|
5123
|
-
}
|
|
5157
|
+
const failed = await extractThenPublishWhole(
|
|
5158
|
+
tools.extract,
|
|
5159
|
+
mediaPath,
|
|
5160
|
+
source.mediaTime,
|
|
5161
|
+
imagePath
|
|
5162
|
+
);
|
|
5163
|
+
if (failed) return failed;
|
|
5124
5164
|
const depth = relative4(projectDir, fileDir).split(sep2).filter(Boolean).length;
|
|
5125
5165
|
const stillPath = `${FREEZE_DIR.join("/")}/${fileName}`;
|
|
5126
5166
|
return { imageSrc: `${"../".repeat(depth)}${stillPath}`, stillPath, imagePath };
|
|
@@ -5409,7 +5449,7 @@ function registerPeakRoutes(api, adapter, decode = decodePeakMap) {
|
|
|
5409
5449
|
}
|
|
5410
5450
|
|
|
5411
5451
|
// src/routes/fonts.ts
|
|
5412
|
-
import { closeSync as
|
|
5452
|
+
import { closeSync as closeSync3, constants, fstatSync as fstatSync3, openSync as openSync3, readSync } from "fs";
|
|
5413
5453
|
import {
|
|
5414
5454
|
collectFontFileEntries,
|
|
5415
5455
|
fontDirectories,
|
|
@@ -5516,7 +5556,7 @@ function registerFontRoutes(api) {
|
|
|
5516
5556
|
if (!located) return c.json({ error: "font not found" }, 404);
|
|
5517
5557
|
let fd;
|
|
5518
5558
|
try {
|
|
5519
|
-
fd =
|
|
5559
|
+
fd = openSync3(located.path, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
5520
5560
|
} catch {
|
|
5521
5561
|
return c.json({ error: "font file not accessible" }, 404);
|
|
5522
5562
|
}
|
|
@@ -5538,7 +5578,7 @@ function registerFontRoutes(api) {
|
|
|
5538
5578
|
} catch {
|
|
5539
5579
|
return c.json({ error: "failed to read font file" }, 500);
|
|
5540
5580
|
} finally {
|
|
5541
|
-
|
|
5581
|
+
closeSync3(fd);
|
|
5542
5582
|
}
|
|
5543
5583
|
});
|
|
5544
5584
|
}
|
|
@@ -5675,7 +5715,7 @@ function registerSelectionRoutes(api, adapter) {
|
|
|
5675
5715
|
// src/routes/media.ts
|
|
5676
5716
|
import { streamSSE as streamSSE2 } from "hono/streaming";
|
|
5677
5717
|
import { existsSync as existsSync10 } from "fs";
|
|
5678
|
-
import { basename as
|
|
5718
|
+
import { basename as basename3, dirname as dirname6, extname, join as join15, posix } from "path";
|
|
5679
5719
|
var VIDEO_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
5680
5720
|
".mp4",
|
|
5681
5721
|
".mov",
|
|
@@ -5705,7 +5745,7 @@ function containsNullByte(path) {
|
|
|
5705
5745
|
return path.includes("\0");
|
|
5706
5746
|
}
|
|
5707
5747
|
function slugFileBase(path) {
|
|
5708
|
-
const name =
|
|
5748
|
+
const name = basename3(path, extname(path)).replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
5709
5749
|
return name || "media";
|
|
5710
5750
|
}
|
|
5711
5751
|
function uniqueAssetPath(projectDir, assetPath, taken) {
|