@hyperframes/core 0.8.214 → 0.8.217

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.
@@ -11,11 +11,16 @@ export declare function readCompositionSize(root: Element): {
11
11
  width: number | null;
12
12
  height: number | null;
13
13
  };
14
- /** The latest end among the document's timed video and audio, at their absolute starts. */
15
- export declare function resolveMediaWindowDurationSeconds(doc: ParentNode, media: {
14
+ type MediaTiming = {
16
15
  mediaStart: (el: HTMLMediaElement) => number;
17
16
  mediaDuration: (el: HTMLMediaElement) => number | null;
18
- }): number | null;
17
+ };
18
+ /** The timed `<video>`/`<audio>` whose window ends last, and where it ends. */
19
+ export declare function longestMediaWindow(doc: ParentNode, media: MediaTiming): {
20
+ seconds: number;
21
+ element: HTMLMediaElement;
22
+ } | null;
23
+ export declare function resolveMediaWindowDurationSeconds(doc: ParentNode, media: MediaTiming): number | null;
19
24
  /** The latest of the root's declared length and its direct sub-compositions' ends. */
20
25
  export declare function resolveAuthoredCompositionFloorSeconds(root: Element, startResolver: StartResolver): number | null;
21
26
  /** The last-resort length: the latest end among the root's timed clips. Until every clip's length
@@ -31,9 +36,24 @@ export type CompositionLengthInputs = {
31
36
  fallback: number;
32
37
  derived: () => number;
33
38
  };
34
- /** A declared root length is the length; else the longest of timeline, floors and fallback (a
35
- * loop-inflated timeline yields to a floor or fallback); else the length derived from the clips. */
39
+ export type CompositionLengthFrom = "declared" | "timeline" | "floor" | "fallback" | "derived";
40
+ export type CompositionLengthSource = {
41
+ from: CompositionLengthFrom;
42
+ } | {
43
+ from: "media";
44
+ tag: string;
45
+ src: string;
46
+ trimmed: boolean;
47
+ };
36
48
  export declare function resolveCompositionLengthSeconds(input: CompositionLengthInputs): number;
49
+ /** A declared root length is the length; else the longest of timeline, floors and fallback (a
50
+ * loop-inflated timeline yields to a floor or fallback); else the length derived from the clips.
51
+ * `from` names the input that set it, and `floorIndex` which floor when a floor did. */
52
+ export declare function resolveCompositionLength(input: CompositionLengthInputs): {
53
+ seconds: number;
54
+ from: CompositionLengthFrom;
55
+ floorIndex: number | null;
56
+ };
37
57
  /** Size, frame rate and length of a composition nothing is running (a server-parsed or detached
38
58
  * document): no timelines, no adapters, and media without a known source length is pending. */
39
59
  export declare function readStaticCompositionMeta(doc: Document): {
@@ -16,9 +16,9 @@ export function readCompositionSize(root) {
16
16
  height: parseCompositionDimension(root.getAttribute("data-height")),
17
17
  };
18
18
  }
