From a508919d202c301c25e9c9416448924983e2d1aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Uyan=C4=B1k?= <99019289+MertUyanik@users.noreply.github.com> Date: Wed, 29 Oct 2025 14:22:08 +0300 Subject: [PATCH] Fix SRT subtitle conversion and default selection --- src/components/VideoElement.tsx | 22 ++++++++++ src/utils/subtitles.ts | 75 +++++++++++++++++++++------------ 2 files changed, 71 insertions(+), 26 deletions(-) diff --git a/src/components/VideoElement.tsx b/src/components/VideoElement.tsx index 173ffec..4d16834 100644 --- a/src/components/VideoElement.tsx +++ b/src/components/VideoElement.tsx @@ -265,6 +265,28 @@ export const VideoElement: React.FC = ({ } }, [subtitles]) + useEffect(() => { + const video = videoRef.current + if (!video) return + if (processedSubtitles.length === 0) return + if (settings.subtitle) return + + const defaultSubtitle = processedSubtitles.find((subtitle) => subtitle.default) + if (!defaultSubtitle) return + + const tracks = video.textTracks + if (!tracks || tracks.length === 0) return + + for (let i = 0; i < tracks.length; i++) { + const track = tracks[i] + if (track.language === defaultSubtitle.lang) { + track.mode = 'showing' + setSubtitle(defaultSubtitle) + break + } + } + }, [processedSubtitles, settings.subtitle, setSubtitle, videoRef]) + // Detect HLS source and load hls.js if needed useEffect(() => { const video = videoRef.current diff --git a/src/utils/subtitles.ts b/src/utils/subtitles.ts index 8c29a10..8135773 100644 --- a/src/utils/subtitles.ts +++ b/src/utils/subtitles.ts @@ -2,34 +2,57 @@ * Parse SRT subtitle format to WebVTT */ export const parseSRT = (srtContent: string): string => { - const lines = srtContent.trim().split('\n') - let vttContent = 'WEBVTT\n\n' + const normalised = srtContent.replace(/\r\n/g, '\n').replace(/\r/g, '\n').trim() - let i = 0 - while (i < lines.length) { - // Skip subtitle number - if (/^\d+$/.test(lines[i].trim())) { - i++ - } - - // Parse timestamp line - if (lines[i] && lines[i].includes('-->')) { - const timeLine = lines[i].replace(/,/g, '.') // SRT uses comma, VTT uses dot - vttContent += timeLine + '\n' - i++ - - // Add subtitle text - while (i < lines.length && lines[i].trim() !== '') { - vttContent += lines[i] + '\n' - i++ - } - vttContent += '\n' - } - - i++ + if (!normalised) { + return 'WEBVTT\n\n' } - return vttContent + 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' } /** @@ -37,7 +60,7 @@ export const parseSRT = (srtContent: string): string => { */ export const createSubtitleBlobURL = (content: string, format: 'vtt' | 'srt'): string => { const vttContent = format === 'srt' ? parseSRT(content) : content - const blob = new Blob([vttContent], { type: 'text/vtt' }) + const blob = new Blob([vttContent], { type: 'text/vtt;charset=utf-8' }) return URL.createObjectURL(blob) }