/** * Parse SRT subtitle format to WebVTT */ export const parseSRT = (srtContent: string): string => { const normalised = srtContent.replace(/\r\n/g, '\n').replace(/\r/g, '\n').trim() if (!normalised) { return 'WEBVTT\n\n' } const cues = normalised .split(/\n{2,}/) .map((cueBlock) => { const lines = cueBlock .split('\n') .map((line) => line.replace(/^\ufeff/, '').trimEnd()) .filter((line, index, arr) => !(line === '' && index === arr.length - 1)) if (lines.length === 0) { return null } if (/^\d+$/.test(lines[0].trim())) { lines.shift() } if (lines.length === 0) { return null } const timeLine = lines.shift() if (!timeLine || !timeLine.includes('-->')) { return null } const vttTimeLine = timeLine .split('-->') .map((part) => part.trim().replace(/,/g, '.')) .join(' --> ') if (!vttTimeLine.includes('-->')) { return null } const text = lines.join('\n') return `${vttTimeLine}\n${text}`.trim() }) .filter((cueBlock): cueBlock is string => Boolean(cueBlock)) const header = 'WEBVTT\n\n' if (cues.length === 0) { return header } return header + cues.join('\n\n') + '\n' } /** * Create a blob URL from subtitle content */ export const createSubtitleBlobURL = (content: string, format: 'vtt' | 'srt'): string => { const vttContent = format === 'srt' ? parseSRT(content) : content const blob = new Blob([vttContent], { type: 'text/vtt;charset=utf-8' }) return URL.createObjectURL(blob) } /** * Fetch and parse subtitle file */ export const fetchSubtitle = async (url: string): Promise => { const response = await fetch(url) const content = await response.text() // Detect format if (url.endsWith('.srt')) { return parseSRT(content) } return content }