@hyperframes/studio-server 0.8.153 → 0.8.155

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.
@@ -238,6 +238,7 @@ import { createHash as createHash2 } from "crypto";
238
238
  import { lstatSync, readFileSync as readFileSync3, readdirSync } from "fs";
239
239
  import { extname, isAbsolute as isAbsolute2, relative as relative2, resolve as resolve2, sep as sep2 } from "path";
240
240
  import { isAtomicTempPath } from "@hyperframes/core/atomic-file";
241
+ import { PREVIEW_IGNORED_DIRS } from "@hyperframes/core/project-folders";
241
242
  var SIGNATURE_TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
242
243
  ".cjs",
243
244
  ".css",
@@ -250,22 +251,6 @@ var SIGNATURE_TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
250
251
  ".ts",
251
252
  ".tsx"
252
253
  ]);
253
- var SIGNATURE_EXCLUDED_DIRS = /* @__PURE__ */ new Set([
254
- ".cache",
255
- ".git",
256
- ".hyperframes",
257
- ".next",
258
- ".thumbnails",
259
- ".transcode-cache",
260
- ".vite",
261
- ".waveform-cache",
262
- "build",
263
- "coverage",
264
- "dist",
265
- "node_modules",
266
- "outputs",
267
- "renders"
268
- ]);
269
254
  var MAX_SIGNATURE_TEXT_BYTES = 2e6;
