diff --git a/backend/services/ytdlp.py b/backend/services/ytdlp.py index f6cf8a0..99f676c 100644 --- a/backend/services/ytdlp.py +++ b/backend/services/ytdlp.py @@ -384,6 +384,25 @@ def fetch_channel_links(channel_id: str) -> list[str]: return list(channel_ids) +def _strip_vtt_cue_settings(video_id: str) -> None: + """Remove position/align/line cue settings from yt-dlp VTT files. + + yt-dlp embeds 'align:start position:0%' in every cue header which pins + subtitles to the bottom-left. Stripping them lets CSS ::cue center them. + """ + for vtt in Path(settings.download_path).glob(f"{video_id}.*.vtt"): + try: + text = vtt.read_text(encoding="utf-8", errors="replace") + cleaned = re.sub( + r'(\d{1,2}:\d{2}:\d{2}\.\d{3} --> \d{1,2}:\d{2}:\d{2}\.\d{3})[^\n]*', + r'\1', + text, + ) + vtt.write_text(cleaned, encoding="utf-8") + except Exception: + pass + + def download_subs_only(video_id: str, subtitle_langs: str) -> bool: """Download subtitle files only (no video) for an already-downloaded video.""" url = f"https://www.youtube.com/watch?v={video_id}" @@ -397,6 +416,8 @@ def download_subs_only(video_id: str, subtitle_langs: str) -> bool: "-o", output_template, *_cookie_args(), ], timeout=60) + if code == 0: + _strip_vtt_cue_settings(video_id) return code == 0 @@ -721,6 +742,7 @@ def start_download( process.wait() if process.returncode == 0: + _strip_vtt_cue_settings(video_id) resolution = detect_resolution(file_path) if file_path else None on_complete(download_id, file_path, resolution) else: diff --git a/frontend/src/pages/Watch.jsx b/frontend/src/pages/Watch.jsx index 7b179f4..24f5f6f 100644 --- a/frontend/src/pages/Watch.jsx +++ b/frontend/src/pages/Watch.jsx @@ -995,15 +995,18 @@ export default function Watch() { )} {(() => { - // Subs already on disk → show indicator (player CC button handles the rest) + // Subs already on disk → show langs + "+" to add more if (subtitleFiles.length > 0 && !subsRequested) return ( - +
+ + CC: {subtitleFiles.map(s => s.lang).join(", ")} + + +
); // Not yet asked → show CC chip if (!subsRequested) return (