@hyperframes/parsers 0.8.50 → 0.8.51

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,48 @@
1
+ /** The one rule for a media element's timeline length; hosts only supply the source length. */
2
+ type MediaTag = "video" | "audio" | "img";
3
+ /** Reads a named attribute's raw string value. Works for a DOM Element's `getAttribute`
4
+ * (browser or linkedom) or a plain attrs-record lookup -- callers pick whichever they have. */
5
+ type AttrReader = (name: string) => string | null | undefined;
6
+ /** Playback offset into the source: `data-playback-start`, falling back to the older
7
+ * `data-media-start`; an invalid or negative value falls through to the next. One reader for
8
+ * every host -- the runtime and `htmlParser` used to read this two different ways. */
9
+ declare function readMediaOffsetSeconds(getAttr: AttrReader): number;
10
+ /** The one bound on a clip's playback rate; core re-exports it for the rate lane. */
11
+ declare const MIN_PLAYBACK_RATE = 0.1;
12
+ declare const MAX_PLAYBACK_RATE = 10;
13
+ /** A playback rate clamped to the bounds above; anything non-positive or non-finite is 1. */
14
+ declare function clampPlaybackRate(raw: number): number;
15
+ /** `data-playback-rate`, parsed like the browser parses a native rate (`"2x"` is 2), else
16
+ * `fallback` (a media element's own `defaultPlaybackRate` in the browser), clamped. */
17
+ declare function readPlaybackRate(getAttr: AttrReader, fallback?: number): number;
18
+ /** `data-duration` when it is a positive number, else null. */
19
+ declare function readDataDurationSeconds(getAttr: AttrReader): number | null;
20
+ /** An authored trim: `data-duration` if positive, else `data-end - start` if `data-end` is
21
+ * authored, else null (nothing authored). */
22
+ declare function readAuthoredDurationSeconds(getAttr: AttrReader, startSeconds: number): number | null;
23
+ /** The dropped-image default: what Studio gives a freshly dropped image before any trim.
24
+ * One owner; everything needing an untrimmed image length imports this. */
25
+ declare const DEFAULT_IMAGE_TIMELINE_DURATION_SECONDS = 3;
26
+ /** The arithmetic alone: no attribute reading, no DOM, no probing. */
27
+ declare function resolveNaturalDurationSeconds(sourceDurationSeconds: number, mediaStartSeconds: number, playbackRate: number): number | null;
28
+ type MediaDurationSource = "authored" | "media" | "default" | "pending";
29
+ interface MediaDurationResult {
30
+ seconds: number | null;
31
+ source: MediaDurationSource;
32
+ /** Set only when source is "pending": why nothing could be resolved yet. */
33
+ reason?: string;
34
+ }
35
+ interface ResolveMediaDurationInput {
36
+ tag: MediaTag;
37
+ /** From `readAuthoredDurationSeconds`, or null when nothing is authored. */
38
+ authoredDurationSeconds: number | null;
39
+ /** The source's own length once a probe has answered, else null. Ignored for `img`. */
40
+ sourceDurationSeconds: number | null;
41
+ mediaStartSeconds: number;
42
+ playbackRate: number;
43
+ }
44
+ declare function resolveMediaDuration(input: ResolveMediaDurationInput): MediaDurationResult;
45
+ /** Readers not yet converted to `resolveMediaDuration`; each reader PR removes its entry, so this only shrinks. */
46
+ declare const PENDING_MEDIA_DURATION_READERS: readonly ["core/src/runtime/init.ts (visibility end, clock, seek, WebAudio scheduling)", "core/src/compiler/timingCompiler.ts compileTag", "core/src/compiler/htmlCompiler.ts compileHtml", "producer/src/services/htmlCompiler.ts resolveMediaDuration", "engine/src/services/videoFrameExtractor.ts (extraction window)", "engine/src/services/audioMixer.ts", "parsers/src/htmlParser.ts (defaults to 5s, reads only data-media-start)", "studio (timelineDOM.ts, timelineElementHelpers.ts, useTimelineSyncCallbacks.ts)"];
47
+
48
+ export { type AttrReader, DEFAULT_IMAGE_TIMELINE_DURATION_SECONDS, MAX_PLAYBACK_RATE, MIN_PLAYBACK_RATE, type MediaDurationResult, type MediaDurationSource, type MediaTag, PENDING_MEDIA_DURATION_READERS, type ResolveMediaDurationInput, clampPlaybackRate, readAuthoredDurationSeconds, readDataDurationSeconds, readMediaOffsetSeconds, readPlaybackRate, resolveMediaDuration, resolveNaturalDurationSeconds };
@@ -0,0 +1,104 @@
1
+ // src/compositionContract.ts
2
+ var COMPOSITION_ATTRIBUTES = Object.freeze({
3
+ start: "data-start",
4
+ duration: "data-duration",
5
+ trackIndex: "data-track-index",
6
+ derivedEnd: "data-end",
7
+ legacyTrack: "data-layer"
8
+ });
9
+ var CANONICAL_AUTHORED_TIMING_ATTRIBUTES = Object.freeze([
10
+ COMPOSITION_ATTRIBUTES.start,
11
+ COMPOSITION_ATTRIBUTES.duration,
12
+ COMPOSITION_ATTRIBUTES.trackIndex
13
+ ]);
14
+ var DERIVED_TIMING_ATTRIBUTES = Object.freeze([
15
+ COMPOSITION_ATTRIBUTES.derivedEnd
16
+ ]);
17
+ var LEGACY_TIMING_ATTRIBUTES = Object.freeze([
18
+ COMPOSITION_ATTRIBUTES.derivedEnd,
19
+ COMPOSITION_ATTRIBUTES.legacyTrack
20
+ ]);
21
+ function parseNumeric(value) {
22
+ if (value == null || value.trim() === "") return null;
23
+ const parsed = Number(value);
24
+ return Number.isFinite(parsed) ? parsed : null;
25
+ }
26
+
27
+ // src/mediaDuration.ts
28
+ var nonNegative = (raw) => {
29
+ const value = parseNumeric(raw);
30
+ return value !== null && value >= 0 ? value : null;
31
+ };
32
+ function readMediaOffsetSeconds(getAttr) {
33
+ return nonNegative(getAttr("data-playback-start")) ?? nonNegative(getAttr("data-media-start")) ?? 0;
34
+ }
35
+ var MIN_PLAYBACK_RATE = 0.1;
36
+ var MAX_PLAYBACK_RATE = 10;
37
+ function clampPlaybackRate(raw) {
38
+ return Number.isFinite(raw) && raw > 0 ? Math.max(MIN_PLAYBACK_RATE, Math.min(MAX_PLAYBACK_RATE, raw)) : 1;
39
+ }
40
+ function readPlaybackRate(getAttr, fallback = 1) {
41
+ const authored = Number.parseFloat(getAttr("data-playback-rate") ?? "");
42
+ return clampPlaybackRate(Number.isFinite(authored) && authored > 0 ? authored : fallback);
43
+ }
44
+ function readDataDurationSeconds(getAttr) {
45
+ const duration = parseNumeric(getAttr("data-duration"));
46
+ return duration !== null && duration > 0 ? duration : null;
47
+ }
48
+ function readAuthoredDurationSeconds(getAttr, startSeconds) {
49
+ const duration = readDataDurationSeconds(getAttr);
50
+ if (duration !== null) return duration;
51
+ const end = parseNumeric(getAttr("data-end"));
52
+ return end !== null ? end - startSeconds : null;
53
+ }
54
+ var DEFAULT_IMAGE_TIMELINE_DURATION_SECONDS = 3;
55
+ function resolveNaturalDurationSeconds(sourceDurationSeconds, mediaStartSeconds, playbackRate) {
56
+ if (!Number.isFinite(sourceDurationSeconds)) return null;
57
+ const remaining = Math.max(0, sourceDurationSeconds - mediaStartSeconds);
58
+ return remaining / clampPlaybackRate(playbackRate);
59
+ }
60
+ function resolveMediaDuration(input) {
61
+ const { tag, authoredDurationSeconds, sourceDurationSeconds, mediaStartSeconds, playbackRate } = input;
62
+ if (authoredDurationSeconds !== null && authoredDurationSeconds > 0) {
63
+ return { seconds: authoredDurationSeconds, source: "authored" };
64
+ }
65
+ if (tag === "img") {
66
+ return { seconds: DEFAULT_IMAGE_TIMELINE_DURATION_SECONDS, source: "default" };
67
+ }
68
+ if (sourceDurationSeconds === null) {
69
+ return { seconds: null, source: "pending", reason: "source duration not yet probed" };
70
+ }
71
+ const seconds = resolveNaturalDurationSeconds(
72
+ sourceDurationSeconds,
73
+ mediaStartSeconds,
74
+ playbackRate
75
+ );
76
+ if (seconds === null) {
77
+ return { seconds: null, source: "pending", reason: "source reported a non-finite duration" };
78
+ }
79
+ return { seconds, source: "media" };
80
+ }
81
+ var PENDING_MEDIA_DURATION_READERS = [
82
+ "core/src/runtime/init.ts (visibility end, clock, seek, WebAudio scheduling)",
83
+ "core/src/compiler/timingCompiler.ts compileTag",
84
+ "core/src/compiler/htmlCompiler.ts compileHtml",
85
+ "producer/src/services/htmlCompiler.ts resolveMediaDuration",
86
+ "engine/src/services/videoFrameExtractor.ts (extraction window)",
87
+ "engine/src/services/audioMixer.ts",
88
+ "parsers/src/htmlParser.ts (defaults to 5s, reads only data-media-start)",
89
+ "studio (timelineDOM.ts, timelineElementHelpers.ts, useTimelineSyncCallbacks.ts)"
90
+ ];
91
+ export {
92
+ DEFAULT_IMAGE_TIMELINE_DURATION_SECONDS,
93
+ MAX_PLAYBACK_RATE,
94
+ MIN_PLAYBACK_RATE,
95
+ PENDING_MEDIA_DURATION_READERS,
96
+ clampPlaybackRate,
97
+ readAuthoredDurationSeconds,
98
+ readDataDurationSeconds,
99
+ readMediaOffsetSeconds,
100
+ readPlaybackRate,
101
+ resolveMediaDuration,
102
+ resolveNaturalDurationSeconds
103
+ };
104
+ //# sourceMappingURL=mediaDuration.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/compositionContract.ts","../src/mediaDuration.ts"],"sourcesContent":["/**\n * Browser-safe authored composition contract.\n *\n * Source HTML is authored with data-start + data-duration + data-track-index.\n * data-end is compiler-derived and data-layer is legacy input only. Readers\n * accept legacy documents; writers always emit the canonical representation.\n */\n\nexport const COMPOSITION_CONTRACT_VERSION = 1 as const;\n\nexport const COMPOSITION_ATTRIBUTES = Object.freeze({\n start: \"data-start\",\n duration: \"data-duration\",\n trackIndex: \"data-track-index\",\n derivedEnd: \"data-end\",\n legacyTrack: \"data-layer\",\n} as const);\n\nexport const CANONICAL_AUTHORED_TIMING_ATTRIBUTES = Object.freeze([\n COMPOSITION_ATTRIBUTES.start,\n COMPOSITION_ATTRIBUTES.duration,\n COMPOSITION_ATTRIBUTES.trackIndex,\n] as const);\n\nexport const DERIVED_TIMING_ATTRIBUTES = Object.freeze([\n COMPOSITION_ATTRIBUTES.derivedEnd,\n] as const);\n\nexport const LEGACY_TIMING_ATTRIBUTES = Object.freeze([\n COMPOSITION_ATTRIBUTES.derivedEnd,\n COMPOSITION_ATTRIBUTES.legacyTrack,\n] as const);\n\nexport type ReferenceExpression =\n | { kind: \"absolute\"; value: number }\n | { kind: \"reference\"; refId: string; offset: number };\n\nexport type ClipTimingDiagnosticCode =\n | \"invalid-start\"\n | \"unresolved-start-reference\"\n | \"invalid-duration\"\n | \"invalid-end\"\n | \"end-before-start\"\n | \"deprecated-end\"\n | \"conflicting-end\"\n | \"invalid-track-index\"\n | \"deprecated-layer\"\n | \"conflicting-layer\";\n\nexport interface ClipTimingDiagnostic {\n code: ClipTimingDiagnosticCode;\n attribute: string;\n value: string | null;\n}\n\nexport interface ClipAttributeReader {\n getAttribute(name: string): string | null;\n}\n\nexport interface ClipAttributeWriter extends ClipAttributeReader {\n setAttribute(name: string, value: string): void;\n removeAttribute(name: string): void;\n}\n\nexport interface ReadClipTimingOptions {\n /** Resolve a reference to the referenced clip's absolute end time. */\n resolveReferenceEnd?: (refId: string) => number | null | undefined;\n /** Start used when data-start is absent. Defaults to zero. */\n defaultStart?: number | null;\n}\n\nexport interface ClipTiming {\n startExpression: ReferenceExpression | null;\n start: number | null;\n duration: number | null;\n end: number | null;\n trackIndex: number;\n durationSource: \"duration\" | \"legacy-end\" | \"missing\" | \"invalid\";\n trackSource: \"track-index\" | \"legacy-layer\" | \"default\" | \"invalid\";\n diagnostics: ClipTimingDiagnostic[];\n}\n\nexport interface ClipTimingUpdate {\n start?: number | string | ReferenceExpression;\n duration?: number | null;\n trackIndex?: number | null;\n}\n\nexport class ClipTimingWriteError extends Error {\n readonly code: \"invalid-start\" | \"invalid-duration\" | \"invalid-track-index\";\n\n constructor(code: ClipTimingWriteError[\"code\"], message: string) {\n super(message);\n this.name = \"ClipTimingWriteError\";\n this.code = code;\n }\n}\n\n/** Parse a value to a finite number, or null if it is absent/invalid. */\nexport function parseNumeric(value: string | null | undefined): number | null {\n if (value == null || value.trim() === \"\") return null;\n const parsed = Number(value);\n return Number.isFinite(parsed) ? parsed : null;\n}\n\nconst REFERENCE_ID_PATTERN = /^[A-Za-z0-9_.:-]+$/;\n\nfunction isAsciiDigitAt(value: string, index: number): boolean {\n const code = value.charCodeAt(index);\n return code >= 48 && code <= 57;\n}\n\nfunction skipDigitsLeft(value: string, start: number): number {\n let cursor = start;\n while (cursor >= 0 && isAsciiDigitAt(value, cursor)) cursor--;\n return cursor;\n}\n\nfunction skipWhitespaceLeft(value: string, start: number): number {\n let cursor = start;\n while (cursor >= 0 && (value[cursor] ?? \"\").trim() === \"\") cursor--;\n return cursor;\n}\n\nfunction findMagnitudeStart(value: string): number | null {\n const last = value.length - 1;\n if (!isAsciiDigitAt(value, last)) return null;\n let cursor = skipDigitsLeft(value, last);\n if (value[cursor] === \".\") cursor = skipDigitsLeft(value, cursor - 1);\n return cursor + 1;\n}\n\nfunction parseReferenceOffset(\n value: string,\n): { refId: string; operator: \"+\" | \"-\"; magnitude: number } | null {\n const magnitudeStart = findMagnitudeStart(value);\n if (magnitudeStart == null) return null;\n const operatorIndex = skipWhitespaceLeft(value, magnitudeStart - 1);\n const operator = value[operatorIndex];\n if (operator !== \"+\" && operator !== \"-\") return null;\n\n const refId = value.slice(0, operatorIndex).trim();\n if (!REFERENCE_ID_PATTERN.test(refId)) return null;\n const magnitude = Number(value.slice(magnitudeStart));\n if (!Number.isFinite(magnitude)) return null;\n return { refId, operator, magnitude };\n}\n\n/**\n * Parse the data-start grammar: absolute seconds, `clip-id`, or\n * `clip-id +/- offset`, where references resolve to the referenced clip's end.\n */\nexport function parseStartExpression(raw: string | null | undefined): ReferenceExpression | null {\n const normalized = (raw ?? \"\").trim();\n if (!normalized) return null;\n const absolute = parseNumeric(normalized);\n if (absolute != null) return { kind: \"absolute\", value: absolute };\n if (REFERENCE_ID_PATTERN.test(normalized)) {\n return { kind: \"reference\", refId: normalized, offset: 0 };\n }\n const reference = parseReferenceOffset(normalized);\n if (!reference) return null;\n return {\n kind: \"reference\",\n refId: reference.refId,\n offset: reference.operator === \"-\" ? -reference.magnitude : reference.magnitude,\n };\n}\n\nfunction pushDiagnostic(\n diagnostics: ClipTimingDiagnostic[],\n code: ClipTimingDiagnosticCode,\n attribute: string,\n value: string | null,\n): void {\n diagnostics.push({ code, attribute, value });\n}\n\nfunction resolveStart(\n expression: ReferenceExpression | null,\n rawStart: string | null,\n options: ReadClipTimingOptions,\n diagnostics: ClipTimingDiagnostic[],\n): number | null {\n if (rawStart == null || rawStart.trim() === \"\") {\n return options.defaultStart === undefined ? 0 : options.defaultStart;\n }\n if (!expression) {\n pushDiagnostic(diagnostics, \"invalid-start\", COMPOSITION_ATTRIBUTES.start, rawStart);\n return null;\n }\n if (expression.kind === \"absolute\") return Math.max(0, expression.value);\n const referencedEnd = options.resolveReferenceEnd?.(expression.refId);\n if (referencedEnd == null || !Number.isFinite(referencedEnd)) {\n pushDiagnostic(\n diagnostics,\n \"unresolved-start-reference\",\n COMPOSITION_ATTRIBUTES.start,\n rawStart,\n );\n return null;\n }\n return Math.max(0, referencedEnd + expression.offset);\n}\n\ntype DurationRead = Pick<ClipTiming, \"duration\" | \"end\" | \"durationSource\">;\ntype TrackRead = Pick<ClipTiming, \"trackIndex\" | \"trackSource\">;\n\n// Float pairs like `data-start=\"0.1\" + data-duration=\"0.2\"` add to\n// `0.30000000000000004`, so the compiler-emitted `data-end` string can be a few\n// ulps off the sum a reader computes from the canonical inputs. Any drift below\n// this ceiling is treated as equal for the derived-end reconciliation check —\n// well below one 60fps frame (~16.67 ms) and orders of magnitude above the\n// worst realistic IEEE-754 residual (~2e-16 s).\nconst DERIVED_END_EQUALITY_EPSILON_SECONDS = 1e-9;\n\nfunction derivedEndsAreConsistent(parsedEnd: number, canonicalEnd: number): boolean {\n return Math.abs(parsedEnd - canonicalEnd) <= DERIVED_END_EQUALITY_EPSILON_SECONDS;\n}\n\n// Reconcile a `data-end` attribute paired with canonical `data-duration`.\n//\n// The compiler (`compileTimingAttrs` in @hyperframes/core) writes\n// `data-end = data-start + data-duration` into the bundled HTML so the runtime\n// can key off a single attribute. That derived attribute is legitimate — it is\n// not a legacy authoring shape. Treating it as `deprecated-end` here made the\n// linter fire `deprecated_data_end` on the compiler's own output whenever\n// StaticGuard re-validated a bundled composition, producing a false-positive\n// cluster reported in the field (n=25+ across cli-feedback crons 61-68).\n//\n// We keep `deprecated-end` for the truly legacy shape (see `readLegacyEnd` —\n// `data-end` present without `data-duration`) and for the *conflicting* case\n// where an author left a stale `data-end` behind a canonical duration edit; a\n// stale end must not be silently overridden without a diagnostic.\nfunction diagnoseDerivedEnd(\n rawEnd: string,\n canonicalEnd: number | null,\n diagnostics: ClipTimingDiagnostic[],\n): void {\n const parsedEnd = parseNumeric(rawEnd);\n if (parsedEnd == null) {\n pushDiagnostic(diagnostics, \"deprecated-end\", COMPOSITION_ATTRIBUTES.derivedEnd, rawEnd);\n pushDiagnostic(diagnostics, \"invalid-end\", COMPOSITION_ATTRIBUTES.derivedEnd, rawEnd);\n return;\n }\n if (canonicalEnd == null) {\n // Canonical duration exists but resolved end is unknown (e.g. unresolved\n // reference start). Preserve prior behavior — flag as deprecated so authors\n // remove the stale companion attribute.\n pushDiagnostic(diagnostics, \"deprecated-end\", COMPOSITION_ATTRIBUTES.derivedEnd, rawEnd);\n return;\n }\n if (derivedEndsAreConsistent(parsedEnd, canonicalEnd)) {\n // Compiler-derived (or manually consistent) `data-end` alongside\n // `data-duration` — silent. No diagnostic.\n return;\n }\n pushDiagnostic(diagnostics, \"deprecated-end\", COMPOSITION_ATTRIBUTES.derivedEnd, rawEnd);\n pushDiagnostic(diagnostics, \"conflicting-end\", COMPOSITION_ATTRIBUTES.derivedEnd, rawEnd);\n}\n\nfunction readCanonicalDuration(\n rawDuration: string,\n start: number | null,\n diagnostics: ClipTimingDiagnostic[],\n): DurationRead {\n const duration = parseNumeric(rawDuration);\n if (duration == null || duration < 0) {\n pushDiagnostic(diagnostics, \"invalid-duration\", COMPOSITION_ATTRIBUTES.duration, rawDuration);\n return { duration: null, end: null, durationSource: \"invalid\" };\n }\n return {\n duration,\n end: start == null ? null : start + duration,\n durationSource: \"duration\",\n };\n}\n\nfunction readLegacyEnd(\n rawEnd: string,\n start: number | null,\n diagnostics: ClipTimingDiagnostic[],\n): DurationRead {\n pushDiagnostic(diagnostics, \"deprecated-end\", COMPOSITION_ATTRIBUTES.derivedEnd, rawEnd);\n const end = parseNumeric(rawEnd);\n if (end == null) {\n pushDiagnostic(diagnostics, \"invalid-end\", COMPOSITION_ATTRIBUTES.derivedEnd, rawEnd);\n return { duration: null, end: null, durationSource: \"invalid\" };\n }\n if (start == null) return { duration: null, end, durationSource: \"legacy-end\" };\n if (end < start) {\n pushDiagnostic(diagnostics, \"end-before-start\", COMPOSITION_ATTRIBUTES.derivedEnd, rawEnd);\n return { duration: null, end: null, durationSource: \"invalid\" };\n }\n return { duration: end - start, end, durationSource: \"legacy-end\" };\n}\n\nfunction readDuration(\n attributes: ClipAttributeReader,\n start: number | null,\n diagnostics: ClipTimingDiagnostic[],\n): DurationRead {\n const rawDuration = attributes.getAttribute(COMPOSITION_ATTRIBUTES.duration);\n const rawEnd = attributes.getAttribute(COMPOSITION_ATTRIBUTES.derivedEnd);\n if (rawDuration == null) {\n return rawEnd == null\n ? { duration: null, end: null, durationSource: \"missing\" }\n : readLegacyEnd(rawEnd, start, diagnostics);\n }\n const canonical = readCanonicalDuration(rawDuration, start, diagnostics);\n if (rawEnd != null) diagnoseDerivedEnd(rawEnd, canonical.end, diagnostics);\n return canonical;\n}\n\nfunction readTrackValue(\n rawValue: string,\n attribute: string,\n source: \"track-index\" | \"legacy-layer\",\n diagnostics: ClipTimingDiagnostic[],\n): TrackRead {\n const trackIndex = parseNumeric(rawValue);\n if (trackIndex == null || !Number.isInteger(trackIndex)) {\n pushDiagnostic(diagnostics, \"invalid-track-index\", attribute, rawValue);\n return { trackIndex: 0, trackSource: \"invalid\" };\n }\n return { trackIndex, trackSource: source };\n}\n\nfunction readTrack(\n attributes: ClipAttributeReader,\n diagnostics: ClipTimingDiagnostic[],\n): TrackRead {\n const rawTrack = attributes.getAttribute(COMPOSITION_ATTRIBUTES.trackIndex);\n const rawLayer = attributes.getAttribute(COMPOSITION_ATTRIBUTES.legacyTrack);\n if (rawTrack == null && rawLayer == null) return { trackIndex: 0, trackSource: \"default\" };\n if (rawTrack == null && rawLayer != null) {\n pushDiagnostic(diagnostics, \"deprecated-layer\", COMPOSITION_ATTRIBUTES.legacyTrack, rawLayer);\n return readTrackValue(\n rawLayer,\n COMPOSITION_ATTRIBUTES.legacyTrack,\n \"legacy-layer\",\n diagnostics,\n );\n }\n const canonical = readTrackValue(\n rawTrack ?? \"\",\n COMPOSITION_ATTRIBUTES.trackIndex,\n \"track-index\",\n diagnostics,\n );\n if (rawLayer == null) return canonical;\n pushDiagnostic(diagnostics, \"deprecated-layer\", COMPOSITION_ATTRIBUTES.legacyTrack, rawLayer);\n const parsedLayer = parseNumeric(rawLayer);\n if (parsedLayer != null && parsedLayer !== canonical.trackIndex) {\n pushDiagnostic(diagnostics, \"conflicting-layer\", COMPOSITION_ATTRIBUTES.legacyTrack, rawLayer);\n }\n return canonical;\n}\n\n/** Read canonical timing, accepting legacy attributes without preferring them. */\nexport function readClipTiming(\n attributes: ClipAttributeReader,\n options: ReadClipTimingOptions = {},\n): ClipTiming {\n const diagnostics: ClipTimingDiagnostic[] = [];\n const rawStart = attributes.getAttribute(COMPOSITION_ATTRIBUTES.start);\n const startExpression = parseStartExpression(rawStart);\n const start = resolveStart(startExpression, rawStart, options, diagnostics);\n const duration = readDuration(attributes, start, diagnostics);\n const track = readTrack(attributes, diagnostics);\n return { startExpression, start, ...duration, ...track, diagnostics };\n}\n\nfunction serializeStartNumber(start: number): string {\n if (!Number.isFinite(start)) {\n throw new ClipTimingWriteError(\"invalid-start\", \"start must be finite\");\n }\n return String(start);\n}\n\nfunction serializeStartReference(start: ReferenceExpression): string {\n if (start.kind === \"absolute\") return serializeStartNumber(start.value);\n if (!start.refId || !Number.isFinite(start.offset)) {\n throw new ClipTimingWriteError(\n \"invalid-start\",\n \"reference start must have an id and finite offset\",\n );\n }\n if (start.offset === 0) return start.refId;\n return `${start.refId} ${start.offset < 0 ? \"-\" : \"+\"} ${Math.abs(start.offset)}`;\n}\n\nfunction serializeStart(start: ClipTimingUpdate[\"start\"]): string {\n if (typeof start === \"number\") return serializeStartNumber(start);\n if (typeof start === \"object\" && start != null) return serializeStartReference(start);\n if (typeof start === \"string\" && parseStartExpression(start)) return start.trim();\n throw new ClipTimingWriteError(\"invalid-start\", `invalid start expression: ${start ?? \"\"}`);\n}\n\nfunction writeDuration(\n attributes: ClipAttributeWriter,\n duration: number | null | undefined,\n current: ClipTiming,\n): void {\n if (duration === null) {\n attributes.removeAttribute(COMPOSITION_ATTRIBUTES.duration);\n return;\n }\n if (duration === undefined) {\n if (\n attributes.getAttribute(COMPOSITION_ATTRIBUTES.duration) == null &&\n current.duration != null\n ) {\n attributes.setAttribute(COMPOSITION_ATTRIBUTES.duration, String(current.duration));\n }\n return;\n }\n if (!Number.isFinite(duration) || duration < 0) {\n throw new ClipTimingWriteError(\n \"invalid-duration\",\n \"duration must be a finite, non-negative number\",\n );\n }\n attributes.setAttribute(COMPOSITION_ATTRIBUTES.duration, String(duration));\n}\n\nfunction writeTrack(\n attributes: ClipAttributeWriter,\n trackIndex: number | null | undefined,\n current: ClipTiming,\n): void {\n if (trackIndex === null) {\n attributes.removeAttribute(COMPOSITION_ATTRIBUTES.trackIndex);\n return;\n }\n if (trackIndex === undefined) {\n if (\n attributes.getAttribute(COMPOSITION_ATTRIBUTES.trackIndex) == null &&\n current.trackSource === \"legacy-layer\"\n ) {\n attributes.setAttribute(COMPOSITION_ATTRIBUTES.trackIndex, String(current.trackIndex));\n }\n return;\n }\n if (!Number.isFinite(trackIndex) || !Number.isInteger(trackIndex)) {\n throw new ClipTimingWriteError(\"invalid-track-index\", \"trackIndex must be a finite integer\");\n }\n attributes.setAttribute(COMPOSITION_ATTRIBUTES.trackIndex, String(trackIndex));\n}\n\n/** Write only canonical authored timing and remove derived/legacy source attributes. */\nexport function writeClipTiming(\n attributes: ClipAttributeWriter,\n update: ClipTimingUpdate,\n): ClipTiming {\n const current = readClipTiming(attributes);\n if (update.start !== undefined) {\n attributes.setAttribute(COMPOSITION_ATTRIBUTES.start, serializeStart(update.start));\n }\n writeDuration(attributes, update.duration, current);\n writeTrack(attributes, update.trackIndex, current);\n // A legacy end paired with an unresolved reference start cannot be converted\n // to data-duration without a reference resolver. On a duration-omitting edit\n // (for example, moving only the track), preserve that sole duration source\n // instead of canonicalizing it into data loss.\n const preserveUnresolvedLegacyEnd =\n update.duration === undefined &&\n attributes.getAttribute(COMPOSITION_ATTRIBUTES.duration) == null &&\n attributes.getAttribute(COMPOSITION_ATTRIBUTES.derivedEnd) != null &&\n current.duration == null;\n if (!preserveUnresolvedLegacyEnd) {\n attributes.removeAttribute(COMPOSITION_ATTRIBUTES.derivedEnd);\n }\n attributes.removeAttribute(COMPOSITION_ATTRIBUTES.legacyTrack);\n return readClipTiming(attributes);\n}\n","/** The one rule for a media element's timeline length; hosts only supply the source length. */\n\nimport { parseNumeric } from \"./compositionContract.js\";\n\nexport type MediaTag = \"video\" | \"audio\" | \"img\";\n\n/** Reads a named attribute's raw string value. Works for a DOM Element's `getAttribute`\n * (browser or linkedom) or a plain attrs-record lookup -- callers pick whichever they have. */\nexport type AttrReader = (name: string) => string | null | undefined;\n\nconst nonNegative = (raw: string | null | undefined): number | null => {\n const value = parseNumeric(raw);\n return value !== null && value >= 0 ? value : null;\n};\n\n/** Playback offset into the source: `data-playback-start`, falling back to the older\n * `data-media-start`; an invalid or negative value falls through to the next. One reader for\n * every host -- the runtime and `htmlParser` used to read this two different ways. */\nexport function readMediaOffsetSeconds(getAttr: AttrReader): number {\n return (\n nonNegative(getAttr(\"data-playback-start\")) ?? nonNegative(getAttr(\"data-media-start\")) ?? 0\n );\n}\n\n/** The one bound on a clip's playback rate; core re-exports it for the rate lane. */\nexport const MIN_PLAYBACK_RATE = 0.1;\nexport const MAX_PLAYBACK_RATE = 10;\n\n/** A playback rate clamped to the bounds above; anything non-positive or non-finite is 1. */\nexport function clampPlaybackRate(raw: number): number {\n return Number.isFinite(raw) && raw > 0\n ? Math.max(MIN_PLAYBACK_RATE, Math.min(MAX_PLAYBACK_RATE, raw))\n : 1;\n}\n\n/** `data-playback-rate`, parsed like the browser parses a native rate (`\"2x\"` is 2), else\n * `fallback` (a media element's own `defaultPlaybackRate` in the browser), clamped. */\nexport function readPlaybackRate(getAttr: AttrReader, fallback = 1): number {\n const authored = Number.parseFloat(getAttr(\"data-playback-rate\") ?? \"\");\n return clampPlaybackRate(Number.isFinite(authored) && authored > 0 ? authored : fallback);\n}\n\n/** `data-duration` when it is a positive number, else null. */\nexport function readDataDurationSeconds(getAttr: AttrReader): number | null {\n const duration = parseNumeric(getAttr(\"data-duration\"));\n return duration !== null && duration > 0 ? duration : null;\n}\n\n/** An authored trim: `data-duration` if positive, else `data-end - start` if `data-end` is\n * authored, else null (nothing authored). */\nexport function readAuthoredDurationSeconds(\n getAttr: AttrReader,\n startSeconds: number,\n): number | null {\n const duration = readDataDurationSeconds(getAttr);\n if (duration !== null) return duration;\n const end = parseNumeric(getAttr(\"data-end\"));\n return end !== null ? end - startSeconds : null;\n}\n\n/** The dropped-image default: what Studio gives a freshly dropped image before any trim.\n * One owner; everything needing an untrimmed image length imports this. */\nexport const DEFAULT_IMAGE_TIMELINE_DURATION_SECONDS = 3;\n\n/** The arithmetic alone: no attribute reading, no DOM, no probing. */\nexport function resolveNaturalDurationSeconds(\n sourceDurationSeconds: number,\n mediaStartSeconds: number,\n playbackRate: number,\n): number | null {\n if (!Number.isFinite(sourceDurationSeconds)) return null;\n const remaining = Math.max(0, sourceDurationSeconds - mediaStartSeconds);\n return remaining / clampPlaybackRate(playbackRate);\n}\n\nexport type MediaDurationSource = \"authored\" | \"media\" | \"default\" | \"pending\";\n\nexport interface MediaDurationResult {\n seconds: number | null;\n source: MediaDurationSource;\n /** Set only when source is \"pending\": why nothing could be resolved yet. */\n reason?: string;\n}\n\nexport interface ResolveMediaDurationInput {\n tag: MediaTag;\n /** From `readAuthoredDurationSeconds`, or null when nothing is authored. */\n authoredDurationSeconds: number | null;\n /** The source's own length once a probe has answered, else null. Ignored for `img`. */\n sourceDurationSeconds: number | null;\n mediaStartSeconds: number;\n playbackRate: number;\n}\n\nexport function resolveMediaDuration(input: ResolveMediaDurationInput): MediaDurationResult {\n const { tag, authoredDurationSeconds, sourceDurationSeconds, mediaStartSeconds, playbackRate } =\n input;\n if (authoredDurationSeconds !== null && authoredDurationSeconds > 0) {\n return { seconds: authoredDurationSeconds, source: \"authored\" };\n }\n if (tag === \"img\") {\n return { seconds: DEFAULT_IMAGE_TIMELINE_DURATION_SECONDS, source: \"default\" };\n }\n if (sourceDurationSeconds === null) {\n return { seconds: null, source: \"pending\", reason: \"source duration not yet probed\" };\n }\n const seconds = resolveNaturalDurationSeconds(\n sourceDurationSeconds,\n mediaStartSeconds,\n playbackRate,\n );\n if (seconds === null) {\n return { seconds: null, source: \"pending\", reason: \"source reported a non-finite duration\" };\n }\n return { seconds, source: \"media\" };\n}\n\n/** Readers not yet converted to `resolveMediaDuration`; each reader PR removes its entry, so this only shrinks. */\nexport const PENDING_MEDIA_DURATION_READERS = [\n \"core/src/runtime/init.ts (visibility end, clock, seek, WebAudio scheduling)\",\n \"core/src/compiler/timingCompiler.ts compileTag\",\n \"core/src/compiler/htmlCompiler.ts compileHtml\",\n \"producer/src/services/htmlCompiler.ts resolveMediaDuration\",\n \"engine/src/services/videoFrameExtractor.ts (extraction window)\",\n \"engine/src/services/audioMixer.ts\",\n \"parsers/src/htmlParser.ts (defaults to 5s, reads only data-media-start)\",\n \"studio (timelineDOM.ts, timelineElementHelpers.ts, useTimelineSyncCallbacks.ts)\",\n] as const;\n"],"mappings":";AAUO,IAAM,yBAAyB,OAAO,OAAO;AAAA,EAClD,OAAO;AAAA,EACP,UAAU;AAAA,EACV,YAAY;AAAA,EACZ,YAAY;AAAA,EACZ,aAAa;AACf,CAAU;AAEH,IAAM,uCAAuC,OAAO,OAAO;AAAA,EAChE,uBAAuB;AAAA,EACvB,uBAAuB;AAAA,EACvB,uBAAuB;AACzB,CAAU;AAEH,IAAM,4BAA4B,OAAO,OAAO;AAAA,EACrD,uBAAuB;AACzB,CAAU;AAEH,IAAM,2BAA2B,OAAO,OAAO;AAAA,EACpD,uBAAuB;AAAA,EACvB,uBAAuB;AACzB,CAAU;AAoEH,SAAS,aAAa,OAAiD;AAC5E,MAAI,SAAS,QAAQ,MAAM,KAAK,MAAM,GAAI,QAAO;AACjD,QAAM,SAAS,OAAO,KAAK;AAC3B,SAAO,OAAO,SAAS,MAAM,IAAI,SAAS;AAC5C;;;AC7FA,IAAM,cAAc,CAAC,QAAkD;AACrE,QAAM,QAAQ,aAAa,GAAG;AAC9B,SAAO,UAAU,QAAQ,SAAS,IAAI,QAAQ;AAChD;AAKO,SAAS,uBAAuB,SAA6B;AAClE,SACE,YAAY,QAAQ,qBAAqB,CAAC,KAAK,YAAY,QAAQ,kBAAkB,CAAC,KAAK;AAE/F;AAGO,IAAM,oBAAoB;AAC1B,IAAM,oBAAoB;AAG1B,SAAS,kBAAkB,KAAqB;AACrD,SAAO,OAAO,SAAS,GAAG,KAAK,MAAM,IACjC,KAAK,IAAI,mBAAmB,KAAK,IAAI,mBAAmB,GAAG,CAAC,IAC5D;AACN;AAIO,SAAS,iBAAiB,SAAqB,WAAW,GAAW;AAC1E,QAAM,WAAW,OAAO,WAAW,QAAQ,oBAAoB,KAAK,EAAE;AACtE,SAAO,kBAAkB,OAAO,SAAS,QAAQ,KAAK,WAAW,IAAI,WAAW,QAAQ;AAC1F;AAGO,SAAS,wBAAwB,SAAoC;AAC1E,QAAM,WAAW,aAAa,QAAQ,eAAe,CAAC;AACtD,SAAO,aAAa,QAAQ,WAAW,IAAI,WAAW;AACxD;AAIO,SAAS,4BACd,SACA,cACe;AACf,QAAM,WAAW,wBAAwB,OAAO;AAChD,MAAI,aAAa,KAAM,QAAO;AAC9B,QAAM,MAAM,aAAa,QAAQ,UAAU,CAAC;AAC5C,SAAO,QAAQ,OAAO,MAAM,eAAe;AAC7C;AAIO,IAAM,0CAA0C;AAGhD,SAAS,8BACd,uBACA,mBACA,cACe;AACf,MAAI,CAAC,OAAO,SAAS,qBAAqB,EAAG,QAAO;AACpD,QAAM,YAAY,KAAK,IAAI,GAAG,wBAAwB,iBAAiB;AACvE,SAAO,YAAY,kBAAkB,YAAY;AACnD;AAqBO,SAAS,qBAAqB,OAAuD;AAC1F,QAAM,EAAE,KAAK,yBAAyB,uBAAuB,mBAAmB,aAAa,IAC3F;AACF,MAAI,4BAA4B,QAAQ,0BAA0B,GAAG;AACnE,WAAO,EAAE,SAAS,yBAAyB,QAAQ,WAAW;AAAA,EAChE;AACA,MAAI,QAAQ,OAAO;AACjB,WAAO,EAAE,SAAS,yCAAyC,QAAQ,UAAU;AAAA,EAC/E;AACA,MAAI,0BAA0B,MAAM;AAClC,WAAO,EAAE,SAAS,MAAM,QAAQ,WAAW,QAAQ,iCAAiC;AAAA,EACtF;AACA,QAAM,UAAU;AAAA,IACd;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACA,MAAI,YAAY,MAAM;AACpB,WAAO,EAAE,SAAS,MAAM,QAAQ,WAAW,QAAQ,wCAAwC;AAAA,EAC7F;AACA,SAAO,EAAE,SAAS,QAAQ,QAAQ;AACpC;AAGO,IAAM,iCAAiC;AAAA,EAC5C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;","names":[]}
@@ -0,0 +1,14 @@
1
+ import { MediaTag, MediaDurationResult } from './mediaDuration.js';
2
+
3
+ /** The one fixture table every media-length reader is proven against: an authored
4
+ * element plus the source length a probe would report, and the `expected` result. */
5
+ interface MediaDurationFixture {
6
+ name: string;
7
+ tag: MediaTag;
8
+ attrs: Readonly<Record<string, string>>;
9
+ sourceDurationSeconds: number | null;
10
+ expected: MediaDurationResult;
11
+ }
12
+ declare const MEDIA_DURATION_FIXTURES: readonly MediaDurationFixture[];
13
+
14
+ export { MEDIA_DURATION_FIXTURES, type MediaDurationFixture };
@@ -0,0 +1,84 @@
1
+ // src/mediaDurationFixtures.ts
2
+ var MEDIA_DURATION_FIXTURES = [
3
+ {
4
+ name: "authored data-duration wins over a known source length",
5
+ tag: "video",
6
+ attrs: { "data-start": "0", "data-duration": "4" },
7
+ sourceDurationSeconds: 20,
8
+ expected: { seconds: 4, source: "authored" }
9
+ },
10
+ {
11
+ name: "video with only data-start resolves to the source length",
12
+ tag: "video",
13
+ attrs: { "data-start": "0" },
14
+ sourceDurationSeconds: 12,
15
+ expected: { seconds: 12, source: "media" }
16
+ },
17
+ {
18
+ name: "video resolves to source minus offset over rate",
19
+ tag: "video",
20
+ attrs: { "data-start": "0", "data-playback-start": "5", "data-playback-rate": "2" },
21
+ sourceDurationSeconds: 20,
22
+ expected: { seconds: 7.5, source: "media" }
23
+ },
24
+ {
25
+ name: "the older data-media-start offset still applies",
26
+ tag: "audio",
27
+ attrs: { "data-start": "0", "data-media-start": "4" },
28
+ sourceDurationSeconds: 10,
29
+ expected: { seconds: 6, source: "media" }
30
+ },
31
+ {
32
+ name: "an offset past the end of the source is a known zero span",
33
+ tag: "audio",
34
+ attrs: { "data-start": "0", "data-media-start": "12" },
35
+ sourceDurationSeconds: 10,
36
+ expected: { seconds: 0, source: "media" }
37
+ },
38
+ {
39
+ name: "a rate above the 10x bound is clamped",
40
+ tag: "video",
41
+ attrs: { "data-start": "0", "data-playback-rate": "20" },
42
+ sourceDurationSeconds: 10,
43
+ expected: { seconds: 1, source: "media" }
44
+ },
45
+ {
46
+ name: "a native-style rate like 2x reads as 2",
47
+ tag: "video",
48
+ attrs: { "data-start": "0", "data-playback-rate": "2x" },
49
+ sourceDurationSeconds: 10,
50
+ expected: { seconds: 5, source: "media" }
51
+ },
52
+ {
53
+ name: "a zero authored duration is not authored",
54
+ tag: "video",
55
+ attrs: { "data-start": "0", "data-duration": "0" },
56
+ sourceDurationSeconds: 8,
57
+ expected: { seconds: 8, source: "media" }
58
+ },
59
+ {
60
+ name: "video with no probed source length is pending, not a guess",
61
+ tag: "video",
62
+ attrs: { "data-start": "0" },
63
+ sourceDurationSeconds: null,
64
+ expected: { seconds: null, source: "pending", reason: "source duration not yet probed" }
65
+ },
66
+ {
67
+ name: "image with no authored duration takes the dropped-image default",
68
+ tag: "img",
69
+ attrs: { "data-start": "0" },
70
+ sourceDurationSeconds: null,
71
+ expected: { seconds: 3, source: "default" }
72
+ },
73
+ {
74
+ name: "image with an authored duration is trimmed like any other media",
75
+ tag: "img",
76
+ attrs: { "data-start": "0", "data-duration": "1.5" },
77
+ sourceDurationSeconds: null,
78
+ expected: { seconds: 1.5, source: "authored" }
79
+ }
80
+ ];
81
+ export {
82
+ MEDIA_DURATION_FIXTURES
83
+ };
84
+ //# sourceMappingURL=mediaDurationFixtures.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/mediaDurationFixtures.ts"],"sourcesContent":["import type { MediaDurationResult, MediaTag } from \"./mediaDuration.js\";\n\n/** The one fixture table every media-length reader is proven against: an authored\n * element plus the source length a probe would report, and the `expected` result. */\nexport interface MediaDurationFixture {\n name: string;\n tag: MediaTag;\n attrs: Readonly<Record<string, string>>;\n sourceDurationSeconds: number | null;\n expected: MediaDurationResult;\n}\n\nexport const MEDIA_DURATION_FIXTURES: readonly MediaDurationFixture[] = [\n {\n name: \"authored data-duration wins over a known source length\",\n tag: \"video\",\n attrs: { \"data-start\": \"0\", \"data-duration\": \"4\" },\n sourceDurationSeconds: 20,\n expected: { seconds: 4, source: \"authored\" },\n },\n {\n name: \"video with only data-start resolves to the source length\",\n tag: \"video\",\n attrs: { \"data-start\": \"0\" },\n sourceDurationSeconds: 12,\n expected: { seconds: 12, source: \"media\" },\n },\n {\n name: \"video resolves to source minus offset over rate\",\n tag: \"video\",\n attrs: { \"data-start\": \"0\", \"data-playback-start\": \"5\", \"data-playback-rate\": \"2\" },\n sourceDurationSeconds: 20,\n expected: { seconds: 7.5, source: \"media\" },\n },\n {\n name: \"the older data-media-start offset still applies\",\n tag: \"audio\",\n attrs: { \"data-start\": \"0\", \"data-media-start\": \"4\" },\n sourceDurationSeconds: 10,\n expected: { seconds: 6, source: \"media\" },\n },\n {\n name: \"an offset past the end of the source is a known zero span\",\n tag: \"audio\",\n attrs: { \"data-start\": \"0\", \"data-media-start\": \"12\" },\n sourceDurationSeconds: 10,\n expected: { seconds: 0, source: \"media\" },\n },\n {\n name: \"a rate above the 10x bound is clamped\",\n tag: \"video\",\n attrs: { \"data-start\": \"0\", \"data-playback-rate\": \"20\" },\n sourceDurationSeconds: 10,\n expected: { seconds: 1, source: \"media\" },\n },\n {\n name: \"a native-style rate like 2x reads as 2\",\n tag: \"video\",\n attrs: { \"data-start\": \"0\", \"data-playback-rate\": \"2x\" },\n sourceDurationSeconds: 10,\n expected: { seconds: 5, source: \"media\" },\n },\n {\n name: \"a zero authored duration is not authored\",\n tag: \"video\",\n attrs: { \"data-start\": \"0\", \"data-duration\": \"0\" },\n sourceDurationSeconds: 8,\n expected: { seconds: 8, source: \"media\" },\n },\n {\n name: \"video with no probed source length is pending, not a guess\",\n tag: \"video\",\n attrs: { \"data-start\": \"0\" },\n sourceDurationSeconds: null,\n expected: { seconds: null, source: \"pending\", reason: \"source duration not yet probed\" },\n },\n {\n name: \"image with no authored duration takes the dropped-image default\",\n tag: \"img\",\n attrs: { \"data-start\": \"0\" },\n sourceDurationSeconds: null,\n expected: { seconds: 3, source: \"default\" },\n },\n {\n name: \"image with an authored duration is trimmed like any other media\",\n tag: \"img\",\n attrs: { \"data-start\": \"0\", \"data-duration\": \"1.5\" },\n sourceDurationSeconds: null,\n expected: { seconds: 1.5, source: \"authored\" },\n },\n];\n"],"mappings":";AAYO,IAAM,0BAA2D;AAAA,EACtE;AAAA,IACE,MAAM;AAAA,IACN,KAAK;AAAA,IACL,OAAO,EAAE,cAAc,KAAK,iBAAiB,IAAI;AAAA,IACjD,uBAAuB;AAAA,IACvB,UAAU,EAAE,SAAS,GAAG,QAAQ,WAAW;AAAA,EAC7C;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,KAAK;AAAA,IACL,OAAO,EAAE,cAAc,IAAI;AAAA,IAC3B,uBAAuB;AAAA,IACvB,UAAU,EAAE,SAAS,IAAI,QAAQ,QAAQ;AAAA,EAC3C;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,KAAK;AAAA,IACL,OAAO,EAAE,cAAc,KAAK,uBAAuB,KAAK,sBAAsB,IAAI;AAAA,IAClF,uBAAuB;AAAA,IACvB,UAAU,EAAE,SAAS,KAAK,QAAQ,QAAQ;AAAA,EAC5C;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,KAAK;AAAA,IACL,OAAO,EAAE,cAAc,KAAK,oBAAoB,IAAI;AAAA,IACpD,uBAAuB;AAAA,IACvB,UAAU,EAAE,SAAS,GAAG,QAAQ,QAAQ;AAAA,EAC1C;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,KAAK;AAAA,IACL,OAAO,EAAE,cAAc,KAAK,oBAAoB,KAAK;AAAA,IACrD,uBAAuB;AAAA,IACvB,UAAU,EAAE,SAAS,GAAG,QAAQ,QAAQ;AAAA,EAC1C;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,KAAK;AAAA,IACL,OAAO,EAAE,cAAc,KAAK,sBAAsB,KAAK;AAAA,IACvD,uBAAuB;AAAA,IACvB,UAAU,EAAE,SAAS,GAAG,QAAQ,QAAQ;AAAA,EAC1C;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,KAAK;AAAA,IACL,OAAO,EAAE,cAAc,KAAK,sBAAsB,KAAK;AAAA,IACvD,uBAAuB;AAAA,IACvB,UAAU,EAAE,SAAS,GAAG,QAAQ,QAAQ;AAAA,EAC1C;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,KAAK;AAAA,IACL,OAAO,EAAE,cAAc,KAAK,iBAAiB,IAAI;AAAA,IACjD,uBAAuB;AAAA,IACvB,UAAU,EAAE,SAAS,GAAG,QAAQ,QAAQ;AAAA,EAC1C;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,KAAK;AAAA,IACL,OAAO,EAAE,cAAc,IAAI;AAAA,IAC3B,uBAAuB;AAAA,IACvB,UAAU,EAAE,SAAS,MAAM,QAAQ,WAAW,QAAQ,iCAAiC;AAAA,EACzF;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,KAAK;AAAA,IACL,OAAO,EAAE,cAAc,IAAI;AAAA,IAC3B,uBAAuB;AAAA,IACvB,UAAU,EAAE,SAAS,GAAG,QAAQ,UAAU;AAAA,EAC5C;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,KAAK;AAAA,IACL,OAAO,EAAE,cAAc,KAAK,iBAAiB,MAAM;AAAA,IACnD,uBAAuB;AAAA,IACvB,UAAU,EAAE,SAAS,KAAK,QAAQ,WAAW;AAAA,EAC/C;AACF;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperframes/parsers",
3
- "version": "0.8.50",
3
+ "version": "0.8.51",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/heygen-com/hyperframes",
@@ -64,6 +64,14 @@
64
64
  "import": "./dist/compositionContract.js",
