From c03cd0bd194832cd845461090b26b80ef5fce291 Mon Sep 17 00:00:00 2001 From: Arkadiusz Fal Date: Fri, 17 Apr 2026 20:00:38 +0200 Subject: [PATCH] Store higher-quality YouTube thumbnail URLs in recent playlists Rewrites /vi/ID/{default|mq|hq|sd}default.jpg to maxresdefault.jpg when saving/updating a recent playlist, so the card shows a sharper image than the low-res search-result thumbnail. --- Yattee/Data/DataManager+Recents.swift | 2 +- Yattee/Data/RecentPlaylist.swift | 19 +++++++++++++++++-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/Yattee/Data/DataManager+Recents.swift b/Yattee/Data/DataManager+Recents.swift index 10b49e9a..254c04b4 100644 --- a/Yattee/Data/DataManager+Recents.swift +++ b/Yattee/Data/DataManager+Recents.swift @@ -338,7 +338,7 @@ extension DataManager { existing.title = playlist.title existing.authorName = playlist.authorName existing.videoCount = playlist.videoCount - existing.thumbnailURLString = playlist.thumbnailURL?.absoluteString + existing.thumbnailURLString = RecentPlaylist.upgradedThumbnailURLString(playlist.thumbnailURL) savedEntry = existing } else { // Create new entry diff --git a/Yattee/Data/RecentPlaylist.swift b/Yattee/Data/RecentPlaylist.swift index 856ca4aa..5e3635aa 100644 --- a/Yattee/Data/RecentPlaylist.swift +++ b/Yattee/Data/RecentPlaylist.swift @@ -57,10 +57,25 @@ final class RecentPlaylist { title: playlist.title, authorName: playlist.authorName, videoCount: playlist.videoCount, - thumbnailURLString: playlist.thumbnailURL?.absoluteString + thumbnailURLString: upgradedThumbnailURLString(playlist.thumbnailURL) ) } - + + /// Rewrites YouTube `/vi/ID/{default|mq|hq|sd}default.jpg` thumbnails to `maxresdefault.jpg` + /// so recent playlist cards show a higher-quality image. + static func upgradedThumbnailURLString(_ url: URL?) -> String? { + guard let url else { return nil } + let path = url.path + let upgradable = ["default.jpg", "mqdefault.jpg", "hqdefault.jpg", "sddefault.jpg"] + guard path.range(of: #"/vi/[^/]+/"#, options: .regularExpression) != nil, + let match = upgradable.first(where: { path.hasSuffix($0) }), + var components = URLComponents(url: url, resolvingAgainstBaseURL: false) else { + return url.absoluteString + } + components.path = String(path.dropLast(match.count)) + "maxresdefault.jpg" + return components.url?.absoluteString ?? url.absoluteString + } + private static func extractSourceInfo(from source: ContentSource) -> (String, String?) { switch source { case .global: