Deprecate "Most Views" download order
Some checks failed
CodeQL / Analyze (push) Has been cancelled
Controller Tests / controller-tests (push) Has been cancelled
Jest Tests / jest-tests (push) Has been cancelled

YouTube's new listing layout means yt-dlp no longer returns `view_count`
in flat channel/playlist entries, so sorting by views raised KeyError and
the download failed and retried forever.

Sorting by views now requires per-video extraction (hundreds of requests
per channel), so the feature is deprecated rather than degraded:

- downloader: the 'views' branch raises UnrecoverableDownloadError so the
  download fails cleanly (no retry loop) with a message to switch to
  Newest/Oldest. May be revisited via the YouTube Data API.
- schema: download_order validation drops 'views' (newest/oldest/null only).
- UI: removed "Most Views" from the download-order dropdown.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Roland 2026-06-28 17:58:55 -06:00
parent e513e36496
commit fcb64fed13
No known key found for this signature in database
GPG key ID: 1BA620CE6C9A4680
5 changed files with 45 additions and 6 deletions

View file

@ -101,7 +101,7 @@ export const validUrlRegex = /^(http|https):\/\/[^ "]+$/;
export const downloadOrderOptions = [
{key: 'newest', text: 'Newest', value: 'newest'},
{key: 'oldest', text: 'Oldest', value: 'oldest'},
{key: 'views', text: 'Most Views', value: 'views'},
// 'views'/'Most Views' was removed: YouTube no longer provides view counts in channel/playlist listings.
];
export const downloadResolutionOptions = [

View file

@ -720,9 +720,12 @@ class ChannelDownloader(Downloader, ABC):
logger.debug(f'Downloading oldest videos from {download.url}')
downloads = downloads.reverse()
elif sort_key == 'views':
# Download videos with most views.
logger.debug(f'Downloading most viewed videos from {download.url}')
downloads = sorted(downloads, key=lambda i: i['view_count'], reverse=True)
# YouTube no longer exposes `view_count` in flat channel/playlist listings, so we can no longer
# sort by views without fetching every video individually. This may become possible again if we
# integrate the YouTube Data API.
raise UnrecoverableDownloadError(
'Downloading by "Most Views" is no longer supported because YouTube no longer provides view '
'counts in channel/playlist listings. Please change this download to "Newest" or "Oldest".')
# Limit the videos that will be downloads (this allows the user to download the "top 100" videos of a Channel)
if video_count_limit := settings.get('video_count_limit'):

View file

@ -706,6 +706,27 @@ async def test_download_playlist(test_session, test_directory, mock_video_extrac
}
def test_get_missing_videos_views_order_deprecated(test_session):
"""The 'views' download order was deprecated. YouTube no longer provides `view_count` in flat
channel/playlist listings, so a download configured for 'views' raises an unrecoverable error rather
than crashing with a KeyError or silently downloading in the wrong order."""
from modules.videos.downloader import channel_downloader
# Flat entries no longer contain a `view_count` key (matches current yt-dlp behavior).
download = Download(
url='https://www.youtube.com/@example/videos',
settings={'download_order': 'views'},
info_json={'entries': [
{'_type': 'url', 'id': 'a', 'title': 'a', 'url': 'https://youtube.com/watch?v=a'},
{'_type': 'url', 'id': 'b', 'title': 'b', 'url': 'https://youtube.com/watch?v=b'},
]},
)
test_session.add(download)
test_session.commit()
with pytest.raises(UnrecoverableDownloadError):
channel_downloader.get_missing_videos(test_session, download)
@pytest.mark.asyncio
async def test_channel_download_crud(test_session, async_client, assert_downloads, tag_factory):
"""Test creating more complex Channel Download."""

View file

@ -301,8 +301,9 @@ class DownloadSettings:
if not self.download_metadata_only:
del self.download_metadata_only
if self.download_order not in (None, 'newest', 'oldest', 'views'):
raise ValidationError(f'Download order must be one of newest, oldest, views, or null.')
# 'views' was deprecated: YouTube no longer provides view counts in channel/playlist listings.
if self.download_order not in (None, 'newest', 'oldest'):
raise ValidationError(f'Download order must be one of newest, oldest, or null.')
if self.video_format not in (None, 'mp4', 'mkv', 'ogg', 'webm'):
raise ValidationError(f'Video format must be one of mp4, mkv, ogg, webm, or null.')

View file

@ -914,6 +914,20 @@ def test_download_settings_coerces_numeric_strings():
assert 'max_pages' not in req.settings
@pytest.mark.parametrize('order', ['newest', 'oldest', None])
def test_download_settings_accepts_valid_download_order(order):
from wrolpi.schema import DownloadSettings
DownloadSettings(download_order=order)
def test_download_settings_rejects_deprecated_views_order():
"""'views' download order was deprecated; YouTube no longer provides view counts in listings."""
from wrolpi.schema import DownloadSettings
from wrolpi.errors import ValidationError
with pytest.raises(ValidationError):
DownloadSettings(download_order='views')
@pytest.mark.asyncio
async def test_downloads_config(test_session, test_download_manager, test_download_manager_config,
test_downloader, assert_downloads, tag_factory, await_switches):