65
65
  "types": "./dist/compositionContract.d.ts"
66
66
  },
67
+ "./media-duration": {
68
+ "import": "./dist/mediaDuration.js",
69
+ "types": "./dist/mediaDuration.d.ts"
70
+ },
71
+ "./media-duration-fixtures": {
72
+ "import": "./dist/mediaDurationFixtures.js",
73
+ "types": "./dist/mediaDurationFixtures.d.ts"
74
+ },
67
75
  "./top-level-elements": {
68
76
  "import": "./dist/topLevelElements.js",
69
77
  "types": "./dist/topLevelElements.d.ts"
@@ -83,6 +91,10 @@
83
91
  "./asset-resolution": {
84
92
  "import": "./dist/assetResolution.js",
85
93
  "types": "./dist/assetResolution.d.ts"
94
+ },
95
+ "./composition-duration": {
96
+ "import": "./dist/compositionDuration.js",
97
+ "types": "./dist/compositionDuration.d.ts"
86
98
  }
87
99
  },
88
100
  "publishConfig": {
@@ -102,7 +114,7 @@
102
114
  "tsx": "^4.21.0",
103
115
  "typescript": "^5.0.0",
104
116
  "vitest": "^4.1.11",
105
- "@hyperframes/core": "0.8.50"
117
+ "@hyperframes/core": "0.8.51"
106
118
  },
107
119
  "scripts": {
108
120
  "build": "tsup",