From a98e7f9f58a9492d2cb216baa59c890ed8ce02f3 Mon Sep 17 00:00:00 2001 From: Robin <69462652+robin-mu@users.noreply.github.com> Date: Wed, 15 Oct 2025 01:23:13 +0200 Subject: [PATCH] [ie/idagio] Add extractors (#14586) Closes #2624 Authored by: robin-mu --- yt_dlp/extractor/_extractors.py | 7 + yt_dlp/extractor/idagio.py | 233 ++++++++++++++++++++++++++++++++ 2 files changed, 240 insertions(+) create mode 100644 yt_dlp/extractor/idagio.py diff --git a/yt_dlp/extractor/_extractors.py b/yt_dlp/extractor/_extractors.py index 15b66cb310..072169d48d 100644 --- a/yt_dlp/extractor/_extractors.py +++ b/yt_dlp/extractor/_extractors.py @@ -824,6 +824,13 @@ IchinanaLiveIE, IchinanaLiveVODIE, ) +from .idagio import ( + IdagioAlbumIE, + IdagioPersonalPlaylistIE, + IdagioPlaylistIE, + IdagioRecordingIE, + IdagioTrackIE, +) from .idolplus import IdolPlusIE from .ign import ( IGNIE, diff --git a/yt_dlp/extractor/idagio.py b/yt_dlp/extractor/idagio.py new file mode 100644 index 0000000000..5790606bea --- /dev/null +++ b/yt_dlp/extractor/idagio.py @@ -0,0 +1,233 @@ +from .common import InfoExtractor +from ..utils import int_or_none, unified_timestamp, url_or_none +from ..utils.traversal import traverse_obj + + +class IdagioTrackIE(InfoExtractor): + _VALID_URL = r'https?://(?:www\.)?app\.idagio\.com/recordings/\d+\?(?:[^#]+&)?trackId=(?P\d+)' + _TESTS = [{ + 'url': 'https://app.idagio.com/recordings/30576934?trackId=30576943', + 'md5': '15148bd71804b2450a2508931a116b56', + 'info_dict': { + 'id': '30576943', + 'ext': 'mp3', + 'title': 'Theme. Andante', + 'duration': 82, + 'composers': ['Edward Elgar'], + 'artists': ['Vasily Petrenko', 'Royal Liverpool Philharmonic Orchestra'], + 'genres': ['Orchestral', 'Other Orchestral Music'], + 'track': 'Theme. Andante', + 'timestamp': 1554474370, + 'upload_date': '20190405', + }, + }, { + 'url': 'https://app.idagio.com/recordings/20514467?trackId=20514478&utm_source=pcl', + 'md5': '3acef2ea0feadf889123b70e5a1e7fa7', + 'info_dict': { + 'id': '20514478', + 'ext': 'mp3', + 'title': 'I. Adagio sostenuto', + 'duration': 316, + 'composers': ['Ludwig van Beethoven'], + 'artists': [], + 'genres': ['Keyboard', 'Sonata (Keyboard)'], + 'track': 'I. Adagio sostenuto', + 'timestamp': 1518076337, + 'upload_date': '20180208', + }, + }] + + def _real_extract(self, url): + track_id = self._match_id(url) + track_info = self._download_json( + f'https://api.idagio.com/v2.0/metadata/tracks/{track_id}', + track_id, fatal=False, expected_status=406) + if traverse_obj(track_info, 'error_code') == 'idagio.error.blocked.location': + self.raise_geo_restricted() + + content_info = self._download_json( + f'https://api.idagio.com/v1.8/content/track/{track_id}', track_id, + query={ + 'quality': '0', + 'format': '2', + 'client_type': 'web-4', + }) + + return { + 'ext': 'mp3', + 'vcodec': 'none', + 'id': track_id, + 'url': traverse_obj(content_info, ('url', {url_or_none})), + **traverse_obj(track_info, ('result', { + 'title': ('piece', 'title', {str}), + 'timestamp': ('recording', 'created_at', {int_or_none(scale=1000)}), + 'location': ('recording', 'location', {str}), + 'duration': ('duration', {int_or_none}), + 'track': ('piece', 'title', {str}), + 'artists': ('recording', ('conductor', ('ensembles', ...), ('soloists', ...)), 'name', {str}, filter), + 'composers': ('piece', 'workpart', 'work', 'composer', 'name', {str}, filter, all, filter), + 'genres': ('piece', 'workpart', 'work', ('genre', 'subgenre'), 'title', {str}, filter), + })), + } + + +class IdagioPlaylistBaseIE(InfoExtractor): + """Subclasses must set _API_URL_TMPL and define _parse_playlist_metadata""" + _PLAYLIST_ID_KEY = 'id' # vs. 'display_id' + + def _entries(self, playlist_info): + for track_data in traverse_obj(playlist_info, ('tracks', lambda _, v: v['id'] and v['recording']['id'])): + track_id = track_data['id'] + recording_id = track_data['recording']['id'] + yield self.url_result( + f'https://app.idagio.com/recordings/{recording_id}?trackId={track_id}', + ie=IdagioTrackIE, video_id=track_id) + + def _real_extract(self, url): + playlist_id = self._match_id(url) + playlist_info = self._download_json( + self._API_URL_TMPL.format(playlist_id), playlist_id)['result'] + + return { + '_type': 'playlist', + self._PLAYLIST_ID_KEY: playlist_id, + 'entries': self._entries(playlist_info), + **self._parse_playlist_metadata(playlist_info), + } + + +class IdagioRecordingIE(IdagioPlaylistBaseIE): + _VALID_URL = r'https?://(?:www\.)?app\.idagio\.com/recordings/(?P\d+)(?![^#]*[&?]trackId=\d+)' + _TESTS = [{ + 'url': 'https://app.idagio.com/recordings/30576934', + 'info_dict': { + 'id': '30576934', + 'title': 'Variations on an Original Theme op. 36', + 'composers': ['Edward Elgar'], + 'artists': ['Vasily Petrenko', 'Royal Liverpool Philharmonic Orchestra'], + 'genres': ['Orchestral', 'Other Orchestral Music'], + 'timestamp': 1554474370, + 'modified_timestamp': 1554474370, + 'modified_date': '20190405', + 'upload_date': '20190405', + }, + 'playlist_count': 15, + }] + _API_URL_TMPL = 'https://api.idagio.com/v2.0/metadata/recordings/{}' + + def _parse_playlist_metadata(self, playlist_info): + return traverse_obj(playlist_info, { + 'title': ('work', 'title', {str}), + 'timestamp': ('created_at', {int_or_none(scale=1000)}), + 'modified_timestamp': ('created_at', {int_or_none(scale=1000)}), + 'location': ('location', {str}), + 'artists': (('conductor', ('ensembles', ...), ('soloists', ...)), 'name', {str}), + 'composers': ('work', 'composer', 'name', {str}, all), + 'genres': ('work', ('genre', 'subgenre'), 'title', {str}), + 'tags': ('tags', ..., {str}), + }) + + +class IdagioAlbumIE(IdagioPlaylistBaseIE): + _VALID_URL = r'https?://(?:www\.)?app\.idagio\.com/albums/(?P[\w-]+)' + _TESTS = [{ + 'url': 'https://app.idagio.com/albums/elgar-enigma-variations-in-the-south-serenade-for-strings', + 'info_dict': { + 'id': 'a9f139b8-f70d-4b8a-a9a4-5fe8d35eaf9c', + 'display_id': 'elgar-enigma-variations-in-the-south-serenade-for-strings', + 'title': 'Elgar: Enigma Variations, In the South, Serenade for Strings', + 'description': '', + 'thumbnail': 'https://idagio-images.global.ssl.fastly.net/albums/880040420521/main.jpg', + 'artists': ['Vasily Petrenko', 'Royal Liverpool Philharmonic Orchestra', 'Edward Elgar'], + 'timestamp': 1553817600, + 'upload_date': '20190329', + 'modified_timestamp': 1562566559.0, + 'modified_date': '20190708', + }, + 'playlist_count': 19, + }, { + 'url': 'https://app.idagio.com/albums/brahms-ein-deutsches-requiem-3B403DF6-62D7-4A42-807B-47173F3E0192', + 'info_dict': { + 'id': '2862ad4e-4a61-45ad-9ce4-7fcf0c2626fe', + 'display_id': 'brahms-ein-deutsches-requiem-3B403DF6-62D7-4A42-807B-47173F3E0192', + 'title': 'Brahms: Ein deutsches Requiem', + 'description': '', + 'thumbnail': 'https://idagio-images.global.ssl.fastly.net/albums/3149020954522/main.jpg', + 'tags': ['recent-release'], + 'artists': ['Sabine Devieilhe', 'Stéphane Degout', 'Raphaël Pichon', 'Pygmalion', 'Johannes Brahms'], + 'timestamp': 1760054400, + 'upload_date': '20251010', + 'modified_timestamp': 1760101611, + 'modified_date': '20251010', + }, + 'playlist_count': 7, + }] + _API_URL_TMPL = 'https://api.idagio.com/v2.0/metadata/albums/{}' + _PLAYLIST_ID_KEY = 'display_id' + + def _parse_playlist_metadata(self, playlist_info): + return traverse_obj(playlist_info, { + 'id': ('id', {str}), + 'title': ('title', {str}), + 'timestamp': ('publishDate', {unified_timestamp}), + 'modified_timestamp': ('lastModified', {unified_timestamp}), + 'thumbnail': ('imageUrl', {url_or_none}), + 'description': ('description', {str}), + 'artists': ('participants', ..., 'name', {str}), + 'tags': ('tags', ..., {str}), + }) + + +class IdagioPlaylistIE(IdagioPlaylistBaseIE): + _VALID_URL = r'https?://(?:www\.)?app\.idagio\.com/playlists/(?!personal/)(?P[\w-]+)' + _TESTS = [{ + 'url': 'https://app.idagio.com/playlists/beethoven-the-most-beautiful-piano-music', + 'info_dict': { + 'id': '31652bec-8c5b-460e-a3f0-cf1f69817f53', + 'display_id': 'beethoven-the-most-beautiful-piano-music', + 'title': 'Beethoven: the most beautiful piano music', + 'description': 'md5:d41bb04b8896bb69377f5c2cd9345ad1', + 'thumbnail': r're:https://.+/playlists/31652bec-8c5b-460e-a3f0-cf1f69817f53/main\.jpg', + 'creators': ['IDAGIO'], + }, + 'playlist_mincount': 16, # one entry is geo-restricted + }] + _API_URL_TMPL = 'https://api.idagio.com/v2.0/playlists/{}' + _PLAYLIST_ID_KEY = 'display_id' + + def _parse_playlist_metadata(self, playlist_info): + return traverse_obj(playlist_info, { + 'id': ('id', {str}), + 'title': ('title', {str}), + 'thumbnail': ('imageUrl', {url_or_none}), + 'description': ('description', {str}), + 'creators': ('curator', 'name', {str}, all), + }) + + +class IdagioPersonalPlaylistIE(IdagioPlaylistBaseIE): + _VALID_URL = r'https?://(?:www\.)?app\.idagio\.com/playlists/personal/(?P[\da-f-]+)' + _TESTS = [{ + 'url': 'https://app.idagio.com/playlists/personal/99dad72e-7b3a-45a4-b216-867c08046ed8', + 'info_dict': { + 'id': '99dad72e-7b3a-45a4-b216-867c08046ed8', + 'title': 'Test', + 'creators': ['1a6f16a6-4514-4d0c-b481-3a9877835626'], + 'thumbnail': r're:https://.+/artists/86371/main\.jpg', + 'timestamp': 1602859138, + 'modified_timestamp': 1755616667, + 'upload_date': '20201016', + 'modified_date': '20250819', + }, + 'playlist_count': 100, + }] + _API_URL_TMPL = 'https://api.idagio.com/v1.0/personal-playlists/{}' + + def _parse_playlist_metadata(self, playlist_info): + return traverse_obj(playlist_info, { + 'title': ('title', {str}), + 'thumbnail': ('image_url', {url_or_none}), + 'creators': ('user_id', {str}, all), + 'timestamp': ('created_at', {int_or_none(scale=1000)}), + 'modified_timestamp': ('updated_at', {int_or_none(scale=1000)}), + })