@hyperframes/parsers 0.7.60 → 0.7.62

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,19 @@
1
+ /**
2
+ * Shared local-asset resolution helpers for every package that maps
3
+ * composition asset URLs to files on disk (lint project rules, the HEVC
4
+ * preview check, studio-server's media codec scan). Import via the
5
+ * `@hyperframes/parsers/asset-resolution` subpath.
6
+ */
7
+ declare function isRemoteOrInlineUrl(url: string): boolean;
8
+ declare function cleanAssetUrl(url: string): string;
9
+ declare function isWithinProjectRoot(projectDir: string, candidate: string): boolean;
10
+ declare function resolveLocalAssetCandidates(projectDir: string, url: string): string[];
11
+ declare function resolveExistingLocalAsset(projectDir: string, url: string): {
12
+ resolved: string;
13
+ rootRelativePath: string;
14
+ } | null;
15
+ /** Blanks out comments, `<style>`, and `<script>` bodies so tag-scanning
16
+ * regexes don't false-positive on commented-out or scripted markup. */
17
+ declare function maskNonScannableRanges(html: string): string;
18
+
19
+ export { cleanAssetUrl, isRemoteOrInlineUrl, isWithinProjectRoot, maskNonScannableRanges, resolveExistingLocalAsset, resolveLocalAssetCandidates };
@@ -0,0 +1,87 @@
1
+ // src/assetResolution.ts
2
+ import { existsSync } from "fs";
3
+ import { isAbsolute, posix, relative, resolve } from "path";
4
+
5
+ // src/utils/urlPath.ts
6
+ function decodeUrlPathVariants(path) {
7
+ const variants = [path];
8
+ try {
9
+ const decoded = decodeURIComponent(path);
10
+ if (decoded !== path) variants.unshift(decoded);
11
+ } catch {
12
+ }
13
+ return variants;
14
+ }
15
+
16
+ // src/assetResolution.ts
17
+ function isRemoteOrInlineUrl(url) {
18
+ return /^(https?:|data:|blob:|\/\/|#)/i.test(url);
19
+ }
20
+ function cleanAssetUrl(url) {
21
+ return url.trim().split(/[?#]/, 1)[0] ?? "";
22
+ }
23
+ function isWithinProjectRoot(projectDir, candidate) {
24
+ const projectRoot = resolve(projectDir);
25
+ const relativePath = relative(projectRoot, candidate);
26
+ return relativePath === "" || !relativePath.startsWith("..") && !isAbsolute(relativePath);
27
+ }
28
+ function addCandidate(candidates, candidate) {
29
+ if (!candidates.includes(candidate)) candidates.push(candidate);
30
+ }
31
+ function resolveLocalAssetCandidates(projectDir, url) {
32
+ const cleanUrl = cleanAssetUrl(url);
33
+ const projectRoot = resolve(projectDir);
34
+ const candidates = [];
35
+ for (const variant of decodeUrlPathVariants(cleanUrl)) {
36
+ const projectRelative = variant.startsWith("/") ? variant.slice(1) : variant;
37
+ const resolved = resolve(projectRoot, projectRelative);
38
+ if (isWithinProjectRoot(projectRoot, resolved)) {
39
+ addCandidate(candidates, resolved);
40
+ continue;
41
+ }
42
+ const normalized = posix.normalize(projectRelative.replace(/\\/g, "/"));
43
+ const clamped = normalized.replace(/^(\.\.\/)+/, "");
44
+ if (clamped && !clamped.startsWith("..")) {
45
+ addCandidate(candidates, resolve(projectRoot, clamped));
46
+ }
47
+ }
48
+ return candidates;
49
+ }
50
+ function resolveExistingLocalAsset(projectDir, url) {
51
+ const projectRoot = resolve(projectDir);
52
+ const resolved = resolveLocalAssetCandidates(projectRoot, url).find(existsSync);
53
+ if (!resolved) return null;
54
+ return { resolved, rootRelativePath: relative(projectRoot, resolved) };
55
+ }
56
+ function maskRange(src, pattern) {
57
+ return src.replace(pattern, (m) => " ".repeat(m.length));
58
+ }
59
+ function maskHtmlComments(src) {
60
+ const chunks = [];
61
+ let cursor = 0;
62
+ while (true) {
63
+ const start = src.indexOf("<!--", cursor);
64
+ if (start === -1) break;
65
+ const end = src.indexOf("-->", start + 4);
66
+ if (end === -1) break;
67
+ const afterComment = end + 3;
68
+ chunks.push(src.slice(cursor, start), " ".repeat(afterComment - start));
69
+ cursor = afterComment;
70
+ }
71
+ return chunks.length === 0 ? src : chunks.join("") + src.slice(cursor);
72
+ }
73
+ function maskNonScannableRanges(html) {
74
+ let out = maskHtmlComments(html);
75
+ out = maskRange(out, /<style\b[^>]*>[\s\S]*?<\/style\b[^>]*>/gi);
76
+ out = maskRange(out, /<script\b[^>]*>[\s\S]*?<\/script\b[^>]*>/gi);
77
+ return out;
78
+ }
79
+ export {
80
+ cleanAssetUrl,
81
+ isRemoteOrInlineUrl,
82
+ isWithinProjectRoot,
83
+ maskNonScannableRanges,
84
+ resolveExistingLocalAsset,
85
+ resolveLocalAssetCandidates
86
+ };
87
+ //# sourceMappingURL=assetResolution.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/assetResolution.ts","../src/utils/urlPath.ts"],"sourcesContent":["import { existsSync } from \"node:fs\";\nimport { isAbsolute, posix, relative, resolve } from \"node:path\";\nimport { decodeUrlPathVariants } from \"./composition.js\";\n\n/**\n * Shared local-asset resolution helpers for every package that maps\n * composition asset URLs to files on disk (lint project rules, the HEVC\n * preview check, studio-server's media codec scan). Import via the\n * `@hyperframes/parsers/asset-resolution` subpath.\n */\n\nexport function isRemoteOrInlineUrl(url: string): boolean {\n return /^(https?:|data:|blob:|\\/\\/|#)/i.test(url);\n}\n\nexport function cleanAssetUrl(url: string): string {\n return url.trim().split(/[?#]/, 1)[0] ?? \"\";\n}\n\nexport function isWithinProjectRoot(projectDir: string, candidate: string): boolean {\n const projectRoot = resolve(projectDir);\n const relativePath = relative(projectRoot, candidate);\n return relativePath === \"\" || (!relativePath.startsWith(\"..\") && !isAbsolute(relativePath));\n}\n\nfunction addCandidate(candidates: string[], candidate: string): void {\n if (!candidates.includes(candidate)) candidates.push(candidate);\n}\n\nexport function resolveLocalAssetCandidates(projectDir: string, url: string): string[] {\n const cleanUrl = cleanAssetUrl(url);\n const projectRoot = resolve(projectDir);\n const candidates: string[] = [];\n\n for (const variant of decodeUrlPathVariants(cleanUrl)) {\n const projectRelative = variant.startsWith(\"/\") ? variant.slice(1) : variant;\n const resolved = resolve(projectRoot, projectRelative);\n if (isWithinProjectRoot(projectRoot, resolved)) {\n addCandidate(candidates, resolved);\n continue;\n }\n\n const normalized = posix.normalize(projectRelative.replace(/\\\\/g, \"/\"));\n const clamped = normalized.replace(/^(\\.\\.\\/)+/, \"\");\n if (clamped && !clamped.startsWith(\"..\")) {\n addCandidate(candidates, resolve(projectRoot, clamped));\n }\n }\n\n return candidates;\n}\n\nexport function resolveExistingLocalAsset(\n projectDir: string,\n url: string,\n): { resolved: string; rootRelativePath: string } | null {\n const projectRoot = resolve(projectDir);\n const resolved = resolveLocalAssetCandidates(projectRoot, url).find(existsSync);\n if (!resolved) return null;\n return { resolved, rootRelativePath: relative(projectRoot, resolved) };\n}\n\nfunction maskRange(src: string, pattern: RegExp): string {\n return src.replace(pattern, (m) => \" \".repeat(m.length));\n}\n\nfunction maskHtmlComments(src: string): string {\n const chunks: string[] = [];\n let cursor = 0;\n\n while (true) {\n const start = src.indexOf(\"<!--\", cursor);\n if (start === -1) break;\n const end = src.indexOf(\"-->\", start + 4);\n if (end === -1) break;\n const afterComment = end + 3;\n chunks.push(src.slice(cursor, start), \" \".repeat(afterComment - start));\n cursor = afterComment;\n }\n\n return chunks.length === 0 ? src : chunks.join(\"\") + src.slice(cursor);\n}\n\n/** Blanks out comments, `<style>`, and `<script>` bodies so tag-scanning\n * regexes don't false-positive on commented-out or scripted markup. */\nexport function maskNonScannableRanges(html: string): string {\n let out = maskHtmlComments(html);\n out = maskRange(out, /<style\\b[^>]*>[\\s\\S]*?<\\/style\\b[^>]*>/gi);\n out = maskRange(out, /<script\\b[^>]*>[\\s\\S]*?<\\/script\\b[^>]*>/gi);\n return out;\n}\n","export function decodeUrlPathVariants(path: string): string[] {\n const variants = [path];\n try {\n const decoded = decodeURIComponent(path);\n if (decoded !== path) variants.unshift(decoded);\n } catch {\n // Malformed percent sequences may be literal filesystem names.\n }\n\n return variants;\n}\n"],"mappings":";AAAA,SAAS,kBAAkB;AAC3B,SAAS,YAAY,OAAO,UAAU,eAAe;;;ACD9C,SAAS,sBAAsB,MAAwB;AAC5D,QAAM,WAAW,CAAC,IAAI;AACtB,MAAI;AACF,UAAM,UAAU,mBAAmB,IAAI;AACvC,QAAI,YAAY,KAAM,UAAS,QAAQ,OAAO;AAAA,EAChD,QAAQ;AAAA,EAER;AAEA,SAAO;AACT;;;ADCO,SAAS,oBAAoB,KAAsB;AACxD,SAAO,iCAAiC,KAAK,GAAG;AAClD;AAEO,SAAS,cAAc,KAAqB;AACjD,SAAO,IAAI,KAAK,EAAE,MAAM,QAAQ,CAAC,EAAE,CAAC,KAAK;AAC3C;AAEO,SAAS,oBAAoB,YAAoB,WAA4B;AAClF,QAAM,cAAc,QAAQ,UAAU;AACtC,QAAM,eAAe,SAAS,aAAa,SAAS;AACpD,SAAO,iBAAiB,MAAO,CAAC,aAAa,WAAW,IAAI,KAAK,CAAC,WAAW,YAAY;AAC3F;AAEA,SAAS,aAAa,YAAsB,WAAyB;AACnE,MAAI,CAAC,WAAW,SAAS,SAAS,EAAG,YAAW,KAAK,SAAS;AAChE;AAEO,SAAS,4BAA4B,YAAoB,KAAuB;AACrF,QAAM,WAAW,cAAc,GAAG;AAClC,QAAM,cAAc,QAAQ,UAAU;AACtC,QAAM,aAAuB,CAAC;AAE9B,aAAW,WAAW,sBAAsB,QAAQ,GAAG;AACrD,UAAM,kBAAkB,QAAQ,WAAW,GAAG,IAAI,QAAQ,MAAM,CAAC,IAAI;AACrE,UAAM,WAAW,QAAQ,aAAa,eAAe;AACrD,QAAI,oBAAoB,aAAa,QAAQ,GAAG;AAC9C,mBAAa,YAAY,QAAQ;AACjC;AAAA,IACF;AAEA,UAAM,aAAa,MAAM,UAAU,gBAAgB,QAAQ,OAAO,GAAG,CAAC;AACtE,UAAM,UAAU,WAAW,QAAQ,cAAc,EAAE;AACnD,QAAI,WAAW,CAAC,QAAQ,WAAW,IAAI,GAAG;AACxC,mBAAa,YAAY,QAAQ,aAAa,OAAO,CAAC;AAAA,IACxD;AAAA,EACF;AAEA,SAAO;AACT;AAEO,SAAS,0BACd,YACA,KACuD;AACvD,QAAM,cAAc,QAAQ,UAAU;AACtC,QAAM,WAAW,4BAA4B,aAAa,GAAG,EAAE,KAAK,UAAU;AAC9E,MAAI,CAAC,SAAU,QAAO;AACtB,SAAO,EAAE,UAAU,kBAAkB,SAAS,aAAa,QAAQ,EAAE;AACvE;AAEA,SAAS,UAAU,KAAa,SAAyB;AACvD,SAAO,IAAI,QAAQ,SAAS,CAAC,MAAM,IAAI,OAAO,EAAE,MAAM,CAAC;AACzD;AAEA,SAAS,iBAAiB,KAAqB;AAC7C,QAAM,SAAmB,CAAC;AAC1B,MAAI,SAAS;AAEb,SAAO,MAAM;AACX,UAAM,QAAQ,IAAI,QAAQ,QAAQ,MAAM;AACxC,QAAI,UAAU,GAAI;AAClB,UAAM,MAAM,IAAI,QAAQ,OAAO,QAAQ,CAAC;AACxC,QAAI,QAAQ,GAAI;AAChB,UAAM,eAAe,MAAM;AAC3B,WAAO,KAAK,IAAI,MAAM,QAAQ,KAAK,GAAG,IAAI,OAAO,eAAe,KAAK,CAAC;AACtE,aAAS;AAAA,EACX;AAEA,SAAO,OAAO,WAAW,IAAI,MAAM,OAAO,KAAK,EAAE,IAAI,IAAI,MAAM,MAAM;AACvE;AAIO,SAAS,uBAAuB,MAAsB;AAC3D,MAAI,MAAM,iBAAiB,IAAI;AAC/B,QAAM,UAAU,KAAK,0CAA0C;AAC/D,QAAM,UAAU,KAAK,4CAA4C;AACjE,SAAO;AACT;","names":[]}
@@ -1,5 +1,6 @@
1
- import { C as CompositionVariable } from './types-CaeOJXdW.js';
2
- export { A as AddElementData, b as Asset, B as BooleanVariable, c as CANVAS_DIMENSIONS, d as COMPOSITION_VARIABLE_TYPES, a as CanvasResolution, e as ColorVariable, f as CompositionAPI, g as CompositionAsset, h as CompositionSpec, i as CompositionVariableBase, j as CompositionVariableType, D as DEFAULT_DURATIONS, E as ElementKeyframes, k as EnumVariable, F as FontVariable, I as ImageVariable, K as Keyframe, l as KeyframeProperties, M as MediaElementType, m as MediaFile, N as NumberVariable, P as PlayerAPI, n as StageZoom, S as StageZoomKeyframe, o as StringVariable, p as TIMELINE_COLORS, q as TimelineCompositionElement, T as TimelineElement, r as TimelineElementBase, s as TimelineElementType, t as TimelineMediaElement, u as TimelineTextElement, v as VALID_CANVAS_RESOLUTIONS, V as ValidationResult, W as WaveformData, w as getDefaultStageZoom, x as isCompositionElement, y as isMediaElement, z as isTextElement, G as normalizeResolutionFlag } from './types-CaeOJXdW.js';
1
+ import { C as CompositionVariable } from './types-ewozML_N.js';
2
+ export { A as AddElementData, b as Asset, B as BooleanVariable, c as CANVAS_DIMENSIONS, d as COMPOSITION_VARIABLE_TYPES, a as CanvasResolution, e as ColorVariable, f as CompositionAPI, g as CompositionAsset, h as CompositionSpec, i as CompositionVariableBase, j as CompositionVariableType, D as DEFAULT_DURATIONS, E as ElementKeyframes, k as EnumVariable, F as FontVariable, I as ImageVariable, K as Keyframe, l as KeyframeProperties, M as MediaElementType, m as MediaFile, N as NumberVariable, P as PlayerAPI, R as ResolvedResolutionFlag, n as StageZoom, S as StageZoomKeyframe, o as StringVariable, p as TIMELINE_COLORS, q as TimelineCompositionElement, T as TimelineElement, r as TimelineElementBase, s as TimelineElementType, t as TimelineMediaElement, u as TimelineTextElement, v as VALID_CANVAS_RESOLUTIONS, V as ValidationResult, W as WaveformData, w as getDefaultStageZoom, x as isAspectAgnosticResolutionAlias, y as isCompositionElement, z as isMediaElement, G as isTextElement, H as normalizeResolutionFlag, J as resolveResolutionFlagPair } from './types-ewozML_N.js';
3
+ export { CANONICAL_AUTHORED_TIMING_ATTRIBUTES, COMPOSITION_ATTRIBUTES, COMPOSITION_CONTRACT_VERSION, ClipAttributeReader, ClipAttributeWriter, ClipTiming, ClipTimingDiagnostic, ClipTimingDiagnosticCode, ClipTimingUpdate, ClipTimingWriteError, DERIVED_TIMING_ATTRIBUTES, LEGACY_TIMING_ATTRIBUTES, ReadClipTimingOptions, ReferenceExpression, parseNumeric, parseStartExpression, readClipTiming, writeClipTiming } from './compositionContract.js';
3
4
 
4
5
  /**
5
6
  * Browser-safe parser for the `data-composition-variables` schema attribute.
@@ -22,6 +22,12 @@ var RESOLUTION_ALIASES = {
22
22
  "square-1080p": "square",
23
23
  "4k-square": "square-4k"
24
24
  };
25
+ var ASPECT_AGNOSTIC_RESOLUTION_ALIASES = /* @__PURE__ */ new Set([
26
+ "1080p",
27
+ "hd",
28
+ "4k",
29
+ "uhd"
30
+ ]);
25
31
  function normalizeResolutionFlag(input) {
26
32
  if (!input) return void 0;
27
33
  const lowered = input.toLowerCase();
@@ -30,6 +36,16 @@ function normalizeResolutionFlag(input) {
30
36
  }
31
37
  return RESOLUTION_ALIASES[lowered];
32
38
  }
39
+ function isAspectAgnosticResolutionAlias(input) {
40
+ if (!input) return false;
41
+ return ASPECT_AGNOSTIC_RESOLUTION_ALIASES.has(input.toLowerCase());
42
+ }
43
+ function resolveResolutionFlagPair(input) {
44
+ return {
45
+ outputResolution: normalizeResolutionFlag(input),
46
+ outputResolutionAspectAgnostic: isAspectAgnosticResolutionAlias(input)
47
+ };
48
+ }
33
49
  var COMPOSITION_VARIABLE_TYPES = [
34
50
  "string",
35
51
  "number",
@@ -334,17 +350,294 @@ function scanVariableUsage(scriptText) {
334
350
  }
335
351
  return { usedIds, scanIncomplete };
336
352
  }
353
+
354
+ // src/compositionContract.ts
355
+ var COMPOSITION_CONTRACT_VERSION = 1;
356
+ var COMPOSITION_ATTRIBUTES = Object.freeze({
357
+ start: "data-start",
358
+ duration: "data-duration",
359
+ trackIndex: "data-track-index",
360
+ derivedEnd: "data-end",
361
+ legacyTrack: "data-layer"
362
+ });
363
+ var CANONICAL_AUTHORED_TIMING_ATTRIBUTES = Object.freeze([
364
+ COMPOSITION_ATTRIBUTES.start,
365
+ COMPOSITION_ATTRIBUTES.duration,
366
+ COMPOSITION_ATTRIBUTES.trackIndex
367
+ ]);
368
+ var DERIVED_TIMING_ATTRIBUTES = Object.freeze([
369
+ COMPOSITION_ATTRIBUTES.derivedEnd
370
+ ]);
371
+ var LEGACY_TIMING_ATTRIBUTES = Object.freeze([
372
+ COMPOSITION_ATTRIBUTES.derivedEnd,
373
+ COMPOSITION_ATTRIBUTES.legacyTrack
374
+ ]);
375
+ var ClipTimingWriteError = class extends Error {
376
+ code;
377
+ constructor(code, message) {
378
+ super(message);
379
+ this.name = "ClipTimingWriteError";
380
+ this.code = code;
381
+ }
382
+ };
383
+ function parseNumeric(value) {
384
+ if (value == null || value.trim() === "") return null;
385
+ const parsed = Number(value);
386
+ return Number.isFinite(parsed) ? parsed : null;
387
+ }
388
+ var REFERENCE_ID_PATTERN = /^[A-Za-z0-9_.:-]+$/;
389
+ function isAsciiDigitAt(value, index) {
390
+ const code = value.charCodeAt(index);
391
+ return code >= 48 && code <= 57;
392
+ }
393
+ function skipDigitsLeft(value, start) {
394
+ let cursor = start;
395
+ while (cursor >= 0 && isAsciiDigitAt(value, cursor)) cursor--;
396
+ return cursor;
397
+ }
398
+ function skipWhitespaceLeft(value, start) {
399
+ let cursor = start;
400
+ while (cursor >= 0 && (value[cursor] ?? "").trim() === "") cursor--;
401
+ return cursor;
402
+ }
403
+ function findMagnitudeStart(value) {
404
+ const last = value.length - 1;
405
+ if (!isAsciiDigitAt(value, last)) return null;
406
+ let cursor = skipDigitsLeft(value, last);
407
+ if (value[cursor] === ".") cursor = skipDigitsLeft(value, cursor - 1);
408
+ return cursor + 1;
409
+ }
410
+ function parseReferenceOffset(value) {
411
+ const magnitudeStart = findMagnitudeStart(value);
412
+ if (magnitudeStart == null) return null;
413
+ const operatorIndex = skipWhitespaceLeft(value, magnitudeStart - 1);
414
+ const operator = value[operatorIndex];
415
+ if (operator !== "+" && operator !== "-") return null;
416
+ const refId = value.slice(0, operatorIndex).trim();
417
+ if (!REFERENCE_ID_PATTERN.test(refId)) return null;
418
+ const magnitude = Number(value.slice(magnitudeStart));
419
+ if (!Number.isFinite(magnitude)) return null;
420
+ return { refId, operator, magnitude };
421
+ }
422
+ function parseStartExpression(raw) {
423
+ const normalized = (raw ?? "").trim();
424
+ if (!normalized) return null;
425
+ const absolute = parseNumeric(normalized);
426
+ if (absolute != null) return { kind: "absolute", value: absolute };
427
+ if (REFERENCE_ID_PATTERN.test(normalized)) {
428
+ return { kind: "reference", refId: normalized, offset: 0 };
429
+ }
430
+ const reference = parseReferenceOffset(normalized);
431
+ if (!reference) return null;
432
+ return {
433
+ kind: "reference",
434
+ refId: reference.refId,
435
+ offset: reference.operator === "-" ? -reference.magnitude : reference.magnitude
436
+ };
437
+ }
438
+ function pushDiagnostic(diagnostics, code, attribute, value) {
439
+ diagnostics.push({ code, attribute, value });
440
+ }
441
+ function resolveStart(expression, rawStart, options, diagnostics) {
442
+ if (rawStart == null || rawStart.trim() === "") {
443
+ return options.defaultStart === void 0 ? 0 : options.defaultStart;
444
+ }
445
+ if (!expression) {
446
+ pushDiagnostic(diagnostics, "invalid-start", COMPOSITION_ATTRIBUTES.start, rawStart);
447
+ return null;
448
+ }
449
+ if (expression.kind === "absolute") return Math.max(0, expression.value);
450
+ const referencedEnd = options.resolveReferenceEnd?.(expression.refId);
451
+ if (referencedEnd == null || !Number.isFinite(referencedEnd)) {
452
+ pushDiagnostic(
453
+ diagnostics,
454
+ "unresolved-start-reference",
455
+ COMPOSITION_ATTRIBUTES.start,
456
+ rawStart
457
+ );
458
+ return null;
459
+ }
460
+ return Math.max(0, referencedEnd + expression.offset);
461
+ }
462
+ function diagnoseDerivedEnd(rawEnd, canonicalEnd, diagnostics) {
463
+ pushDiagnostic(diagnostics, "deprecated-end", COMPOSITION_ATTRIBUTES.derivedEnd, rawEnd);
464
+ const parsedEnd = parseNumeric(rawEnd);
465
+ if (parsedEnd == null) {
466
+ pushDiagnostic(diagnostics, "invalid-end", COMPOSITION_ATTRIBUTES.derivedEnd, rawEnd);
467
+ } else if (canonicalEnd != null && parsedEnd !== canonicalEnd) {
468
+ pushDiagnostic(diagnostics, "conflicting-end", COMPOSITION_ATTRIBUTES.derivedEnd, rawEnd);
469
+ }
470
+ }
471
+ function readCanonicalDuration(rawDuration, start, diagnostics) {
472
+ const duration = parseNumeric(rawDuration);
473
+ if (duration == null || duration < 0) {
474
+ pushDiagnostic(diagnostics, "invalid-duration", COMPOSITION_ATTRIBUTES.duration, rawDuration);
475
+ return { duration: null, end: null, durationSource: "invalid" };
476
+ }
477
+ return {
478
+ duration,
479
+ end: start == null ? null : start + duration,
480
+ durationSource: "duration"
481
+ };
482
+ }
483
+ function readLegacyEnd(rawEnd, start, diagnostics) {
484
+ pushDiagnostic(diagnostics, "deprecated-end", COMPOSITION_ATTRIBUTES.derivedEnd, rawEnd);
485
+ const end = parseNumeric(rawEnd);
486
+ if (end == null) {
487
+ pushDiagnostic(diagnostics, "invalid-end", COMPOSITION_ATTRIBUTES.derivedEnd, rawEnd);
488
+ return { duration: null, end: null, durationSource: "invalid" };
489
+ }
490
+ if (start == null) return { duration: null, end, durationSource: "legacy-end" };
491
+ if (end < start) {
492
+ pushDiagnostic(diagnostics, "end-before-start", COMPOSITION_ATTRIBUTES.derivedEnd, rawEnd);
493
+ return { duration: null, end: null, durationSource: "invalid" };
494
+ }
495
+ return { duration: end - start, end, durationSource: "legacy-end" };
496
+ }
497
+ function readDuration(attributes, start, diagnostics) {
498
+ const rawDuration = attributes.getAttribute(COMPOSITION_ATTRIBUTES.duration);
499
+ const rawEnd = attributes.getAttribute(COMPOSITION_ATTRIBUTES.derivedEnd);
500
+ if (rawDuration == null) {
501
+ return rawEnd == null ? { duration: null, end: null, durationSource: "missing" } : readLegacyEnd(rawEnd, start, diagnostics);
502
+ }
503
+ const canonical = readCanonicalDuration(rawDuration, start, diagnostics);
504
+ if (rawEnd != null) diagnoseDerivedEnd(rawEnd, canonical.end, diagnostics);
505
+ return canonical;
506
+ }
507
+ function readTrackValue(rawValue, attribute, source, diagnostics) {
508
+ const trackIndex = parseNumeric(rawValue);
509
+ if (trackIndex == null || !Number.isInteger(trackIndex)) {
510
+ pushDiagnostic(diagnostics, "invalid-track-index", attribute, rawValue);
511
+ return { trackIndex: 0, trackSource: "invalid" };
512
+ }
513
+ return { trackIndex, trackSource: source };
514
+ }
515
+ function readTrack(attributes, diagnostics) {
516
+ const rawTrack = attributes.getAttribute(COMPOSITION_ATTRIBUTES.trackIndex);
517
+ const rawLayer = attributes.getAttribute(COMPOSITION_ATTRIBUTES.legacyTrack);
518
+ if (rawTrack == null && rawLayer == null) return { trackIndex: 0, trackSource: "default" };
519
+ if (rawTrack == null && rawLayer != null) {
520
+ pushDiagnostic(diagnostics, "deprecated-layer", COMPOSITION_ATTRIBUTES.legacyTrack, rawLayer);
521
+ return readTrackValue(
522
+ rawLayer,
523
+ COMPOSITION_ATTRIBUTES.legacyTrack,
524
+ "legacy-layer",
525
+ diagnostics
526
+ );
527
+ }
528
+ const canonical = readTrackValue(
529
+ rawTrack ?? "",
530
+ COMPOSITION_ATTRIBUTES.trackIndex,
531
+ "track-index",
532
+ diagnostics
533
+ );
534
+ if (rawLayer == null) return canonical;
535
+ pushDiagnostic(diagnostics, "deprecated-layer", COMPOSITION_ATTRIBUTES.legacyTrack, rawLayer);
536
+ const parsedLayer = parseNumeric(rawLayer);
537
+ if (parsedLayer != null && parsedLayer !== canonical.trackIndex) {
538
+ pushDiagnostic(diagnostics, "conflicting-layer", COMPOSITION_ATTRIBUTES.legacyTrack, rawLayer);
539
+ }
540
+ return canonical;
541
+ }
542
+ function readClipTiming(attributes, options = {}) {
543
+ const diagnostics = [];
544
+ const rawStart = attributes.getAttribute(COMPOSITION_ATTRIBUTES.start);
545
+ const startExpression = parseStartExpression(rawStart);
546
+ const start = resolveStart(startExpression, rawStart, options, diagnostics);
547
+ const duration = readDuration(attributes, start, diagnostics);
548
+ const track = readTrack(attributes, diagnostics);
549
+ return { startExpression, start, ...duration, ...track, diagnostics };
550
+ }
551
+ function serializeStartNumber(start) {
552
+ if (!Number.isFinite(start)) {
553
+ throw new ClipTimingWriteError("invalid-start", "start must be finite");
554
+ }
555
+ return String(start);
556
+ }
557
+ function serializeStartReference(start) {
558
+ if (start.kind === "absolute") return serializeStartNumber(start.value);
559
+ if (!start.refId || !Number.isFinite(start.offset)) {
560
+ throw new ClipTimingWriteError(
561
+ "invalid-start",
562
+ "reference start must have an id and finite offset"
563
+ );
564
+ }
565
+ if (start.offset === 0) return start.refId;
566
+ return `${start.refId} ${start.offset < 0 ? "-" : "+"} ${Math.abs(start.offset)}`;
567
+ }
568
+ function serializeStart(start) {
569
+ if (typeof start === "number") return serializeStartNumber(start);
570
+ if (typeof start === "object" && start != null) return serializeStartReference(start);
571
+ if (typeof start === "string" && parseStartExpression(start)) return start.trim();
572
+ throw new ClipTimingWriteError("invalid-start", `invalid start expression: ${start ?? ""}`);
573
+ }
574
+ function writeDuration(attributes, duration, current) {
575
+ if (duration === null) {
576
+ attributes.removeAttribute(COMPOSITION_ATTRIBUTES.duration);
577
+ return;
578
+ }
579
+ if (duration === void 0) {
580
+ if (attributes.getAttribute(COMPOSITION_ATTRIBUTES.duration) == null && current.duration != null) {
581
+ attributes.setAttribute(COMPOSITION_ATTRIBUTES.duration, String(current.duration));
582
+ }
583
+ return;
584
+ }
585
+ if (!Number.isFinite(duration) || duration < 0) {
586
+ throw new ClipTimingWriteError(
587
+ "invalid-duration",
588
+ "duration must be a finite, non-negative number"
589
+ );
590
+ }
591
+ attributes.setAttribute(COMPOSITION_ATTRIBUTES.duration, String(duration));
592
+ }
593
+ function writeTrack(attributes, trackIndex, current) {
594
+ if (trackIndex === null) {
595
+ attributes.removeAttribute(COMPOSITION_ATTRIBUTES.trackIndex);
596
+ return;
597
+ }
598
+ if (trackIndex === void 0) {
599
+ if (attributes.getAttribute(COMPOSITION_ATTRIBUTES.trackIndex) == null && current.trackSource === "legacy-layer") {
600
+ attributes.setAttribute(COMPOSITION_ATTRIBUTES.trackIndex, String(current.trackIndex));
601
+ }
602
+ return;
603
+ }
604
+ if (!Number.isFinite(trackIndex) || !Number.isInteger(trackIndex)) {
605
+ throw new ClipTimingWriteError("invalid-track-index", "trackIndex must be a finite integer");
606
+ }
607
+ attributes.setAttribute(COMPOSITION_ATTRIBUTES.trackIndex, String(trackIndex));
608
+ }
609
+ function writeClipTiming(attributes, update) {
610
+ const current = readClipTiming(attributes);
611
+ if (update.start !== void 0) {
612
+ attributes.setAttribute(COMPOSITION_ATTRIBUTES.start, serializeStart(update.start));
613
+ }
614
+ writeDuration(attributes, update.duration, current);
615
+ writeTrack(attributes, update.trackIndex, current);
616
+ const preserveUnresolvedLegacyEnd = update.duration === void 0 && attributes.getAttribute(COMPOSITION_ATTRIBUTES.duration) == null && attributes.getAttribute(COMPOSITION_ATTRIBUTES.derivedEnd) != null && current.duration == null;
617
+ if (!preserveUnresolvedLegacyEnd) {
618
+ attributes.removeAttribute(COMPOSITION_ATTRIBUTES.derivedEnd);
619
+ }
620
+ attributes.removeAttribute(COMPOSITION_ATTRIBUTES.legacyTrack);
621
+ return readClipTiming(attributes);
622
+ }
337
623
  export {
624
+ CANONICAL_AUTHORED_TIMING_ATTRIBUTES,
338
625
  CANONICAL_FONT_DISPLAY_NAMES,
339
626
  CANVAS_DIMENSIONS,
627
+ COMPOSITION_ATTRIBUTES,
628
+ COMPOSITION_CONTRACT_VERSION,
340
629
  COMPOSITION_VARIABLE_TYPES,
630
+ ClipTimingWriteError,
341
631
  DEFAULT_DURATIONS,
632
+ DERIVED_TIMING_ATTRIBUTES,
342
633
  FONT_ALIAS_KEYS,
343
634
  FONT_ALIAS_MAP,
635
+ LEGACY_TIMING_ATTRIBUTES,
344
636
  TIMELINE_COLORS,
345
637
  VALID_CANVAS_RESOLUTIONS,
346
638
  decodeUrlPathVariants,
347
639
  getDefaultStageZoom,
640
+ isAspectAgnosticResolutionAlias,
348
641
  isCompositionElement,
349
642
  isCompositionVariable,
350
643
  isMediaElement,
@@ -352,7 +645,12 @@ export {
352
645
  isTextElement,
353
646
  normalizeResolutionFlag,
354
647
  parseCompositionVariables,
648
+ parseNumeric,
649
+ parseStartExpression,
650
+ readClipTiming,
355
651
  resolveAliasDisplayName,
356
- scanVariableUsage
652
+ resolveResolutionFlagPair,
653
+ scanVariableUsage,
654
+ writeClipTiming
357
655
  };
358
656
  //# sourceMappingURL=composition.js.map