diff options
| -rw-r--r-- | server.py | 1 | ||||
| -rw-r--r-- | youtube/channel.py | 12 | ||||
| -rw-r--r-- | youtube/comments.py | 2 | ||||
| -rw-r--r-- | youtube/playlist.py | 4 | ||||
| -rw-r--r-- | youtube/static/js/common.js | 33 | ||||
| -rw-r--r-- | youtube/subscriptions.py | 26 | ||||
| -rw-r--r-- | youtube/util.py | 38 | ||||
| -rw-r--r-- | youtube/watch.py | 18 | ||||
| -rw-r--r-- | youtube/yt_data_extract/common.py | 6 | ||||
| -rw-r--r-- | youtube/yt_data_extract/everything_else.py | 2 |
10 files changed, 81 insertions, 61 deletions
@@ -99,7 +99,6 @@ def proxy_site(env, start_response, video=False): if response.status >= 400: print('Error: YouTube returned "%d %s" while routing %s' % ( response.status, response.reason, url.split('?')[0])) - total_received = 0 retry = False while True: diff --git a/youtube/channel.py b/youtube/channel.py index 72fac07..55c1124 100644 --- a/youtube/channel.py +++ b/youtube/channel.py @@ -406,12 +406,12 @@ def post_process_channel_info(info): info['avatar'] = util.prefix_url(info['avatar']) info['channel_url'] = util.prefix_url(info['channel_url']) for item in info['items']: - # For playlists, use first_video_id for thumbnail, not playlist id - if item.get('type') == 'playlist' and item.get('first_video_id'): - item['thumbnail'] = "https://i.ytimg.com/vi/{}/hq720.jpg".format(item['first_video_id']) - elif item.get('type') == 'video': - item['thumbnail'] = "https://i.ytimg.com/vi/{}/hq720.jpg".format(item['id']) - # For channels and other types, keep existing thumbnail + # Only set thumbnail if YouTube didn't provide one + if not item.get('thumbnail'): + if item.get('type') == 'playlist' and item.get('first_video_id'): + item['thumbnail'] = "https://i.ytimg.com/vi/{}/hqdefault.jpg".format(item['first_video_id']) + elif item.get('type') == 'video' and item.get('id'): + item['thumbnail'] = "https://i.ytimg.com/vi/{}/hqdefault.jpg".format(item['id']) util.prefix_urls(item) util.add_extra_html_info(item) if info['current_tab'] == 'about': diff --git a/youtube/comments.py b/youtube/comments.py index 5e40b14..1ff1a21 100644 --- a/youtube/comments.py +++ b/youtube/comments.py @@ -150,7 +150,7 @@ def post_process_comments_info(comments_info): util.URL_ORIGIN, '/watch?v=', comments_info['video_id']) comments_info['video_thumbnail'] = concat_or_none( settings.img_prefix, 'https://i.ytimg.com/vi/', - comments_info['video_id'], '/hq720.jpg' + comments_info['video_id'], '/hqdefault.jpg' ) diff --git a/youtube/playlist.py b/youtube/playlist.py index 2765a30..bedf2d2 100644 --- a/youtube/playlist.py +++ b/youtube/playlist.py @@ -106,8 +106,8 @@ def get_playlist_page(): for item in info.get('items', ()): util.prefix_urls(item) util.add_extra_html_info(item) - if 'id' in item: - item['thumbnail'] = f"{settings.img_prefix}https://i.ytimg.com/vi/{item['id']}/hq720.jpg" + if 'id' in item and not item.get('thumbnail'): + item['thumbnail'] = f"{settings.img_prefix}https://i.ytimg.com/vi/{item['id']}/hqdefault.jpg" item['url'] += '&list=' + playlist_id if item['index']: diff --git a/youtube/static/js/common.js b/youtube/static/js/common.js index bcd1539..ac4413b 100644 --- a/youtube/static/js/common.js +++ b/youtube/static/js/common.js @@ -121,11 +121,12 @@ window.addEventListener('DOMContentLoaded', function() { * Priority: hq720.jpg -> sddefault.jpg -> hqdefault.jpg -> mqdefault.jpg -> default.jpg */ function thumbnail_fallback(img) { - const src = img.src || img.dataset.src; + // Once src is set (image was loaded or attempted), always work with src + const src = img.src; if (!src) return; // Handle YouTube video thumbnails - if (src.includes('/i.ytimg.com/')) { + if (src.includes('/i.ytimg.com/') || src.includes('/i.ytimg.com%2F')) { // Extract video ID from URL const match = src.match(/\/vi\/([^/]+)/); if (!match) return; @@ -138,36 +139,32 @@ function thumbnail_fallback(img) { 'hq720.jpg', 'sddefault.jpg', 'hqdefault.jpg', - 'mqdefault.jpg', - 'default.jpg' ]; // Find current quality and try next fallback for (let i = 0; i < fallbacks.length; i++) { if (src.includes(fallbacks[i])) { - // Try next quality if (i < fallbacks.length - 1) { - const newSrc = imgPrefix + 'https://i.ytimg.com/vi/' + videoId + '/' + fallbacks[i + 1]; - if (img.dataset.src) { - img.dataset.src = newSrc; - } else { - img.src = newSrc; - } + img.src = imgPrefix + 'https://i.ytimg.com/vi/' + videoId + '/' + fallbacks[i + 1]; + } else { + // Last fallback failed, stop retrying + img.onerror = null; } - break; + return; } } + // Unknown quality format, stop retrying + img.onerror = null; } // Handle YouTube channel avatars (ggpht.com) else if (src.includes('ggpht.com') || src.includes('yt3.ggpht.com')) { - // Try to increase avatar size (s88 -> s240) const newSrc = src.replace(/=s\d+-c-k/, '=s240-c-k-c0x00ffffff-no-rj'); if (newSrc !== src) { - if (img.dataset.src) { - img.dataset.src = newSrc; - } else { - img.src = newSrc; - } + img.src = newSrc; + } else { + img.onerror = null; } + } else { + img.onerror = null; } } diff --git a/youtube/subscriptions.py b/youtube/subscriptions.py index 0cb5e95..3326a51 100644 --- a/youtube/subscriptions.py +++ b/youtube/subscriptions.py @@ -1089,12 +1089,26 @@ def serve_subscription_thumbnail(thumbnail): f.close() return flask.Response(image, mimetype='image/jpeg') - url = f"https://i.ytimg.com/vi/{video_id}/hq720.jpg" - try: - image = util.fetch_url(url, report_text="Saved thumbnail: " + video_id) - except urllib.error.HTTPError as e: - print("Failed to download thumbnail for " + video_id + ": " + str(e)) - flask.abort(e.code) + image = None + for quality in ('hq720.jpg', 'sddefault.jpg', 'hqdefault.jpg'): + url = f"https://i.ytimg.com/vi/{video_id}/{quality}" + try: + image = util.fetch_url(url, report_text="Saved thumbnail: " + video_id) + break + except util.FetchError as e: + if '404' in str(e): + continue + print("Failed to download thumbnail for " + video_id + ": " + str(e)) + flask.abort(500) + except urllib.error.HTTPError as e: + if e.code == 404: + continue + print("Failed to download thumbnail for " + video_id + ": " + str(e)) + flask.abort(e.code) + + if image is None: + flask.abort(404) + try: f = open(thumbnail_path, 'wb') except FileNotFoundError: diff --git a/youtube/util.py b/youtube/util.py index e6f1961..ae948ae 100644 --- a/youtube/util.py +++ b/youtube/util.py @@ -542,21 +542,31 @@ class RateLimitedQueue(gevent.queue.Queue): def download_thumbnail(save_directory, video_id): - url = f"https://i.ytimg.com/vi/{video_id}/hq720.jpg" save_location = os.path.join(save_directory, video_id + ".jpg") - try: - thumbnail = fetch_url(url, report_text="Saved thumbnail: " + video_id) - except urllib.error.HTTPError as e: - print("Failed to download thumbnail for " + video_id + ": " + str(e)) - return False - try: - f = open(save_location, 'wb') - except FileNotFoundError: - os.makedirs(save_directory, exist_ok=True) - f = open(save_location, 'wb') - f.write(thumbnail) - f.close() - return True + for quality in ('hq720.jpg', 'sddefault.jpg', 'hqdefault.jpg'): + url = f"https://i.ytimg.com/vi/{video_id}/{quality}" + try: + thumbnail = fetch_url(url, report_text="Saved thumbnail: " + video_id) + except FetchError as e: + if '404' in str(e): + continue + print("Failed to download thumbnail for " + video_id + ": " + str(e)) + return False + except urllib.error.HTTPError as e: + if e.code == 404: + continue + print("Failed to download thumbnail for " + video_id + ": " + str(e)) + return False + try: + f = open(save_location, 'wb') + except FileNotFoundError: + os.makedirs(save_directory, exist_ok=True) + f = open(save_location, 'wb') + f.write(thumbnail) + f.close() + return True + print("No thumbnail available for " + video_id) + return False def download_thumbnails(save_directory, ids): diff --git a/youtube/watch.py b/youtube/watch.py index 14f1dae..b76a462 100644 --- a/youtube/watch.py +++ b/youtube/watch.py @@ -628,12 +628,12 @@ def get_watch_page(video_id=None): # prefix urls, and other post-processing not handled by yt_data_extract for item in info['related_videos']: - # For playlists, use first_video_id for thumbnail, not playlist id - if item.get('type') == 'playlist' and item.get('first_video_id'): - item['thumbnail'] = "https://i.ytimg.com/vi/{}/hq720.jpg".format(item['first_video_id']) - elif item.get('type') == 'video': - item['thumbnail'] = "https://i.ytimg.com/vi/{}/hq720.jpg".format(item['id']) - # For other types, keep existing thumbnail or skip + # Only set thumbnail if YouTube didn't provide one + if not item.get('thumbnail'): + if item.get('type') == 'playlist' and item.get('first_video_id'): + item['thumbnail'] = "https://i.ytimg.com/vi/{}/hqdefault.jpg".format(item['first_video_id']) + elif item.get('type') == 'video' and item.get('id'): + item['thumbnail'] = "https://i.ytimg.com/vi/{}/hqdefault.jpg".format(item['id']) util.prefix_urls(item) util.add_extra_html_info(item) for song in info['music_list']: @@ -641,9 +641,9 @@ def get_watch_page(video_id=None): if info['playlist']: playlist_id = info['playlist']['id'] for item in info['playlist']['items']: - # Set high quality thumbnail for playlist videos - if item.get('type') == 'video' and item.get('id'): - item['thumbnail'] = "https://i.ytimg.com/vi/{}/hq720.jpg".format(item['id']) + # Only set thumbnail if YouTube didn't provide one + if not item.get('thumbnail') and item.get('type') == 'video' and item.get('id'): + item['thumbnail'] = "https://i.ytimg.com/vi/{}/hqdefault.jpg".format(item['id']) util.prefix_urls(item) util.add_extra_html_info(item) if playlist_id: diff --git a/youtube/yt_data_extract/common.py b/youtube/yt_data_extract/common.py index 6a98280..7d44fae 100644 --- a/youtube/yt_data_extract/common.py +++ b/youtube/yt_data_extract/common.py @@ -369,9 +369,9 @@ def extract_item_info(item, additional_info={}): ['detailedMetadataSnippets', 0, 'snippetText'], )) info['thumbnail'] = normalize_url(multi_deep_get(item, - ['thumbnail', 'thumbnails', 0, 'url'], # videos - ['thumbnails', 0, 'thumbnails', 0, 'url'], # playlists - ['thumbnailRenderer', 'showCustomThumbnailRenderer', 'thumbnail', 'thumbnails', 0, 'url'], # shows + ['thumbnail', 'thumbnails', -1, 'url'], # videos (highest quality) + ['thumbnails', 0, 'thumbnails', -1, 'url'], # playlists + ['thumbnailRenderer', 'showCustomThumbnailRenderer', 'thumbnail', 'thumbnails', -1, 'url'], # shows )) info['badges'] = [] diff --git a/youtube/yt_data_extract/everything_else.py b/youtube/yt_data_extract/everything_else.py index 1f5b6a2..0f64649 100644 --- a/youtube/yt_data_extract/everything_else.py +++ b/youtube/yt_data_extract/everything_else.py @@ -229,7 +229,7 @@ def extract_playlist_metadata(polymer_json): if metadata['first_video_id'] is None: metadata['thumbnail'] = None else: - metadata['thumbnail'] = f"https://i.ytimg.com/vi/{metadata['first_video_id']}/hq720.jpg" + metadata['thumbnail'] = f"https://i.ytimg.com/vi/{metadata['first_video_id']}/hqdefault.jpg" metadata['video_count'] = extract_int(header.get('numVideosText')) metadata['description'] = extract_str(header.get('descriptionText'), default='') |
