Fix storyboard downloads with yattee-server direct YouTube URLs

yattee-server returns direct YouTube CDN URLs in the storyboard `url`
and `templateUrl` fields instead of an Invidious-style VTT proxy path.
Two resulting issues:

- `Storyboard.directSheetURL` was replacing the whole `M$M` token with
  just the index, producing `.../0.jpg` (404) instead of `.../M0.jpg`.
  Replace `M$M` with `M\(index)` to preserve the literal `M` prefix;
  matching the full token also avoids clobbering `$M` sequences that
  may appear in `sigh=rs$...` query params.
- The download code fetched `proxyUrl` as if it were a WebVTT file;
  with yattee-server that downloads a JPEG that fails UTF-8 parsing.
  Skip the VTT round-trip when `proxyUrl` obviously points at an image.

Also align the on-disk filename with the local-playback template
(`sb_M$M.jpg` → `sb_M{N}.jpg`) so offline seek-bar previews resolve,
and add [Storyboard] debug logs at each decision point so future
failures can be diagnosed without guessing.
This commit is contained in:
Arkadiusz Fal
2026-04-16 06:28:44 +02:00
parent 58f1b8c1ad
commit 82a8ac2afa
2 changed files with 115 additions and 24 deletions

View File

@@ -99,7 +99,12 @@ struct Storyboard: Hashable, Sendable, Codable {
/// - Returns: Direct URL for the sprite sheet, or nil if invalid
func directSheetURL(for index: Int) -> URL? {
guard index >= 0, index < storyboardCount else { return nil }
let urlString = templateUrl.replacingOccurrences(of: "M$M", with: "\(index)")
// YouTube storyboard filenames are `M{N}.jpg` and the templateUrl encodes the
// slot as `M$M`. The leading `M` is literal, so replace `M$M` with `M{index}`
// (not bare `\(index)`) otherwise the file becomes `0.jpg` instead of `M0.jpg`
// and YouTube returns 404. Matching the full `M$M` token also avoids accidentally
// rewriting any `$M` that appears later in query params such as `sigh=rs$...`.
let urlString = templateUrl.replacingOccurrences(of: "M$M", with: "M\(index)")
return URL(string: urlString)
}