{"version":3,"sources":["../src/subtitles/srt.ts","../src/subtitles/vtt.ts","../src/subtitles/render.ts","../src/subtitles/index.ts"],"names":["fetchWith"],"mappings":"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","file":"chunk-WRKO6Q42.cjs","sourcesContent":["/**\n * SRT → WebVTT converter.\n *\n * SRT cues:\n *\n *     1\n *     00:00:20,000 --> 00:00:24,400\n *     Subtitle text, possibly multiple lines.\n *\n * WebVTT cues:\n *\n *     WEBVTT\n *\n *     00:00:20.000 --> 00:00:24.400\n *     Subtitle text, possibly multiple lines.\n *\n * The differences in v1 are:\n *  - leading `WEBVTT` magic line\n *  - `,` → `.` for milliseconds\n *  - cue index lines are stripped (WebVTT allows them but SRT-style ints can\n *    confuse some parsers; we drop them)\n *  - BOM is stripped\n */\nexport function srtToVtt(srt: string): string {\n  // Strip BOM\n  if (srt.charCodeAt(0) === 0xfeff) srt = srt.slice(1);\n  // Normalize line endings\n  const normalized = srt.replace(/\\r\\n/g, \"\\n\").replace(/\\r/g, \"\\n\").trim();\n\n  const blocks = normalized.split(/\\n{2,}/);\n  const out: string[] = [\"WEBVTT\", \"\"];\n\n  for (const block of blocks) {\n    const lines = block.split(\"\\n\");\n    // Drop the leading numeric index, if present.\n    if (lines.length > 0 && /^\\d+$/.test(lines[0].trim())) {\n      lines.shift();\n    }\n    if (lines.length === 0) continue;\n\n    const timing = lines.shift()!;\n    const vttTiming = convertTiming(timing);\n    if (!vttTiming) continue; // skip malformed cue\n\n    out.push(vttTiming);\n    for (const l of lines) out.push(l);\n    out.push(\"\");\n  }\n\n  return out.join(\"\\n\");\n}\n\nfunction convertTiming(line: string): string | null {\n  // SRT: HH:MM:SS,mmm --> HH:MM:SS,mmm  (optional cue settings after)\n  const m = /^(\\d{1,2}):(\\d{2}):(\\d{2})[,.](\\d{1,3})\\s*-->\\s*(\\d{1,2}):(\\d{2}):(\\d{2})[,.](\\d{1,3})(.*)$/.exec(\n    line.trim(),\n  );\n  if (!m) return null;\n  const fmt = (h: string, mm: string, s: string, ms: string) =>\n    `${h.padStart(2, \"0\")}:${mm}:${s}.${ms.padEnd(3, \"0\").slice(0, 3)}`;\n  return `${fmt(m[1], m[2], m[3], m[4])} --> ${fmt(m[5], m[6], m[7], m[8])}${m[9] ?? \"\"}`;\n}\n","/** Light validation for incoming VTT — we do not parse cues, just confirm header. */\nexport function isVtt(text: string): boolean {\n  const trimmed = text.replace(/^\\ufeff/, \"\").trimStart();\n  return trimmed.startsWith(\"WEBVTT\");\n}\n","/**\n * Custom subtitle overlay for the fallback strategy. We don't have a `<video>`\n * with text tracks here, so we render cues into a positioned div ourselves.\n *\n * v1 only handles plain-text WebVTT cues with `HH:MM:SS.mmm` timing. Cue\n * settings, voice tags, and styling are ignored.\n */\n\ninterface Cue {\n  start: number;\n  end: number;\n  text: string;\n}\n\nexport class SubtitleOverlay {\n  private el: HTMLDivElement;\n  private cues: Cue[] = [];\n\n  constructor(parent: HTMLElement) {\n    this.el = document.createElement(\"div\");\n    this.el.style.cssText =\n      \"position:absolute;left:0;right:0;bottom:8%;text-align:center;color:white;text-shadow:0 0 4px black;font-family:sans-serif;font-size:1.4em;pointer-events:none;\";\n    parent.appendChild(this.el);\n  }\n\n  loadVtt(text: string): void {\n    this.cues = parseVtt(text);\n  }\n\n  update(currentTime: number): void {\n    const active = this.cues.find((c) => currentTime >= c.start && currentTime <= c.end);\n    this.el.textContent = active?.text ?? \"\";\n  }\n\n  /** Set the currently-displayed text directly (bypasses loadVtt/update). */\n  setText(text: string): void {\n    // Only touch the DOM if it actually changed — rAF tick runs 60Hz.\n    if (this.el.textContent !== text) {\n      this.el.textContent = text;\n    }\n  }\n\n  destroy(): void {\n    this.el.remove();\n    this.cues = [];\n  }\n}\n\nfunction parseVtt(text: string): Cue[] {\n  const cues: Cue[] = [];\n  const blocks = text.replace(/\\r\\n/g, \"\\n\").split(/\\n{2,}/);\n  for (const block of blocks) {\n    const lines = block.split(\"\\n\").filter(Boolean);\n    if (lines.length === 0 || lines[0] === \"WEBVTT\") continue;\n    const timingIdx = lines.findIndex((l) => l.includes(\"-->\"));\n    if (timingIdx < 0) continue;\n    const m = /(\\d{2}):(\\d{2}):(\\d{2})\\.(\\d{3})\\s*-->\\s*(\\d{2}):(\\d{2}):(\\d{2})\\.(\\d{3})/.exec(\n      lines[timingIdx],\n    );\n    if (!m) continue;\n    const t = (h: string, mm: string, s: string, ms: string) =>\n      Number(h) * 3600 + Number(mm) * 60 + Number(s) + Number(ms) / 1000;\n    cues.push({\n      start: t(m[1], m[2], m[3], m[4]),\n      end: t(m[5], m[6], m[7], m[8]),\n      text: lines.slice(timingIdx + 1).join(\"\\n\"),\n    });\n  }\n  return cues;\n}\n","import type { SubtitleTrackInfo, TransportConfig } from \"../types.js\";\nimport { fetchWith } from \"../util/transport.js\";\nimport { srtToVtt } from \"./srt.js\";\nimport { isVtt } from \"./vtt.js\";\n\nexport { srtToVtt } from \"./srt.js\";\nexport { SubtitleOverlay } from \"./render.js\";\n\n/**\n * Discover sidecar `.srt` / `.vtt` files next to the source. Requires the\n * caller to pass a `FileSystemDirectoryHandle` (e.g. via the File System\n * Access API). Without that handle we can't enumerate sibling files.\n *\n * The returned `url` fields are blob URLs created via `URL.createObjectURL`.\n * They must be revoked by the caller (e.g. via `revokeSubtitleResources()`)\n * when the player tears down or the source changes — otherwise repeated\n * source swaps in a single-page app will leak.\n */\nexport interface DiscoveredSidecar {\n  url: string;\n  format: \"srt\" | \"vtt\";\n  language?: string;\n}\n\nexport async function discoverSidecars(\n  file: File,\n  directory: FileSystemDirectoryHandle,\n): Promise<DiscoveredSidecar[]> {\n  const baseName = file.name.replace(/\\.[^.]+$/, \"\");\n  const found: DiscoveredSidecar[] = [];\n\n  // Walk the directory and look for `${baseName}*.srt` / `*.vtt`.\n  for await (const [name, handle] of (directory as unknown as AsyncIterable<[string, FileSystemHandle]>)) {\n    if (handle.kind !== \"file\") continue;\n    if (!name.startsWith(baseName)) continue;\n    const lower = name.toLowerCase();\n    let format: \"srt\" | \"vtt\" | null = null;\n    if (lower.endsWith(\".srt\")) format = \"srt\";\n    else if (lower.endsWith(\".vtt\")) format = \"vtt\";\n    if (!format) continue;\n\n    const sidecarFile = await (handle as FileSystemFileHandle).getFile();\n    const url = URL.createObjectURL(sidecarFile);\n\n    // Try to extract a language tag (eg. movie.en.srt → \"en\").\n    const langMatch = name.slice(baseName.length).match(/[._-]([a-z]{2,3})(?:[._-]|\\.)/i);\n    found.push({\n      url,\n      format,\n      language: langMatch?.[1],\n    });\n  }\n\n  return found;\n}\n\n/**\n * Owns every blob URL created during sidecar discovery and SRT→VTT\n * conversion for a single player session. Revoking the bag releases all of\n * them in one shot at teardown.\n */\nexport class SubtitleResourceBag {\n  private urls = new Set<string>();\n\n  /** Track an externally-created blob URL (e.g. from `discoverSidecars`). */\n  track(url: string): void {\n    this.urls.add(url);\n  }\n\n  /** Convenience: create a blob URL and track it in one call. */\n  createObjectURL(blob: Blob): string {\n    const url = URL.createObjectURL(blob);\n    this.urls.add(url);\n    return url;\n  }\n\n  /** Revoke every tracked URL. Idempotent — safe to call multiple times. */\n  revokeAll(): void {\n    for (const u of this.urls) URL.revokeObjectURL(u);\n    this.urls.clear();\n  }\n}\n\n/**\n * Attach `<track>` elements for each subtitle to the player's `<video>`. SRT\n * sources are converted to VTT first via blob URLs because `<track>` only\n * accepts WebVTT.\n *\n * Pass a {@link SubtitleResourceBag} so the player can revoke the generated\n * blob URLs at teardown. Without one, every SRT subtitle leaks a blob URL\n * per attach.\n *\n * Errors during fetch/parse are caught per-track and reported via the\n * `onError` callback (if provided) so a single bad subtitle doesn't break\n * bootstrap. Subtitles are *not* load-bearing for playback.\n */\nexport async function attachSubtitleTracks(\n  video: HTMLVideoElement,\n  tracks: SubtitleTrackInfo[],\n  bag?: SubtitleResourceBag,\n  onError?: (err: Error, track: SubtitleTrackInfo) => void,\n  transport?: TransportConfig,\n): Promise<void> {\n  const doFetch = fetchWith(transport);\n\n  // Clear existing dynamically-attached tracks.\n  for (const t of Array.from(video.querySelectorAll(\"track[data-avbridge]\"))) {\n    t.remove();\n  }\n\n  for (const t of tracks) {\n    if (!t.sidecarUrl) continue;\n    // eslint-disable-next-line no-console\n    console.log(`[avbridge:subs] attaching track id=${t.id} format=${t.format} url=${t.sidecarUrl.slice(0, 60)}`);\n    try {\n      let url = t.sidecarUrl;\n      if (t.format === \"srt\") {\n        const res = await doFetch(t.sidecarUrl, transport?.requestInit);\n        const text = await res.text();\n        const vtt = srtToVtt(text);\n        const blob = new Blob([vtt], { type: \"text/vtt\" });\n        url = bag ? bag.createObjectURL(blob) : URL.createObjectURL(blob);\n      } else if (t.format === \"vtt\") {\n        // Validate quickly so a malformed file fails loudly here.\n        const res = await doFetch(t.sidecarUrl, transport?.requestInit);\n        const text = await res.text();\n        if (!isVtt(text)) {\n          // eslint-disable-next-line no-console\n          console.warn(\"[avbridge] subtitle missing WEBVTT header:\", t.sidecarUrl);\n        }\n      }\n      const trackEl = document.createElement(\"track\");\n      trackEl.kind = \"subtitles\";\n      trackEl.src = url;\n      trackEl.srclang = t.language ?? \"und\";\n      trackEl.label = t.language ?? `Subtitle ${t.id}`;\n      trackEl.dataset.avbridge = \"true\";\n      video.appendChild(trackEl);\n    } catch (err) {\n      const e = err instanceof Error ? err : new Error(String(err));\n      onError?.(e, t);\n    }\n  }\n}\n"]}