diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 4b6964260..010226418 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -713,3 +713,5 @@ xiaomac wesson09 Crypto90 MutantPiggieGolem1 +Sanceilaks +Strkmn diff --git a/Changelog.md b/Changelog.md index 22a9a6e4b..b996d35f7 100644 --- a/Changelog.md +++ b/Changelog.md @@ -4,6 +4,30 @@ # Changelog # To create a release, dispatch the https://github.com/yt-dlp/yt-dlp/actions/workflows/release.yml workflow on master --> +### 2025.01.15 + +#### Extractor changes +- **youtube**: [Do not use `web_creator` as a default client](https://github.com/yt-dlp/yt-dlp/commit/c8541f8b13e743fcfa06667530d13fee8686e22a) ([#12087](https://github.com/yt-dlp/yt-dlp/issues/12087)) by [bashonly](https://github.com/bashonly) + +### 2025.01.12 + +#### Core changes +- [Fix filename sanitization with `--no-windows-filenames`](https://github.com/yt-dlp/yt-dlp/commit/8346b549150003df988538e54c9d8bc4de568979) ([#11988](https://github.com/yt-dlp/yt-dlp/issues/11988)) by [bashonly](https://github.com/bashonly) +- [Validate retries values are non-negative](https://github.com/yt-dlp/yt-dlp/commit/1f4e1e85a27c5b43e34d7706cfd88ffce1b56a4a) ([#11927](https://github.com/yt-dlp/yt-dlp/issues/11927)) by [Strkmn](https://github.com/Strkmn) + +#### Extractor changes +- **drtalks**: [Add extractor](https://github.com/yt-dlp/yt-dlp/commit/1f489f4a45691cac3f9e787d22a3a8a086229ba6) ([#10831](https://github.com/yt-dlp/yt-dlp/issues/10831)) by [pzhlkj6612](https://github.com/pzhlkj6612), [seproDev](https://github.com/seproDev) +- **plvideo**: [Add extractor](https://github.com/yt-dlp/yt-dlp/commit/3c14e9191f3035b9a729d1d87bc0381f42de57cf) ([#10657](https://github.com/yt-dlp/yt-dlp/issues/10657)) by [Sanceilaks](https://github.com/Sanceilaks), [seproDev](https://github.com/seproDev) +- **vine**: [Remove extractors](https://github.com/yt-dlp/yt-dlp/commit/e2ef4fece6c9742d1733e3bae408c4787765f78c) ([#11700](https://github.com/yt-dlp/yt-dlp/issues/11700)) by [allendema](https://github.com/allendema) +- **xiaohongshu**: [Extend `_VALID_URL`](https://github.com/yt-dlp/yt-dlp/commit/763ed06ee69f13949397897bd42ff2ec3dc3d384) ([#11806](https://github.com/yt-dlp/yt-dlp/issues/11806)) by [HobbyistDev](https://github.com/HobbyistDev) +- **youtube** + - [Fix DASH formats incorrectly skipped in some situations](https://github.com/yt-dlp/yt-dlp/commit/0b6b7742c2e7f2a1fcb0b54ef3dd484bab404b3f) ([#11910](https://github.com/yt-dlp/yt-dlp/issues/11910)) by [coletdjnz](https://github.com/coletdjnz) + - [Refactor cookie auth](https://github.com/yt-dlp/yt-dlp/commit/75079f4e3f7dce49b61ef01da7adcd9876a0ca3b) ([#11989](https://github.com/yt-dlp/yt-dlp/issues/11989)) by [coletdjnz](https://github.com/coletdjnz) + - [Use `tv` instead of `mweb` client by default](https://github.com/yt-dlp/yt-dlp/commit/712d2abb32f59b2d246be2901255f84f1a4c30b3) ([#12059](https://github.com/yt-dlp/yt-dlp/issues/12059)) by [coletdjnz](https://github.com/coletdjnz) + +#### Misc. changes +- **cleanup**: Miscellaneous: [dade5e3](https://github.com/yt-dlp/yt-dlp/commit/dade5e35c89adaad04408bfef766820dbca06ebe) by [grqz](https://github.com/grqz), [Grub4K](https://github.com/Grub4K), [seproDev](https://github.com/seproDev) + ### 2024.12.23 #### Core changes diff --git a/README.md b/README.md index 1c628d025..56e4458dc 100644 --- a/README.md +++ b/README.md @@ -1769,7 +1769,7 @@ # EXTRACTOR ARGUMENTS #### youtube * `lang`: Prefer translated metadata (`title`, `description` etc) of this language code (case-sensitive). By default, the video primary language metadata is preferred, with a fallback to `en` translated. See [youtube.py](https://github.com/yt-dlp/yt-dlp/blob/c26f9b991a0681fd3ea548d535919cec1fbbd430/yt_dlp/extractor/youtube.py#L381-L390) for list of supported content language codes * `skip`: One or more of `hls`, `dash` or `translated_subs` to skip extraction of the m3u8 manifests, dash manifests and [auto-translated subtitles](https://github.com/yt-dlp/yt-dlp/issues/4090#issuecomment-1158102032) respectively -* `player_client`: Clients to extract video data from. The main clients are `web`, `ios` and `android`, with variants `_music` and `_creator` (e.g. `ios_creator`); and `mweb`, `android_vr`, `web_safari`, `web_embedded`, `tv` and `tv_embedded` with no variants. By default, `ios,mweb` is used, or `web_creator,mweb` is used when authenticating with cookies. The `_music` variants are added for `music.youtube.com` URLs. Some clients, such as `web` and `android`, require a `po_token` for their formats to be downloadable. Some clients, such as the `_creator` variants, will only work with authentication. Not all clients support authentication via cookies. You can use `all` to use all the clients, and `default` for the default clients. You can prefix a client with `-` to exclude it, e.g. `youtube:player_client=all,-web` +* `player_client`: Clients to extract video data from. The main clients are `web`, `ios` and `android`, with variants `_music` and `_creator` (e.g. `ios_creator`); and `mweb`, `android_vr`, `web_safari`, `web_embedded`, `tv` and `tv_embedded` with no variants. By default, `tv,ios,web` is used, or `tv,web` is used when authenticating with cookies. The `_music` variants may be added for `music.youtube.com` URLs. Some clients, such as `web` and `android`, require a `po_token` for their formats to be downloadable. Some clients, such as the `_creator` variants, will only work with authentication. Not all clients support authentication via cookies. You can use `default` for the default clients, or you can use `all` for all clients (not recommended). You can prefix a client with `-` to exclude it, e.g. `youtube:player_client=default,-ios` * `player_skip`: Skip some network requests that are generally needed for robust extraction. One or more of `configs` (skip client configs), `webpage` (skip initial webpage), `js` (skip js player). While these options can help reduce the number of requests needed or avoid some rate-limiting, they could cause some issues. See [#860](https://github.com/yt-dlp/yt-dlp/pull/860) for more details * `player_params`: YouTube player parameters to use for player requests. Will overwrite any default ones set by yt-dlp. * `comment_sort`: `top` or `new` (default) - choose comment sorting mode (on YouTube's side) diff --git a/pyproject.toml b/pyproject.toml index 96e2d669a..5eb9a9644 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -76,7 +76,7 @@ dev = [ ] static-analysis = [ "autopep8~=2.0", - "ruff~=0.8.0", + "ruff~=0.9.0", ] test = [ "pytest~=8.1", @@ -195,6 +195,7 @@ ignore = [ "B023", # function-uses-loop-variable (false positives) "B028", # no-explicit-stacklevel "B904", # raise-without-from-inside-except + "A005", # stdlib-module-shadowing "C401", # unnecessary-generator-set "C402", # unnecessary-generator-dict "PIE790", # unnecessary-placeholder diff --git a/supportedsites.md b/supportedsites.md index 916735e08..1420742d1 100644 --- a/supportedsites.md +++ b/supportedsites.md @@ -374,6 +374,7 @@ # Supported sites - **Dropbox** - **Dropout**: [*dropout*](## "netrc machine") - **DropoutSeason** + - **DrTalks** - **DrTuber** - **drtv** - **drtv:live** @@ -1086,6 +1087,7 @@ # Supported sites - **pluralsight**: [*pluralsight*](## "netrc machine") - **pluralsight:course** - **PlutoTV**: (**Currently broken**) + - **PlVideo**: Платформа - **PodbayFM** - **PodbayFMChannel** - **Podchaser** @@ -1641,8 +1643,6 @@ # Supported sites - **Vimm:stream** - **ViMP** - **ViMP:Playlist** - - **Vine** - - **vine:user** - **Viously** - **Viqeo**: (**Currently broken**) - **Viu** diff --git a/yt_dlp/YoutubeDL.py b/yt_dlp/YoutubeDL.py index 0844548d4..5352c39cb 100644 --- a/yt_dlp/YoutubeDL.py +++ b/yt_dlp/YoutubeDL.py @@ -283,7 +283,10 @@ class YoutubeDL: lazy_playlist: Process playlist entries as they are received. matchtitle: Download only matching titles. rejecttitle: Reject downloads for matching titles. - logger: Log messages to a logging.Logger instance. + logger: A class having a `debug`, `warning` and `error` function where + each has a single string parameter, the message to be logged. + For compatibility reasons, both debug and info messages are passed to `debug`. + A debug message will have a prefix of `[debug] ` to discern it from info messages. logtostderr: Print everything to stderr instead of stdout. consoletitle: Display progress in the console window's titlebar. writedescription: Write the video description to a .description file @@ -1323,7 +1326,7 @@ def filename_sanitizer(key, value, restricted): elif (sys.platform != 'win32' and not self.params.get('restrictfilenames') and self.params.get('windowsfilenames') is False): def sanitize(key, value): - return value.replace('/', '\u29F8').replace('\0', '') + return str(value).replace('/', '\u29F8').replace('\0', '') else: def sanitize(key, value): return filename_sanitizer(key, value, restricted=self.params.get('restrictfilenames')) diff --git a/yt_dlp/__init__.py b/yt_dlp/__init__.py index 0dc9f19a0..682d697ea 100644 --- a/yt_dlp/__init__.py +++ b/yt_dlp/__init__.py @@ -261,9 +261,11 @@ def parse_retries(name, value): elif value in ('inf', 'infinite'): return float('inf') try: - return int(value) + int_value = int(value) except (TypeError, ValueError): validate(False, f'{name} retry count', value) + validate_positive(f'{name} retry count', int_value) + return int_value opts.retries = parse_retries('download', opts.retries) opts.fragment_retries = parse_retries('fragment', opts.fragment_retries) diff --git a/yt_dlp/extractor/_extractors.py b/yt_dlp/extractor/_extractors.py index 967010826..c03d4b3f5 100644 --- a/yt_dlp/extractor/_extractors.py +++ b/yt_dlp/extractor/_extractors.py @@ -256,6 +256,7 @@ BilibiliCheeseIE, BilibiliCheeseSeasonIE, BilibiliCollectionListIE, + BiliBiliDynamicIE, BilibiliFavoritesListIE, BiliBiliIE, BiliBiliPlayerIE, @@ -555,6 +556,7 @@ DropoutIE, DropoutSeasonIE, ) +from .drtalks import DrTalksIE from .drtuber import DrTuberIE from .drtv import ( DRTVIE, @@ -584,6 +586,10 @@ EggheadCourseIE, EggheadLessonIE, ) +from .eggs import ( + EggsArtistIE, + EggsIE, +) from .eighttracks import EightTracksIE from .eitb import EitbIE from .elementorembed import ElementorEmbedIE @@ -1278,6 +1284,10 @@ ) from .nekohacker import NekoHackerIE from .nerdcubed import NerdCubedFeedIE +from .nest import ( + NestClipIE, + NestIE, +) from .neteasemusic import ( NetEaseMusicAlbumIE, NetEaseMusicDjRadioIE, @@ -1532,6 +1542,10 @@ PinterestCollectionIE, PinterestIE, ) +from .piramidetv import ( + PiramideTVChannelIE, + PiramideTVIE, +) from .pixivsketch import ( PixivSketchIE, PixivSketchUserIE, @@ -1551,6 +1565,7 @@ PluralsightIE, ) from .plutotv import PlutoTVIE +from .plvideo import PlVideoIE from .podbayfm import ( PodbayFMChannelIE, PodbayFMIE, @@ -2354,10 +2369,6 @@ VimmIE, VimmRecordingIE, ) -from .vine import ( - VineIE, - VineUserIE, -) from .viously import ViouslyIE from .viqeo import ViqeoIE from .viu import ( diff --git a/yt_dlp/extractor/bilibili.py b/yt_dlp/extractor/bilibili.py index 2db951a60..33d9d92a0 100644 --- a/yt_dlp/extractor/bilibili.py +++ b/yt_dlp/extractor/bilibili.py @@ -32,6 +32,7 @@ parse_qs, parse_resolution, qualities, + sanitize_url, smuggle_url, srt_subtitles_timecode, str_or_none, @@ -1861,6 +1862,47 @@ def _real_extract(self, url): ie=BiliBiliIE.ie_key(), video_id=video_id) +class BiliBiliDynamicIE(InfoExtractor): + _VALID_URL = r'https?://(?:t\.bilibili\.com|(?:www\.)?bilibili\.com/opus)/(?P\d+)' + _TESTS = [{ + 'url': 'https://t.bilibili.com/998134289197432852', + 'info_dict': { + 'id': 'BV1TAmBYVEJr', + 'ext': 'mp4', + 'uploader_id': '1192648858', + 'comment_count': int, + '_old_archive_ids': ['bilibili 113457567568273_part1'], + 'thumbnail': 'http://i2.hdslb.com/bfs/archive/50091efd965d9f13ff6814f7ad374f90ab21e77d.jpg', + 'duration': 929.238, + 'upload_date': '20241110', + 'uploader': '何同学工作室', + 'like_count': int, + 'view_count': int, + 'title': '美国小朋友就玩这个?!何同学工作室11月开箱', + 'description': '本期产品信息:\n机器狗\n气味模拟器\nCloudboom Strike LS\n无弦吉他\n蓝牙磁带音箱\n神奇画板', + 'timestamp': 1731232800, + 'tags': list, + 'chapters': list, + }, + }] + + def _real_extract(self, url): + post_id = self._match_id(url) + # Without the newer chrome UA, the API will return an error (-352) + post_data = self._download_json( + 'https://api.bilibili.com/x/polymer/web-dynamic/v1/detail', post_id, + query={'id': post_id}, headers={ + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36', + }) + video_url = traverse_obj(post_data, ( + 'data', 'item', (None, 'orig'), 'modules', 'module_dynamic', + (('major', ('archive', 'pgc')), ('additional', ('reserve', 'common'))), + 'jump_url', {url_or_none}, any, {sanitize_url})) + if not video_url or (self.suitable(video_url) and post_id == self._match_id(video_url)): + raise ExtractorError('No valid video URL found', expected=True) + return self.url_result(video_url) + + class BiliIntlBaseIE(InfoExtractor): _API_URL = 'https://api.bilibili.tv/intl/gateway' _NETRC_MACHINE = 'biliintl' diff --git a/yt_dlp/extractor/bluesky.py b/yt_dlp/extractor/bluesky.py index 0e58a0932..42dadf7b9 100644 --- a/yt_dlp/extractor/bluesky.py +++ b/yt_dlp/extractor/bluesky.py @@ -88,7 +88,7 @@ class BlueskyIE(InfoExtractor): }, }, { 'url': 'https://bsky.app/profile/de1.pds.tentacle.expert/post/3l3w4tnezek2e', - 'md5': '1af9c7fda061cf7593bbffca89e43d1c', + 'md5': 'cc0110ed1f6b0247caac8234cc1e861d', 'info_dict': { 'id': '3l3w4tnezek2e', 'ext': 'mp4', @@ -133,6 +133,8 @@ class BlueskyIE(InfoExtractor): 'channel_follower_count': int, 'categories': ['Entertainment'], 'tags': [], + 'chapters': list, + 'heatmap': 'count:100', }, 'add_ie': ['Youtube'], }, { @@ -184,14 +186,14 @@ class BlueskyIE(InfoExtractor): }, }, }, { - 'url': 'https://bsky.app/profile/alt.bun.how/post/3l7rdfxhyds2f', + 'url': 'https://bsky.app/profile/cinny.bun.how/post/3l7rdfxhyds2f', 'md5': '8775118b235cf9fa6b5ad30f95cda75c', 'info_dict': { 'id': '3l7rdfxhyds2f', 'ext': 'mp4', 'uploader': 'cinnamon', - 'uploader_id': 'alt.bun.how', - 'uploader_url': 'https://bsky.app/profile/alt.bun.how', + 'uploader_id': 'cinny.bun.how', + 'uploader_url': 'https://bsky.app/profile/cinny.bun.how', 'channel_id': 'did:plc:7x6rtuenkuvxq3zsvffp2ide', 'channel_url': 'https://bsky.app/profile/did:plc:7x6rtuenkuvxq3zsvffp2ide', 'thumbnail': r're:https://video.bsky.app/watch/.*\.jpg$', @@ -341,6 +343,7 @@ def _extract_videos(self, root, video_id, embed_path='embed', record_path='recor formats.append({ 'format_id': 'blob', + 'quality': 1, 'url': update_url_query( self._BLOB_URL_TMPL.format(endpoint), {'did': did, 'cid': video_cid}), **traverse_obj(root, (*embed_path, 'aspectRatio', { diff --git a/yt_dlp/extractor/dropout.py b/yt_dlp/extractor/dropout.py index 7e97c4d40..a0d8aacdb 100644 --- a/yt_dlp/extractor/dropout.py +++ b/yt_dlp/extractor/dropout.py @@ -135,7 +135,7 @@ def _real_extract(self, url): self.raise_login_required(method='any') raise ExtractorError(login_err, expected=True) - embed_url = self._search_regex(r'embed_url:\s*["\'](.+?)["\']', webpage, 'embed url') + embed_url = self._html_search_regex(r'embed_url:\s*["\'](.+?)["\']', webpage, 'embed url') thumbnail = self._og_search_thumbnail(webpage) watch_info = get_element_by_id('watch-info', webpage) or '' diff --git a/yt_dlp/extractor/drtalks.py b/yt_dlp/extractor/drtalks.py new file mode 100644 index 000000000..5ea7f7580 --- /dev/null +++ b/yt_dlp/extractor/drtalks.py @@ -0,0 +1,51 @@ +from .brightcove import BrightcoveNewIE +from .common import InfoExtractor +from ..utils import url_or_none +from ..utils.traversal import traverse_obj + + +class DrTalksIE(InfoExtractor): + _VALID_URL = r'https?://(?:www\.)?drtalks\.com/videos/(?P[\w-]+)' + _TESTS = [{ + 'url': 'https://drtalks.com/videos/six-pillars-of-resilience-tools-for-managing-stress-and-flourishing/', + 'info_dict': { + 'id': '6366193757112', + 'ext': 'mp4', + 'uploader_id': '6314452011001', + 'tags': ['resilience'], + 'description': 'md5:9c6805aee237ee6de8052461855b9dda', + 'timestamp': 1734546659, + 'thumbnail': 'https://drtalks.com/wp-content/uploads/2024/12/Episode-82-Eva-Selhub-DrTalks-Thumbs.jpg', + 'title': 'Six Pillars of Resilience: Tools for Managing Stress and Flourishing', + 'duration': 2800.682, + 'upload_date': '20241218', + }, + }, { + 'url': 'https://drtalks.com/videos/the-pcos-puzzle-mastering-metabolic-health-with-marcelle-pick/', + 'info_dict': { + 'id': '6364699891112', + 'ext': 'mp4', + 'title': 'The PCOS Puzzle: Mastering Metabolic Health with Marcelle Pick', + 'description': 'md5:e87cbe00ca50135d5702787fc4043aaa', + 'thumbnail': 'https://drtalks.com/wp-content/uploads/2024/11/Episode-34-Marcelle-Pick-OBGYN-NP-DrTalks.jpg', + 'duration': 3515.2, + 'tags': ['pcos'], + 'upload_date': '20241114', + 'timestamp': 1731592119, + 'uploader_id': '6314452011001', + }, + }] + + def _real_extract(self, url): + video_id = self._match_id(url) + webpage = self._download_webpage(url, video_id) + next_data = self._search_nextjs_data(webpage, video_id)['props']['pageProps']['data']['video'] + + return self.url_result( + next_data['videos']['brightcoveVideoLink'], BrightcoveNewIE, video_id, + url_transparent=True, + **traverse_obj(next_data, { + 'title': ('title', {str}), + 'description': ('videos', 'summury', {str}), + 'thumbnail': ('featuredImage', 'node', 'sourceUrl', {url_or_none}), + })) diff --git a/yt_dlp/extractor/eggs.py b/yt_dlp/extractor/eggs.py new file mode 100644 index 000000000..6e032441c --- /dev/null +++ b/yt_dlp/extractor/eggs.py @@ -0,0 +1,155 @@ +import secrets + +from .common import InfoExtractor +from .youtube import YoutubeIE +from ..utils import ( + int_or_none, + parse_iso8601, + str_or_none, + url_or_none, +) +from ..utils.traversal import traverse_obj + + +class EggsBaseIE(InfoExtractor): + _API_HEADERS = { + 'Accept': '*/*', + 'apVersion': '8.2.00', + 'deviceName': 'Android', + } + + def _real_initialize(self): + self._API_HEADERS['deviceId'] = secrets.token_hex(8) + + def _call_api(self, endpoint, video_id): + return self._download_json( + f'https://app-front-api.eggs.mu/v1/{endpoint}', video_id, + headers=self._API_HEADERS) + + def _extract_music_info(self, data): + if yt_url := traverse_obj(data, ('youtubeUrl', {url_or_none})): + return self.url_result(yt_url, ie=YoutubeIE) + + artist_name = traverse_obj(data, ('artist', 'artistName', {str_or_none})) + music_id = traverse_obj(data, ('musicId', {str_or_none})) + webpage_url = None + if artist_name and music_id: + webpage_url = f'https://eggs.mu/artist/{artist_name}/song/{music_id}' + + return { + 'id': music_id, + 'vcodec': 'none', + 'webpage_url': webpage_url, + 'extractor_key': EggsIE.ie_key(), + 'extractor': EggsIE.IE_NAME, + **traverse_obj(data, { + 'title': ('musicTitle', {str}), + 'url': ('musicDataPath', {url_or_none}), + 'uploader': ('artist', 'displayName', {str}), + 'uploader_id': ('artist', 'artistId', {str_or_none}), + 'thumbnail': ('imageDataPath', {url_or_none}), + 'view_count': ('numberOfMusicPlays', {int_or_none}), + 'like_count': ('numberOfLikes', {int_or_none}), + 'comment_count': ('numberOfComments', {int_or_none}), + 'composers': ('composer', {str}, all), + 'tags': ('tags', ..., {str}), + 'timestamp': ('releaseDate', {parse_iso8601}), + 'artist': ('artist', 'displayName', {str}), + })} + + +class EggsIE(EggsBaseIE): + IE_NAME = 'eggs:single' + _VALID_URL = r'https?://eggs\.mu/artist/[^/?#]+/song/(?P[\da-f-]+)' + + _TESTS = [{ + 'url': 'https://eggs.mu/artist/32_sunny_girl/song/0e95fd1d-4d61-4d5b-8b18-6092c551da90', + 'info_dict': { + 'id': '0e95fd1d-4d61-4d5b-8b18-6092c551da90', + 'ext': 'm4a', + 'title': 'シネマと信号', + 'uploader': 'Sunny Girl', + 'thumbnail': r're:https?://.*\.jpg(?:\?.*)?$', + 'uploader_id': '1607', + 'like_count': int, + 'timestamp': 1731327327, + 'composers': ['橘高連太郎'], + 'view_count': int, + 'comment_count': int, + 'artists': ['Sunny Girl'], + 'upload_date': '20241111', + 'tags': ['SunnyGirl', 'シネマと信号'], + }, + }, { + 'url': 'https://eggs.mu/artist/KAMO_3pband/song/1d4bc45f-1af6-47a9-8b30-a70cae350b4f', + 'info_dict': { + 'id': '80cLKA2wnoA', + 'ext': 'mp4', + 'title': 'KAMO「いい女だから」Audio', + 'uploader': 'KAMO', + 'live_status': 'not_live', + 'channel_id': 'UCsHLBw2__5Q9y55skXPotOg', + 'channel_follower_count': int, + 'description': 'md5:d260da711ecbec3e720293dc11401b87', + 'availability': 'public', + 'uploader_id': '@KAMO_band', + 'upload_date': '20240925', + 'thumbnail': 'https://i.ytimg.com/vi/80cLKA2wnoA/maxresdefault.jpg', + 'comment_count': int, + 'channel_url': 'https://www.youtube.com/channel/UCsHLBw2__5Q9y55skXPotOg', + 'view_count': int, + 'duration': 151, + 'like_count': int, + 'channel': 'KAMO', + 'playable_in_embed': True, + 'uploader_url': 'https://www.youtube.com/@KAMO_band', + 'tags': [], + 'timestamp': 1727271121, + 'age_limit': 0, + 'categories': ['People & Blogs'], + }, + 'add_ie': ['Youtube'], + 'params': {'skip_download': 'Youtube'}, + }] + + def _real_extract(self, url): + song_id = self._match_id(url) + json_data = self._call_api(f'musics/{song_id}', song_id) + return self._extract_music_info(json_data) + + +class EggsArtistIE(EggsBaseIE): + IE_NAME = 'eggs:artist' + _VALID_URL = r'https?://eggs\.mu/artist/(?P\w+)/?(?:[?#&]|$)' + + _TESTS = [{ + 'url': 'https://eggs.mu/artist/32_sunny_girl', + 'info_dict': { + 'id': '32_sunny_girl', + 'thumbnail': 'https://image-pro.eggs.mu/profile/1607.jpeg?updated_at=2024-04-03T20%3A06%3A00%2B09%3A00', + 'description': 'Muddy Mine / 東京高田馬場CLUB PHASE / Gt.Vo 橘高 連太郎 / Ba.Cho 小野 ゆうき / Dr 大森 りゅうひこ', + 'title': 'Sunny Girl', + }, + 'playlist_mincount': 18, + }, { + 'url': 'https://eggs.mu/artist/KAMO_3pband', + 'info_dict': { + 'id': 'KAMO_3pband', + 'description': '川崎発3ピースバンド', + 'thumbnail': 'https://image-pro.eggs.mu/profile/35217.jpeg?updated_at=2024-11-27T16%3A31%3A50%2B09%3A00', + 'title': 'KAMO', + }, + 'playlist_mincount': 2, + }] + + def _real_extract(self, url): + artist_id = self._match_id(url) + artist_data = self._call_api(f'artists/{artist_id}', artist_id) + song_data = self._call_api(f'artists/{artist_id}/musics', artist_id) + return self.playlist_result( + traverse_obj(song_data, ('data', ..., {dict}, {self._extract_music_info})), + playlist_id=artist_id, **traverse_obj(artist_data, { + 'title': ('displayName', {str}), + 'description': ('profile', {str}), + 'thumbnail': ('imageDataPath', {url_or_none}), + })) diff --git a/yt_dlp/extractor/lbry.py b/yt_dlp/extractor/lbry.py index 0445b7cbf..7b22f90e9 100644 --- a/yt_dlp/extractor/lbry.py +++ b/yt_dlp/extractor/lbry.py @@ -310,7 +310,13 @@ def _real_extract(self, url): if stream_type in self._SUPPORTED_STREAM_TYPES: claim_id, is_live = result['claim_id'], False streaming_url = self._call_api_proxy( - 'get', claim_id, {'uri': uri}, 'streaming url')['streaming_url'] + 'get', claim_id, { + 'uri': uri, + **traverse_obj(parse_qs(url), { + 'signature': ('signature', 0), + 'signature_ts': ('signature_ts', 0), + }), + }, 'streaming url')['streaming_url'] # GET request to v3 API returns original video/audio file if available direct_url = re.sub(r'/api/v\d+/', '/api/v3/', streaming_url) diff --git a/yt_dlp/extractor/nest.py b/yt_dlp/extractor/nest.py new file mode 100644 index 000000000..3f8316b3e --- /dev/null +++ b/yt_dlp/extractor/nest.py @@ -0,0 +1,117 @@ +from .common import InfoExtractor +from ..utils import ExtractorError, float_or_none, update_url_query, url_or_none +from ..utils.traversal import traverse_obj + + +class NestIE(InfoExtractor): + _VALID_URL = r'https?://video\.nest\.com/(?:embedded/)?live/(?P\w+)' + _EMBED_REGEX = [rf'