Fix subtitle positioning and show existing langs without re-downloading

- Strip yt-dlp's align:start position:0% cue settings from VTT files
  after both video download and subtitle-only download so CSS ::cue centers them
- CC chip now shows already-downloaded langs (e.g. 'CC: en') directly
  from disk with a '+' button to add more — no YouTube call needed

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-26 21:22:33 +02:00
parent 75493ed80e
commit da765ce76e
2 changed files with 33 additions and 8 deletions

View File

@@ -384,6 +384,25 @@ def fetch_channel_links(channel_id: str) -> list[str]:
return list(channel_ids) 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: def download_subs_only(video_id: str, subtitle_langs: str) -> bool:
"""Download subtitle files only (no video) for an already-downloaded video.""" """Download subtitle files only (no video) for an already-downloaded video."""
url = f"https://www.youtube.com/watch?v={video_id}" 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, "-o", output_template,
*_cookie_args(), *_cookie_args(),
], timeout=60) ], timeout=60)
if code == 0:
_strip_vtt_cue_settings(video_id)
return code == 0 return code == 0
@@ -721,6 +742,7 @@ def start_download(
process.wait() process.wait()
if process.returncode == 0: if process.returncode == 0:
_strip_vtt_cue_settings(video_id)
resolution = detect_resolution(file_path) if file_path else None resolution = detect_resolution(file_path) if file_path else None
on_complete(download_id, file_path, resolution) on_complete(download_id, file_path, resolution)
else: else:

View File

@@ -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 ( if (subtitleFiles.length > 0 && !subsRequested) return (
<button <div className="flex items-center gap-1">
onClick={() => setSubsRequested(true)} <span className="px-3 py-1.5 rounded-full bg-zinc-800 text-zinc-400 text-xs font-mono">
className="flex items-center gap-1 px-3 py-1.5 rounded-full bg-zinc-800 text-zinc-400 text-xs hover:bg-zinc-700 transition-colors" CC: {subtitleFiles.map(s => s.lang).join(", ")}
title="Subtitles loaded — click to add more languages" </span>
> <button
CC onClick={() => setSubsRequested(true)}
</button> className="px-2.5 py-1.5 rounded-full text-xs bg-zinc-800 text-zinc-500 hover:bg-zinc-700 transition-colors"
title="Add subtitle language"
>+</button>
</div>
); );
// Not yet asked → show CC chip // Not yet asked → show CC chip
if (!subsRequested) return ( if (!subsRequested) return (