Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package yt-dlp for openSUSE:Factory checked in at 2026-03-07 20:10:29 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/yt-dlp (Old) and /work/SRC/openSUSE:Factory/.yt-dlp.new.8177 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "yt-dlp" Sat Mar 7 20:10:29 2026 rev:88 rq:1337443 version:2026.03.03 Changes: -------- --- /work/SRC/openSUSE:Factory/yt-dlp/yt-dlp.changes 2026-02-23 16:12:32.813334740 +0100 +++ /work/SRC/openSUSE:Factory/.yt-dlp.new.8177/yt-dlp.changes 2026-03-07 20:15:21.981359849 +0100 @@ -1,0 +2,11 @@ +Wed Mar 4 10:03:21 UTC 2026 - Luigi Baldoni <[email protected]> + +- Update to version 2026.03.03 + * Extractor changes: + * aenetworks: Fix extraction + * patreon: Fix extractors + * thechosen: Rework extractor + * yt: Skip webpage player response by default + * zapiks: Improve extraction + +------------------------------------------------------------------- ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ yt-dlp.spec ++++++ --- /var/tmp/diff_new_pack.uDtNQT/_old 2026-03-07 20:15:23.221411146 +0100 +++ /var/tmp/diff_new_pack.uDtNQT/_new 2026-03-07 20:15:23.225411312 +0100 @@ -27,7 +27,7 @@ %endif Name: yt-dlp -Version: 2026.02.21 +Version: 2026.03.03 %define ejsver 0.5.0 Release: 0 Summary: Enhanced fork of youtube-dl, a video site downloader for offline watching ++++++ _scmsync.obsinfo ++++++ --- /var/tmp/diff_new_pack.uDtNQT/_old 2026-03-07 20:15:23.353416607 +0100 +++ /var/tmp/diff_new_pack.uDtNQT/_new 2026-03-07 20:15:23.361416937 +0100 @@ -1,5 +1,5 @@ -mtime: 1771719307 -commit: e4f319d809018843c24aecdea617a010b3d19b3b3e0a38a5f136450ae06cbe92 +mtime: 1772648117 +commit: ffb0e8fa4812abe13422a4a47afd3325fdddc1ac324dbbaa88d69cc060b69dc9 url: https://src.opensuse.org/jengelh/yt-dlp revision: master ++++++ build.specials.obscpio ++++++ ++++++ build.specials.obscpio ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/.gitignore new/.gitignore --- old/.gitignore 1970-01-01 01:00:00.000000000 +0100 +++ new/.gitignore 2026-03-04 19:16:21.000000000 +0100 @@ -0,0 +1 @@ +.osc ++++++ yt-dlp.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yt-dlp/Changelog.md new/yt-dlp/Changelog.md --- old/yt-dlp/Changelog.md 2026-02-21 21:22:55.000000000 +0100 +++ new/yt-dlp/Changelog.md 2026-03-03 17:37:33.000000000 +0100 @@ -4,6 +4,17 @@ # To create a release, dispatch the https://github.com/yt-dlp/yt-dlp/actions/workflows/release.yml workflow on master --> +### 2026.03.03 + +#### Extractor changes +- **aenetworks**: [Fix extraction](https://github.com/yt-dlp/yt-dlp/commit/338dbebdb8627a95bd8f72ed86fdc2d50c8e2d14) ([#16036](https://github.com/yt-dlp/yt-dlp/issues/16036)) by [bashonly](https://github.com/bashonly) +- **patreon**: [Fix extractors](https://github.com/yt-dlp/yt-dlp/commit/bf4dfffe0164385c29a2dcb0367110babe4d4f27) ([#16112](https://github.com/yt-dlp/yt-dlp/issues/16112)) by [bashonly](https://github.com/bashonly) +- **thechosen**: [Rework extractor](https://github.com/yt-dlp/yt-dlp/commit/e3118604aa99a5514342d6a002c9b4a3fe1235b4) ([#16021](https://github.com/yt-dlp/yt-dlp/issues/16021)) by [0xvd](https://github.com/0xvd) +- **youtube** + - [Force player `9f4cc5e4`](https://github.com/yt-dlp/yt-dlp/commit/d3165e83ffc0088eef5e594927ea9ac99a6e2ce6) ([#16123](https://github.com/yt-dlp/yt-dlp/issues/16123)) by [bashonly](https://github.com/bashonly) + - [Skip webpage player response by default](https://github.com/yt-dlp/yt-dlp/commit/2ecc4c3bc300701d85e2cbaeb2b28a921a68f0f0) ([#16126](https://github.com/yt-dlp/yt-dlp/issues/16126)) by [bashonly](https://github.com/bashonly) +- **zapiks**: [Improve extraction](https://github.com/yt-dlp/yt-dlp/commit/6f796a2bff332f72c3f250207cdf10db852f6016) ([#16030](https://github.com/yt-dlp/yt-dlp/issues/16030)) by [doe1080](https://github.com/doe1080) + ### 2026.02.21 #### Important changes diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yt-dlp/README.md new/yt-dlp/README.md --- old/yt-dlp/README.md 2026-02-21 21:23:01.000000000 +0100 +++ new/yt-dlp/README.md 2026-03-03 17:37:39.000000000 +0100 @@ -1862,10 +1862,10 @@ * `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 currently available clients are `web`, `web_safari`, `web_embedded`, `web_music`, `web_creator`, `mweb`, `ios`, `android`, `android_vr`, `tv`, `tv_downgraded`, and `tv_simply`. By default, `android_vr,web,web_safari` is used. If no JavaScript runtime/engine is available, then only `android_vr` is used. If logged-in cookies are passed to yt-dlp, then `tv_downgraded,web,web_safari` is used for free accounts and `tv_downgraded,web_creator,web` is used for premium accounts. The `web_music` client is added for `music.youtube.com` URLs when logged-in cookies are used. The `web_embedded` client is added for age-restricted videos but only successfully works around the age-restriction sometimes (e.g. if the video is embeddable), and may be added as a fallback if `android_vr` is unable to access a video. The `web_creator` client is added for age-restricted videos if account age-verification is required. Some clients, such as `web_crea tor` and `web_music`, require a `po_token` for their formats to be downloadable. Some clients, such as `web_creator`, 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,-web` * `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), `initial_data` (skip initial data/next ep request). While these options can help reduce the number of requests needed or avoid some rate-limiting, they could cause issues such as missing formats or metadata. See [#860](https://github.com/yt-dlp/yt-dlp/pull/860) and [#12826](https://github.com/yt-dlp/yt-dlp/issues/12826) for more details -* `webpage_skip`: Skip extraction of embedded webpage data. One or both of `player_response`, `initial_data`. These options are for testing purposes and don't skip any network requests +* `webpage_skip`: Skip extraction of embedded webpage data. One or both of `player_response`, `initial_data`. Using these will not skip any network requests, and in some cases will result in additional network requests. Currently, the default is `player_response`; however, typically these are for testing purposes only * `player_params`: YouTube player parameters to use for player requests. Will overwrite any default ones set by yt-dlp. * `player_js_variant`: The player javascript variant to use for n/sig deciphering. The known variants are: `main`, `tcc`, `tce`, `es5`, `es6`, `es6_tcc`, `es6_tce`, `tv`, `tv_es6`, `phone`, `house`. The default is `tv`, and the others are for debugging purposes. You can use `actual` to go with what is prescribed by the site -* `player_js_version`: The player javascript version to use for n/sig deciphering, in the format of `signature_timestamp@hash` (e.g. `20348@0004de42`). The default is to use what is prescribed by the site, and can be selected with `actual` +* `player_js_version`: The player javascript version to use for n/sig deciphering, in the format of `signature_timestamp@hash` (e.g. `20348@0004de42`). Currently, the default is to force `20514@9f4cc5e4`. You can use `actual` to go with what is prescribed by the site * `comment_sort`: `top` or `new` (default) - choose comment sorting mode (on YouTube's side) * `max_comments`: Limit the amount of comments to gather. Comma-separated list of integers representing `max-comments,max-parents,max-replies,max-replies-per-thread,max-depth`. Default is `all,all,all,all,all` * A `max-depth` value of `1` will discard all replies, regardless of the `max-replies` or `max-replies-per-thread` values given diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yt-dlp/README.txt new/yt-dlp/README.txt --- old/yt-dlp/README.txt 2026-02-21 21:23:02.000000000 +0100 +++ new/yt-dlp/README.txt 2026-03-03 17:37:40.000000000 +0100 @@ -2339,8 +2339,10 @@ they could cause issues such as missing formats or metadata. See #860 and #12826 for more details - webpage_skip: Skip extraction of embedded webpage data. One or both - of player_response, initial_data. These options are for testing - purposes and don't skip any network requests + of player_response, initial_data. Using these will not skip any + network requests, and in some cases will result in additional + network requests. Currently, the default is player_response; + however, typically these are for testing purposes only - player_params: YouTube player parameters to use for player requests. Will overwrite any default ones set by yt-dlp. - player_js_variant: The player javascript variant to use for n/sig @@ -2350,8 +2352,8 @@ what is prescribed by the site - player_js_version: The player javascript version to use for n/sig deciphering, in the format of signature_timestamp@hash (e.g. - 20348@0004de42). The default is to use what is prescribed by the - site, and can be selected with actual + 20348@0004de42). Currently, the default is to force 20514@9f4cc5e4. + You can use actual to go with what is prescribed by the site - comment_sort: top or new (default) - choose comment sorting mode (on YouTube's side) - max_comments: Limit the amount of comments to gather. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yt-dlp/supportedsites.md new/yt-dlp/supportedsites.md --- old/yt-dlp/supportedsites.md 2026-02-21 21:23:01.000000000 +0100 +++ new/yt-dlp/supportedsites.md 2026-03-03 17:37:39.000000000 +0100 @@ -1473,7 +1473,7 @@ - **theatercomplextown:ppv**: [*theatercomplextown*](## "netrc machine") - **theatercomplextown:vod**: [*theatercomplextown*](## "netrc machine") - **TheChosen** - - **TheChosenGroup** + - **TheChosenGroup**: (**Currently broken**) - **TheGuardianPodcast** - **TheGuardianPodcastPlaylist** - **TheHighWire** Binary files old/yt-dlp/yt-dlp and new/yt-dlp/yt-dlp differ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yt-dlp/yt-dlp.1 new/yt-dlp/yt-dlp.1 --- old/yt-dlp/yt-dlp.1 2026-02-21 21:23:03.000000000 +0100 +++ new/yt-dlp/yt-dlp.1 2026-03-03 17:37:41.000000000 +0100 @@ -2752,8 +2752,10 @@ .IP \[bu] 2 \f[V]webpage_skip\f[R]: Skip extraction of embedded webpage data. One or both of \f[V]player_response\f[R], \f[V]initial_data\f[R]. -These options are for testing purposes and don\[aq]t skip any network -requests +Using these will not skip any network requests, and in some cases will +result in additional network requests. +Currently, the default is \f[V]player_response\f[R]; however, typically +these are for testing purposes only .IP \[bu] 2 \f[V]player_params\f[R]: YouTube player parameters to use for player requests. @@ -2771,8 +2773,8 @@ n/sig deciphering, in the format of \f[V]signature_timestamp\[at]hash\f[R] (e.g. \f[V]20348\[at]0004de42\f[R]). -The default is to use what is prescribed by the site, and can be -selected with \f[V]actual\f[R] +Currently, the default is to force \f[V]20514\[at]9f4cc5e4\f[R]. +You can use \f[V]actual\f[R] to go with what is prescribed by the site .IP \[bu] 2 \f[V]comment_sort\f[R]: \f[V]top\f[R] or \f[V]new\f[R] (default) - choose comment sorting mode (on YouTube\[aq]s side) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yt-dlp/yt_dlp/extractor/_extractors.py new/yt-dlp/yt_dlp/extractor/_extractors.py --- old/yt-dlp/yt_dlp/extractor/_extractors.py 2026-02-21 21:22:46.000000000 +0100 +++ new/yt-dlp/yt_dlp/extractor/_extractors.py 2026-03-03 17:37:22.000000000 +0100 @@ -672,10 +672,6 @@ FrontendMastersIE, FrontendMastersLessonIE, ) -from .frontro import ( - TheChosenGroupIE, - TheChosenIE, -) from .fujitv import FujiTVFODPlus7IE from .funk import FunkIE from .funker530 import Funker530IE @@ -2063,6 +2059,10 @@ from .testurl import TestURLIE from .tf1 import TF1IE from .tfo import TFOIE +from .thechosen import ( + TheChosenGroupIE, + TheChosenIE, +) from .theguardian import ( TheGuardianPodcastIE, TheGuardianPodcastPlaylistIE, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yt-dlp/yt_dlp/extractor/aenetworks.py new/yt-dlp/yt_dlp/extractor/aenetworks.py --- old/yt-dlp/yt_dlp/extractor/aenetworks.py 2026-02-21 21:22:46.000000000 +0100 +++ new/yt-dlp/yt_dlp/extractor/aenetworks.py 2026-03-03 17:37:22.000000000 +0100 @@ -91,8 +91,8 @@ if filter_key == 'canonical': webpage = self._download_webpage(url, filter_value) graphql_video_id = self._search_regex( - r'<meta\b[^>]+\bcontent="[^"]*\btpid/(\d+)"', webpage, - 'id') or self._html_search_meta('videoId', webpage, 'GraphQL video ID', fatal=True) + r'<meta\b[^>]+\bcontent="[^"]*\btpid/(\d+)"', webpage, 'id', + default=None) or self._html_search_meta('videoId', webpage, 'GraphQL video ID', fatal=True) else: graphql_video_id = filter_value diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yt-dlp/yt_dlp/extractor/frontro.py new/yt-dlp/yt_dlp/extractor/frontro.py --- old/yt-dlp/yt_dlp/extractor/frontro.py 2026-02-21 21:22:47.000000000 +0100 +++ new/yt-dlp/yt_dlp/extractor/frontro.py 2026-03-03 17:37:22.000000000 +0100 @@ -99,66 +99,3 @@ 'modified_timestamp': ('updatedAt', {parse_iso8601}), }), } - - -class TheChosenIE(FrontroVideoBaseIE): - _CHANNEL_ID = '12884901895' - - _VALID_URL = r'https?://(?:www\.)?watch\.thechosen\.tv/watch/(?P<id>[0-9]+)' - _TESTS = [{ - 'url': 'https://watch.thechosen.tv/watch/184683594325', - 'md5': '3f878b689588c71b38ec9943c54ff5b0', - 'info_dict': { - 'id': '184683594325', - 'ext': 'mp4', - 'title': 'Season 3 Episode 2: Two by Two', - 'description': 'md5:174c373756ecc8df46b403f4fcfbaf8c', - 'comment_count': int, - 'view_count': int, - 'like_count': int, - 'duration': 4212, - 'thumbnail': r're:https://fastly\.frontrowcdn\.com/channels/12884901895/VIDEO_THUMBNAIL/184683594325/', - 'timestamp': 1698954546, - 'upload_date': '20231102', - 'modified_timestamp': int, - 'modified_date': str, - }, - }, { - 'url': 'https://watch.thechosen.tv/watch/184683596189', - 'md5': 'd581562f9d29ce82f5b7770415334151', - 'info_dict': { - 'id': '184683596189', - 'ext': 'mp4', - 'title': 'Season 4 Episode 8: Humble', - 'description': 'md5:20a57bead43da1cf77cd5b0fe29bbc76', - 'comment_count': int, - 'view_count': int, - 'like_count': int, - 'duration': 5092, - 'thumbnail': r're:https://fastly\.frontrowcdn\.com/channels/12884901895/VIDEO_THUMBNAIL/184683596189/', - 'timestamp': 1715019474, - 'upload_date': '20240506', - 'modified_timestamp': int, - 'modified_date': str, - }, - }] - - -class TheChosenGroupIE(FrontroGroupBaseIE): - _CHANNEL_ID = '12884901895' - _VIDEO_EXTRACTOR = TheChosenIE - _VIDEO_URL_TMPL = 'https://watch.thechosen.tv/watch/%s' - - _VALID_URL = r'https?://(?:www\.)?watch\.thechosen\.tv/group/(?P<id>[0-9]+)' - _TESTS = [{ - 'url': 'https://watch.thechosen.tv/group/309237658592', - 'info_dict': { - 'id': '309237658592', - 'title': 'Season 3', - 'timestamp': 1746203969, - 'upload_date': '20250502', - 'modified_timestamp': int, - 'modified_date': str, - }, - 'playlist_count': 8, - }] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yt-dlp/yt_dlp/extractor/lazy_extractors.py new/yt-dlp/yt_dlp/extractor/lazy_extractors.py --- old/yt-dlp/yt_dlp/extractor/lazy_extractors.py 2026-02-21 21:22:58.000000000 +0100 +++ new/yt-dlp/yt_dlp/extractor/lazy_extractors.py 2026-03-03 17:37:35.000000000 +0100 @@ -12948,21 +12948,17 @@ class TheChosenGroupIE(FrontroGroupBaseIE): - _module = 'yt_dlp.extractor.frontro' + _module = 'yt_dlp.extractor.thechosen' IE_NAME = 'TheChosenGroup' _VALID_URL = 'https?://(?:www\\.)?watch\\.thechosen\\.tv/group/(?P<id>[0-9]+)' + _WORKING = False _RETURN_TYPE = 'playlist' -class FrontroVideoBaseIE(FrontoBaseIE): - _module = 'yt_dlp.extractor.frontro' - IE_NAME = 'FrontroVideoBase' - - -class TheChosenIE(FrontroVideoBaseIE): - _module = 'yt_dlp.extractor.frontro' +class TheChosenIE(LazyLoadExtractor): + _module = 'yt_dlp.extractor.thechosen' IE_NAME = 'TheChosen' - _VALID_URL = 'https?://(?:www\\.)?watch\\.thechosen\\.tv/watch/(?P<id>[0-9]+)' + _VALID_URL = 'https?://(?:www\\.)?watch\\.thechosen\\.tv/(?:video|watch)/(?P<id>[0-9]+)' _RETURN_TYPE = 'video' @@ -15701,7 +15697,7 @@ class ZapiksIE(LazyLoadExtractor): _module = 'yt_dlp.extractor.zapiks' IE_NAME = 'Zapiks' - _VALID_URL = 'https?://(?:www\\.)?zapiks\\.(?:fr|com)/(?:(?:[a-z]{2}/)?(?P<display_id>.+?)\\.html|index\\.php\\?.*\\bmedia_id=(?P<id>\\d+))' + _VALID_URL = ['https?://(?:www\\.)?zapiks\\.(?:com|fr)/(?P<id>[\\w-]+)\\.html', 'https?://(?:www\\.)?zapiks\\.fr/index\\.php\\?(?:[^#]+&)?media_id=(?P<id>\\d+)'] _RETURN_TYPE = 'video' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yt-dlp/yt_dlp/extractor/patreon.py new/yt-dlp/yt_dlp/extractor/patreon.py --- old/yt-dlp/yt_dlp/extractor/patreon.py 2026-02-21 21:22:47.000000000 +0100 +++ new/yt-dlp/yt_dlp/extractor/patreon.py 2026-03-03 17:37:22.000000000 +0100 @@ -25,6 +25,7 @@ find_elements, require, traverse_obj, + trim_str, value, ) @@ -32,16 +33,15 @@ class PatreonBaseIE(InfoExtractor): @functools.cached_property def patreon_user_agent(self): - # Patreon mobile UA is needed to avoid triggering Cloudflare anti-bot protection. - # Newer UA yields higher res m3u8 formats for locked posts, but gives 401 if not logged-in + # Patreon mobile UA yields higher res m3u8 for locked posts, but gives 401 if not logged-in if self._get_cookies('https://www.patreon.com/').get('session_id'): - return 'Patreon/72.2.28 (Android; Android 14; Scale/2.10)' - return 'Patreon/7.6.28 (Android; Android 11; Scale/2.10)' + return 'Patreon/126.9.0.15 (Android; Android 14; Scale/2.10)' + return None def _call_api(self, ep, item_id, query=None, headers=None, fatal=True, note=None): if headers is None: headers = {} - if 'User-Agent' not in headers: + if 'User-Agent' not in headers and self.patreon_user_agent: headers['User-Agent'] = self.patreon_user_agent if query: query.update({'json-api-version': 1.0}) @@ -50,7 +50,9 @@ return self._download_json( f'https://www.patreon.com/api/{ep}', item_id, note=note if note else 'Downloading API JSON', - query=query, fatal=fatal, headers=headers) + query=query, fatal=fatal, headers=headers, + # If not using Patreon mobile UA, we need impersonation due to Cloudflare + impersonate=not self.patreon_user_agent) except ExtractorError as e: if not isinstance(e.cause, HTTPError) or mimetype2ext(e.cause.response.headers.get('Content-Type')) != 'json': raise @@ -623,14 +625,13 @@ 'info_dict': { 'id': '9631148', 'title': 'Anything Else?', - 'description': 'md5:2ee1db4aed2f9460c2b295825a24aa08', + 'description': 'md5:b2f20eec4cb5520d9a4be4971f28add5', 'uploader': 'dan ', 'uploader_id': '13852412', 'uploader_url': 'https://www.patreon.com/anythingelse', 'channel': 'Anything Else?', 'channel_id': '9631148', 'channel_url': 'https://www.patreon.com/anythingelse', - 'channel_follower_count': int, 'age_limit': 0, 'thumbnail': r're:https?://.+/.+', }, @@ -675,16 +676,15 @@ break def _real_extract(self, url): - campaign_id, vanity = self._match_valid_url(url).group('campaign_id', 'vanity') if campaign_id is None: - webpage = self._download_webpage(url, vanity, headers={'User-Agent': self.patreon_user_agent}) - campaign_id = traverse_obj(self._search_nextjs_data(webpage, vanity, default=None), ( - 'props', 'pageProps', 'bootstrapEnvelope', 'pageBootstrap', 'campaign', 'data', 'id', {str})) - if not campaign_id: - campaign_id = traverse_obj(self._search_nextjs_v13_data(webpage, vanity), ( - ((..., 'value', 'campaign', 'data'), lambda _, v: v['type'] == 'campaign'), - 'id', {str}, any, {require('campaign ID')})) + results = self._call_api('search', vanity, query={ + 'q': vanity, + 'page[size]': '5', + })['data'] + campaign_id = traverse_obj(results, ( + lambda _, v: v['type'] == 'campaign-document' and v['attributes']['url'].lower().endswith(f'/{vanity.lower()}'), + 'id', {trim_str(start='campaign_')}, filter, any, {require('campaign ID')})) params = { 'json-api-use-default-includes': 'false', diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yt-dlp/yt_dlp/extractor/thechosen.py new/yt-dlp/yt_dlp/extractor/thechosen.py --- old/yt-dlp/yt_dlp/extractor/thechosen.py 1970-01-01 01:00:00.000000000 +0100 +++ new/yt-dlp/yt_dlp/extractor/thechosen.py 2026-03-03 17:37:22.000000000 +0100 @@ -0,0 +1,118 @@ +from .common import InfoExtractor +from .frontro import FrontroGroupBaseIE +from ..utils import ( + determine_ext, + int_or_none, + url_or_none, +) +from ..utils.traversal import traverse_obj + + +class TheChosenIE(InfoExtractor): + _VALID_URL = r'https?://(?:www\.)?watch\.thechosen\.tv/(?:video|watch)/(?P<id>[0-9]+)' + _TESTS = [{ + 'url': 'https://watch.thechosen.tv/video/184683594325', + 'md5': '3f878b689588c71b38ec9943c54ff5b0', + 'info_dict': { + 'id': '184683594325', + 'ext': 'mp4', + 'title': 'Season 3 Episode 2: Two by Two', + 'description': 'md5:174c373756ecc8df46b403f4fcfbaf8c', + 'duration': 4212, + 'thumbnail': 'https://cas.global.ssl.fastly.net/hls-10-4/184683594325/thumbnail.png', + }, + 'params': {'skip_download': 'm3u8'}, + }, { + 'url': 'https://watch.thechosen.tv/video/184683596189', + 'md5': 'd581562f9d29ce82f5b7770415334151', + 'info_dict': { + 'id': '184683596189', + 'ext': 'mp4', + 'title': 'Season 4 Episode 8: Humble', + 'description': 'md5:20a57bead43da1cf77cd5b0fe29bbc76', + 'duration': 5092, + 'thumbnail': 'https://cdn.thechosen.media/videos/cmkvu7nn500nhfm0wpgmm6180/thumbnail.jpg', + }, + 'params': {'skip_download': 'm3u8'}, + }, { + 'url': 'https://watch.thechosen.tv/video/184683621748', + 'info_dict': { + 'id': '184683621748', + 'ext': 'mp4', + 'title': 'Season 5 Episode 2: House of Cards', + 'description': 'md5:55b389cbb4b7a01d8c2d837102905617', + 'duration': 3086, + 'thumbnail': 'https://cdn.thechosen.media/videos/cmkolt4el000afd5zd6x0aeph/thumbnail.jpg', + }, + 'params': {'skip_download': 'm3u8'}, + }, { + 'url': 'https://watch.thechosen.tv/video/184683621750', + 'info_dict': { + 'id': '184683621750', + 'ext': 'mp4', + 'title': 'Season 5 Episode 3: Woes', + 'description': 'md5:90ca3cc41316a965fd1cd3d5b3458784', + 'duration': 3519, + 'thumbnail': 'https://cdn.thechosen.media/videos/cmkoltsl8000dfd5z3luid3mg/thumbnail.jpg', + }, + 'params': {'skip_download': 'm3u8'}, + }] + + def _real_extract(self, url): + video_id = self._match_id(url) + metadata = self._download_json(f'https://api.watch.thechosen.tv/v1/videos/{video_id}', video_id) + + formats, subtitles = [], {} + for fmt_url in traverse_obj(metadata, ('details', 'video', ..., 'url', {url_or_none})): + ext = determine_ext(fmt_url) + if ext == 'm3u8': + fmts, subs = self._extract_m3u8_formats_and_subtitles(fmt_url, video_id, 'mp4', fatal=False) + elif ext == 'mpd': + fmts, subs = self._extract_mpd_formats_and_subtitles(fmt_url, video_id, fatal=False) + else: + self.report_warning(f'Skipping unsupported format extension "{ext}"', video_id=video_id) + continue + formats.extend(fmts) + self._merge_subtitles(subs, target=subtitles) + + thumbnails = [] + for thumb_id, thumb_url in traverse_obj(metadata, ( + ('thumbs', 'thumbnails'), {dict.items}, lambda _, v: url_or_none(v[1]), + )): + thumbnails.append({ + 'id': thumb_id, + 'url': thumb_url, + }) + + return { + 'id': video_id, + **traverse_obj(metadata, ({ + 'title': ('title', {str}), + 'description': ('description', {str}), + 'duration': ('duration', {int_or_none}), + })), + 'thumbnails': thumbnails, + 'formats': formats, + 'subtitles': subtitles, + } + + +class TheChosenGroupIE(FrontroGroupBaseIE): + _WORKING = False + _CHANNEL_ID = '12884901895' + _VIDEO_EXTRACTOR = TheChosenIE + _VIDEO_URL_TMPL = 'https://watch.thechosen.tv/watch/%s' + + _VALID_URL = r'https?://(?:www\.)?watch\.thechosen\.tv/group/(?P<id>[0-9]+)' + _TESTS = [{ + 'url': 'https://watch.thechosen.tv/group/309237658592', + 'info_dict': { + 'id': '309237658592', + 'title': 'Season 3', + 'timestamp': 1746203969, + 'upload_date': '20250502', + 'modified_timestamp': int, + 'modified_date': str, + }, + 'playlist_count': 8, + }] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yt-dlp/yt_dlp/extractor/youtube/_video.py new/yt-dlp/yt_dlp/extractor/youtube/_video.py --- old/yt-dlp/yt_dlp/extractor/youtube/_video.py 2026-02-21 21:22:47.000000000 +0100 +++ new/yt-dlp/yt_dlp/extractor/youtube/_video.py 2026-03-03 17:37:22.000000000 +0100 @@ -1873,7 +1873,12 @@ 'params': {'skip_download': True}, }] - _DEFAULT_PLAYER_JS_VERSION = 'actual' + @property + def _skipped_webpage_data(self): + # XXX: player_response as a default is a TEMPORARY workaround for pinning _DEFAULT_PLAYER_JS_VERSION + return self._configuration_arg('webpage_skip', default=['player_response']) + + _DEFAULT_PLAYER_JS_VERSION = '20514@9f4cc5e4' _DEFAULT_PLAYER_JS_VARIANT = 'tv' _PLAYER_JS_VARIANT_MAP = { 'main': 'player_ias.vflset/en_US/base.js', @@ -3044,7 +3049,7 @@ tried_iframe_fallback = True pr = None - if client == webpage_client and 'player_response' not in self._configuration_arg('webpage_skip'): + if client == webpage_client and 'player_response' not in self._skipped_webpage_data: pr = initial_pr visitor_data = visitor_data or self._extract_visitor_data(webpage_ytcfg, initial_pr, player_ytcfg) @@ -3827,7 +3832,7 @@ def _download_initial_data(self, video_id, webpage, webpage_client, webpage_ytcfg): initial_data = None - if webpage and 'initial_data' not in self._configuration_arg('webpage_skip'): + if webpage and 'initial_data' not in self._skipped_webpage_data: initial_data = self.extract_yt_initial_data(video_id, webpage, fatal=False) if not traverse_obj(initial_data, 'contents'): self.report_warning('Incomplete data received in embedded initial data; re-fetching using API.') diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yt-dlp/yt_dlp/extractor/zapiks.py new/yt-dlp/yt_dlp/extractor/zapiks.py --- old/yt-dlp/yt_dlp/extractor/zapiks.py 2026-02-21 21:22:47.000000000 +0100 +++ new/yt-dlp/yt_dlp/extractor/zapiks.py 2026-03-03 17:37:22.000000000 +0100 @@ -1,110 +1,205 @@ +import json import re +import urllib.parse from .common import InfoExtractor from ..utils import ( + clean_html, + extract_attributes, int_or_none, parse_duration, - parse_iso8601, - xpath_text, - xpath_with_ns, + parse_resolution, + str_or_none, + unified_timestamp, + url_or_none, +) +from ..utils.traversal import ( + find_element, + find_elements, + traverse_obj, ) class ZapiksIE(InfoExtractor): - _VALID_URL = r'https?://(?:www\.)?zapiks\.(?:fr|com)/(?:(?:[a-z]{2}/)?(?P<display_id>.+?)\.html|index\.php\?.*\bmedia_id=(?P<id>\d+))' - _EMBED_REGEX = [r'<iframe[^>]+src="(?P<url>https?://(?:www\.)?zapiks\.fr/index\.php\?.+?)"'] + _VALID_URL = [ + r'https?://(?:www\.)?zapiks\.(?:com|fr)/(?P<id>[\w-]+)\.html', + r'https?://(?:www\.)?zapiks\.fr/index\.php\?(?:[^#]+&)?media_id=(?P<id>\d+)', + ] + _EMBED_REGEX = [r'<iframe\b[^>]+\bsrc=["\'](?P<url>(?:https?:)?//(?:www\.)?zapiks\.fr/index\.php\?(?:[^#"\']+&(?:amp;)?)?media_id=\d+)'] _TESTS = [{ - 'url': 'http://www.zapiks.fr/ep2s3-bon-appetit-eh-be-viva.html', + 'url': 'https://www.zapiks.fr/ep2s3-bon-appetit-eh-be-viva.html', 'md5': 'aeb3c473b2d564b2d46d664d28d5f050', 'info_dict': { 'id': '80798', 'ext': 'mp4', 'title': 'EP2S3 - Bon Appétit - Eh bé viva les pyrénées con!', - 'description': 'md5:7054d6f6f620c6519be1fe710d4da847', - 'thumbnail': r're:https?://zpks\.com/.+\.jpg', + 'description': 'md5:db07a553c1550e2905bceafa923000fd', + 'display_id': 'ep2s3-bon-appetit-eh-be-viva', 'duration': 528, + 'tags': 'count:5', + 'thumbnail': r're:https?://zpks\.com/.+', 'timestamp': 1359044972, 'upload_date': '20130124', + 'uploader': 'BonAppetit', + 'uploader_id': 'bonappetit', 'view_count': int, }, }, { - 'url': 'http://www.zapiks.com/ep3s5-bon-appetit-baqueira-m-1.html', - 'only_matching': True, + 'url': 'https://www.zapiks.com/ep3s5-bon-appetit-baqueira-m-1.html', + 'md5': '196fe42901639d868956b1dcaa48de15', + 'info_dict': { + 'id': '118046', + 'ext': 'mp4', + 'title': 'EP3S5 - Bon Appétit - Baqueira Mi Corazon !', + 'display_id': 'ep3s5-bon-appetit-baqueira-m-1', + 'duration': 642, + 'tags': 'count:8', + 'thumbnail': r're:https?://zpks\.com/.+', + 'timestamp': 1424370543, + 'upload_date': '20150219', + 'uploader': 'BonAppetit', + 'uploader_id': 'bonappetit', + 'view_count': int, + }, }, { - 'url': 'http://www.zapiks.com/nl/ep3s5-bon-appetit-baqueira-m-1.html', - 'only_matching': True, + 'url': 'https://www.zapiks.fr/index.php?action=playerIframe&media_id=164049', + 'md5': 'fb81a7c9b7b84c00ba111028aee593b8', + 'info_dict': { + 'id': '164049', + 'ext': 'mp4', + 'title': 'Courchevel Hiver 2025/2026', + 'display_id': 'courchevel-hiver-2025-2026', + 'duration': 38, + 'tags': 'count:1', + 'thumbnail': r're:https?://zpks\.com/.+', + 'timestamp': 1769019147, + 'upload_date': '20260121', + 'uploader': 'jamrek', + 'uploader_id': 'jamrek', + 'view_count': int, + }, }, { - 'url': 'http://www.zapiks.fr/index.php?action=playerIframe&media_id=118046&width=640&height=360&autoStart=false&language=fr', - 'only_matching': True, + # https://www.youtube.com/watch?v=UBAABvegu2M + 'url': 'https://www.zapiks.com/live-fwt18-vallnord-arcalis-.html', + 'info_dict': { + 'id': 'UBAABvegu2M', + 'ext': 'mp4', + 'title': 'Replay Live - FWT18 Vallnord-Arcalís Andorra - Freeride World Tour 2018', + 'age_limit': 0, + 'availability': 'public', + 'categories': ['Sports'], + 'channel': 'FIS Freeride World Tour by Peak Performance', + 'channel_follower_count': int, + 'channel_id': 'UCraJ3GNFfw6LXFuCV6McByg', + 'channel_url': 'https://www.youtube.com/channel/UCraJ3GNFfw6LXFuCV6McByg', + 'comment_count': int, + 'description': 'md5:2d9fefef758d5ad0d5a987d46aff7572', + 'duration': 11328, + 'heatmap': 'count:100', + 'like_count': int, + 'live_status': 'was_live', + 'media_type': 'livestream', + 'playable_in_embed': True, + 'release_date': '20180306', + 'release_timestamp': 1520321809, + 'tags': 'count:27', + 'thumbnail': r're:https?://i\.ytimg\.com/.+', + 'timestamp': 1520336958, + 'upload_date': '20180306', + 'uploader': 'FIS Freeride World Tour by Peak Performance', + 'uploader_id': '@FISFreerideWorldTour', + 'uploader_url': 'https://www.youtube.com/@FISFreerideWorldTour', + 'view_count': int, + }, + 'add_ie': ['Youtube'], + }, { + # https://vimeo.com/235746460 + 'url': 'https://www.zapiks.fr/waking-dream-2017-full-movie.html', + 'info_dict': { + 'id': '235746460', + 'ext': 'mp4', + 'title': '"WAKING DREAM" (2017) Full Movie by Sam Favret & Julien Herry', + 'duration': 1649, + 'thumbnail': r're:https?://i\.vimeocdn\.com/video/.+', + 'uploader': 'Favret Sam', + 'uploader_id': 'samfavret', + 'uploader_url': 'https://vimeo.com/samfavret', + }, + 'add_ie': ['Vimeo'], + 'expected_warnings': ['Failed to parse XML: not well-formed'], }] _WEBPAGE_TESTS = [{ + # https://www.zapiks.fr/ep3s5-bon-appetit-baqueira-m-1.html + # https://www.zapiks.fr/index.php?action=playerIframe&media_id=118046 'url': 'https://www.skipass.com/news/116090-bon-appetit-s5ep3-baqueira-mi-cor.html', + 'md5': '196fe42901639d868956b1dcaa48de15', 'info_dict': { 'id': '118046', 'ext': 'mp4', 'title': 'EP3S5 - Bon Appétit - Baqueira Mi Corazon !', - 'thumbnail': r're:https?://zpks\.com/.+\.jpg', + 'description': 'md5:b45295c3897c4c01d7c04e8484c26aaf', + 'display_id': 'ep3s5-bon-appetit-baqueira-m-1', + 'duration': 642, + 'tags': 'count:8', + 'thumbnail': r're:https?://zpks\.com/.+', + 'timestamp': 1424370543, + 'upload_date': '20150219', + 'uploader': 'BonAppetit', + 'uploader_id': 'bonappetit', + 'view_count': int, }, }] + _UPLOADER_ID_RE = re.compile(r'/pro(?:fil)?/(?P<id>[^/?#]+)/?') def _real_extract(self, url): - mobj = self._match_valid_url(url) - video_id = mobj.group('id') - display_id = mobj.group('display_id') or video_id - + display_id = self._match_id(url) webpage = self._download_webpage(url, display_id) + if embed_url := traverse_obj(webpage, ( + {find_element(cls='embed-container')}, {find_element(tag='iframe', html=True)}, + {extract_attributes}, 'src', {self._proto_relative_url}, {url_or_none}, + )): + if not self.suitable(embed_url): + return self.url_result(embed_url) + + video_responsive = traverse_obj(webpage, ( + {find_element(cls='video-responsive', html=True)}, {extract_attributes}, {dict})) + data_media_url = traverse_obj(video_responsive, ('data-media-url', {url_or_none})) + if data_media_url and urllib.parse.urlparse(url).path == '/index.php': + return self.url_result(data_media_url, ZapiksIE) - if not video_id: - video_id = self._search_regex( - r'data-media-id="(\d+)"', webpage, 'video id') - - playlist = self._download_xml( - f'http://www.zapiks.fr/view/index.php?action=playlist&media_id={video_id}&lang=en', - display_id) - - NS_MAP = { - 'jwplayer': 'http://rss.jwpcdn.com/', - } - - def ns(path): - return xpath_with_ns(path, NS_MAP) - - item = playlist.find('./channel/item') - - title = xpath_text(item, 'title', 'title') or self._og_search_title(webpage) - description = self._og_search_description(webpage, default=None) - thumbnail = xpath_text( - item, ns('./jwplayer:image'), 'thumbnail') or self._og_search_thumbnail(webpage, default=None) - duration = parse_duration(self._html_search_meta( - 'duration', webpage, 'duration', default=None)) - timestamp = parse_iso8601(self._html_search_meta( - 'uploadDate', webpage, 'upload date', default=None), ' ') - - view_count = int_or_none(self._search_regex( - r'UserPlays:(\d+)', webpage, 'view count', default=None)) - comment_count = int_or_none(self._search_regex( - r'UserComments:(\d+)', webpage, 'comment count', default=None)) - + data_playlist = traverse_obj(video_responsive, ('data-playlist', {json.loads}, ..., any)) formats = [] - for source in item.findall(ns('./jwplayer:source')): - format_id = source.attrib['label'] - f = { - 'url': source.attrib['file'], + for source in traverse_obj(data_playlist, ( + 'sources', lambda _, v: url_or_none(v['file']), + )): + format_id = traverse_obj(source, ('label', {str_or_none})) + formats.append({ 'format_id': format_id, - } - m = re.search(r'^(?P<height>\d+)[pP]', format_id) - if m: - f['height'] = int(m.group('height')) - formats.append(f) + 'url': source['file'], + **parse_resolution(format_id), + }) return { - 'id': video_id, - 'title': title, - 'description': description, - 'thumbnail': thumbnail, - 'duration': duration, - 'timestamp': timestamp, - 'view_count': view_count, - 'comment_count': comment_count, + 'display_id': display_id, + 'duration': parse_duration(self._html_search_meta('duration', webpage, default=None)), 'formats': formats, + 'timestamp': unified_timestamp(self._html_search_meta('uploadDate', webpage, default=None)), + **traverse_obj(webpage, { + 'description': ({find_element(cls='description-text')}, {clean_html}, filter), + 'tags': ( + {find_elements(cls='bs-label', html=True)}, + ..., {extract_attributes}, 'title', {clean_html}, filter), + 'view_count': ( + {find_element(cls='video-content-view-counter')}, {clean_html}, + {lambda x: re.sub(r'(?:vues|views|\s+)', '', x)}, {int_or_none}), + }), + **traverse_obj(webpage, ({find_element(cls='video-content-user-link', html=True)}, { + 'uploader': ({clean_html}, filter), + 'uploader_id': ({extract_attributes}, 'href', {self._UPLOADER_ID_RE.fullmatch}, 'id'), + })), + **traverse_obj(data_playlist, { + 'id': ('mediaid', {str_or_none}), + 'title': ('title', {clean_html}, filter), + 'thumbnail': ('image', {url_or_none}), + }), } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yt-dlp/yt_dlp/version.py new/yt-dlp/yt_dlp/version.py --- old/yt-dlp/yt_dlp/version.py 2026-02-21 21:22:55.000000000 +0100 +++ new/yt-dlp/yt_dlp/version.py 2026-03-03 17:37:32.000000000 +0100 @@ -1,8 +1,8 @@ # Autogenerated by devscripts/update-version.py -__version__ = '2026.02.21' +__version__ = '2026.03.03' -RELEASE_GIT_HEAD = '646bb31f39614e6c2f7ba687c53e7496394cbadb' +RELEASE_GIT_HEAD = '2ecc4c3bc300701d85e2cbaeb2b28a921a68f0f0' VARIANT = None @@ -12,4 +12,4 @@ ORIGIN = 'yt-dlp/yt-dlp' -_pkg_version = '2026.02.21' +_pkg_version = '2026.03.03'
