@onmark/cli 0.5.1 → 0.6.0
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/README.md +63 -2
- package/onmark-release.json +46 -36
- package/package.json +4 -4
- package/packages/authoring/dist/src/presentation.js +13 -1
- package/packages/authoring/dist/src/variant.d.ts +3 -0
- package/packages/authoring/dist/src/variant.js +76 -0
- package/packages/bundler/dist/src/authored_html.js +3 -1
- package/packages/motion-gsap/dist/src/index.js +4 -1
- package/packages/runtime/dist/src/generated/browser-request.d.ts +49 -2
- package/packages/runtime/dist/src/generated/browser-response.d.ts +19 -1
- package/packages/runtime/dist/src/generated/runtime-contract.d.ts +4 -1
- package/packages/runtime/dist/src/generated/runtime-contract.js +4 -1
- package/packages/runtime/dist/src/generated/validators.d.ts +2 -2
- package/packages/runtime/dist/src/generated/validators.js +1532 -576
- package/packages/runtime/dist/src/index.d.ts +2 -2
- package/packages/runtime/dist/src/index.js +1 -1
- package/packages/runtime/dist/src/presentation.d.ts +2 -2
- package/packages/runtime/dist/src/presentation.js +79 -1
- package/packages/runtime/dist/src/session.d.ts +3 -3
- package/packages/runtime/dist/src/session.js +138 -8
- package/packages/runtime/dist/src/types.d.ts +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { ProtocolDecodeError, decodeBrowserRequest, decodeBrowserResponse, } from "./generated/codec.js";
|
|
2
2
|
export type { BrowserRequest } from "./generated/browser-request.js";
|
|
3
|
-
export type { BrowserMediaLayout, BrowserMediaPlacement, BrowserResponse, } from "./generated/browser-response.js";
|
|
3
|
+
export type { BrowserMediaLayout, BrowserMediaPlacement, BrowserResponse, BrowserVisualFinding, BrowserVisualFindings, BrowserVisualIssue, } from "./generated/browser-response.js";
|
|
4
4
|
export type { BrowserPlan, BrowserVideo } from "./generated/browser-request.js";
|
|
5
5
|
export { runtimeFrameAt, type RuntimeFrame } from "./clock.js";
|
|
6
6
|
export { videoFrameSelection, type RuntimeVideo, type VideoFrameSelection, } from "./media.js";
|
|
@@ -8,6 +8,6 @@ export { DecodedVideo, materializedVideoSource, type BrowserVideoElement, type D
|
|
|
8
8
|
export { MAX_PRESENTATION_EFFECTS, PresentationRuntimeAdapter, type ContainerPresentation, type FrameEffect, type OverlayPresentation, type PresentationBindings, type PresentationExtensions, type RuntimeOverlay, type RuntimeNode, type RuntimeScene, type RuntimeShot, type RuntimeTransition, type TransitionPresentation, type VideoPresentation, } from "./presentation.js";
|
|
9
9
|
export { MAX_PRESENTATION_RESOURCES, PRESENTATION_RESOURCE_KINDS, type PresentationResource, type PresentationResourceKind, } from "./resource.js";
|
|
10
10
|
export { BUNDLE_ASSET_DIRECTORY, PRESENTATION_TEMPORAL_CAPABILITIES, type PresentationTemporalCapability, } from "./generated/bundle-layout.js";
|
|
11
|
-
export { BROWSER_PROTOCOL_VERSION, BROWSER_OBJECT_POSITION_SCALE, MAX_BROWSER_MEDIA_LAYOUTS, MAX_BROWSER_OVERLAYS, MAX_BROWSER_OVERLAY_TEXT_CHARACTERS, MAX_BROWSER_VIDEOS, MAX_BROWSER_TRANSITIONS, MAX_FAILURE_MESSAGE_CHARACTERS, MAX_PENDING_RESOURCE_CHARACTERS, MAX_PENDING_RESOURCES, } from "./generated/runtime-contract.js";
|
|
11
|
+
export { BROWSER_PROTOCOL_VERSION, BROWSER_OBJECT_POSITION_SCALE, MAX_BROWSER_MEDIA_LAYOUTS, MAX_BROWSER_OVERLAYS, MAX_BROWSER_VISUAL_FINDINGS, MAX_BROWSER_OVERLAY_TEXT_CHARACTERS, MAX_BROWSER_VIDEOS, MAX_BROWSER_TRANSITIONS, MAX_FAILURE_MESSAGE_CHARACTERS, MAX_PENDING_RESOURCE_CHARACTERS, MAX_PENDING_RESOURCES, } from "./generated/runtime-contract.js";
|
|
12
12
|
export { RUNTIME_HOST_NAME, installRuntimeHost, type RuntimeHost, } from "./host.js";
|
|
13
13
|
export { RuntimeAdapterError, RuntimeSession, type RuntimeAdapter, type RuntimeAdapterFailureKind, type RuntimeMediaMode, type RuntimePlan, } from "./session.js";
|
|
@@ -7,6 +7,6 @@ export { DecodedVideo, materializedVideoSource, } from "./video.js";
|
|
|
7
7
|
export { MAX_PRESENTATION_EFFECTS, PresentationRuntimeAdapter, } from "./presentation.js";
|
|
8
8
|
export { MAX_PRESENTATION_RESOURCES, PRESENTATION_RESOURCE_KINDS, } from "./resource.js";
|
|
9
9
|
export { BUNDLE_ASSET_DIRECTORY, PRESENTATION_TEMPORAL_CAPABILITIES, } from "./generated/bundle-layout.js";
|
|
10
|
-
export { BROWSER_PROTOCOL_VERSION, BROWSER_OBJECT_POSITION_SCALE, MAX_BROWSER_MEDIA_LAYOUTS, MAX_BROWSER_OVERLAYS, MAX_BROWSER_OVERLAY_TEXT_CHARACTERS, MAX_BROWSER_VIDEOS, MAX_BROWSER_TRANSITIONS, MAX_FAILURE_MESSAGE_CHARACTERS, MAX_PENDING_RESOURCE_CHARACTERS, MAX_PENDING_RESOURCES, } from "./generated/runtime-contract.js";
|
|
10
|
+
export { BROWSER_PROTOCOL_VERSION, BROWSER_OBJECT_POSITION_SCALE, MAX_BROWSER_MEDIA_LAYOUTS, MAX_BROWSER_OVERLAYS, MAX_BROWSER_VISUAL_FINDINGS, MAX_BROWSER_OVERLAY_TEXT_CHARACTERS, MAX_BROWSER_VIDEOS, MAX_BROWSER_TRANSITIONS, MAX_FAILURE_MESSAGE_CHARACTERS, MAX_PENDING_RESOURCE_CHARACTERS, MAX_PENDING_RESOURCES, } from "./generated/runtime-contract.js";
|
|
11
11
|
export { RUNTIME_HOST_NAME, installRuntimeHost, } from "./host.js";
|
|
12
12
|
export { RuntimeAdapterError, RuntimeSession, } from "./session.js";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { RuntimeFrame } from "./clock.js";
|
|
2
|
-
import type { BrowserMediaLayout, BrowserMediaPlacement } from "./generated/browser-response.js";
|
|
2
|
+
import type { BrowserMediaLayout, BrowserMediaPlacement, BrowserVisualFindings } from "./generated/browser-response.js";
|
|
3
3
|
import { type RuntimeVideo } from "./media.js";
|
|
4
4
|
import { type RuntimeAdapter, type RuntimeMediaMode, type RuntimePlan } from "./session.js";
|
|
5
5
|
import { type BrowserVideoElement } from "./video.js";
|
|
@@ -76,6 +76,6 @@ export declare class PresentationRuntimeAdapter implements RuntimeAdapter {
|
|
|
76
76
|
load(plan: RuntimePlan, mediaMode?: RuntimeMediaMode): Promise<void>;
|
|
77
77
|
prepare(_frame: RuntimeFrame): Promise<BrowserMediaLayout>;
|
|
78
78
|
seek(frame: RuntimeFrame): Promise<void>;
|
|
79
|
-
confirm(frame: RuntimeFrame): Promise<
|
|
79
|
+
confirm(frame: RuntimeFrame): Promise<BrowserVisualFindings>;
|
|
80
80
|
dispose(): Promise<void>;
|
|
81
81
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// Rust owns every interval; presentation callbacks own DOM and layout effects.
|
|
3
3
|
import { videoFrameSelection, } from "./media.js";
|
|
4
4
|
import { RuntimeAdapterError, } from "./session.js";
|
|
5
|
+
import { MAX_BROWSER_VISUAL_FINDINGS } from "./generated/runtime-contract.js";
|
|
5
6
|
import { DecodedVideo } from "./video.js";
|
|
6
7
|
import { ownPresentationResources, preparePresentationResources, releasePresentationResources, requireReadinessTimeout, validatePresentationResources, } from "./resource.js";
|
|
7
8
|
// ── Presentation boundary ──
|
|
@@ -33,6 +34,7 @@ export class PresentationRuntimeAdapter {
|
|
|
33
34
|
shots: [],
|
|
34
35
|
};
|
|
35
36
|
let boundStructure;
|
|
37
|
+
let visualSubjects;
|
|
36
38
|
const videos = [];
|
|
37
39
|
const layoutVideos = [];
|
|
38
40
|
const overlays = [];
|
|
@@ -42,6 +44,7 @@ export class PresentationRuntimeAdapter {
|
|
|
42
44
|
this.#bindTransitions(plan, transitions);
|
|
43
45
|
this.#bindVideos(plan, mediaMode, videos, layoutVideos);
|
|
44
46
|
this.#bindOverlays(plan, overlays);
|
|
47
|
+
visualSubjects = bindVisualSubjects(boundStructure.shots, overlays);
|
|
45
48
|
const extensions = await this.#bindings.bindExtensions(plan);
|
|
46
49
|
// Take both returned collections before either ownership projection or
|
|
47
50
|
// validation can fail; cleanup must retain every transferred resource.
|
|
@@ -70,6 +73,7 @@ export class PresentationRuntimeAdapter {
|
|
|
70
73
|
videos,
|
|
71
74
|
layoutVideos,
|
|
72
75
|
overlays,
|
|
76
|
+
visualSubjects,
|
|
73
77
|
};
|
|
74
78
|
}
|
|
75
79
|
async prepare(_frame) {
|
|
@@ -104,14 +108,16 @@ export class PresentationRuntimeAdapter {
|
|
|
104
108
|
}
|
|
105
109
|
}
|
|
106
110
|
async confirm(frame) {
|
|
107
|
-
this.#loadedState("confirm");
|
|
111
|
+
const state = this.#loadedState("confirm");
|
|
108
112
|
const staged = this.#staged;
|
|
109
113
|
if (staged === undefined || staged.frame.index !== frame.index) {
|
|
110
114
|
throw new RuntimeAdapterError("operation", "presentation confirmation requires the staged frame");
|
|
111
115
|
}
|
|
112
116
|
try {
|
|
113
117
|
await confirmVideos(staged.videos);
|
|
118
|
+
const findings = inspectPresentation(frame, state.visualSubjects);
|
|
114
119
|
this.#staged = undefined;
|
|
120
|
+
return findings;
|
|
115
121
|
}
|
|
116
122
|
catch (error) {
|
|
117
123
|
this.#staged = undefined;
|
|
@@ -193,6 +199,78 @@ export class PresentationRuntimeAdapter {
|
|
|
193
199
|
return this.#state;
|
|
194
200
|
}
|
|
195
201
|
}
|
|
202
|
+
function inspectPresentation(frame, subjects) {
|
|
203
|
+
const findings = [];
|
|
204
|
+
for (const subject of subjects) {
|
|
205
|
+
const { interval } = subject;
|
|
206
|
+
if (frame.index < interval.start || frame.index >= interval.end) {
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
const issues = inspectVisualSubject(subject);
|
|
210
|
+
for (const issue of issues) {
|
|
211
|
+
findings.push({
|
|
212
|
+
nodeId: subject.nodeId,
|
|
213
|
+
issue,
|
|
214
|
+
});
|
|
215
|
+
if (findings.length === MAX_BROWSER_VISUAL_FINDINGS) {
|
|
216
|
+
return findings;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return findings;
|
|
221
|
+
}
|
|
222
|
+
function bindVisualSubjects(shots, overlays) {
|
|
223
|
+
const subjects = [];
|
|
224
|
+
for (const shot of shots) {
|
|
225
|
+
subjects.push({
|
|
226
|
+
element: shot.presentation.element,
|
|
227
|
+
interval: shot.placement.interval,
|
|
228
|
+
kind: "shot",
|
|
229
|
+
nodeId: shot.placement.node.nodeId,
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
for (const overlay of overlays) {
|
|
233
|
+
subjects.push({
|
|
234
|
+
element: overlay.presentation.element,
|
|
235
|
+
interval: overlay.placement.interval,
|
|
236
|
+
kind: "overlay",
|
|
237
|
+
nodeId: overlay.placement.node.nodeId,
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
subjects.sort((left, right) => left.nodeId - right.nodeId);
|
|
241
|
+
return subjects;
|
|
242
|
+
}
|
|
243
|
+
function inspectVisualSubject(subject) {
|
|
244
|
+
if (hasEmptyBox(subject.element)) {
|
|
245
|
+
return ["emptyBox"];
|
|
246
|
+
}
|
|
247
|
+
return subject.kind === "overlay" ? inspectOverlay(subject.element) : [];
|
|
248
|
+
}
|
|
249
|
+
function inspectOverlay(element) {
|
|
250
|
+
if (hasEmptyBox(element)) {
|
|
251
|
+
return ["emptyBox"];
|
|
252
|
+
}
|
|
253
|
+
const view = element.ownerDocument.defaultView;
|
|
254
|
+
if (view === null) {
|
|
255
|
+
throw new RuntimeAdapterError("operation", "overlay layout requires an active browser document");
|
|
256
|
+
}
|
|
257
|
+
const style = view.getComputedStyle(element);
|
|
258
|
+
const issues = [];
|
|
259
|
+
if (clips(style.overflowX) && element.scrollWidth > element.clientWidth) {
|
|
260
|
+
issues.push("clippedHorizontally");
|
|
261
|
+
}
|
|
262
|
+
if (clips(style.overflowY) && element.scrollHeight > element.clientHeight) {
|
|
263
|
+
issues.push("clippedVertically");
|
|
264
|
+
}
|
|
265
|
+
return issues;
|
|
266
|
+
}
|
|
267
|
+
function hasEmptyBox(element) {
|
|
268
|
+
const rectangle = element.getBoundingClientRect();
|
|
269
|
+
return rectangle.width <= 0 || rectangle.height <= 0;
|
|
270
|
+
}
|
|
271
|
+
function clips(overflow) {
|
|
272
|
+
return overflow === "hidden" || overflow === "clip";
|
|
273
|
+
}
|
|
196
274
|
// ── Frame application ──
|
|
197
275
|
function ownFrameEffects(effects) {
|
|
198
276
|
return Object.freeze(effects.map(ownFrameEffect));
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { BrowserPlan, BrowserRequest } from "./generated/browser-request.js";
|
|
2
|
-
import type { BrowserMediaLayout, BrowserResponse } from "./generated/browser-response.js";
|
|
2
|
+
import type { BrowserMediaLayout, BrowserResponse, BrowserVisualFindings } from "./generated/browser-response.js";
|
|
3
3
|
import { type RuntimeFrame } from "./clock.js";
|
|
4
4
|
type Immutable<T> = T extends object ? {
|
|
5
5
|
readonly [Key in keyof T]: Immutable<T[Key]>;
|
|
@@ -19,8 +19,8 @@ export interface RuntimeAdapter {
|
|
|
19
19
|
prepare(frame: RuntimeFrame): Promise<BrowserMediaLayout>;
|
|
20
20
|
/** Stages one exact frame and registers media presentation observers. */
|
|
21
21
|
seek(frame: RuntimeFrame): Promise<void>;
|
|
22
|
-
/** Verifies media
|
|
23
|
-
confirm(frame: RuntimeFrame): Promise<
|
|
22
|
+
/** Verifies captured media and returns bounded facts about the staged layout. */
|
|
23
|
+
confirm(frame: RuntimeFrame): Promise<BrowserVisualFindings>;
|
|
24
24
|
/** Releases all resources owned by this adapter. */
|
|
25
25
|
dispose(): Promise<void>;
|
|
26
26
|
}
|
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
// Owns command ordering and state while a narrow adapter owns browser effects;
|
|
3
3
|
// this split keeps protocol behavior deterministic and directly testable.
|
|
4
4
|
import { decodeBrowserResponse } from "./generated/codec.js";
|
|
5
|
-
import { BROWSER_PROTOCOL_VERSION, MAX_BROWSER_MEDIA_LAYOUTS, MAX_FAILURE_MESSAGE_CHARACTERS, MAX_PENDING_RESOURCE_CHARACTERS, MAX_PENDING_RESOURCES, } from "./generated/runtime-contract.js";
|
|
5
|
+
import { BROWSER_PROTOCOL_VERSION, MAX_BROWSER_TEXT_BYTES, MAX_BROWSER_MEDIA_LAYOUTS, MAX_BROWSER_VISUAL_FINDINGS, MAX_FAILURE_MESSAGE_CHARACTERS, MAX_PENDING_RESOURCE_CHARACTERS, MAX_PENDING_RESOURCES, MAX_VARIANT_TEXT_BYTES, } from "./generated/runtime-contract.js";
|
|
6
6
|
import { runtimeFrameAt } from "./clock.js";
|
|
7
7
|
import { exactRatioIsCanonical, videoSourceMappingIsValid } from "./media.js";
|
|
8
|
+
const UTF8 = new TextEncoder();
|
|
8
9
|
/** Expected failure reported by a browser adapter. */
|
|
9
10
|
export class RuntimeAdapterError extends Error {
|
|
10
11
|
kind;
|
|
@@ -133,7 +134,14 @@ export class RuntimeSession {
|
|
|
133
134
|
return invalidRequest(requestId, "confirm must use the staged frame");
|
|
134
135
|
}
|
|
135
136
|
try {
|
|
136
|
-
await this.#adapter.confirm(runtimeFrameAt(frame, this.#state.frameRate));
|
|
137
|
+
const findings = await this.#adapter.confirm(runtimeFrameAt(frame, this.#state.frameRate));
|
|
138
|
+
const visualFindings = snapshotVisualFindings(findings);
|
|
139
|
+
this.#state = { ...this.#state, kind: "ready" };
|
|
140
|
+
return response(requestId, {
|
|
141
|
+
type: "frameReady",
|
|
142
|
+
frame,
|
|
143
|
+
visualFindings,
|
|
144
|
+
});
|
|
137
145
|
}
|
|
138
146
|
catch (error) {
|
|
139
147
|
// Confirmation consumes staged media observers and may succeed for only
|
|
@@ -141,8 +149,6 @@ export class RuntimeSession {
|
|
|
141
149
|
this.#state = { kind: "failed" };
|
|
142
150
|
return readinessFailure(requestId, "confirmFailed", error);
|
|
143
151
|
}
|
|
144
|
-
this.#state = { ...this.#state, kind: "ready" };
|
|
145
|
-
return response(requestId, { type: "frameReady", frame });
|
|
146
152
|
}
|
|
147
153
|
async #dispose(requestId) {
|
|
148
154
|
if (this.#state.kind === "disposed") {
|
|
@@ -167,6 +173,47 @@ function response(requestId, event) {
|
|
|
167
173
|
event,
|
|
168
174
|
});
|
|
169
175
|
}
|
|
176
|
+
function snapshotVisualFindings(findings) {
|
|
177
|
+
if (findings.length > MAX_BROWSER_VISUAL_FINDINGS) {
|
|
178
|
+
throw new RuntimeAdapterError("operation", "browser visual findings exceed the per-frame limit");
|
|
179
|
+
}
|
|
180
|
+
const owned = findings.map((finding) => {
|
|
181
|
+
if (!Number.isSafeInteger(finding.nodeId) ||
|
|
182
|
+
finding.nodeId < 0 ||
|
|
183
|
+
finding.nodeId > 2 ** 32 - 1) {
|
|
184
|
+
throw new RuntimeAdapterError("operation", "browser visual finding has an invalid node identity");
|
|
185
|
+
}
|
|
186
|
+
visualFindingOrder(finding.issue);
|
|
187
|
+
return { nodeId: finding.nodeId, issue: finding.issue };
|
|
188
|
+
});
|
|
189
|
+
owned.sort(compareVisualFindings);
|
|
190
|
+
let previous;
|
|
191
|
+
for (const finding of owned) {
|
|
192
|
+
if (previous?.nodeId === finding.nodeId &&
|
|
193
|
+
previous.issue === finding.issue) {
|
|
194
|
+
throw new RuntimeAdapterError("operation", "browser visual findings contain a duplicate");
|
|
195
|
+
}
|
|
196
|
+
previous = finding;
|
|
197
|
+
Object.freeze(finding);
|
|
198
|
+
}
|
|
199
|
+
return Object.freeze(owned);
|
|
200
|
+
}
|
|
201
|
+
function compareVisualFindings(left, right) {
|
|
202
|
+
return (left.nodeId - right.nodeId ||
|
|
203
|
+
visualFindingOrder(left.issue) - visualFindingOrder(right.issue));
|
|
204
|
+
}
|
|
205
|
+
function visualFindingOrder(issue) {
|
|
206
|
+
switch (issue) {
|
|
207
|
+
case "emptyBox":
|
|
208
|
+
return 0;
|
|
209
|
+
case "clippedHorizontally":
|
|
210
|
+
return 1;
|
|
211
|
+
case "clippedVertically":
|
|
212
|
+
return 2;
|
|
213
|
+
default:
|
|
214
|
+
throw new RuntimeAdapterError("operation", "browser visual finding has an invalid issue");
|
|
215
|
+
}
|
|
216
|
+
}
|
|
170
217
|
function invalidRequest(requestId, message) {
|
|
171
218
|
return response(requestId, {
|
|
172
219
|
type: "failed",
|
|
@@ -234,6 +281,13 @@ function planViolation(plan, mediaMode) {
|
|
|
234
281
|
!hasCanonicalNodeOrder(plan.overlays)) {
|
|
235
282
|
return "plan node collection is not in canonical order";
|
|
236
283
|
}
|
|
284
|
+
const variantTextBytes = canonicalVariantFieldsTextBytes(plan.variantFields);
|
|
285
|
+
if (variantTextBytes === undefined) {
|
|
286
|
+
return "plan variant fields are not canonical";
|
|
287
|
+
}
|
|
288
|
+
if (!browserTextFitsBudget(plan.overlays, variantTextBytes)) {
|
|
289
|
+
return "plan text exceeds the browser request budget";
|
|
290
|
+
}
|
|
237
291
|
const nodeIds = new Set();
|
|
238
292
|
const authoredIds = new Set();
|
|
239
293
|
const filmViolation = claimNode(plan.film, nodeIds, authoredIds);
|
|
@@ -358,11 +412,71 @@ function hasCanonicalNodeOrder(entries) {
|
|
|
358
412
|
}
|
|
359
413
|
return true;
|
|
360
414
|
}
|
|
415
|
+
function canonicalVariantFieldsTextBytes(fields) {
|
|
416
|
+
let previous;
|
|
417
|
+
let textBytes = 0;
|
|
418
|
+
for (const field of fields) {
|
|
419
|
+
const valueBytes = canonicalVariantTextBytes(field.value);
|
|
420
|
+
if (!/^[a-z][A-Za-z0-9]{0,63}$/u.test(field.name) ||
|
|
421
|
+
(previous !== undefined && field.name <= previous) ||
|
|
422
|
+
valueBytes === undefined) {
|
|
423
|
+
return undefined;
|
|
424
|
+
}
|
|
425
|
+
textBytes += valueBytes;
|
|
426
|
+
previous = field.name;
|
|
427
|
+
}
|
|
428
|
+
return textBytes;
|
|
429
|
+
}
|
|
430
|
+
function canonicalVariantTextBytes(value) {
|
|
431
|
+
switch (value.kind) {
|
|
432
|
+
case "text": {
|
|
433
|
+
const bytes = UTF8.encode(value.value).byteLength;
|
|
434
|
+
return bytes <= MAX_VARIANT_TEXT_BYTES ? bytes : undefined;
|
|
435
|
+
}
|
|
436
|
+
case "integer":
|
|
437
|
+
return Number.isSafeInteger(value.value) ? 0 : undefined;
|
|
438
|
+
case "boolean":
|
|
439
|
+
return 0;
|
|
440
|
+
case "color":
|
|
441
|
+
return /^#[0-9a-f]{6}(?:[0-9a-f]{2})?$/u.test(value.value)
|
|
442
|
+
? 0
|
|
443
|
+
: undefined;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
function browserTextFitsBudget(overlays, variantTextBytes) {
|
|
447
|
+
let textBytes = variantTextBytes;
|
|
448
|
+
for (const overlay of overlays) {
|
|
449
|
+
textBytes += UTF8.encode(overlay.text).byteLength;
|
|
450
|
+
const track = overlay.captionTrack;
|
|
451
|
+
if (track !== undefined && track !== null) {
|
|
452
|
+
textBytes += UTF8.encode(track.id).byteLength;
|
|
453
|
+
textBytes += UTF8.encode(track.language).byteLength;
|
|
454
|
+
}
|
|
455
|
+
if (textBytes > MAX_BROWSER_TEXT_BYTES) {
|
|
456
|
+
return false;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
return true;
|
|
460
|
+
}
|
|
361
461
|
function overlayParentViolation(overlay, shots) {
|
|
362
462
|
if (overlay.kind === "caption") {
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
463
|
+
if (overlay.shotId !== undefined && overlay.shotId !== null) {
|
|
464
|
+
return "plan caption names a structural parent";
|
|
465
|
+
}
|
|
466
|
+
const track = overlay.captionTrack;
|
|
467
|
+
if (track === undefined || track === null) {
|
|
468
|
+
return "plan caption omits track metadata";
|
|
469
|
+
}
|
|
470
|
+
if (track.id.length === 0 || /[\t\n\f\r ]/u.test(track.id)) {
|
|
471
|
+
return "plan caption track identity is invalid";
|
|
472
|
+
}
|
|
473
|
+
if (!/^[A-Za-z0-9]+(?:-[A-Za-z0-9]+)*$/u.test(track.language)) {
|
|
474
|
+
return "plan caption language is invalid";
|
|
475
|
+
}
|
|
476
|
+
return undefined;
|
|
477
|
+
}
|
|
478
|
+
if (overlay.captionTrack !== undefined && overlay.captionTrack !== null) {
|
|
479
|
+
return "plan authored overlay names caption track metadata";
|
|
366
480
|
}
|
|
367
481
|
if (overlay.shotId === undefined || overlay.shotId === null) {
|
|
368
482
|
return "plan overlay names an unknown shot";
|
|
@@ -420,6 +534,7 @@ function snapshotPlan(plan) {
|
|
|
420
534
|
const scenes = Object.freeze(plan.scenes.map(snapshotScene));
|
|
421
535
|
const shots = Object.freeze(plan.shots.map(snapshotShot));
|
|
422
536
|
const transitions = Object.freeze(plan.transitions.map(snapshotTransition));
|
|
537
|
+
const variantFields = Object.freeze(plan.variantFields.map(snapshotVariantField));
|
|
423
538
|
const videos = Object.freeze(plan.videos.map(snapshotVideo));
|
|
424
539
|
const overlays = Object.freeze(plan.overlays.map(snapshotOverlay));
|
|
425
540
|
return Object.freeze({
|
|
@@ -432,10 +547,17 @@ function snapshotPlan(plan) {
|
|
|
432
547
|
scenes,
|
|
433
548
|
shots,
|
|
434
549
|
transitions,
|
|
550
|
+
variantFields,
|
|
435
551
|
videos,
|
|
436
552
|
overlays,
|
|
437
553
|
});
|
|
438
554
|
}
|
|
555
|
+
function snapshotVariantField(field) {
|
|
556
|
+
return Object.freeze({
|
|
557
|
+
name: field.name,
|
|
558
|
+
value: Object.freeze({ ...field.value }),
|
|
559
|
+
});
|
|
560
|
+
}
|
|
439
561
|
function snapshotTransition(transition) {
|
|
440
562
|
return Object.freeze({
|
|
441
563
|
incomingShotId: transition.incomingShotId,
|
|
@@ -489,12 +611,20 @@ function snapshotFrameBoundaries([first, second, third, ...rest]) {
|
|
|
489
611
|
return Object.freeze([first, second, third, ...rest]);
|
|
490
612
|
}
|
|
491
613
|
function snapshotOverlay(overlay) {
|
|
492
|
-
|
|
614
|
+
const snapshot = {
|
|
493
615
|
node: snapshotNode(overlay.node),
|
|
494
616
|
shotId: overlay.shotId ?? null,
|
|
495
617
|
kind: overlay.kind,
|
|
496
618
|
text: overlay.text,
|
|
497
619
|
interval: Object.freeze({ ...overlay.interval }),
|
|
620
|
+
};
|
|
621
|
+
const track = overlay.captionTrack;
|
|
622
|
+
if (track === undefined || track === null) {
|
|
623
|
+
return Object.freeze(snapshot);
|
|
624
|
+
}
|
|
625
|
+
return Object.freeze({
|
|
626
|
+
...snapshot,
|
|
627
|
+
captionTrack: Object.freeze({ ...track }),
|
|
498
628
|
});
|
|
499
629
|
}
|
|
500
630
|
function snapshotNode(node) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export type { RuntimeFrame } from "./clock.js";
|
|
2
|
-
export type { BrowserMediaLayout, BrowserMediaPlacement, } from "./generated/browser-response.js";
|
|
2
|
+
export type { BrowserMediaLayout, BrowserMediaPlacement, BrowserVisualFinding, BrowserVisualFindings, BrowserVisualIssue, } from "./generated/browser-response.js";
|
|
3
3
|
export type { RuntimeVideo } from "./media.js";
|
|
4
4
|
export type { PresentationFrameBehavior, PresentationTemporalCapability, PresentationVisualCapability, } from "./generated/bundle-layout.js";
|
|
5
5
|
export type { RuntimeMediaMode, RuntimePlan } from "./session.js";
|