@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.
@@ -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
- let last = {};
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 absPath = join(projectDir, file);
27
- let html;
28
- try {
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 spawnFfmpeg(ffmpegPath, args, stallMs, signal, onProgress) {
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()
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  PROXY_PARAMS_VERSION,
3
3
  resolveProxy
4
- } from "./chunk-G4VPDLXC.js";
4
+ } from "./chunk-CEQWOCYI.js";
5
5
  import {
6
6
  createMediaCodecProbeCache,
7
7
  proxyVariantFor,
@@ -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
- } from "../chunk-FJBJJURY.js";
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-JVMMZB22.js";
8
- import "../chunk-G4VPDLXC.js";
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";
@@ -0,0 +1,4 @@
1
+ declare const MAX_OPEN_MEDIA_RANGE_BYTES: number;
2
+ declare function openRangeEnd(start: number, last: number, contentType: string): number;
3
+
4
+ export { MAX_OPEN_MEDIA_RANGE_BYTES, openRangeEnd };
@@ -0,0 +1,8 @@
1
+ import {
2
+ MAX_OPEN_MEDIA_RANGE_BYTES,
3
+ openRangeEnd
4
+ } from "../chunk-6HAB346W.js";
5
+ export {
6
+ MAX_OPEN_MEDIA_RANGE_BYTES,
7
+ openRangeEnd
8
+ };
@@ -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;
@@ -20,7 +20,7 @@ import {
20
20
  retryProxyCopy,
21
21
  sourceFolderKind,
22
22
  waitForProxy
23
- } from "../chunk-G4VPDLXC.js";
23
+ } from "../chunk-CEQWOCYI.js";
24
24
  import "../chunk-FUTDRYQK.js";
25
25
  import "../chunk-X73QTST2.js";
26
26
  import "../chunk-QF7MFJJU.js";
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
- } from "./chunk-FJBJJURY.js";
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-JVMMZB22.js";
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-G4VPDLXC.js";
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 priority = c.req.header(PREVIEW_PROXY_PRIORITY_HEADER) === "1";
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, { priority }),
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.1",
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.1",
88
- "@hyperframes/parsers": "0.9.1"
91
+ "@hyperframes/core": "0.9.3",
92
+ "@hyperframes/parsers": "0.9.3"
89
93
  },
90
94
  "devDependencies": {
91
95
  "@types/node": "^25.0.10",