diff --git a/app/src/components/Vars.js b/app/src/components/Vars.js index 99071eb9..3642f907 100644 --- a/app/src/components/Vars.js +++ b/app/src/components/Vars.js @@ -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 = [ diff --git a/modules/videos/downloader.py b/modules/videos/downloader.py index ed0a2a26..111e51cc 100755 --- a/modules/videos/downloader.py +++ b/modules/videos/downloader.py @@ -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'): diff --git a/modules/videos/test/test_downloader.py b/modules/videos/test/test_downloader.py index e15fca7c..83dd5421 100644 --- a/modules/videos/test/test_downloader.py +++ b/modules/videos/test/test_downloader.py @@ -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.""" diff --git a/wrolpi/schema.py b/wrolpi/schema.py index 5bfef337..6d13e4ac 100644 --- a/wrolpi/schema.py +++ b/wrolpi/schema.py @@ -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.') diff --git a/wrolpi/test/test_downloader.py b/wrolpi/test/test_downloader.py index 3172b222..c4d060f0 100644 --- a/wrolpi/test/test_downloader.py +++ b/wrolpi/test/test_downloader.py @@ -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):