19
- /** The latest end among the document's timed video and audio, at their absolute starts. */
20
- export function resolveMediaWindowDurationSeconds(doc, media) {
21
- const clipEnds = [];
19
+ /** The timed `<video>`/`<audio>` whose window ends last, and where it ends. */
20
+ export function longestMediaWindow(doc, media) {
21
+ let longest = null;
22
22
  for (const node of doc.querySelectorAll("video[data-start], audio[data-start]")) {
23
23
  const start = media.mediaStart(node);
24
24
  if (!Number.isFinite(start))
@@ -26,10 +26,14 @@ export function resolveMediaWindowDurationSeconds(doc, media) {
26
26
  const duration = media.mediaDuration(node);
27
27
  if (duration == null || duration <= MIN_VALID_TIMELINE_DURATION_SECONDS)
28
28
  continue;
29
- clipEnds.push(Math.max(0, start) + duration);
29
+ const end = Math.max(0, start) + duration;
30
+ if (!longest || end > longest.seconds)
31
+ longest = { seconds: end, element: node };
30
32
  }
31
- return aboveOneFrame(resolveCompositionDuration({ authoredDurationSeconds: null, clipEndsSeconds: clipEnds })
32
- .seconds);
33
+ return longest && aboveOneFrame(longest.seconds) !== null ? longest : null;
34
+ }
35
+ export function resolveMediaWindowDurationSeconds(doc, media) {
36
+ return longestMediaWindow(doc, media)?.seconds ?? null;
33
37
  }
34
38
  /** The latest of the root's declared length and its direct sub-compositions' ends. */
35
39
  export function resolveAuthoredCompositionFloorSeconds(root, startResolver) {
@@ -75,14 +79,19 @@ export function resolveContentDerivedDuration(root, startResolver, { unregistere
75
79
  ? { ...result, seconds: null, source: "unresolved", reason: "a clip's length is pending" }
76
80
  : result;
77
81
  }
78
- /** A declared root length is the length; else the longest of timeline, floors and fallback (a
79
- * loop-inflated timeline yields to a floor or fallback); else the length derived from the clips. */
80
82
  export function resolveCompositionLengthSeconds(input) {
83
+ return resolveCompositionLength(input).seconds;
84
+ }
85
+ /** A declared root length is the length; else the longest of timeline, floors and fallback (a
86
+ * loop-inflated timeline yields to a floor or fallback); else the length derived from the clips.
87
+ * `from` names the input that set it, and `floorIndex` which floor when a floor did. */
88
+ export function resolveCompositionLength(input) {
81
89
  if (input.declared !== null && Number.isFinite(input.declared) && input.declared > 0) {
82
- return input.declared;
90
+ return { seconds: input.declared, from: "declared", floorIndex: null };
83
91
  }
84
92
  const rawTimeline = aboveOneFrame(input.timeline());
85
- const floor = Math.max(0, ...input.floors().map((seconds) => seconds ?? 0));
93
+ const floors = input.floors().map((seconds) => seconds ?? 0);
94
+ const floor = Math.max(0, ...floors);
86
95
  const fallback = Number.isFinite(input.fallback) && input.fallback > MIN_VALID_TIMELINE_DURATION_SECONDS
87
96
  ? input.fallback
88
97
  : 0;
@@ -91,16 +100,23 @@ export function resolveCompositionLengthSeconds(input) {
91
100
  rawTimeline >= LOOP_INFLATED_TIMELINE_SECONDS &&
92
101
  aboveOneFrame(floorOrFallback) !== null;
93
102
  const timeline = loopInflated ? null : rawTimeline;
94
- let seconds;
95
- if (timeline !== null)
96
- seconds = Math.max(timeline, floorOrFallback);
97
- else if (aboveOneFrame(floor) !== null)
98
- seconds = floorOrFallback;
99
- else if (fallback > 0)
100
- seconds = fallback;
101
- else
102
- seconds = input.derived();
103
- return seconds > 0 ? seconds : 0;
103
+ const floorWins = floor >= fallback;
104
+ const fromFloorOrFallback = (seconds) => ({
105
+ seconds,
106
+ from: floorWins ? "floor" : "fallback",
107
+ floorIndex: floorWins ? floors.indexOf(floor) : null,
108
+ });
109
+ if (timeline !== null) {
110
+ return timeline >= floorOrFallback
111
+ ? { seconds: timeline, from: "timeline", floorIndex: null }
112
+ : fromFloorOrFallback(floorOrFallback);
113
+ }
114
+ if (aboveOneFrame(floor) !== null)
115
+ return fromFloorOrFallback(floorOrFallback);
116
+ if (fallback > 0)
117
+ return { seconds: fallback, from: "fallback", floorIndex: null };
118
+ const derived = input.derived();
119
+ return { seconds: derived > 0 ? derived : 0, from: "derived", floorIndex: null };
104
120
  }
105
121
  /** Size, frame rate and length of a composition nothing is running (a server-parsed or detached
106
122
  * document): no timelines, no adapters, and media without a known source length is pending. */
@@ -1,2 +1,3 @@
1
1
  export declare const MEDIA_ELEMENT_LEAD_SECONDS = 0.00001;
2
2
  export declare function mediaElementTime(sourceSeconds: number): number;
3
+ export declare function startsSoundOnlyInsideATap(): boolean;
@@ -2,3 +2,7 @@ export const MEDIA_ELEMENT_LEAD_SECONDS = 1e-5;
2
2
  export function mediaElementTime(sourceSeconds) {
3
3
  return sourceSeconds + MEDIA_ELEMENT_LEAD_SECONDS;
4
4
  }
5
+ export function startsSoundOnlyInsideATap() {
6
+ const { userAgent, maxTouchPoints } = navigator;
7
+ return /iP(hone|ad|od)/.test(userAgent) || (/Macintosh/.test(userAgent) && maxTouchPoints > 1);
8
+ }
@@ -10,12 +10,6 @@ export function redactUrlQueryStrings(value) {
10
10
  .replace(/\b(https?:\/\/[^\s?]+)\?[^\s]*/g, "$1?…")
11
11
  .replace(/((?:X-(?:Amz|Goog)-[\w-]*|Signature)=)[^&\s]*/gi, "$1…");
12
12
  }
13
- /**
14
- * Path characters we treat as part of a single segment. Space is deliberately
15
- * excluded: including it would let a match run past the path and swallow the
16
- * prose after it, and a path with a space still gets its remaining segments
17
- * redacted, which is the part that carries the identifying information.
18
- */
19
13
  /**
20
14
  * A path segment is defined by what ENDS it, not by an alphabet.
21
15
  *
@@ -31,6 +25,9 @@ export function redactUrlQueryStrings(value) {
31
25
  * run past the path and swallow the prose after it.
32
26
  */
33
27
  const SEGMENT = String.raw `[^\s/\\'"]+`;
28
+ /** A folder that another separator follows may hold spaces (`My Drive`, `내 드라이브`, `OneDrive - Contoso`); only
29
+ * the last segment stops at a space, so prose after a path survives. Line breaks still end it. */
30
+ const FOLDER = String.raw `${SEGMENT}(?:[^\S\r\n]+(?![A-Za-z]:[\\/])${SEGMENT})*(?=[\\/])`;
34
31
  /** Same, minus the dot, so a trailing `.ext` can be matched separately. */
35
32
  const SEGMENT_NODOT = String.raw `[^\s/\\'".]+`;
36
33
  /**
@@ -54,10 +51,12 @@ const TOKEN_TAIL = String.raw `[^\s'")]*`;
54
51
  * position inside a URL can start a match. URLs are handled above, where the
55
52
  * host is kept and only the query is dropped.
56
53
  */
57
- const ABSOLUTE_PATH = new RegExp(String.raw `(?<![:\w/\\])(?:[A-Za-z]:)?(?:[\\/]${SEGMENT}){2,}${TOKEN_TAIL}`, "g");
54
+ const ABSOLUTE_PATH = new RegExp(
55
+ // A Windows share (`\\server\share`) starts with two separators; the first is taken as its root.
56
+ String.raw `(?<![:\w\p{L}\p{N}\p{M}/\\])(?:[A-Za-z]:|\\(?=\\[^\s\\/]))?(?:[\\/]${FOLDER})+[\\/]${SEGMENT}${TOKEN_TAIL}`, "gu");
58
57
  /** `./assets/bgm.mp3`, `../out.wav`, `.\tmp\x` — relative paths leak the same
59
58
  * project structure absolute ones do, and were previously untouched. */
60
- const RELATIVE_PATH = new RegExp(String.raw `(?<![\w/\\.])\.{1,2}(?:[\\/]${SEGMENT})+${TOKEN_TAIL}`, "g");
59
+ const RELATIVE_PATH = new RegExp(String.raw `(?<![\w/\\.])\.{1,2}(?:[\\/]${FOLDER})*[\\/]${SEGMENT}${TOKEN_TAIL}`, "g");
61
60
  /**
62
61
  * A bare basename with an asset extension. ffprobe reports the input by the
63
62
  * name it was given, so a caller that passes a basename (or a path this
@@ -82,6 +81,8 @@ const BARE_RELATIVE_PATH = new RegExp([
82
81
  // Two or more separators: `customer/acme/video.mp4`. No extension needed —
83
82
  // that much structure is already a path.
84
83
  String.raw `(?<![^\s'\"(=,\[])(?:${SEGMENT}[\\/]){2,}${SEGMENT}${TOKEN_TAIL}`,
84
+ // Spaced folders only before a file name with an extension, so ffprobe's `N/A, … bitrate: N/A` is not a path.
85
+ String.raw `(?<![^\s'\"(=,\[])${SEGMENT}[\\/](?:${FOLDER}[\\/]){1,8}${SEGMENT_NODOT}\.\w{1,8}\b${TOKEN_TAIL}`,
85
86
  // One separator, but the last segment carries a file extension:
86
87
  // `assets/bgm.mp3`. That segment is dot-free on purpose — SEGMENT includes
87
88
  // `.`, so a greedy one swallows the extension this rule needs.
@@ -122,10 +123,10 @@ function redactFilePaths(value) {
122
123
  // (`/assets/x.mp3`), so the absolute rule would consume it and leave the
123
124
  // leading `.` stranded outside the redaction.
124
125
  .replace(RELATIVE_PATH, "[path]")
125
- // Bare-relative BEFORE absolute: a bare path's interior satisfies the
126
- // absolute rule, which would claim it and strand the first segment.
127
- .replace(BARE_RELATIVE_PATH, "[path]")
126
+ // Absolute BEFORE bare-relative: the folders after a space in `/Users/kim/My Drive/x.mp4` look like a bare path
127
+ // on their own, and claiming them first would strand the start of the absolute one.
128
128
  .replace(ABSOLUTE_PATH, "[path]")
129
+ .replace(BARE_RELATIVE_PATH, "[path]")
129
130
  .replace(ASSET_BASENAME, "[file]"));
130
131
  }
131
132
  export function redactTelemetryString(value, maxLength = MAX_TELEMETRY_STRING_LENGTH) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperframes/core",
3
- "version": "0.8.214",
3
+ "version": "0.8.217",
4
4
  "description": "",
5
5
  "repository": {
6
6
  "type": "git",
@@ -64,6 +64,10 @@
64
64
  "import": "./dist/removeTree.js",
65
65
  "types": "./dist/removeTree.d.ts"
66
66
  },
67
+ "./env-proxy": {
68
+ "import": "./dist/envProxy.js",
69
+ "types": "./dist/envProxy.d.ts"
70
+ },
67
71
  "./rename-retry": {
68
72
  "import": "./dist/renameWithRetry.js",
69
73
  "types": "./dist/renameWithRetry.d.ts"
@@ -425,9 +429,9 @@
425
429
  "linkedom": "^0.18.12",
426
430
  "postcss": "^8.5.8",
427
431
  "wawoff2": "^2.0.1",
428
- "@hyperframes/lint": "0.8.214",
429
- "@hyperframes/parsers": "0.8.214",
430
- "@hyperframes/studio-server": "0.8.214"
432
+ "@hyperframes/parsers": "0.8.217",
433
+ "@hyperframes/lint": "0.8.217",
434
+ "@hyperframes/studio-server": "0.8.217"
431
435
  },
432
436
  "devDependencies": {
433
437
  "@fontsource/archivo-black": "^5.2.8",