@hyperframes/studio-server 0.9.1 → 0.9.3
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/chunk-6HAB346W.js +11 -0
- package/dist/{chunk-FJBJJURY.js → chunk-7ROCGO5U.js} +35 -19
- package/dist/{chunk-G4VPDLXC.js → chunk-CEQWOCYI.js} +17 -5
- package/dist/{chunk-JVMMZB22.js → chunk-PN2VGFAA.js} +1 -1
- package/dist/helpers/hfIdPersist.d.ts +3 -1
- package/dist/helpers/hfIdPersist.js +5 -3
- package/dist/helpers/mediaProxyPreview.js +2 -2
- package/dist/helpers/mediaRange.d.ts +4 -0
- package/dist/helpers/mediaRange.js +8 -0
- package/dist/helpers/proxyTranscoder.d.ts +1 -0
- package/dist/helpers/proxyTranscoder.js +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +16 -7
- package/package.json +7 -3
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// src/helpers/mediaRange.ts
|
|
2
|
+
var MAX_OPEN_MEDIA_RANGE_BYTES = 4 * 1024 * 1024;
|
|
3
|
+
var isMedia = (contentType) => /^(audio|video)\//i.test(contentType);
|
|
4
|
+
function openRangeEnd(start, last, contentType) {
|
|
5
|
+
return isMedia(contentType) ? Math.min(last, start + MAX_OPEN_MEDIA_RANGE_BYTES - 1) : last;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export {
|
|
9
|
+
MAX_OPEN_MEDIA_RANGE_BYTES,
|
|
10
|
+
openRangeEnd
|
|
11
|
+
};
|
|
@@ -14,36 +14,51 @@ var isCompositionSource = (html) => /data-composition-id\s*=/.test(html);
|
|
|
14
14
|
var STAMP_RECORD = join(".hyperframes", "hf-ids-stamped.json");
|
|
15
15
|
var contentHash = (text) => createHash("sha1").update(text).digest("base64url");
|
|
16
16
|
function stampProjectHfIds(projectDir) {
|
|
17
|
+
const pass = stampPass(projectDir);
|
|
18
|
+
while (!pass.next().done) ;
|
|
19
|
+
}
|
|
20
|
+
async function stampProjectHfIdsAsync(projectDir) {
|
|
21
|
+
const pass = stampPass(projectDir);
|
|
22
|
+
while (!pass.next().done) await new Promise((resolve) => setImmediate(resolve));
|
|
23
|
+
}
|
|
24
|
+
function* stampPass(projectDir) {
|
|
17
25
|
const recordPath = join(projectDir, STAMP_RECORD);
|
|
18
|
-
|
|
19
|
-
try {
|
|
20
|
-
last = JSON.parse(readFileSync(recordPath, "utf-8"));
|
|
21
|
-
} catch {
|
|
22
|
-
}
|
|
26
|
+
const last = readStampRecord(recordPath);
|
|
23
27
|
const next = {};
|
|
24
28
|
for (const file of walkDir(projectDir)) {
|
|
25
29
|
if (!file.endsWith(".html") || isInHiddenOrVendorDir(file)) continue;
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
html = readFileSync(absPath, "utf-8");
|
|
30
|
-
} catch {
|
|
31
|
-
continue;
|
|
32
|
-
}
|
|
33
|
-
let hash = contentHash(html);
|
|
34
|
-
if (last[file] !== hash && isCompositionSource(html)) {
|
|
35
|
-
const stamped = stampFileHfIds(absPath);
|
|
36
|
-
if (stamped === null) continue;
|
|
37
|
-
hash = contentHash(stamped);
|
|
38
|
-
}
|
|
39
|
-
next[file] = hash;
|
|
30
|
+
const hash = hashAfterStamp(join(projectDir, file), last[file]);
|
|
31
|
+
if (hash !== null) next[file] = hash;
|
|
32
|
+
yield;
|
|
40
33
|
}
|
|
34
|
+
writeStampRecord(projectDir, recordPath, next);
|
|
35
|
+
}
|
|
36
|
+
function readStampRecord(recordPath) {
|
|
37
|
+
try {
|
|
38
|
+
return JSON.parse(readFileSync(recordPath, "utf-8"));
|
|
39
|
+
} catch {
|
|
40
|
+
return {};
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function writeStampRecord(projectDir, recordPath, next) {
|
|
41
44
|
try {
|
|
42
45
|
mkdirWithinProject(projectDir, dirname(recordPath));
|
|
43
46
|
writeFileSync(recordPath, JSON.stringify(next));
|
|
44
47
|
} catch {
|
|
45
48
|
}
|
|
46
49
|
}
|
|
50
|
+
function hashAfterStamp(absPath, recorded) {
|
|
51
|
+
let html;
|
|
52
|
+
try {
|
|
53
|
+
html = readFileSync(absPath, "utf-8");
|
|
54
|
+
} catch {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
const hash = contentHash(html);
|
|
58
|
+
if (recorded === hash || !isCompositionSource(html)) return hash;
|
|
59
|
+
const stamped = stampFileHfIds(absPath);
|
|
60
|
+
return stamped === null ? null : contentHash(stamped);
|
|
61
|
+
}
|
|
47
62
|
function openNoFollow(filePath, flags) {
|
|
48
63
|
const noFollow = constants.O_NOFOLLOW ?? 0;
|
|
49
64
|
try {
|
|
@@ -93,5 +108,6 @@ function stampFileHfIds(filePath) {
|
|
|
93
108
|
export {
|
|
94
109
|
isCompositionSource,
|
|
95
110
|
stampProjectHfIds,
|
|
111
|
+
stampProjectHfIdsAsync,
|
|
96
112
|
stampFileHfIds
|
|
97
113
|
};
|
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
utimesSync,
|
|
25
25
|
writeFileSync as writeFileSync2
|
|
26
26
|
} from "fs";
|
|
27
|
-
import { availableParallelism, tmpdir, totalmem } from "os";
|
|
27
|
+
import os, { availableParallelism, tmpdir, totalmem } from "os";
|
|
28
28
|
import { basename as basename3, dirname as dirname3, extname as extname4, isAbsolute, join as join4, relative, resolve, sep } from "path";
|
|
29
29
|
import { hdrToSdrToneMapFilter } from "@hyperframes/core";
|
|
30
30
|
import { findFfBinary } from "@hyperframes/parsers/ff-binaries";
|
|
@@ -317,6 +317,7 @@ function boundedEnvInteger(name, fallback, min, max) {
|
|
|
317
317
|
return Number.isSafeInteger(parsed) && parsed >= min && parsed <= max ? parsed : fallback;
|
|
318
318
|
}
|
|
319
319
|
var MAX_CONCURRENT_TRANSCODES = boundedEnvInteger("HYPERFRAMES_PROXY_MAX_CONCURRENCY", 2, 1, 16);
|
|
320
|
+
var BACKGROUND_NICENESS = 19;
|
|
320
321
|
var MAX_QUEUED_TRANSCODES = boundedEnvInteger("HYPERFRAMES_PROXY_MAX_QUEUE", 8, 0, 256);
|
|
321
322
|
var STDERR_TAIL_MAX_CHARS = 4e3;
|
|
322
323
|
var TRANSCODE_STALL_MS = boundedEnvInteger(
|
|
@@ -754,7 +755,7 @@ async function buildVideoFilter(ffmpegPath, sourcePath, metadata, keepsAlpha, bo
|
|
|
754
755
|
`format=${pixelFormat}`
|
|
755
756
|
].join(",") : [evenScale, `format=${pixelFormat}`].join(",");
|
|
756
757
|
}
|
|
757
|
-
async function runFfmpeg(sourcePath, outputPath, chunkPrefix, variant, box, signal, onProgress) {
|
|
758
|
+
async function runFfmpeg(sourcePath, outputPath, chunkPrefix, variant, box, signal, onProgress, isBackground = () => false) {
|
|
758
759
|
const metadata = await probeMediaMetadata(sourcePath);
|
|
759
760
|
const ffmpegPath = findFfBinary("ffmpeg", { configuredMustExist: true });
|
|
760
761
|
if (!ffmpegPath) {
|
|
@@ -795,7 +796,8 @@ async function runFfmpeg(sourcePath, outputPath, chunkPrefix, variant, box, sign
|
|
|
795
796
|
],
|
|
796
797
|
TRANSCODE_STALL_MS,
|
|
797
798
|
pieceSignal,
|
|
798
|
-
onEncoded
|
|
799
|
+
onEncoded,
|
|
800
|
+
isBackground()
|
|
799
801
|
);
|
|
800
802
|
const ranges = chunkRanges(totalSeconds);
|
|
801
803
|
try {
|
|
@@ -837,7 +839,14 @@ function concatArgs(listPath, target, variant) {
|
|
|
837
839
|
];
|
|
838
840
|
}
|
|
839
841
|
var OUT_TIME = /^out_time_(?:us|ms)=(\d+)$/;
|
|
840
|
-
function
|
|
842
|
+
function lowerPriorityUnlessExited(pid) {
|
|
843
|
+
try {
|
|
844
|
+
os.setPriority(pid, BACKGROUND_NICENESS);
|
|
845
|
+
} catch {
|
|
846
|
+
return;
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
function spawnFfmpeg(ffmpegPath, args, stallMs, signal, onProgress, background = false) {
|
|
841
850
|
return new Promise((resolvePromise, reject) => {
|
|
842
851
|
signal.throwIfAborted();
|
|
843
852
|
const proc = spawn(ffmpegPath, args, {
|
|
@@ -846,6 +855,7 @@ function spawnFfmpeg(ffmpegPath, args, stallMs, signal, onProgress) {
|
|
|
846
855
|
signal,
|
|
847
856
|
windowsHide: true
|
|
848
857
|
});
|
|
858
|
+
if (background && proc.pid !== void 0) lowerPriorityUnlessExited(proc.pid);
|
|
849
859
|
let stalled = false;
|
|
850
860
|
let timer;
|
|
851
861
|
const armStall = () => {
|
|
@@ -951,7 +961,8 @@ async function transcodeToCache(job, projectDir, absoluteSourcePath, cachePath,
|
|
|
951
961
|
variant,
|
|
952
962
|
box,
|
|
953
963
|
job.abandoned.signal,
|
|
954
|
-
(progress) => reportProgress(job, progress)
|
|
964
|
+
(progress) => reportProgress(job, progress),
|
|
965
|
+
() => job.background && !job.priority
|
|
955
966
|
);
|
|
956
967
|
renameSync(tempPath, cachePath);
|
|
957
968
|
writeCopyIndex(contentIndex, cachePath);
|
|
@@ -1004,6 +1015,7 @@ async function resolveProxy(projectDir, absoluteSourcePath, variant = "h264", bo
|
|
|
1004
1015
|
const job = {
|
|
1005
1016
|
cachePath,
|
|
1006
1017
|
priority: options.priority === true,
|
|
1018
|
+
background: options.background === true,
|
|
1007
1019
|
callers: 0,
|
|
1008
1020
|
abandoned: new AbortController(),
|
|
1009
1021
|
progress: /* @__PURE__ */ new Set()
|
|
@@ -3,6 +3,8 @@ declare const isCompositionSource: (html: string) => boolean;
|
|
|
3
3
|
* watches the project: an id write during a session reaches Studio as an outside edit and reloads it.
|
|
4
4
|
* A file whose content hash matches the last run's (kept in `.hyperframes`) is not parsed again. */
|
|
5
5
|
declare function stampProjectHfIds(projectDir: string): void;
|
|
6
|
+
/** stampProjectHfIds for a host whose event loop must keep answering: it yields to the loop after each file. */
|
|
7
|
+
declare function stampProjectHfIdsAsync(projectDir: string): Promise<void>;
|
|
6
8
|
/**
|
|
7
9
|
* Read `filePath`, mint any missing `data-hf-id`s, write the stamped content
|
|
8
10
|
* back if new ids were added, and return the stamped content. Every open of the path is
|
|
@@ -20,4 +22,4 @@ declare function stampProjectHfIds(projectDir: string): void;
|
|
|
20
22
|
*/
|
|
21
23
|
declare function stampFileHfIds(filePath: string): string | null;
|
|
22
24
|
|
|
23
|
-
export { isCompositionSource, stampFileHfIds, stampProjectHfIds };
|
|
25
|
+
export { isCompositionSource, stampFileHfIds, stampProjectHfIds, stampProjectHfIdsAsync };
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
isCompositionSource,
|
|
3
3
|
stampFileHfIds,
|
|
4
|
-
stampProjectHfIds
|
|
5
|
-
|
|
4
|
+
stampProjectHfIds,
|
|
5
|
+
stampProjectHfIdsAsync
|
|
6
|
+
} from "../chunk-7ROCGO5U.js";
|
|
6
7
|
import "../chunk-QF7MFJJU.js";
|
|
7
8
|
import "../chunk-SVQFE44C.js";
|
|
8
9
|
import "../chunk-L3HJVTQE.js";
|
|
9
10
|
export {
|
|
10
11
|
isCompositionSource,
|
|
11
12
|
stampFileHfIds,
|
|
12
|
-
stampProjectHfIds
|
|
13
|
+
stampProjectHfIds,
|
|
14
|
+
stampProjectHfIdsAsync
|
|
13
15
|
};
|
|
@@ -4,8 +4,8 @@ import {
|
|
|
4
4
|
isAutoProxyEnabled,
|
|
5
5
|
proxyEtagSalt,
|
|
6
6
|
resolvePreviewMediaCodecProbeCache
|
|
7
|
-
} from "../chunk-
|
|
8
|
-
import "../chunk-
|
|
7
|
+
} from "../chunk-PN2VGFAA.js";
|
|
8
|
+
import "../chunk-CEQWOCYI.js";
|
|
9
9
|
import "../chunk-FUTDRYQK.js";
|
|
10
10
|
import "../chunk-X73QTST2.js";
|
|
11
11
|
import "../chunk-QF7MFJJU.js";
|
|
@@ -89,6 +89,7 @@ declare function resolveProxies(projectDir: string, sources: ReadonlyArray<{
|
|
|
89
89
|
}>, onSettled?: (index: number) => void, onProgress?: (index: number, progress: ProxyProgress) => void, signal?: AbortSignal): Promise<PromiseSettledResult<string>[]>;
|
|
90
90
|
interface ResolveProxyOptions {
|
|
91
91
|
priority?: boolean;
|
|
92
|
+
background?: boolean;
|
|
92
93
|
signal?: AbortSignal;
|
|
93
94
|
/** Hears how far the shared copy has got, from the first caller's start to the copy's end. */
|
|
94
95
|
onProgress?: (progress: ProxyProgress) => void;
|
package/dist/index.d.ts
CHANGED
|
@@ -5,9 +5,10 @@ export { C as ClosedWindow, D as DEFAULT_HISTORY_ROOT, S as HISTORY_START, H as
|
|
|
5
5
|
export { ArtifactCapture, ArtifactInput, ArtifactRecord, HistoryBusyError, HistoryIdError, InPlaceOutcome, ProjectHistoryRecord, PrunedHistory, ReclaimedHistory, RenderRecord, VideoState, artifactDirectory, beginArtifactCapture, finishSnapshotRun, forgetRecordedArtifacts, historyCache, isUncopiedMedia, listProjectHistories, onRestoredInPlace, pruneGoneProjectHistories, reclaimUncopiedBlobs, recordArtifact, recordArtifacts, recordRenderSuccess, registerRenderOutput, retainSnapshotOutput } from './history/index.js';
|
|
6
6
|
export { CACHE_DIR_NAME as PROXY_CACHE_DIR_NAME } from './helpers/proxyTranscoder.js';
|
|
7
7
|
export { walkDir } from './helpers/safePath.js';
|
|
8
|
-
export { stampProjectHfIds } from './helpers/hfIdPersist.js';
|
|
8
|
+
export { stampProjectHfIds, stampProjectHfIdsAsync } from './helpers/hfIdPersist.js';
|
|
9
9
|
import { SourceMutationTarget } from './helpers/sourceMutation.js';
|
|
10
10
|
export { PatchOperation, dedupeClonedCompositionId, findTargetElement, parseSourceDocument, patchElementInHtml, removeElementFromHtml, splitElementInHtml } from './helpers/sourceMutation.js';
|
|
11
|
+
export { MAX_OPEN_MEDIA_RANGE_BYTES, openRangeEnd } from './helpers/mediaRange.js';
|
|
11
12
|
export { ScreenshotClip, clearElementScreenshotIsolation, getElementScreenshotClip } from './helpers/screenshotClip.js';
|
|
12
13
|
export { STUDIO_MANUAL_EDITS_PATH, StudioManualEditsRenderScriptOptions, createStudioManualEditsRenderBodyScript, createStudioPositionSeekReapplyScript } from './helpers/manualEditsRenderScript.js';
|
|
13
14
|
export { STUDIO_MOTION_PATH, StudioMotionRenderScriptOptions, createStudioMotionRenderBodyScript } from './helpers/studioMotionRenderScript.js';
|
package/dist/index.js
CHANGED
|
@@ -20,11 +20,16 @@ import {
|
|
|
20
20
|
} from "./chunk-LRTNNDXS.js";
|
|
21
21
|
import {
|
|
22
22
|
isCompositionSource,
|
|
23
|
-
stampProjectHfIds
|
|
24
|
-
|
|
23
|
+
stampProjectHfIds,
|
|
24
|
+
stampProjectHfIdsAsync
|
|
25
|
+
} from "./chunk-7ROCGO5U.js";
|
|
25
26
|
import {
|
|
26
27
|
parseEncoderOptions
|
|
27
28
|
} from "./chunk-H5AB5322.js";
|
|
29
|
+
import {
|
|
30
|
+
MAX_OPEN_MEDIA_RANGE_BYTES,
|
|
31
|
+
openRangeEnd
|
|
32
|
+
} from "./chunk-6HAB346W.js";
|
|
28
33
|
import {
|
|
29
34
|
DEFAULT_HISTORY_ROOT,
|
|
30
35
|
DELETED_VERSION,
|
|
@@ -86,7 +91,7 @@ import {
|
|
|
86
91
|
isAutoProxyEnabled,
|
|
87
92
|
proxyEtagSalt,
|
|
88
93
|
resolvePreviewMediaCodecProbeCache
|
|
89
|
-
} from "./chunk-
|
|
94
|
+
} from "./chunk-PN2VGFAA.js";
|
|
90
95
|
import {
|
|
91
96
|
CACHE_DIR_NAME,
|
|
92
97
|
PROXY_PENDING_RETRY_AFTER_SECONDS,
|
|
@@ -99,7 +104,7 @@ import {
|
|
|
99
104
|
resolveProxy,
|
|
100
105
|
retryProxyCopy,
|
|
101
106
|
waitForProxy
|
|
102
|
-
} from "./chunk-
|
|
107
|
+
} from "./chunk-CEQWOCYI.js";
|
|
103
108
|
import {
|
|
104
109
|
PROXY_VARIANT_CONFIG,
|
|
105
110
|
decideMediaProxyEligibility,
|
|
@@ -4635,9 +4640,10 @@ ${etag}`;
|
|
|
4635
4640
|
if (c.req.query(PREVIEW_PROXY_RETRY_PARAM) !== void 0) {
|
|
4636
4641
|
retryProxyCopy(project.dir, file, proxyVariant, proxyBox);
|
|
4637
4642
|
}
|
|
4638
|
-
const
|
|
4643
|
+
const asked = c.req.header(PREVIEW_PROXY_PRIORITY_HEADER);
|
|
4644
|
+
const urgency = asked === "0" ? { background: true } : { priority: asked === "1" };
|
|
4639
4645
|
servedPath = await waitForProxy(
|
|
4640
|
-
resolveProxy(project.dir, file, proxyVariant, proxyBox,
|
|
4646
|
+
resolveProxy(project.dir, file, proxyVariant, proxyBox, urgency),
|
|
4641
4647
|
0
|
|
4642
4648
|
);
|
|
4643
4649
|
} catch (err) {
|
|
@@ -4673,7 +4679,7 @@ ${etag}`;
|
|
|
4673
4679
|
const match = rangeHeader ? /bytes=(\d+)-(\d*)/.exec(rangeHeader) : null;
|
|
4674
4680
|
if (match) {
|
|
4675
4681
|
const start = parseInt(match[1], 10);
|
|
4676
|
-
const end = match[2] ? parseInt(match[2], 10) : totalSize - 1;
|
|
4682
|
+
const end = match[2] ? parseInt(match[2], 10) : openRangeEnd(start, totalSize - 1, servedContentType);
|
|
4677
4683
|
const safeEnd = Math.min(end, totalSize - 1);
|
|
4678
4684
|
if (start > safeEnd) {
|
|
4679
4685
|
return new Response(null, {
|
|
@@ -6970,6 +6976,7 @@ export {
|
|
|
6970
6976
|
HistoryClosedError,
|
|
6971
6977
|
HistoryIdError,
|
|
6972
6978
|
HistoryRefusedError,
|
|
6979
|
+
MAX_OPEN_MEDIA_RANGE_BYTES,
|
|
6973
6980
|
MAX_UPLOAD_BYTES,
|
|
6974
6981
|
MAX_WINDOW_IDLE_MS,
|
|
6975
6982
|
MIME_TYPES,
|
|
@@ -7013,6 +7020,7 @@ export {
|
|
|
7013
7020
|
listProjectHistories,
|
|
7014
7021
|
onRestoredInPlace,
|
|
7015
7022
|
openProjectHistory,
|
|
7023
|
+
openRangeEnd,
|
|
7016
7024
|
parseSourceDocument,
|
|
7017
7025
|
patchElementInHtml,
|
|
7018
7026
|
pruneGoneProjectHistories,
|
|
@@ -7028,6 +7036,7 @@ export {
|
|
|
7028
7036
|
settledFileTag,
|
|
7029
7037
|
splitElementInHtml,
|
|
7030
7038
|
stampProjectHfIds,
|
|
7039
|
+
stampProjectHfIdsAsync,
|
|
7031
7040
|
thumbnailDeviceScaleFactor,
|
|
7032
7041
|
walkDir
|
|
7033
7042
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperframes/studio-server",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.3",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "https://github.com/heygen-com/hyperframes",
|
|
@@ -74,6 +74,10 @@
|
|
|
74
74
|
"./encoder-options": {
|
|
75
75
|
"import": "./dist/helpers/encoderOptions.js",
|
|
76
76
|
"types": "./dist/helpers/encoderOptions.d.ts"
|
|
77
|
+
},
|
|
78
|
+
"./media-range": {
|
|
79
|
+
"import": "./dist/helpers/mediaRange.js",
|
|
80
|
+
"types": "./dist/helpers/mediaRange.d.ts"
|
|
77
81
|
}
|
|
78
82
|
},
|
|
79
83
|
"publishConfig": {
|
|
@@ -84,8 +88,8 @@
|
|
|
84
88
|
"linkedom": "^0.18.12",
|
|
85
89
|
"postcss": "^8.5.8",
|
|
86
90
|
"postcss-selector-parser": "^7.1.2",
|
|
87
|
-
"@hyperframes/core": "0.9.
|
|
88
|
-
"@hyperframes/parsers": "0.9.
|
|
91
|
+
"@hyperframes/core": "0.9.3",
|
|
92
|
+
"@hyperframes/parsers": "0.9.3"
|
|
89
93
|
},
|
|
90
94
|
"devDependencies": {
|
|
91
95
|
"@types/node": "^25.0.10",
|