270
255
  var STUDIO_SIGNATURE_MANIFEST_PATHS = [
271
256
  ".hyperframes/studio-manual-edits.json",
@@ -279,7 +264,7 @@ function affectsProjectSignature(projectDir, changedPath) {
279
264
  if (isRenderOutput(projectDir, changedPath, readRenderOutputsOrNone(projectDir))) return false;
280
265
  const segments = relativePath.split(sep2);
281
266
  if (STUDIO_SIGNATURE_MANIFEST_PATHS.includes(segments.join("/"))) return true;
282
- return !segments.some((segment) => SIGNATURE_EXCLUDED_DIRS.has(segment));
267
+ return !segments.some((segment) => PREVIEW_IGNORED_DIRS.has(segment));
283
268
  }
284
269
  var projectSignatureCache = /* @__PURE__ */ new Map();
285
270
  var COARSEST_FILE_TIME_TICK_MS = 2e3;
@@ -290,7 +275,7 @@ function isPathWithin(parentDir, childPath) {
290
275
  function isTextContentEligible(file, size) {
291
276
  return SIGNATURE_TEXT_EXTENSIONS.has(extname(file).toLowerCase()) && size <= MAX_SIGNATURE_TEXT_BYTES;
292
277
  }
293
- var isSkippedEntry = (entry) => SIGNATURE_EXCLUDED_DIRS.has(entry) || isAtomicTempPath(entry);
278
+ var isSkippedEntry = (entry) => PREVIEW_IGNORED_DIRS.has(entry) || isAtomicTempPath(entry);
294
279
  function signatureEntryStat(projectDir, entry, file, outputs) {
295
280
  if (isSkippedEntry(entry) || !isPathWithin(projectDir, file)) return null;
296
281
  if (isRenderOutput(projectDir, file, outputs)) return null;
@@ -0,0 +1,56 @@
1
+ // src/helpers/encoderOptions.ts
2
+ var CRF_MAX_BY_FORMAT = { mp4: 51, webm: 63 };
3
+ var VIDEO_BITRATE = /^\d+(\.\d+)?[kKmMgG]?$/;
4
+ var VIDEO_FRAME_FORMATS = ["auto", "jpg", "png"];
5
+ var HDR_MODES = ["auto", "force-hdr", "force-sdr"];
6
+ var isSet = (value) => value !== void 0 && value !== null;
7
+ function oneOf(value, allowed) {
8
+ return typeof value === "string" && allowed.includes(value);
9
+ }
10
+ function parseCrf(crf, format, max) {
11
+ if (typeof crf !== "number" || !Number.isInteger(crf) || crf < 0 || crf > max) {
12
+ return { ok: false, error: `crf must be an integer from 0 to ${max} for ${format}` };
13
+ }
14
+ return { ok: true, options: { crf } };
15
+ }
16
+ function parseVideoBitrate(videoBitrate) {
17
+ if (typeof videoBitrate !== "string" || !VIDEO_BITRATE.test(videoBitrate)) {
18
+ return { ok: false, error: 'videoBitrate must look like "10M", "2500k" or "8000000"' };
19
+ }
20
+ return { ok: true, options: { videoBitrate } };
21
+ }
22
+ function parseRateControl(body, format) {
23
+ const max = CRF_MAX_BY_FORMAT[format];
24
+ if (max === void 0) return { ok: true, options: {} };
25
+ if (isSet(body.crf) && isSet(body.videoBitrate)) {
26
+ return { ok: false, error: "use either crf or videoBitrate, not both" };
27
+ }
28
+ if (isSet(body.crf)) return parseCrf(body.crf, format, max);
29
+ if (isSet(body.videoBitrate)) return parseVideoBitrate(body.videoBitrate);
30
+ return { ok: true, options: {} };
31
+ }
32
+ function parseEncoderOptions(body, format) {
33
+ const rate = parseRateControl(body, format);
34
+ if (!rate.ok) return rate;
35
+ const options = { ...rate.options };
36
+ if (isSet(body.videoFrameFormat)) {
37
+ if (!oneOf(body.videoFrameFormat, VIDEO_FRAME_FORMATS)) {
38
+ return {
39
+ ok: false,
40
+ error: `videoFrameFormat must be one of ${VIDEO_FRAME_FORMATS.join(", ")}`
41
+ };
42
+ }
43
+ options.videoFrameFormat = body.videoFrameFormat;
44
+ }
45
+ if (isSet(body.hdrMode)) {
46
+ if (!oneOf(body.hdrMode, HDR_MODES)) {
47
+ return { ok: false, error: `hdrMode must be one of ${HDR_MODES.join(", ")}` };
48
+ }
49
+ options.hdrMode = body.hdrMode;
50
+ }
51
+ return { ok: true, options };
52
+ }
53
+
54
+ export {
55
+ parseEncoderOptions
56
+ };
@@ -0,0 +1,25 @@
1
+ declare const VIDEO_FRAME_FORMATS: readonly ["auto", "jpg", "png"];
2
+ declare const HDR_MODES: readonly ["auto", "force-hdr", "force-sdr"];
3
+ interface EncoderOptions {
4
+ crf?: number;
5
+ videoBitrate?: string;
6
+ videoFrameFormat?: (typeof VIDEO_FRAME_FORMATS)[number];
7
+ hdrMode?: (typeof HDR_MODES)[number];
8
+ }
9
+ interface EncoderOptionsBody {
10
+ crf?: unknown;
11
+ videoBitrate?: unknown;
12
+ videoFrameFormat?: unknown;
13
+ hdrMode?: unknown;
14
+ }
15
+ type Parsed = {
16
+ ok: true;
17
+ options: EncoderOptions;
18
+ } | {
19
+ ok: false;
20
+ error: string;
21
+ };
22
+ /** Explicit encoder overrides from a render request. MOV (ProRes) takes no rate control. */
23
+ declare function parseEncoderOptions(body: EncoderOptionsBody, format: string): Parsed;
24
+
25
+ export { type EncoderOptionsBody, parseEncoderOptions };
@@ -0,0 +1,6 @@
1
+ import {
2
+ parseEncoderOptions
3
+ } from "../chunk-GR4TX52A.js";
4
+ export {
5
+ parseEncoderOptions
6
+ };
@@ -1,5 +1,5 @@
1
1
  import '@hyperframes/core';
2
- export { a as PreviewApiAdapter, P as PreviewDocumentStore, i as injectMediaCodecMap, f as injectMediaCodecMapIntoHtml, g as isAutoProxyEnabled, p as proxyEtagSalt, r as resolvePreviewMediaCodecProbeCache } from '../mediaProxyPreview-DB1EEdxc.js';
2
+ export { a as PreviewApiAdapter, P as PreviewDocumentStore, i as injectMediaCodecMap, f as injectMediaCodecMapIntoHtml, g as isAutoProxyEnabled, p as proxyEtagSalt, r as resolvePreviewMediaCodecProbeCache } from '../mediaProxyPreview-J8vlAvrx.js';
3
3
  import './mediaCodecMap.js';
4
4
  import '@hyperframes/parsers';
5
5
  import '@hyperframes/core/compiler';
@@ -18,7 +18,7 @@ import {
18
18
  recordArtifacts,
19
19
  recordRenderSuccess,
20
20
  registerRenderOutput
21
- } from "../chunk-CW5G6XXR.js";
21
+ } from "../chunk-66HD2HFT.js";
22
22
  import "../chunk-K56J3CL7.js";
23
23
  import "../chunk-SVQFE44C.js";
24
24
  import "../chunk-L3HJVTQE.js";
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Hono } from 'hono';
2
- import { S as StudioApiAdapter, P as PreviewDocumentStore, M as MediaProcessingJobState } from './mediaProxyPreview-DB1EEdxc.js';
3
- export { L as LintResult, a as PreviewApiAdapter, R as RenderJobState, b as ResolvedProject, c as StudioSelectionResponse, d as StudioSelectionSnapshot, e as StudioSelectionTextField } from './mediaProxyPreview-DB1EEdxc.js';
2
+ import { S as StudioApiAdapter, P as PreviewDocumentStore, M as MediaProcessingJobState } from './mediaProxyPreview-J8vlAvrx.js';
3
+ export { L as LintResult, a as PreviewApiAdapter, R as RenderJobState, b as ResolvedProject, c as StudioSelectionResponse, d as StudioSelectionSnapshot, e as StudioSelectionTextField } from './mediaProxyPreview-J8vlAvrx.js';
4
4
  export { C as ClosedWindow, D as DEFAULT_HISTORY_ROOT, S as HISTORY_START, H as HistoryClosedError, a as HistoryEntry, b as HistoryEntrySide, c as HistoryFileChange, d as HistoryListItem, e as HistoryResult, f as HistoryTarget, g as HistoryWho, h as HistoryWindow, M as MAX_WINDOW_IDLE_MS, P as ProjectHistory, i as ProjectHistoryOptions, j as ProjectHistoryView, U as UNDO_MODES, k as UndoMode, o as openProjectHistory, r as readProjectHistory } from './projectHistory-C5dpCjPU.js';
5
5
  export { ArtifactCapture, ArtifactInput, ArtifactRecord, HistoryBusyError, HistoryIdError, ProjectHistoryRecord, PrunedHistory, RenderRecord, VideoState, artifactDirectory, beginArtifactCapture, forgetRecordedArtifacts, historyCache, listProjectHistories, pruneGoneProjectHistories, recordArtifact, recordArtifacts, recordRenderSuccess, registerRenderOutput } from './history/index.js';
6
6
  export { CACHE_DIR_NAME as PROXY_CACHE_DIR_NAME } from './helpers/proxyTranscoder.js';
package/dist/index.js CHANGED
@@ -22,6 +22,9 @@ import {
22
22
  isCompositionSource,
23
23
  stampProjectHfIds
24
24
  } from "./chunk-6RICWA3B.js";
25
+ import {
26
+ parseEncoderOptions
27
+ } from "./chunk-GR4TX52A.js";
25
28
  import {
26
29
  DEFAULT_HISTORY_ROOT,
27
30
  DELETED_VERSION,
@@ -55,7 +58,7 @@ import {
55
58
  resolveProjectAndSignature,
56
59
  resolveProjectSignature,
57
60
  settledFileTag
58
- } from "./chunk-CW5G6XXR.js";
61
+ } from "./chunk-66HD2HFT.js";
59
62
  import {
60
63
  clearElementScreenshotIsolation,
61
64
  getElementScreenshotClip
@@ -4634,6 +4637,8 @@ function registerRenderRoutes(api, adapter) {
4634
4637
  }
4635
4638
  variables = body.variables;
4636
4639
  }
4640
+ const encoder = parseEncoderOptions(body, format);
4641
+ if (!encoder.ok) return c.json({ error: encoder.error }, 400);
4637
4642
  const now = /* @__PURE__ */ new Date();
4638
4643
  const jobId = `${project.id}_${formatRenderOutputTimestamp(now)}`;
4639
4644
  const rendersDir = adapter.rendersDir(project);
@@ -4650,6 +4655,7 @@ function registerRenderRoutes(api, adapter) {
4650
4655
  outputResolution,
4651
4656
  composition,
4652
4657
  variables,
4658
+ ...encoder.options,
4653
4659
  useGpu: body.gpu === true && format === "mp4",
4654
4660
  distinctId: typeof body.telemetryDistinctId === "string" ? body.telemetryDistinctId : void 0,
4655
4661
  telemetryOptOut: body.telemetryOptOut === true
@@ -161,6 +161,10 @@ interface StudioApiAdapter {
161
161
  */
162
162
  fps: _hyperframes_core.Fps;
163
163
  quality: string;
164
+ crf?: number;
165
+ videoBitrate?: string;
166
+ videoFrameFormat?: "auto" | "jpg" | "png";
167
+ hdrMode?: "auto" | "force-hdr" | "force-sdr";
164
168
  jobId: string;
165
169
  /**
166
170
  * The triggering browser profile has telemetry disabled (localStorage
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperframes/studio-server",
3
- "version": "0.8.153",
3
+ "version": "0.8.155",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/heygen-com/hyperframes",
@@ -70,6 +70,10 @@
70
70
  "./media-proxy-preview": {
71
71
  "import": "./dist/helpers/mediaProxyPreview.js",
72
72
  "types": "./dist/helpers/mediaProxyPreview.d.ts"
73
+ },
74
+ "./encoder-options": {
75
+ "import": "./dist/helpers/encoderOptions.js",
76
+ "types": "./dist/helpers/encoderOptions.d.ts"
73
77
  }
74
78
  },
75
79
  "publishConfig": {
@@ -81,8 +85,8 @@
81
85
  "postcss": "^8.5.8",
82
86
  "postcss-selector-parser": "^7.1.2",
83
87
  "sharp": "^0.35.0",
84
- "@hyperframes/parsers": "0.8.153",
85
- "@hyperframes/core": "0.8.153"
88
+ "@hyperframes/core": "0.8.155",
89
+ "@hyperframes/parsers": "0.8.155"
86
90
  },
87
91
  "devDependencies": {
88
92
  "@types/node": "^25.0.10",