From 196eb0fe77b78e2e5ca02c506c3837c2b1a7964c Mon Sep 17 00:00:00 2001
From: coletdjnz
Date: Wed, 20 Dec 2023 19:15:38 +1300
Subject: [PATCH 001/175] [networking] Strip whitespace around header values
(#8802)
Fixes https://github.com/yt-dlp/yt-dlp/issues/8729
Authored by: coletdjnz
---
test/test_utils.py | 5 +++++
yt_dlp/utils/networking.py | 2 +-
2 files changed, 6 insertions(+), 1 deletion(-)
diff --git a/test/test_utils.py b/test/test_utils.py
index 100f11788993..6c8571f980f2 100644
--- a/test/test_utils.py
+++ b/test/test_utils.py
@@ -2370,6 +2370,11 @@ def test_http_header_dict(self):
headers4 = HTTPHeaderDict({'ytdl-test': 'data;'})
self.assertEqual(set(headers4.items()), {('Ytdl-Test', 'data;')})
+ # common mistake: strip whitespace from values
+ # https://github.com/yt-dlp/yt-dlp/issues/8729
+ headers5 = HTTPHeaderDict({'ytdl-test': ' data; '})
+ self.assertEqual(set(headers5.items()), {('Ytdl-Test', 'data;')})
+
def test_extract_basic_auth(self):
assert extract_basic_auth('http://:foo.bar') == ('http://:foo.bar', None)
assert extract_basic_auth('http://foo.bar') == ('http://foo.bar', None)
diff --git a/yt_dlp/utils/networking.py b/yt_dlp/utils/networking.py
index ed02500110ae..4b73252cbd06 100644
--- a/yt_dlp/utils/networking.py
+++ b/yt_dlp/utils/networking.py
@@ -67,7 +67,7 @@ def __init__(self, *args, **kwargs):
def __setitem__(self, key, value):
if isinstance(value, bytes):
value = value.decode('latin-1')
- super().__setitem__(key.title(), str(value))
+ super().__setitem__(key.title(), str(value).strip())
def __getitem__(self, key):
return super().__getitem__(key.title())
From 37755a037e612bfc608c3d4722e8ef2ce6a022ee Mon Sep 17 00:00:00 2001
From: bashonly <88596187+bashonly@users.noreply.github.com>
Date: Wed, 20 Dec 2023 13:03:54 -0600
Subject: [PATCH 002/175] [test:networking] Update tests for OpenSSL 3.2
(#8814)
Authored by: bashonly
---
test/test_networking.py | 2 +-
test/test_websockets.py | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/test/test_networking.py b/test/test_networking.py
index 64af6e459a73..dc60ca699457 100644
--- a/test/test_networking.py
+++ b/test/test_networking.py
@@ -328,7 +328,7 @@ def test_ssl_error(self, handler):
https_server_thread.start()
with handler(verify=False) as rh:
- with pytest.raises(SSLError, match='sslv3 alert handshake failure') as exc_info:
+ with pytest.raises(SSLError, match=r'ssl(?:v3|/tls) alert handshake failure') as exc_info:
validate_and_send(rh, Request(f'https://127.0.0.1:{https_port}/headers'))
assert not issubclass(exc_info.type, CertificateVerifyError)
diff --git a/test/test_websockets.py b/test/test_websockets.py
index 39d3c7d72214..af6142ea3b34 100644
--- a/test/test_websockets.py
+++ b/test/test_websockets.py
@@ -148,7 +148,7 @@ def test_verify_cert(self, handler):
@pytest.mark.parametrize('handler', ['Websockets'], indirect=True)
def test_ssl_error(self, handler):
with handler(verify=False) as rh:
- with pytest.raises(SSLError, match='sslv3 alert handshake failure') as exc_info:
+ with pytest.raises(SSLError, match=r'ssl(?:v3|/tls) alert handshake failure') as exc_info:
validate_and_send(rh, Request(self.bad_wss_host))
assert not issubclass(exc_info.type, CertificateVerifyError)
From 19741ab8a401ec64d5e84fdbfcfb141d105e7bc8 Mon Sep 17 00:00:00 2001
From: bashonly
Date: Thu, 21 Dec 2023 14:46:00 -0600
Subject: [PATCH 003/175] [ie/bbc] Fix JSON parsing bug
Authored by: bashonly
---
yt_dlp/extractor/bbc.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/yt_dlp/extractor/bbc.py b/yt_dlp/extractor/bbc.py
index d1d6e04faaf7..c94184bf07cf 100644
--- a/yt_dlp/extractor/bbc.py
+++ b/yt_dlp/extractor/bbc.py
@@ -1188,7 +1188,7 @@ def _real_extract(self, url):
if initial_data is None:
initial_data = self._search_regex(
r'window\.__INITIAL_DATA__\s*=\s*({.+?})\s*;', webpage,
- 'preload state', default={})
+ 'preload state', default='{}')
else:
initial_data = self._parse_json(initial_data or '"{}"', playlist_id, fatal=False)
initial_data = self._parse_json(initial_data, playlist_id, fatal=False)
From c919b68f7e79ea5010f75f648d3c9e45405a8011 Mon Sep 17 00:00:00 2001
From: barsnick
Date: Thu, 21 Dec 2023 21:47:32 +0100
Subject: [PATCH 004/175] [ie/bbc] Extract more formats (#8321)
Closes #4902
Authored by: barsnick, dirkf
---
yt_dlp/extractor/bbc.py | 13 +++++++++++--
1 file changed, 11 insertions(+), 2 deletions(-)
diff --git a/yt_dlp/extractor/bbc.py b/yt_dlp/extractor/bbc.py
index c94184bf07cf..015af9e1d616 100644
--- a/yt_dlp/extractor/bbc.py
+++ b/yt_dlp/extractor/bbc.py
@@ -317,16 +317,25 @@ def _raise_extractor_error(self, media_selection_error):
def _download_media_selector(self, programme_id):
last_exception = None
+ formats, subtitles = [], {}
for media_set in self._MEDIA_SETS:
try:
- return self._download_media_selector_url(
+ fmts, subs = self._download_media_selector_url(
self._MEDIA_SELECTOR_URL_TEMPL % (media_set, programme_id), programme_id)
+ formats.extend(fmts)
+ if subs:
+ self._merge_subtitles(subs, target=subtitles)
except BBCCoUkIE.MediaSelectionError as e:
if e.id in ('notukerror', 'geolocation', 'selectionunavailable'):
last_exception = e
continue
self._raise_extractor_error(e)
- self._raise_extractor_error(last_exception)
+ if last_exception:
+ if formats or subtitles:
+ self.report_warning(f'{self.IE_NAME} returned error: {last_exception.id}')
+ else:
+ self._raise_extractor_error(last_exception)
+ return formats, subtitles
def _download_media_selector_url(self, url, programme_id=None):
media_selection = self._download_json(
From 632b8ee54eb2df8ac6e20746a0bd95b7ebb053aa Mon Sep 17 00:00:00 2001
From: bashonly <88596187+bashonly@users.noreply.github.com>
Date: Thu, 21 Dec 2023 15:06:26 -0600
Subject: [PATCH 005/175] [core] Release workflow and Updater cleanup (#8640)
- Only use trusted publishing with PyPI and remove support for PyPI tokens from release workflow
- Clean up improper actions syntax in the build workflow inputs
- Refactor Updater to allow for consistent unit testing with `UPDATE_SOURCES`
Authored by: bashonly
---
.github/workflows/build.yml | 8 ++++----
.github/workflows/release.yml | 24 +-----------------------
test/test_update.py | 9 +++++++++
yt_dlp/update.py | 7 ++++---
4 files changed, 18 insertions(+), 30 deletions(-)
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index d944659b85f3..036ce434894b 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -80,12 +80,12 @@ on:
default: true
type: boolean
origin:
- description: .
+ description: Origin
required: false
- default: ''
+ default: 'current repo'
type: choice
options:
- - ''
+ - 'current repo'
permissions:
contents: read
@@ -99,7 +99,7 @@ jobs:
- name: Process origin
id: process_origin
run: |
- echo "origin=${{ inputs.origin || github.repository }}" >> "$GITHUB_OUTPUT"
+ echo "origin=${{ inputs.origin == 'current repo' && github.repository || inputs.origin }}" | tee "$GITHUB_OUTPUT"
unix:
needs: process
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 84e892ffe139..69b5e315296e 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -64,7 +64,6 @@ jobs:
target_tag: ${{ steps.setup_variables.outputs.target_tag }}
pypi_project: ${{ steps.setup_variables.outputs.pypi_project }}
pypi_suffix: ${{ steps.setup_variables.outputs.pypi_suffix }}
- pypi_token: ${{ steps.setup_variables.outputs.pypi_token }}
head_sha: ${{ steps.get_target.outputs.head_sha }}
steps:
@@ -153,7 +152,6 @@ jobs:
${{ !!secrets[format('{0}_archive_repo_token', env.target_repo)] }} || fallback_token
pypi_project='${{ vars[format('{0}_pypi_project', env.target_repo)] }}'
pypi_suffix='${{ vars[format('{0}_pypi_suffix', env.target_repo)] }}'
- ${{ !secrets[format('{0}_pypi_token', env.target_repo)] }} || pypi_token='${{ env.target_repo }}_pypi_token'
fi
else
target_tag="${source_tag:-${version}}"
@@ -163,7 +161,6 @@ jobs:
${{ !!secrets[format('{0}_archive_repo_token', env.source_repo)] }} || fallback_token
pypi_project='${{ vars[format('{0}_pypi_project', env.source_repo)] }}'
pypi_suffix='${{ vars[format('{0}_pypi_suffix', env.source_repo)] }}'
- ${{ !secrets[format('{0}_pypi_token', env.source_repo)] }} || pypi_token='${{ env.source_repo }}_pypi_token'
else
target_repo='${{ github.repository }}'
fi
@@ -172,13 +169,6 @@ jobs:
if [[ "${target_repo}" == '${{ github.repository }}' ]] && ${{ !inputs.prerelease }}; then
pypi_project='${{ vars.PYPI_PROJECT }}'
fi
- if [[ -z "${pypi_token}" && "${pypi_project}" ]]; then
- if ${{ !secrets.PYPI_TOKEN }}; then
- pypi_token=OIDC
- else
- pypi_token=PYPI_TOKEN
- fi
- fi
echo "::group::Output variables"
cat << EOF | tee -a "$GITHUB_OUTPUT"
@@ -189,7 +179,6 @@ jobs:
target_tag=${target_tag}
pypi_project=${pypi_project}
pypi_suffix=${pypi_suffix}
- pypi_token=${pypi_token}
EOF
echo "::endgroup::"
@@ -286,18 +275,7 @@ jobs:
python devscripts/set-variant.py pip -M "You installed yt-dlp with pip or using the wheel from PyPi; Use that to update"
python setup.py sdist bdist_wheel
- - name: Publish to PyPI via token
- env:
- TWINE_USERNAME: __token__
- TWINE_PASSWORD: ${{ secrets[needs.prepare.outputs.pypi_token] }}
- if: |
- needs.prepare.outputs.pypi_token != 'OIDC' && env.TWINE_PASSWORD
- run: |
- twine upload dist/*
-
- - name: Publish to PyPI via trusted publishing
- if: |
- needs.prepare.outputs.pypi_token == 'OIDC'
+ - name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
verbose: true
diff --git a/test/test_update.py b/test/test_update.py
index 2a5647e44a26..a5a388c10698 100644
--- a/test/test_update.py
+++ b/test/test_update.py
@@ -11,6 +11,14 @@
from test.helper import FakeYDL, report_warning
from yt_dlp.update import Updater, UpdateInfo
+
+# XXX: Keep in sync with yt_dlp.update.UPDATE_SOURCES
+TEST_UPDATE_SOURCES = {
+ 'stable': 'yt-dlp/yt-dlp',
+ 'nightly': 'yt-dlp/yt-dlp-nightly-builds',
+ 'master': 'yt-dlp/yt-dlp-master-builds',
+}
+
TEST_API_DATA = {
'yt-dlp/yt-dlp/latest': {
'tag_name': '2023.12.31',
@@ -104,6 +112,7 @@ class FakeUpdater(Updater):
_channel = 'stable'
_origin = 'yt-dlp/yt-dlp'
+ _update_sources = TEST_UPDATE_SOURCES
def _download_update_spec(self, *args, **kwargs):
return TEST_LOCKFILE_ACTUAL
diff --git a/yt_dlp/update.py b/yt_dlp/update.py
index f99583b081f9..ba7eadf81f80 100644
--- a/yt_dlp/update.py
+++ b/yt_dlp/update.py
@@ -206,13 +206,14 @@ class Updater:
# XXX: use class variables to simplify testing
_channel = CHANNEL
_origin = ORIGIN
+ _update_sources = UPDATE_SOURCES
def __init__(self, ydl, target: str | None = None):
self.ydl = ydl
# For backwards compat, target needs to be treated as if it could be None
self.requested_channel, sep, self.requested_tag = (target or self._channel).rpartition('@')
# Check if requested_tag is actually the requested repo/channel
- if not sep and ('/' in self.requested_tag or self.requested_tag in UPDATE_SOURCES):
+ if not sep and ('/' in self.requested_tag or self.requested_tag in self._update_sources):
self.requested_channel = self.requested_tag
self.requested_tag: str = None # type: ignore (we set it later)
elif not self.requested_channel:
@@ -237,11 +238,11 @@ def __init__(self, ydl, target: str | None = None):
self._block_restart('Automatically restarting into custom builds is disabled for security reasons')
else:
# Check if requested_channel resolves to a known repository or else raise
- self.requested_repo = UPDATE_SOURCES.get(self.requested_channel)
+ self.requested_repo = self._update_sources.get(self.requested_channel)
if not self.requested_repo:
self._report_error(
f'Invalid update channel {self.requested_channel!r} requested. '
- f'Valid channels are {", ".join(UPDATE_SOURCES)}', True)
+ f'Valid channels are {", ".join(self._update_sources)}', True)
self._identifier = f'{detect_variant()} {system_identifier()}'
From bc4ab17b38f01000d99c5c2bedec89721fee65ec Mon Sep 17 00:00:00 2001
From: barsnick
Date: Fri, 22 Dec 2023 02:32:29 +0100
Subject: [PATCH 006/175] [cleanup] Fix spelling of `IE_NAME` (#8810)
Authored by: barsnick
---
yt_dlp/extractor/iheart.py | 2 +-
yt_dlp/extractor/kinja.py | 2 +-
yt_dlp/extractor/nba.py | 8 ++++----
3 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/yt_dlp/extractor/iheart.py b/yt_dlp/extractor/iheart.py
index 2c6a5b6a1d43..fb6f51e2ca8f 100644
--- a/yt_dlp/extractor/iheart.py
+++ b/yt_dlp/extractor/iheart.py
@@ -23,7 +23,7 @@ def _extract_episode(self, episode):
class IHeartRadioIE(IHeartRadioBaseIE):
- IENAME = 'iheartradio'
+ IE_NAME = 'iheartradio'
_VALID_URL = r'(?:https?://(?:www\.)?iheart\.com/podcast/[^/]+/episode/(?P[^/?]+)-|iheartradio:)(?P\d+)'
_TEST = {
'url': 'https://www.iheart.com/podcast/105-behind-the-bastards-29236323/episode/part-one-alexander-lukashenko-the-dictator-70346499/?embed=true',
diff --git a/yt_dlp/extractor/kinja.py b/yt_dlp/extractor/kinja.py
index a225d0a0d29f..f4e5c4c4797e 100644
--- a/yt_dlp/extractor/kinja.py
+++ b/yt_dlp/extractor/kinja.py
@@ -12,7 +12,7 @@
class KinjaEmbedIE(InfoExtractor):
- IENAME = 'kinja:embed'
+ IE_NAME = 'kinja:embed'
_DOMAIN_REGEX = r'''(?:[^.]+\.)?
(?:
avclub|
diff --git a/yt_dlp/extractor/nba.py b/yt_dlp/extractor/nba.py
index d8fc82488d36..81d11e3a5061 100644
--- a/yt_dlp/extractor/nba.py
+++ b/yt_dlp/extractor/nba.py
@@ -97,7 +97,7 @@ def _extract_video(self, filter_key, filter_value):
class NBAWatchEmbedIE(NBAWatchBaseIE):
- IENAME = 'nba:watch:embed'
+ IE_NAME = 'nba:watch:embed'
_VALID_URL = NBAWatchBaseIE._VALID_URL_BASE + r'embed\?.*?\bid=(?P\d+)'
_TESTS = [{
'url': 'http://watch.nba.com/embed?id=659395',
@@ -339,7 +339,7 @@ def _real_extract(self, url):
class NBAEmbedIE(NBABaseIE):
- IENAME = 'nba:embed'
+ IE_NAME = 'nba:embed'
_VALID_URL = r'https?://secure\.nba\.com/assets/amp/include/video/(?:topI|i)frame\.html\?.*?\bcontentId=(?P[^?#&]+)'
_TESTS = [{
'url': 'https://secure.nba.com/assets/amp/include/video/topIframe.html?contentId=teams/bulls/2020/12/04/3478774/1607105587854-20201204_SCHEDULE_RELEASE_FINAL_DRUPAL-3478774&team=bulls&adFree=false&profile=71&videoPlayerName=TAMPCVP&baseUrl=&videoAdsection=nba.com_mobile_web_teamsites_chicagobulls&Env=',
@@ -361,7 +361,7 @@ def _real_extract(self, url):
class NBAIE(NBABaseIE):
- IENAME = 'nba'
+ IE_NAME = 'nba'
_VALID_URL = NBABaseIE._VALID_URL_BASE + '(?!%s)video/(?P(?:[^/]+/)*[^/?#&]+)' % NBABaseIE._CHANNEL_PATH_REGEX
_TESTS = [{
'url': 'https://www.nba.com/bulls/video/teams/bulls/2020/12/04/3478774/1607105587854-20201204schedulereleasefinaldrupal-3478774',
@@ -388,7 +388,7 @@ def _extract_url_results(self, team, content_id):
class NBAChannelIE(NBABaseIE):
- IENAME = 'nba:channel'
+ IE_NAME = 'nba:channel'
_VALID_URL = NBABaseIE._VALID_URL_BASE + '(?:%s)/(?P[^/?#&]+)' % NBABaseIE._CHANNEL_PATH_REGEX
_TESTS = [{
'url': 'https://www.nba.com/blazers/video/channel/summer_league',
From 0d531c35eca4c2eb36e160530a7a333edbc727cc Mon Sep 17 00:00:00 2001
From: Nicolas Dato <67328748+nicodato@users.noreply.github.com>
Date: Fri, 22 Dec 2023 18:52:07 -0300
Subject: [PATCH 007/175] [ie/RudoVideo] Add extractor (#8664)
Authored by: nicodato
---
yt_dlp/extractor/_extractors.py | 1 +
yt_dlp/extractor/rudovideo.py | 135 ++++++++++++++++++++++++++++++++
2 files changed, 136 insertions(+)
create mode 100644 yt_dlp/extractor/rudovideo.py
diff --git a/yt_dlp/extractor/_extractors.py b/yt_dlp/extractor/_extractors.py
index d5f030c6b078..5c34bb7f4b09 100644
--- a/yt_dlp/extractor/_extractors.py
+++ b/yt_dlp/extractor/_extractors.py
@@ -1647,6 +1647,7 @@
RumbleIE,
RumbleChannelIE,
)
+from .rudovideo import RudoVideoIE
from .rutube import (
RutubeIE,
RutubeChannelIE,
diff --git a/yt_dlp/extractor/rudovideo.py b/yt_dlp/extractor/rudovideo.py
new file mode 100644
index 000000000000..1b8595593ddb
--- /dev/null
+++ b/yt_dlp/extractor/rudovideo.py
@@ -0,0 +1,135 @@
+from .common import InfoExtractor
+from ..utils import (
+ ExtractorError,
+ determine_ext,
+ js_to_json,
+ traverse_obj,
+ update_url_query,
+ url_or_none,
+)
+
+
+class RudoVideoIE(InfoExtractor):
+ _VALID_URL = r'https?://rudo\.video/(?Pvod|podcast|live)/(?P[^/?]+)'
+ _EMBED_REGEX = [r'
', webpage, 'author', default=None),
+ }
-class NYTimesCookingIE(NYTimesBaseIE):
- _VALID_URL = r'https?://cooking\.nytimes\.com/(?:guid|recip)es/(?P\d+)'
+
+class NYTimesCookingRecipeIE(InfoExtractor):
+ _VALID_URL = r'https?://cooking\.nytimes\.com/recipes/(?P\d+)'
_TESTS = [{
'url': 'https://cooking.nytimes.com/recipes/1017817-cranberry-curd-tart',
- 'md5': 'dab81fa2eaeb3f9ed47498bdcfcdc1d3',
+ 'md5': '579e83bbe8e61e9de67f80edba8a78a8',
'info_dict': {
- 'id': '100000004756089',
- 'ext': 'mov',
- 'timestamp': 1479383008,
- 'uploader': 'By SHAW LASH, ADAM SAEWITZ and JAMES HERRON',
- 'title': 'Cranberry Tart',
- 'upload_date': '20161117',
- 'description': 'If you are a fan of lemon curd or the classic French tarte au citron, you will love this cranberry version.',
+ 'id': '1017817',
+ 'ext': 'mp4',
+ 'title': 'Cranberry Curd Tart',
+ 'description': 'md5:ad77a3fc321db636256d4343c5742152',
+ 'timestamp': 1447804800,
+ 'upload_date': '20151118',
+ 'creator': 'David Tanis',
+ 'thumbnail': r're:https?://\w+\.nyt.com/images/.*\.jpg',
},
}, {
- 'url': 'https://cooking.nytimes.com/guides/13-how-to-cook-a-turkey',
- 'md5': '4b2e8c70530a89b8d905a2b572316eb8',
+ 'url': 'https://cooking.nytimes.com/recipes/1024781-neapolitan-checkerboard-cookies',
+ 'md5': '58df35998241dcf0620e99e646331b42',
'info_dict': {
- 'id': '100000003951728',
- 'ext': 'mov',
- 'timestamp': 1445509539,
- 'description': 'Turkey guide',
- 'upload_date': '20151022',
- 'title': 'Turkey',
- }
+ 'id': '1024781',
+ 'ext': 'mp4',
+ 'title': 'Neapolitan Checkerboard Cookies',
+ 'description': 'md5:ba12394c585ababea951cb6d2fcc6631',
+ 'timestamp': 1701302400,
+ 'upload_date': '20231130',
+ 'creator': 'Sue Li',
+ 'thumbnail': r're:https?://\w+\.nyt.com/images/.*\.jpg',
+ },
+ }, {
+ 'url': 'https://cooking.nytimes.com/recipes/1019516-overnight-oats',
+ 'md5': '2fe7965a3adc899913b8e25ada360823',
+ 'info_dict': {
+ 'id': '1019516',
+ 'ext': 'mp4',
+ 'timestamp': 1546387200,
+ 'description': 'md5:8856ce10239161bd2596ac335b9f9bfb',
+ 'upload_date': '20190102',
+ 'title': 'Overnight Oats',
+ 'creator': 'Genevieve Ko',
+ 'thumbnail': r're:https?://\w+\.nyt.com/images/.*\.jpg',
+ },
}]
def _real_extract(self, url):
page_id = self._match_id(url)
-
webpage = self._download_webpage(url, page_id)
+ recipe_data = self._search_nextjs_data(webpage, page_id)['props']['pageProps']['recipe']
- video_id = self._search_regex(
- r'data-video-id=["\'](\d+)', webpage, 'video id')
+ formats, subtitles = self._extract_m3u8_formats_and_subtitles(
+ recipe_data['videoSrc'], page_id, 'mp4', m3u8_id='hls')
- return self._extract_video_from_id(video_id)
+ return {
+ **traverse_obj(recipe_data, {
+ 'id': ('id', {str_or_none}),
+ 'title': ('title', {str}),
+ 'description': ('topnote', {clean_html}),
+ 'timestamp': ('publishedAt', {int_or_none}),
+ 'creator': ('contentAttribution', 'cardByline', {str}),
+ }),
+ 'formats': formats,
+ 'subtitles': subtitles,
+ 'thumbnails': [{'url': thumb_url} for thumb_url in traverse_obj(
+ recipe_data, ('image', 'crops', 'recipe', ..., {url_or_none}))],
+ }
From acaf806c15f0a802ba286c23af02a10cf4bd4731 Mon Sep 17 00:00:00 2001
From: DmitryScaletta
Date: Mon, 5 Feb 2024 05:17:39 +0300
Subject: [PATCH 106/175] [ie/nuum] Add extractors (#8868)
Authored by: DmitryScaletta, seproDev
Co-authored-by: sepro <4618135+seproDev@users.noreply.github.com>
---
yt_dlp/extractor/_extractors.py | 10 +-
yt_dlp/extractor/nuum.py | 199 ++++++++++++++++++++++++++++++++
yt_dlp/extractor/wasdtv.py | 159 -------------------------
3 files changed, 204 insertions(+), 164 deletions(-)
create mode 100644 yt_dlp/extractor/nuum.py
delete mode 100644 yt_dlp/extractor/wasdtv.py
diff --git a/yt_dlp/extractor/_extractors.py b/yt_dlp/extractor/_extractors.py
index 36335286c3a0..e7dd34c77bda 100644
--- a/yt_dlp/extractor/_extractors.py
+++ b/yt_dlp/extractor/_extractors.py
@@ -1354,6 +1354,11 @@
NYTimesCookingIE,
NYTimesCookingRecipeIE,
)
+from .nuum import (
+ NuumLiveIE,
+ NuumTabIE,
+ NuumMediaIE,
+)
from .nuvid import NuvidIE
from .nzherald import NZHeraldIE
from .nzonscreen import NZOnScreenIE
@@ -2315,11 +2320,6 @@
WashingtonPostIE,
WashingtonPostArticleIE,
)
-from .wasdtv import (
- WASDTVStreamIE,
- WASDTVRecordIE,
- WASDTVClipIE,
-)
from .wat import WatIE
from .wdr import (
WDRIE,
diff --git a/yt_dlp/extractor/nuum.py b/yt_dlp/extractor/nuum.py
new file mode 100644
index 000000000000..3db663ded0dd
--- /dev/null
+++ b/yt_dlp/extractor/nuum.py
@@ -0,0 +1,199 @@
+import functools
+
+from .common import InfoExtractor
+from ..utils import (
+ ExtractorError,
+ OnDemandPagedList,
+ UserNotLive,
+ filter_dict,
+ int_or_none,
+ parse_iso8601,
+ str_or_none,
+ url_or_none,
+)
+from ..utils.traversal import traverse_obj
+
+
+class NuumBaseIE(InfoExtractor):
+ def _call_api(self, path, video_id, description, query={}):
+ response = self._download_json(
+ f'https://nuum.ru/api/v2/{path}', video_id, query=query,
+ note=f'Downloading {description} metadata',
+ errnote=f'Unable to download {description} metadata')
+ if error := response.get('error'):
+ raise ExtractorError(f'API returned error: {error!r}')
+ return response['result']
+
+ def _get_channel_info(self, channel_name):
+ return self._call_api(
+ 'broadcasts/public', video_id=channel_name, description='channel',
+ query={
+ 'with_extra': 'true',
+ 'channel_name': channel_name,
+ 'with_deleted': 'true',
+ })
+
+ def _parse_video_data(self, container, extract_formats=True):
+ stream = traverse_obj(container, ('media_container_streams', 0, {dict})) or {}
+ media = traverse_obj(stream, ('stream_media', 0, {dict})) or {}
+ media_url = traverse_obj(media, (
+ 'media_meta', ('media_archive_url', 'media_url'), {url_or_none}), get_all=False)
+
+ video_id = str(container['media_container_id'])
+ is_live = media.get('media_status') == 'RUNNING'
+
+ formats, subtitles = None, None
+ if extract_formats:
+ formats, subtitles = self._extract_m3u8_formats_and_subtitles(
+ media_url, video_id, 'mp4', live=is_live)
+
+ return filter_dict({
+ 'id': video_id,
+ 'is_live': is_live,
+ 'formats': formats,
+ 'subtitles': subtitles,
+ **traverse_obj(container, {
+ 'title': ('media_container_name', {str}),
+ 'description': ('media_container_description', {str}),
+ 'timestamp': ('created_at', {parse_iso8601}),
+ 'channel': ('media_container_channel', 'channel_name', {str}),
+ 'channel_id': ('media_container_channel', 'channel_id', {str_or_none}),
+ }),
+ **traverse_obj(stream, {
+ 'view_count': ('stream_total_viewers', {int_or_none}),
+ 'concurrent_view_count': ('stream_current_viewers', {int_or_none}),
+ }),
+ **traverse_obj(media, {
+ 'duration': ('media_duration', {int_or_none}),
+ 'thumbnail': ('media_meta', ('media_preview_archive_url', 'media_preview_url'), {url_or_none}),
+ }, get_all=False),
+ })
+
+
+class NuumMediaIE(NuumBaseIE):
+ IE_NAME = 'nuum:media'
+ _VALID_URL = r'https?://nuum\.ru/(?:streams|videos|clips)/(?P[\d]+)'
+ _TESTS = [{
+ 'url': 'https://nuum.ru/streams/1592713-7-days-to-die',
+ 'only_matching': True,
+ }, {
+ 'url': 'https://nuum.ru/videos/1567547-toxi-hurtz',
+ 'md5': 'f1d9118a30403e32b702a204eb03aca3',
+ 'info_dict': {
+ 'id': '1567547',
+ 'ext': 'mp4',
+ 'title': 'Toxi$ - Hurtz',
+ 'description': '',
+ 'timestamp': 1702631651,
+ 'upload_date': '20231215',
+ 'thumbnail': r're:^https?://.+\.jpg',
+ 'view_count': int,
+ 'concurrent_view_count': int,
+ 'channel_id': '6911',
+ 'channel': 'toxis',
+ 'duration': 116,
+ },
+ }, {
+ 'url': 'https://nuum.ru/clips/1552564-pro-misu',
+ 'md5': 'b248ae1565b1e55433188f11beeb0ca1',
+ 'info_dict': {
+ 'id': '1552564',
+ 'ext': 'mp4',
+ 'title': 'Про Мису 🙃',
+ 'timestamp': 1701971828,
+ 'upload_date': '20231207',
+ 'thumbnail': r're:^https?://.+\.jpg',
+ 'view_count': int,
+ 'concurrent_view_count': int,
+ 'channel_id': '3320',
+ 'channel': 'Misalelik',
+ 'duration': 41,
+ },
+ }]
+
+ def _real_extract(self, url):
+ video_id = self._match_id(url)
+ video_data = self._call_api(f'media-containers/{video_id}', video_id, 'media')
+
+ return self._parse_video_data(video_data)
+
+
+class NuumLiveIE(NuumBaseIE):
+ IE_NAME = 'nuum:live'
+ _VALID_URL = r'https?://nuum\.ru/channel/(?P[^/#?]+)/?(?:$|[#?])'
+ _TESTS = [{
+ 'url': 'https://nuum.ru/channel/mts_live',
+ 'only_matching': True,
+ }]
+
+ def _real_extract(self, url):
+ channel = self._match_id(url)
+ channel_info = self._get_channel_info(channel)
+ if traverse_obj(channel_info, ('channel', 'channel_is_live')) is False:
+ raise UserNotLive(video_id=channel)
+
+ info = self._parse_video_data(channel_info['media_container'])
+ return {
+ 'webpage_url': f'https://nuum.ru/streams/{info["id"]}',
+ 'extractor_key': NuumMediaIE.ie_key(),
+ 'extractor': NuumMediaIE.IE_NAME,
+ **info,
+ }
+
+
+class NuumTabIE(NuumBaseIE):
+ IE_NAME = 'nuum:tab'
+ _VALID_URL = r'https?://nuum\.ru/channel/(?P[^/#?]+)/(?Pstreams|videos|clips)'
+ _TESTS = [{
+ 'url': 'https://nuum.ru/channel/dankon_/clips',
+ 'info_dict': {
+ 'id': 'dankon__clips',
+ 'title': 'Dankon_',
+ },
+ 'playlist_mincount': 29,
+ }, {
+ 'url': 'https://nuum.ru/channel/dankon_/videos',
+ 'info_dict': {
+ 'id': 'dankon__videos',
+ 'title': 'Dankon_',
+ },
+ 'playlist_mincount': 2,
+ }, {
+ 'url': 'https://nuum.ru/channel/dankon_/streams',
+ 'info_dict': {
+ 'id': 'dankon__streams',
+ 'title': 'Dankon_',
+ },
+ 'playlist_mincount': 1,
+ }]
+
+ _PAGE_SIZE = 50
+
+ def _fetch_page(self, channel_id, tab_type, tab_id, page):
+ CONTAINER_TYPES = {
+ 'clips': ['SHORT_VIDEO', 'REVIEW_VIDEO'],
+ 'videos': ['LONG_VIDEO'],
+ 'streams': ['SINGLE'],
+ }
+
+ media_containers = self._call_api(
+ 'media-containers', video_id=tab_id, description=f'{tab_type} tab page {page + 1}',
+ query={
+ 'limit': self._PAGE_SIZE,
+ 'offset': page * self._PAGE_SIZE,
+ 'channel_id': channel_id,
+ 'media_container_status': 'STOPPED',
+ 'media_container_type': CONTAINER_TYPES[tab_type],
+ })
+ for container in traverse_obj(media_containers, (..., {dict})):
+ metadata = self._parse_video_data(container, extract_formats=False)
+ yield self.url_result(f'https://nuum.ru/videos/{metadata["id"]}', NuumMediaIE, **metadata)
+
+ def _real_extract(self, url):
+ channel_name, tab_type = self._match_valid_url(url).group('id', 'type')
+ tab_id = f'{channel_name}_{tab_type}'
+ channel_data = self._get_channel_info(channel_name)['channel']
+
+ return self.playlist_result(OnDemandPagedList(functools.partial(
+ self._fetch_page, channel_data['channel_id'], tab_type, tab_id), self._PAGE_SIZE),
+ playlist_id=tab_id, playlist_title=channel_data.get('channel_name'))
diff --git a/yt_dlp/extractor/wasdtv.py b/yt_dlp/extractor/wasdtv.py
deleted file mode 100644
index f57c619b5f88..000000000000
--- a/yt_dlp/extractor/wasdtv.py
+++ /dev/null
@@ -1,159 +0,0 @@
-from .common import InfoExtractor
-from ..utils import (
- ExtractorError,
- int_or_none,
- parse_iso8601,
- traverse_obj,
- try_get,
-)
-
-
-class WASDTVBaseIE(InfoExtractor):
-
- def _fetch(self, path, video_id, description, query={}):
- response = self._download_json(
- f'https://wasd.tv/api/{path}', video_id, query=query,
- note=f'Downloading {description} metadata',
- errnote=f'Unable to download {description} metadata')
- error = response.get('error')
- if error:
- raise ExtractorError(f'{self.IE_NAME} returned error: {error}', expected=True)
- return response.get('result')
-
- def _extract_thumbnails(self, thumbnails_dict):
- return [{
- 'url': url,
- 'preference': index,
- } for index, url in enumerate(
- traverse_obj(thumbnails_dict, (('small', 'medium', 'large'),))) if url]
-
- def _real_extract(self, url):
- container = self._get_container(url)
- stream = traverse_obj(container, ('media_container_streams', 0))
- media = try_get(stream, lambda x: x['stream_media'][0])
- if not media:
- raise ExtractorError('Can not extract media data.', expected=True)
- media_meta = media.get('media_meta')
- media_url, is_live = self._get_media_url(media_meta)
- video_id = media.get('media_id') or container.get('media_container_id')
- formats, subtitles = self._extract_m3u8_formats_and_subtitles(media_url, video_id, 'mp4')
- return {
- 'id': str(video_id),
- 'title': container.get('media_container_name') or self._og_search_title(self._download_webpage(url, video_id)),
- 'description': container.get('media_container_description'),
- 'thumbnails': self._extract_thumbnails(media_meta.get('media_preview_images')),
- 'timestamp': parse_iso8601(container.get('created_at')),
- 'view_count': int_or_none(stream.get('stream_current_viewers' if is_live else 'stream_total_viewers')),
- 'is_live': is_live,
- 'formats': formats,
- 'subtitles': subtitles,
- }
-
- def _get_container(self, url):
- raise NotImplementedError('Subclass for get media container')
-
- def _get_media_url(self, media_meta):
- raise NotImplementedError('Subclass for get media url')
-
-
-class WASDTVStreamIE(WASDTVBaseIE):
- IE_NAME = 'wasdtv:stream'
- _VALID_URL = r'https?://wasd\.tv/(?P[^/#?]+)$'
- _TESTS = [{
- 'url': 'https://wasd.tv/24_7',
- 'info_dict': {
- 'id': '559738',
- 'ext': 'mp4',
- 'title': 'Live 24/7 Music',
- 'description': '24/7 Music',
- 'timestamp': int,
- 'upload_date': r're:^\d{8}$',
- 'is_live': True,
- 'view_count': int,
- },
- }]
-
- def _get_container(self, url):
- nickname = self._match_id(url)
- channel = self._fetch(f'channels/nicknames/{nickname}', video_id=nickname, description='channel')
- channel_id = channel.get('channel_id')
- containers = self._fetch(
- 'v2/media-containers', channel_id, 'running media containers',
- query={
- 'channel_id': channel_id,
- 'media_container_type': 'SINGLE',
- 'media_container_status': 'RUNNING',
- })
- if not containers:
- raise ExtractorError(f'{nickname} is offline', expected=True)
- return containers[0]
-
- def _get_media_url(self, media_meta):
- return media_meta['media_url'], True
-
-
-class WASDTVRecordIE(WASDTVBaseIE):
- IE_NAME = 'wasdtv:record'
- _VALID_URL = r'https?://wasd\.tv/[^/#?]+(?:/videos)?\?record=(?P\d+)$'
- _TESTS = [{
- 'url': 'https://wasd.tv/spacemita/videos?record=907755',
- 'md5': 'c9899dd85be4cc997816ff9f9ca516ce',
- 'info_dict': {
- 'id': '906825',
- 'ext': 'mp4',
- 'title': 'Музыкальный',
- 'description': 'md5:f510388d929ff60ae61d4c3cab3137cc',
- 'timestamp': 1645812079,
- 'upload_date': '20220225',
- 'thumbnail': r're:^https?://.+\.jpg',
- 'is_live': False,
- 'view_count': int,
- },
- }, {
- 'url': 'https://wasd.tv/spacemita?record=907755',
- 'only_matching': True,
- }]
-
- def _get_container(self, url):
- container_id = self._match_id(url)
- return self._fetch(
- f'v2/media-containers/{container_id}', container_id, 'media container')
-
- def _get_media_url(self, media_meta):
- media_archive_url = media_meta.get('media_archive_url')
- if media_archive_url:
- return media_archive_url, False
- return media_meta['media_url'], True
-
-
-class WASDTVClipIE(WASDTVBaseIE):
- IE_NAME = 'wasdtv:clip'
- _VALID_URL = r'https?://wasd\.tv/[^/#?]+/clips\?clip=(?P\d+)$'
- _TESTS = [{
- 'url': 'https://wasd.tv/spacemita/clips?clip=26804',
- 'md5': '818885e720143d7a4e776ff66fcff148',
- 'info_dict': {
- 'id': '26804',
- 'ext': 'mp4',
- 'title': 'Пуш флексит на голове стримера',
- 'timestamp': 1646682908,
- 'upload_date': '20220307',
- 'thumbnail': r're:^https?://.+\.jpg',
- 'view_count': int,
- },
- }]
-
- def _real_extract(self, url):
- clip_id = self._match_id(url)
- clip = self._fetch(f'v2/clips/{clip_id}', video_id=clip_id, description='clip')
- clip_data = clip.get('clip_data')
- formats, subtitles = self._extract_m3u8_formats_and_subtitles(clip_data.get('url'), video_id=clip_id, ext='mp4')
- return {
- 'id': clip_id,
- 'title': clip.get('clip_title') or self._og_search_title(self._download_webpage(url, clip_id, fatal=False)),
- 'thumbnails': self._extract_thumbnails(clip_data.get('preview')),
- 'timestamp': parse_iso8601(clip.get('created_at')),
- 'view_count': int_or_none(clip.get('clip_views_count')),
- 'formats': formats,
- 'subtitles': subtitles,
- }
From 35d96982f1033e36215d323317981ee17e8ab0d5 Mon Sep 17 00:00:00 2001
From: Chocobozzz
Date: Mon, 5 Feb 2024 20:58:32 +0100
Subject: [PATCH 107/175] [ie/peertube] Update instances (#9070)
Authored by: Chocobozzz
---
yt_dlp/extractor/peertube.py | 972 ++++++++++++++++++++++-------------
1 file changed, 610 insertions(+), 362 deletions(-)
diff --git a/yt_dlp/extractor/peertube.py b/yt_dlp/extractor/peertube.py
index 68e15737b978..730b2393e0a9 100644
--- a/yt_dlp/extractor/peertube.py
+++ b/yt_dlp/extractor/peertube.py
@@ -19,636 +19,902 @@
class PeerTubeIE(InfoExtractor):
_INSTANCES_RE = r'''(?:
# Taken from https://instances.joinpeertube.org/instances
- 40two\.tube|
- a\.metube\.ch|
- advtv\.ml|
- algorithmic\.tv|
- alimulama\.com|
- arcana\.fun|
- archive\.vidicon\.org|
- artefac-paris\.tv|
- auf1\.eu|
+ 0ch\.tv|
+ 3dctube\.3dcandy\.social|
+ all\.electric\.kitchen|
+ alterscope\.fr|
+ anarchy\.tube|
+ apathy\.tv|
+ apertatube\.net|
+ archive\.nocopyrightintended\.tv|
+ archive\.reclaim\.tv|
+ area51\.media|
+ astrotube-ufe\.obspm\.fr|
+ astrotube\.obspm\.fr|
+ audio\.freediverse\.com|
+ azxtube\.youssefc\.tn|
+ bark\.video|
battlepenguin\.video|
- beertube\.epgn\.ch|
- befree\.nohost\.me|
+ bava\.tv|
+ bee-tube\.fr|
+ beetoons\.tv|
+ biblion\.refchat\.net|
+ biblioteca\.theowlclub\.net|
bideoak\.argia\.eus|
- birkeundnymphe\.de|
+ bideoteka\.eus|
+ birdtu\.be|
bitcointv\.com|
- cattube\.org|
- clap\.nerv-project\.eu|
- climatejustice\.video|
+ bonn\.video|
+ breeze\.tube|
+ brioco\.live|
+ brocosoup\.fr|
+ canal\.facil\.services|
+ canard\.tube|
+ cdn01\.tilvids\.com|
+ celluloid-media\.huma-num\.fr|
+ chicago1\.peertube\.support|
+ cliptube\.org|
+ cloudtube\.ise\.fraunhofer\.de|
comf\.tube|
+ comics\.peertube\.biz|
+ commons\.tube|
+ communitymedia\.video|
conspiracydistillery\.com|
+ crank\.recoil\.org|
+ dalek\.zone|
+ dalliance\.network|
+ dangly\.parts|
darkvapor\.nohost\.me|
daschauher\.aksel\.rocks|
digitalcourage\.video|
- dreiecksnebel\.alex-detsch\.de|
- eduvid\.org|
+ displayeurope\.video|
+ ds106\.tv|
+ dud-video\.inf\.tu-dresden\.de|
+ dud175\.inf\.tu-dresden\.de|
+ dytube\.com|
+ ebildungslabor\.video|
evangelisch\.video|
- exo\.tube|
fair\.tube|
+ fedi\.video|
+ fedimovie\.com|
fediverse\.tv|
film\.k-prod\.fr|
- flim\.txmn\.tk|
+ flipboard\.video|
+ foss\.video|
+ fossfarmers\.company|
fotogramas\.politicaconciencia\.org|
- ftsi\.ru|
- gary\.vger\.cloud|
- graeber\.video|
+ freediverse\.com|
+ freesoto-u2151\.vm\.elestio\.app|
+ freesoto\.tv|
+ garr\.tv|
greatview\.video|
grypstube\.uni-greifswald\.de|
- highvoltage\.tv|
- hpstube\.fr|
- htp\.live|
- hyperreal\.tube|
+ habratube\.site|
+ ilbjach\.ru|
+ infothema\.net|
+ itvplus\.iiens\.net|
+ johnydeep\.net|
juggling\.digital|
+ jupiter\.tube|
+ kadras\.live|
kino\.kompot\.si|
kino\.schuerz\.at|
kinowolnosc\.pl|
kirche\.peertube-host\.de|
+ kiwi\.froggirl\.club|
kodcast\.com|
kolektiva\.media|
- kraut\.zone|
+ kpop\.22x22\.ru|
kumi\.tube|
+ la2\.peertube\.support|
+ la3\.peertube\.support|
+ la4\.peertube\.support|
lastbreach\.tv|
- lepetitmayennais\.fr\.nf|
- lexx\.impa\.me|
- libertynode\.tv|
- libra\.syntazia\.org|
- libremedia\.video|
+ lawsplaining\.peertube\.biz|
+ leopard\.tube|
+ live\.codinglab\.ch|
live\.libratoi\.org|
- live\.nanao\.moe|
- live\.toobnix\.org|
- livegram\.net|
- lolitube\.freedomchan\.moe|
+ live\.oldskool\.fi|
+ live\.solari\.com|
lucarne\.balsamine\.be|
- maindreieck-tv\.de|
- mani\.tube|
- manicphase\.me|
+ luxtube\.lu|
+ makertube\.net|
+ media\.econoalchemist\.com|
+ media\.exo\.cat|
media\.fsfe\.org|
media\.gzevd\.de|
- media\.inno3\.cricket|
- media\.kaitaia\.life|
+ media\.interior\.edu\.uy|
media\.krashboyz\.org|
- media\.over-world\.org|
- media\.skewed\.de|
+ media\.mzhd\.de|
+ media\.smz-ma\.de|
+ media\.theplattform\.net|
media\.undeadnetwork\.de|
+ medias\.debrouillonet\.org|
medias\.pingbase\.net|
+ mediatube\.fermalo\.fr|
melsungen\.peertube-host\.de|
- mirametube\.fr|
- mojotube\.net|
- monplaisirtube\.ddns\.net|
+ merci-la-police\.fr|
+ mindlyvideos\.com|
+ mirror\.peertube\.metalbanana\.net|
+ mirrored\.rocks|
+ mix\.video|
mountaintown\.video|
- my\.bunny\.cafe|
- myfreetube\.de|
+ movies\.metricsmaster\.eu|
+ mtube\.mooo\.com|
mytube\.kn-cloud\.de|
+ mytube\.le5emeaxe\.fr|
mytube\.madzel\.de|
- myworkoutarenapeertube\.cf|
+ nadajemy\.com|
nanawel-peertube\.dyndns\.org|
- nastub\.cz|
- offenes\.tv|
- orgdup\.media|
- ovaltube\.codinglab\.ch|
+ neat\.tube|
+ nethack\.tv|
+ nicecrew\.tv|
+ nightshift\.minnix\.dev|
+ nolog\.media|
+ nyltube\.nylarea\.com|
+ ocfedtest\.hosted\.spacebear\.ee|
+ openmedia\.edunova\.it|
p2ptv\.ru|
p\.eertu\.be|
p\.lu|
+ pastafriday\.club|
+ patriottube\.sonsofliberty\.red|
+ pcbu\.nl|
peer\.azurs\.fr|
- peertube1\.zeteo\.me|
+ peer\.d0g4\.me|
+ peer\.lukeog\.com|
+ peer\.madiator\.cloud|
+ peer\.raise-uav\.com|
+ peershare\.togart\.de|
+ peertube-blablalinux\.be|
+ peertube-demo\.learning-hub\.fr|
+ peertube-docker\.cpy\.re|
+ peertube-eu\.howlround\.com|
+ peertube-u5014\.vm\.elestio\.app|
+ peertube-us\.howlround\.com|
peertube\.020\.pl|
peertube\.0x5e\.eu|
+ peertube\.1984\.cz|
+ peertube\.2i2l\.net|
+ peertube\.adjutor\.xyz|
+ peertube\.adresse\.data\.gouv\.fr|
peertube\.alpharius\.io|
peertube\.am-networks\.fr|
peertube\.anduin\.net|
- peertube\.anzui\.dev|
- peertube\.arbleizez\.bzh|
+ peertube\.anti-logic\.com|
+ peertube\.arch-linux\.cz|
peertube\.art3mis\.de|
- peertube\.atilla\.org|
+ peertube\.artsrn\.ualberta\.ca|
+ peertube\.askan\.info|
+ peertube\.astral0pitek\.synology\.me|
peertube\.atsuchan\.page|
- peertube\.aukfood\.net|
- peertube\.aventer\.biz|
+ peertube\.automat\.click|
peertube\.b38\.rural-it\.org|
- peertube\.beeldengeluid\.nl|
peertube\.be|
+ peertube\.beeldengeluid\.nl|
peertube\.bgzashtita\.es|
- peertube\.bitsandlinux\.com|
+ peertube\.bike|
+ peertube\.bildung-ekhn\.de|
peertube\.biz|
- peertube\.boba\.best|
peertube\.br0\.fr|
peertube\.bridaahost\.ynh\.fr|
peertube\.bubbletea\.dev|
peertube\.bubuit\.net|
peertube\.cabaal\.net|
- peertube\.cats-home\.net|
- peertube\.chemnitz\.freifunk\.net|
- peertube\.chevro\.fr|
- peertube\.chrisspiegl\.com|
+ peertube\.chatinbit\.com|
+ peertube\.chaunchy\.com|
+ peertube\.chir\.rs|
+ peertube\.christianpacaud\.com|
peertube\.chtisurel\.net|
+ peertube\.chuggybumba\.com|
peertube\.cipherbliss\.com|
+ peertube\.cirkau\.art|
+ peertube\.cloud\.nerdraum\.de|
peertube\.cloud\.sans\.pub|
+ peertube\.coko\.foundation|
+ peertube\.communecter\.org|
+ peertube\.concordia\.social|
+ peertube\.corrigan\.xyz|
peertube\.cpge-brizeux\.fr|
peertube\.ctseuro\.com|
peertube\.cuatrolibertades\.org|
- peertube\.cybercirujas\.club|
- peertube\.cythin\.com|
+ peertube\.cube4fun\.net|
+ peertube\.dair-institute\.org|
peertube\.davigge\.com|
peertube\.dc\.pini\.fr|
+ peertube\.deadtom\.me|
peertube\.debian\.social|
+ peertube\.delta0189\.xyz|
peertube\.demonix\.fr|
peertube\.designersethiques\.org|
peertube\.desmu\.fr|
- peertube\.devloprog\.org|
peertube\.devol\.it|
- peertube\.dtmf\.ca|
- peertube\.ecologie\.bzh|
+ peertube\.dk|
+ peertube\.doesstuff\.social|
+ peertube\.eb8\.org|
+ peertube\.education-forum\.com|
+ peertube\.elforcer\.ru|
+ peertube\.em\.id\.lv|
+ peertube\.ethibox\.fr|
peertube\.eu\.org|
peertube\.european-pirates\.eu|
+ peertube\.eus|
peertube\.euskarabildua\.eus|
+ peertube\.expi\.studio|
+ peertube\.familie-berner\.de|
+ peertube\.familleboisteau\.fr|
+ peertube\.fedihost\.website|
peertube\.fenarinarsa\.com|
- peertube\.fomin\.site|
- peertube\.forsud\.be|
- peertube\.francoispelletier\.org|
- peertube\.freenet\.ru|
- peertube\.freetalklive\.com|
+ peertube\.festnoz\.de|
+ peertube\.forteza\.fr|
+ peertube\.freestorm\.online|
peertube\.functional\.cafe|
- peertube\.gardeludwig\.fr|
+ peertube\.gaminglinux\.fr|
peertube\.gargantia\.fr|
- peertube\.gcfamily\.fr|
+ peertube\.geekgalaxy\.fr|
+ peertube\.gemlog\.ca|
peertube\.genma\.fr|
peertube\.get-racing\.de|
+ peertube\.ghis94\.ovh|
peertube\.gidikroon\.eu|
- peertube\.gruezishop\.ch|
- peertube\.habets\.house|
- peertube\.hackerfraternity\.org|
+ peertube\.giftedmc\.com|
+ peertube\.grosist\.fr|
+ peertube\.gruntwerk\.org|
+ peertube\.gsugambit\.com|
+ peertube\.hackerfoo\.com|
+ peertube\.hellsite\.net|
+ peertube\.helvetet\.eu|
+ peertube\.histoirescrepues\.fr|
+ peertube\.home\.x0r\.fr|
+ peertube\.hyperfreedom\.org|
peertube\.ichigo\.everydayimshuflin\.com|
- peertube\.ignifi\.me|
+ peertube\.ifwo\.eu|
+ peertube\.in\.ua|
peertube\.inapurna\.org|
peertube\.informaction\.info|
peertube\.interhop\.org|
- peertube\.iselfhost\.com|
peertube\.it|
+ peertube\.it-arts\.net|
peertube\.jensdiemer\.de|
- peertube\.joffreyverd\.fr|
+ peertube\.johntheserg\.al|
+ peertube\.kaleidos\.net|
peertube\.kalua\.im|
- peertube\.kathryl\.fr|
+ peertube\.kcore\.org|
peertube\.keazilla\.net|
peertube\.klaewyss\.fr|
- peertube\.kodcast\.com|
+ peertube\.kleph\.eu|
+ peertube\.kodein\.be|
+ peertube\.kooperatywa\.tech|
+ peertube\.kriom\.net|
peertube\.kx\.studio|
+ peertube\.kyriog\.eu|
+ peertube\.la-famille-muller\.fr|
+ peertube\.labeuropereunion\.eu|
peertube\.lagvoid\.com|
- peertube\.lavallee\.tech|
- peertube\.le5emeaxe\.fr|
- peertube\.lestutosdeprocessus\.fr|
- peertube\.librenet\.co\.za|
+ peertube\.lhc\.net\.br|
+ peertube\.libresolutions\.network|
+ peertube\.libretic\.fr|
+ peertube\.librosphere\.fr|
peertube\.logilab\.fr|
+ peertube\.lon\.tv|
peertube\.louisematic\.site|
peertube\.luckow\.org|
peertube\.luga\.at|
peertube\.lyceeconnecte\.fr|
- peertube\.manalejandro\.com|
+ peertube\.madixam\.xyz|
+ peertube\.magicstone\.dev|
+ peertube\.marienschule\.de|
peertube\.marud\.fr|
- peertube\.mattone\.net|
peertube\.maxweiss\.io|
+ peertube\.miguelcr\.me|
+ peertube\.mikemestnik\.net|
+ peertube\.mobilsicher\.de|
peertube\.monlycee\.net|
peertube\.mxinfo\.fr|
- peertube\.myrasp\.eu|
- peertube\.nebelcloud\.de|
+ peertube\.naln1\.ca|
peertube\.netzbegruenung\.de|
- peertube\.newsocial\.tech|
peertube\.nicolastissot\.fr|
+ peertube\.nogafam\.fr|
+ peertube\.normalgamingcommunity\.cz|
peertube\.nz|
peertube\.offerman\.com|
+ peertube\.ohioskates\.com|
+ peertube\.onionstorm\.net|
peertube\.opencloud\.lu|
- peertube\.orthus\.link|
- peertube\.patapouf\.xyz|
- peertube\.pi2\.dev|
- peertube\.plataformess\.org|
- peertube\.pl|
- peertube\.portaesgnos\.org|
+ peertube\.otakufarms\.com|
+ peertube\.paladyn\.org|
+ peertube\.pix-n-chill\.fr|
peertube\.r2\.enst\.fr|
peertube\.r5c3\.fr|
- peertube\.radres\.xyz|
- peertube\.red|
- peertube\.robonomics\.network|
- peertube\.rtnkv\.cloud|
- peertube\.runfox\.tk|
+ peertube\.redpill-insight\.com|
+ peertube\.researchinstitute\.at|
+ peertube\.revelin\.fr|
+ peertube\.rlp\.schule|
+ peertube\.rokugan\.fr|
+ peertube\.rougevertbleu\.tv|
+ peertube\.roundpond\.net|
+ peertube\.rural-it\.org|
peertube\.satoshishop\.de|
- peertube\.scic-tetris\.org|
+ peertube\.scyldings\.com|
peertube\.securitymadein\.lu|
+ peertube\.semperpax\.com|
peertube\.semweb\.pro|
- peertube\.social\.my-wan\.de|
- peertube\.soykaf\.org|
- peertube\.stefofficiel\.me|
+ peertube\.sensin\.eu|
+ peertube\.sidh\.bzh|
+ peertube\.skorpil\.cz|
+ peertube\.smertrios\.com|
+ peertube\.sqweeb\.net|
+ peertube\.stattzeitung\.org|
peertube\.stream|
peertube\.su|
peertube\.swrs\.net|
peertube\.takeko\.cyou|
- peertube\.tangentfox\.com|
peertube\.taxinachtegel\.de|
- peertube\.thenewoil\.xyz|
+ peertube\.teftera\.com|
+ peertube\.teutronic-services\.de|
peertube\.ti-fr\.com|
peertube\.tiennot\.net|
- peertube\.troback\.com|
+ peertube\.tmp\.rcp\.tf|
peertube\.tspu\.edu\.ru|
- peertube\.tux\.ovh|
peertube\.tv|
peertube\.tweb\.tv|
- peertube\.ucy\.de|
peertube\.underworld\.fr|
- peertube\.us\.to|
- peertube\.ventresmous\.fr|
+ peertube\.vapronva\.pw|
+ peertube\.veen\.world|
+ peertube\.vesdia\.eu|
+ peertube\.virtual-assembly\.org|
+ peertube\.viviers-fibre\.net|
peertube\.vlaki\.cz|
- peertube\.w\.utnw\.de|
- peertube\.westring\.digital|
+ peertube\.wiesbaden\.social|
+ peertube\.wivodaim\.net|
+ peertube\.wtf|
+ peertube\.wtfayla\.net|
+ peertube\.xrcb\.cat|
peertube\.xwiki\.com|
+ peertube\.zd\.do|
+ peertube\.zetamc\.net|
+ peertube\.zmuuf\.org|
peertube\.zoz-serv\.org|
+ peertube\.zwindler\.fr|
peervideo\.ru|
periscope\.numenaute\.org|
- perron-tube\.de|
+ pete\.warpnine\.de|
petitlutinartube\.fr|
phijkchu\.com|
- pierre\.tube|
+ phoenixproject\.group|
piraten\.space|
- play\.rosano\.ca|
+ pirtube\.calut\.fr|
+ pityu\.flaki\.hu|
+ play\.mittdata\.se|
player\.ojamajo\.moe|
- plextube\.nl|
- pocketnetpeertube1\.nohost\.me|
- pocketnetpeertube3\.nohost\.me|
- pocketnetpeertube4\.nohost\.me|
- pocketnetpeertube5\.nohost\.me|
- pocketnetpeertube6\.nohost\.me|
- pt\.24-7\.ro|
- pt\.apathy\.top|
+ podlibre\.video|
+ portal\.digilab\.nfa\.cz|
+ private\.fedimovie\.com|
+ pt01\.lehrerfortbildung-bw\.de|
pt\.diaspodon\.fr|
- pt\.fedi\.tech|
- pt\.maciej\.website|
+ pt\.freedomwolf\.cc|
+ pt\.gordons\.gen\.nz|
+ pt\.ilyamikcoder\.com|
+ pt\.irnok\.net|
+ pt\.mezzo\.moe|
+ pt\.na4\.eu|
+ pt\.netcraft\.ch|
+ pt\.rwx\.ch|
+ pt\.sfunk1x\.com|
+ pt\.thishorsie\.rocks|
+ pt\.vern\.cc|
ptb\.lunarviews\.net|
- ptmir1\.inter21\.net|
- ptmir2\.inter21\.net|
- ptmir3\.inter21\.net|
- ptmir4\.inter21\.net|
- ptmir5\.inter21\.net|
- ptube\.horsentiers\.fr|
- ptube\.xmanifesto\.club|
- queermotion\.org|
- re-wizja\.re-medium\.com|
- regarder\.sans\.pub|
- ruraletv\.ovh|
- s1\.gegenstimme\.tv|
- s2\.veezee\.tube|
+ ptube\.de|
+ ptube\.ranranhome\.info|
+ puffy\.tube|
+ puppet\.zone|
+ qtube\.qlyoung\.net|
+ quantube\.win|
+ rankett\.net|
+ replay\.jres\.org|
+ review\.peertube\.biz|
sdmtube\.fr|
- sender-fm\.veezee\.tube|
- serv1\.wiki-tube\.de|
+ secure\.direct-live\.net|
+ secure\.scanovid\.com|
+ seka\.pona\.la|
serv3\.wiki-tube\.de|
- sickstream\.net|
- sleepy\.tube|
+ skeptube\.fr|
+ social\.fedimovie\.com|
+ socpeertube\.ru|
sovran\.video|
+ special\.videovortex\.tv|
spectra\.video|
+ stl1988\.peertube-host\.de|
+ stream\.biovisata\.lt|
+ stream\.conesphere\.cloud|
stream\.elven\.pw|
+ stream\.jurnalfm\.md|
stream\.k-prod\.fr|
- stream\.shahab\.nohost\.me|
- streamsource\.video|
+ stream\.litera\.tools|
+ stream\.nuemedia\.se|
+ stream\.rlp-media\.de|
+ stream\.vrse\.be|
studios\.racer159\.com|
- testtube\.florimond\.eu|
+ styxhexenhammer666\.com|
+ syrteplay\.obspm\.fr|
+ t\.0x0\.st|
+ tbh\.co-shaoghal\.net|
+ test-fab\.ynh\.fr|
+ testube\.distrilab\.fr|
tgi\.hosted\.spacebear\.ee|
- thaitube\.in\.th|
- the\.jokertv\.eu|
theater\.ethernia\.net|
thecool\.tube|
+ thevideoverse\.com|
tilvids\.com|
- toob\.bub\.org|
- tpaw\.video|
- truetube\.media|
- tuba\.lhub\.pl|
- tube-aix-marseille\.beta\.education\.fr|
- tube-amiens\.beta\.education\.fr|
- tube-besancon\.beta\.education\.fr|
- tube-bordeaux\.beta\.education\.fr|
- tube-clermont-ferrand\.beta\.education\.fr|
- tube-corse\.beta\.education\.fr|
- tube-creteil\.beta\.education\.fr|
- tube-dijon\.beta\.education\.fr|
- tube-education\.beta\.education\.fr|
- tube-grenoble\.beta\.education\.fr|
- tube-lille\.beta\.education\.fr|
- tube-limoges\.beta\.education\.fr|
- tube-montpellier\.beta\.education\.fr|
- tube-nancy\.beta\.education\.fr|
- tube-nantes\.beta\.education\.fr|
- tube-nice\.beta\.education\.fr|
- tube-normandie\.beta\.education\.fr|
- tube-orleans-tours\.beta\.education\.fr|
- tube-outremer\.beta\.education\.fr|
- tube-paris\.beta\.education\.fr|
- tube-poitiers\.beta\.education\.fr|
- tube-reims\.beta\.education\.fr|
- tube-rennes\.beta\.education\.fr|
- tube-strasbourg\.beta\.education\.fr|
- tube-toulouse\.beta\.education\.fr|
- tube-versailles\.beta\.education\.fr|
- tube1\.it\.tuwien\.ac\.at|
+ tinkerbetter\.tube|
+ tinsley\.video|
+ trailers\.ddigest\.com|
+ tube-action-educative\.apps\.education\.fr|
+ tube-arts-lettres-sciences-humaines\.apps\.education\.fr|
+ tube-cycle-2\.apps\.education\.fr|
+ tube-cycle-3\.apps\.education\.fr|
+ tube-education-physique-et-sportive\.apps\.education\.fr|
+ tube-enseignement-professionnel\.apps\.education\.fr|
+ tube-institutionnel\.apps\.education\.fr|
+ tube-langues-vivantes\.apps\.education\.fr|
+ tube-maternelle\.apps\.education\.fr|
+ tube-numerique-educatif\.apps\.education\.fr|
+ tube-sciences-technologies\.apps\.education\.fr|
+ tube-test\.apps\.education\.fr|
+ tube1\.perron-service\.de|
+ tube\.9minuti\.it|
tube\.abolivier\.bzh|
- tube\.ac-amiens\.fr|
- tube\.aerztefueraufklaerung\.de|
- tube\.alexx\.ml|
+ tube\.alado\.space|
tube\.amic37\.fr|
- tube\.anufrij\.de|
- tube\.apolut\.net|
- tube\.arkhalabs\.io|
+ tube\.area404\.cloud|
tube\.arthack\.nz|
- tube\.as211696\.net|
- tube\.avensio\.de|
+ tube\.asulia\.fr|
+ tube\.awkward\.company|
tube\.azbyka\.ru|
tube\.azkware\.net|
- tube\.bachaner\.fr|
- tube\.bmesh\.org|
- tube\.borked\.host|
+ tube\.bartrip\.me\.uk|
+ tube\.belowtoxic\.media|
+ tube\.bingle\.plus|
+ tube\.bit-friends\.de|
tube\.bstly\.de|
- tube\.chaoszone\.tv|
- tube\.chatelet\.ovh|
- tube\.cloud-libre\.eu|
+ tube\.chosto\.me|
tube\.cms\.garden|
- tube\.cowfee\.moe|
- tube\.cryptography\.dog|
- tube\.darknight-coffee\.org|
- tube\.dev\.lhub\.pl|
+ tube\.communia\.org|
+ tube\.cyberia\.club|
+ tube\.cybershock\.life|
+ tube\.dembased\.xyz|
+ tube\.dev\.displ\.eu|
+ tube\.digitalesozialearbeit\.de|
tube\.distrilab\.fr|
+ tube\.doortofreedom\.org|
tube\.dsocialize\.net|
+ tube\.e-jeremy\.com|
tube\.ebin\.club|
+ tube\.elemac\.fr|
+ tube\.erzbistum-hamburg\.de|
+ tube\.exozy\.me|
tube\.fdn\.fr|
- tube\.florimond\.eu|
- tube\.foxarmy\.ml|
- tube\.foxden\.party|
- tube\.frischesicht\.de|
+ tube\.fedi\.quebec|
+ tube\.fediverse\.at|
+ tube\.felinn\.org|
+ tube\.flokinet\.is|
+ tube\.foad\.me\.uk|
+ tube\.freepeople\.fr|
+ tube\.friloux\.me|
+ tube\.froth\.zone|
+ tube\.fulda\.social|
tube\.futuretic\.fr|
- tube\.gnous\.eu|
+ tube\.g1zm0\.de|
+ tube\.g4rf\.net|
+ tube\.gaiac\.io|
+ tube\.geekyboo\.net|
+ tube\.genb\.de|
+ tube\.ghk-academy\.info|
+ tube\.gi-it\.de|
tube\.grap\.coop|
tube\.graz\.social|
tube\.grin\.hu|
- tube\.hackerscop\.org|
- tube\.hordearii\.fr|
+ tube\.hokai\.lol|
+ tube\.int5\.net|
+ tube\.interhacker\.space|
+ tube\.invisible\.ch|
+ tube\.io18\.top|
+ tube\.itsg\.host|
tube\.jeena\.net|
- tube\.kai-stuht\.com|
+ tube\.kh-berlin\.de|
tube\.kockatoo\.org|
tube\.kotur\.org|
+ tube\.koweb\.fr|
+ tube\.la-dina\.net|
+ tube\.lab\.nrw|
tube\.lacaveatonton\.ovh|
+ tube\.laurent-malys\.fr|
+ tube\.leetdreams\.ch|
tube\.linkse\.media|
tube\.lokad\.com|
tube\.lucie-philou\.com|
- tube\.melonbread\.xyz|
- tube\.mfraters\.net|
- tube\.motuhake\.xyz|
- tube\.mrbesen\.de|
- tube\.nah\.re|
- tube\.nchoco\.net|
+ tube\.media-techport\.de|
+ tube\.morozoff\.pro|
+ tube\.neshweb\.net|
+ tube\.nestor\.coop|
+ tube\.network\.europa\.eu|
+ tube\.nicfab\.eu|
+ tube\.nieuwwestbrabant\.nl|
+ tube\.nogafa\.org|
tube\.novg\.net|
tube\.nox-rhea\.org|
tube\.nuagelibre\.fr|
+ tube\.numerique\.gouv\.fr|
+ tube\.nuxnik\.com|
tube\.nx12\.net|
tube\.octaplex\.net|
- tube\.odat\.xyz|
tube\.oisux\.org|
+ tube\.okcinfo\.news|
+ tube\.onlinekirche\.net|
tube\.opportunis\.me|
+ tube\.oraclefilms\.com|
tube\.org\.il|
- tube\.ortion\.xyz|
- tube\.others\.social|
+ tube\.pacapime\.ovh|
+ tube\.parinux\.org|
+ tube\.pastwind\.top|
tube\.picasoft\.net|
- tube\.plomlompom\.com|
+ tube\.pilgerweg-21\.de|
tube\.pmj\.rocks|
+ tube\.pol\.social|
+ tube\.ponsonaille\.fr|
tube\.portes-imaginaire\.org|
+ tube\.public\.apolut\.net|
+ tube\.pustule\.org|
tube\.pyngu\.com|
+ tube\.querdenken-711\.de|
tube\.rebellion\.global|
+ tube\.reseau-canope\.fr|
tube\.rhythms-of-resistance\.org|
- tube\.rita\.moe|
+ tube\.risedsky\.ovh|
+ tube\.rooty\.fr|
tube\.rsi\.cnr\.it|
- tube\.s1gm4\.eu|
- tube\.saumon\.io|
+ tube\.ryne\.moe|
tube\.schleuss\.online|
tube\.schule\.social|
- tube\.seditio\.fr|
+ tube\.sekretaerbaer\.net|
tube\.shanti\.cafe|
tube\.shela\.nu|
tube\.skrep\.in|
+ tube\.sleeping\.town|
tube\.sp-codes\.de|
- tube\.sp4ke\.com|
- tube\.superseriousbusiness\.org|
+ tube\.spdns\.org|
+ tube\.systerserver\.net|
tube\.systest\.eu|
tube\.tappret\.fr|
- tube\.tardis\.world|
- tube\.toontoet\.nl|
+ tube\.techeasy\.org|
+ tube\.thierrytalbert\.fr|
+ tube\.tinfoil-hat\.net|
+ tube\.toldi\.eu|
tube\.tpshd\.de|
+ tube\.trax\.im|
tube\.troopers\.agency|
+ tube\.ttk\.is|
+ tube\.tuxfriend\.fr|
tube\.tylerdavis\.xyz|
+ tube\.ullihome\.de|
+ tube\.ulne\.be|
tube\.undernet\.uy|
- tube\.vigilian-consulting\.nl|
- tube\.vraphim\.com|
- tube\.wehost\.lgbt|
- tube\.wien\.rocks|
+ tube\.vrpnet\.org|
tube\.wolfe\.casa|
tube\.xd0\.de|
+ tube\.xn--baw-joa\.social|
tube\.xy-space\.de|
tube\.yapbreak\.fr|
tubedu\.org|
- tubes\.jodh\.us|
- tuktube\.com|
- turkum\.me|
+ tubulus\.openlatin\.org|
+ turtleisland\.video|
tututu\.tube|
- tuvideo\.encanarias\.info|
- tv1\.cocu\.cc|
- tv1\.gomntu\.space|
- tv2\.cocu\.cc|
+ tv\.adast\.dk|
tv\.adn\.life|
+ tv\.arns\.lt|
tv\.atmx\.ca|
- tv\.bitma\.st|
- tv\.generallyrubbish\.net\.au|
+ tv\.based\.quest|
+ tv\.farewellutopia\.com|
+ tv\.filmfreedom\.net|
+ tv\.gravitons\.org|
+ tv\.io\.seg\.br|
tv\.lumbung\.space|
- tv\.mattchristiansenmedia\.com|
- tv\.netwhood\.online|
- tv\.neue\.city|
- tv\.piejacker\.net|
tv\.pirateradio\.social|
+ tv\.pirati\.cz|
+ tv\.santic-zombie\.ru|
tv\.undersco\.re|
+ tv\.zonepl\.net|
tvox\.ru|
twctube\.twc-zone\.eu|
- unfilter\.tube|
+ twobeek\.com|
+ urbanists\.video|
+ v\.9tail\.net|
v\.basspistol\.org|
+ v\.j4\.lc|
v\.kisombrella\.top|
- v\.lastorder\.xyz|
+ v\.koa\.im|
+ v\.kyaru\.xyz|
v\.lor\.sh|
- v\.phreedom\.club|
- v\.sil\.sh|
- v\.szy\.io|
- v\.xxxapex\.com|
- veezee\.tube|
- vid\.dascoyote\.xyz|
- vid\.garwood\.io|
- vid\.ncrypt\.at|
- vid\.pravdastalina\.info|
- vid\.qorg11\.net|
- vid\.rajeshtaylor\.com|
- vid\.samtripoli\.com|
- vid\.werefox\.dev|
+ v\.mkp\.ca|
+ v\.posm\.gay|
+ v\.slaycer\.top|
+ veedeo\.org|
+ vhs\.absturztau\.be|
+ vid\.cthos\.dev|
+ vid\.kinuseka\.us|
+ vid\.mkp\.ca|
+ vid\.nocogabriel\.fr|
+ vid\.norbipeti\.eu|
+ vid\.northbound\.online|
+ vid\.ohboii\.de|
+ vid\.plantplotting\.co\.uk|
+ vid\.pretok\.tv|
+ vid\.prometheus\.systems|
+ vid\.soafen\.love|
+ vid\.twhtv\.club|
vid\.wildeboer\.net|
video-cave-v2\.de|
+ video-liberty\.com|
video\.076\.ne\.jp|
video\.1146\.nohost\.me|
- video\.altertek\.org|
+ video\.9wd\.eu|
+ video\.abraum\.de|
+ video\.ados\.accoord\.fr|
+ video\.amiga-ng\.org|
video\.anartist\.org|
- video\.apps\.thedoodleproject\.net|
- video\.artist\.cx|
video\.asgardius\.company|
- video\.balsillie\.net|
+ video\.audiovisuel-participatif\.org|
video\.bards\.online|
- video\.binarydad\.com|
+ video\.barkoczy\.social|
+ video\.benetou\.fr|
+ video\.beyondwatts\.social|
+ video\.bgeneric\.net|
+ video\.bilecik\.edu\.tr|
video\.blast-info\.fr|
+ video\.bmu\.cloud|
video\.catgirl\.biz|
+ video\.causa-arcana\.com|
+ video\.chasmcity\.net|
+ video\.chbmeyer\.de|
video\.cigliola\.com|
- video\.cm-en-transition\.fr|
+ video\.citizen4\.eu|
+ video\.clumsy\.computer|
+ video\.cnnumerique\.fr|
+ video\.cnr\.it|
video\.cnt\.social|
video\.coales\.co|
- video\.codingfield\.com|
- video\.comptoir\.net|
video\.comune\.trento\.it|
- video\.cpn\.so|
+ video\.coyp\.us|
video\.csc49\.fr|
- video\.cybre\.town|
- video\.demokratischer-sommer\.de|
- video\.discord-insoumis\.fr|
- video\.dolphincastle\.com|
+ video\.davduf\.net|
+ video\.davejansen\.com|
+ video\.dlearning\.nl|
+ video\.dnfi\.no|
video\.dresden\.network|
- video\.ecole-89\.com|
- video\.elgrillolibertario\.org|
+ video\.drgnz\.club|
+ video\.dudenas\.lt|
+ video\.eientei\.org|
+ video\.ellijaymakerspace\.org|
video\.emergeheart\.info|
video\.eradicatinglove\.xyz|
- video\.ethantheenigma\.me|
- video\.exodus-privacy\.eu\.org|
- video\.fbxl\.net|
+ video\.everythingbagel\.me|
+ video\.extremelycorporate\.ca|
+ video\.fabiomanganiello\.com|
+ video\.fedi\.bzh|
video\.fhtagn\.org|
- video\.greenmycity\.eu|
- video\.guerredeclasse\.fr|
+ video\.firehawk-systems\.com|
+ video\.fox-romka\.ru|
+ video\.fuss\.bz\.it|
+ video\.glassbeadcollective\.org|
+ video\.graine-pdl\.org|
video\.gyt\.is|
- video\.hackers\.town|
+ video\.hainry\.fr|
video\.hardlimit\.com|
- video\.hooli\.co|
+ video\.hostux\.net|
video\.igem\.org|
+ video\.infojournal\.fr|
video\.internet-czas-dzialac\.pl|
+ video\.interru\.io|
+ video\.ipng\.ch|
+ video\.ironsysadmin\.com|
video\.islameye\.com|
- video\.kicik\.fr|
+ video\.jacen\.moe|
+ video\.jadin\.me|
+ video\.jeffmcbride\.net|
+ video\.jigmedatse\.com|
video\.kuba-orlik\.name|
- video\.kyushojitsu\.ca|
+ video\.lacalligramme\.fr|
+ video\.lanceurs-alerte\.fr|
+ video\.laotra\.red|
+ video\.lapineige\.fr|
+ video\.laraffinerie\.re|
video\.lavolte\.net|
- video\.lespoesiesdheloise\.fr|
video\.liberta\.vip|
- video\.liege\.bike|
+ video\.libreti\.net|
+ video\.licentia\.net|
video\.linc\.systems|
video\.linux\.it|
video\.linuxtrent\.it|
- video\.lokal\.social|
+ video\.liveitlive\.show|
video\.lono\.space|
- video\.lunasqu\.ee|
+ video\.lrose\.de|
+ video\.lunago\.net|
video\.lundi\.am|
+ video\.lycee-experimental\.org|
+ video\.maechler\.cloud|
video\.marcorennmaus\.de|
video\.mass-trespass\.uk|
+ video\.matomocamp\.org|
+ video\.medienzentrum-harburg\.de|
+ video\.mentality\.rip|
+ video\.metaversum\.wtf|
+ video\.midreality\.com|
+ video\.mttv\.it|
video\.mugoreve\.fr|
- video\.mundodesconocido\.com|
+ video\.mxtthxw\.art|
video\.mycrowd\.ca|
+ video\.niboe\.info|
video\.nogafam\.es|
- video\.odayacres\.farm|
+ video\.nstr\.no|
+ video\.occm\.cc|
+ video\.off-investigation\.fr|
+ video\.olos311\.org|
+ video\.ordinobsolete\.fr|
+ video\.osvoj\.ru|
+ video\.ourcommon\.cloud|
video\.ozgurkon\.org|
- video\.p1ng0ut\.social|
- video\.p3x\.de|
video\.pcf\.fr|
- video\.pony\.gallery|
- video\.potate\.space|
- video\.pourpenser\.pro|
- video\.progressiv\.dev|
+ video\.pcgaldo\.com|
+ video\.phyrone\.de|
+ video\.poul\.org|
+ video\.publicspaces\.net|
+ video\.pullopen\.xyz|
+ video\.r3s\.nrw|
+ video\.rainevixen\.com|
video\.resolutions\.it|
- video\.rw501\.de|
- video\.screamer\.wiki|
- video\.sdm-tools\.net|
+ video\.retroedge\.tech|
+ video\.rhizome\.org|
+ video\.rlp-media\.de|
+ video\.rs-einrich\.de|
+ video\.rubdos\.be|
+ video\.sadmin\.io|
video\.sftblw\.moe|
video\.shitposter\.club|
- video\.skyn3t\.in|
+ video\.simplex-software\.ru|
+ video\.slipfox\.xyz|
+ video\.snug\.moe|
+ video\.software-fuer-engagierte\.de|
video\.soi\.ch|
- video\.stuartbrand\.co\.uk|
+ video\.sonet\.ws|
+ video\.surazal\.net|
+ video\.taskcards\.eu|
+ video\.team-lcbs\.eu|
+ video\.techforgood\.social|
+ video\.telemillevaches\.net|
+ video\.thepolarbear\.co\.uk|
video\.thinkof\.name|
- video\.toot\.pt|
+ video\.tii\.space|
+ video\.tkz\.es|
+ video\.trankil\.info|
video\.triplea\.fr|
+ video\.tum\.social|
video\.turbo\.chat|
+ video\.uriopss-pdl\.fr|
+ video\.ustim\.ru|
+ video\.ut0pia\.org|
video\.vaku\.org\.ua|
+ video\.vegafjord\.me|
video\.veloma\.org|
video\.violoncello\.ch|
- video\.wilkie\.how|
- video\.wsf2021\.info|
- videorelay\.co|
+ video\.voidconspiracy\.band|
+ video\.wakkeren\.nl|
+ video\.windfluechter\.org|
+ video\.ziez\.eu|
videos-passages\.huma-num\.fr|
- videos\.3d-wolf\.com|
+ videos\.aadtp\.be|
videos\.ahp-numerique\.fr|
- videos\.alexandrebadalo\.pt|
+ videos\.alamaisondulibre\.org|
videos\.archigny\.net|
+ videos\.aroaduntraveled\.com|
+ videos\.b4tech\.org|
videos\.benjaminbrady\.ie|
- videos\.buceoluegoexisto\.com|
- videos\.capas\.se|
- videos\.casually\.cat|
+ videos\.bik\.opencloud\.lu|
videos\.cloudron\.io|
+ videos\.codingotaku\.com|
videos\.coletivos\.org|
+ videos\.collate\.social|
videos\.danksquad\.org|
- videos\.denshi\.live|
- videos\.fromouter\.space|
+ videos\.digitaldragons\.eu|
+ videos\.dromeadhere\.fr|
+ videos\.explain-it\.org|
+ videos\.factsonthegroundshow\.com|
+ videos\.foilen\.com|
videos\.fsci\.in|
+ videos\.gamercast\.net|
+ videos\.gianmarco\.gg|
videos\.globenet\.org|
+ videos\.grafo\.zone|
videos\.hauspie\.fr|
videos\.hush\.is|
+ videos\.hyphalfusion\.network|
+ videos\.icum\.to|
+ videos\.im\.allmendenetz\.de|
+ videos\.jacksonchen666\.com|
videos\.john-livingston\.fr|
- videos\.jordanwarne\.xyz|
- videos\.lavoixdessansvoix\.org|
+ videos\.knazarov\.com|
+ videos\.kuoushi\.com|
+ videos\.laliguepaysdelaloire\.org|
+ videos\.lemouvementassociatif-pdl\.org|
videos\.leslionsfloorball\.fr|
- videos\.lucero\.top|
- videos\.martyn\.berlin|
+ videos\.librescrum\.org|
videos\.mastodont\.cat|
- videos\.monstro1\.com|
- videos\.npo\.city|
- videos\.optoutpod\.com|
- videos\.petch\.rocks|
- videos\.pzelawski\.xyz|
+ videos\.metus\.ca|
+ videos\.miolo\.org|
+ videos\.offroad\.town|
+ videos\.openmandriva\.org|
+ videos\.parleur\.net|
+ videos\.pcorp\.us|
+ videos\.pop\.eu\.com|
videos\.rampin\.org|
+ videos\.rauten\.co\.za|
+ videos\.ritimo\.org|
+ videos\.sarcasmstardust\.com|
videos\.scanlines\.xyz|
videos\.shmalls\.pw|
- videos\.sibear\.fr|
videos\.stadtfabrikanten\.org|
- videos\.tankernn\.eu|
+ videos\.supertuxkart\.net|
videos\.testimonia\.org|
- videos\.thisishowidontdisappear\.com|
- videos\.traumaheilung\.net|
+ videos\.thinkerview\.com|
+ videos\.torrenezzi10\.xyz|
videos\.trom\.tf|
- videos\.wakkerewereld\.nu|
- videos\.weblib\.re|
+ videos\.utsukta\.org|
+ videos\.viorsan\.com|
+ videos\.wherelinux\.xyz|
+ videos\.wikilibriste\.fr|
videos\.yesil\.club|
+ videos\.yeswiki\.net|
+ videotube\.duckdns\.org|
+ vids\.capypara\.de|
vids\.roshless\.me|
+ vids\.stary\.pc\.pl|
vids\.tekdmn\.me|
- vidz\.dou\.bet|
- vod\.lumikko\.dev|
- vs\.uniter\.network|
+ vidz\.julien\.ovh|
+ views\.southfox\.me|
+ virtual-girls-are\.definitely-for\.me|
+ viste\.pt|
+ vnchich\.com|
+ vnop\.org|
+ vod\.newellijay\.tv|
+ voluntarytube\.com|
+ vtr\.chikichiki\.tube|
vulgarisation-informatique\.fr|
- watch\.breadtube\.tv|
- watch\.deranalyst\.ch|
+ watch\.easya\.solutions|
+ watch\.goodluckgabe\.life|
watch\.ignorance\.eu|
- watch\.krazy\.party|
+ watch\.jimmydore\.com|
watch\.libertaria\.space|
- watch\.rt4mn\.org|
- watch\.softinio\.com|
+ watch\.nuked\.social|
+ watch\.ocaml\.org|
+ watch\.thelema\.social|
watch\.tubelab\.video|
web-fellow\.de|
webtv\.vandoeuvre\.net|
- wechill\.space|
+ wetubevid\.online|
wikileaks\.video|
wiwi\.video|
- worldofvids\.com|
- wwtube\.net|
- www4\.mir\.inter21\.net|
- www\.birkeundnymphe\.de|
- www\.captain-german\.com|
- www\.wiki-tube\.de|
+ wow\.such\.disappointment\.fail|
+ www\.jvideos\.net|
+ www\.kotikoff\.net|
+ www\.makertube\.net|
+ www\.mypeer\.tube|
+ www\.nadajemy\.com|
+ www\.neptube\.io|
+ www\.rocaguinarda\.tv|
+ www\.vnshow\.net|
xxivproduction\.video|
- xxx\.noho\.st|
+ yt\.orokoro\.ru|
+ ytube\.retronerd\.at|
+ zumvideo\.de|
# from youtube-dl
peertube\.rainbowswingers\.net|
@@ -1305,24 +1571,6 @@ class PeerTubePlaylistIE(InfoExtractor):
(?P[^/]+)
''' % (PeerTubeIE._INSTANCES_RE, '|'.join(_TYPES.keys()))
_TESTS = [{
- 'url': 'https://peertube.tux.ovh/w/p/3af94cba-95e8-4b74-b37a-807ab6d82526',
- 'info_dict': {
- 'id': '3af94cba-95e8-4b74-b37a-807ab6d82526',
- 'description': 'playlist',
- 'timestamp': 1611171863,
- 'title': 'playlist',
- },
- 'playlist_mincount': 6,
- }, {
- 'url': 'https://peertube.tux.ovh/w/p/wkyqcQBnsvFxtUB2pkYc1e',
- 'info_dict': {
- 'id': 'wkyqcQBnsvFxtUB2pkYc1e',
- 'description': 'Cette liste de vidéos contient uniquement les jeux qui peuvent être terminés en une seule vidéo.',
- 'title': 'Let\'s Play',
- 'timestamp': 1604147331,
- },
- 'playlist_mincount': 6,
- }, {
'url': 'https://peertube.debian.social/w/p/hFdJoTuyhNJVa1cDWd1d12',
'info_dict': {
'id': 'hFdJoTuyhNJVa1cDWd1d12',
From 05420227aaab60a39c0f9ade069c5862be36b1fa Mon Sep 17 00:00:00 2001
From: SirElderling <148036781+SirElderling@users.noreply.github.com>
Date: Mon, 5 Feb 2024 20:39:07 +0000
Subject: [PATCH 108/175] [ie/nytimes] Extract timestamp (#9142)
Authored by: SirElderling
---
yt_dlp/extractor/nytimes.py | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/yt_dlp/extractor/nytimes.py b/yt_dlp/extractor/nytimes.py
index 354eb02c3485..3019202a2e8a 100644
--- a/yt_dlp/extractor/nytimes.py
+++ b/yt_dlp/extractor/nytimes.py
@@ -32,6 +32,7 @@ class NYTimesBaseIE(InfoExtractor):
renderedRepresentation
}
duration
+ firstPublished
promotionalHeadline
promotionalMedia {
... on Image {
@@ -124,6 +125,7 @@ def _extract_video(self, media_id):
'id': media_id,
'title': data.get('promotionalHeadline'),
'description': data.get('summary'),
+ 'timestamp': parse_iso8601(data.get('firstPublished')),
'duration': float_or_none(data.get('duration'), scale=1000),
'creator': ', '.join(traverse_obj(data, ( # TODO: change to 'creators'
'bylines', ..., 'renderedRepresentation', {lambda x: remove_start(x, 'By ')}))),
@@ -145,8 +147,8 @@ class NYTimesIE(NYTimesBaseIE):
'ext': 'mp4',
'title': 'Verbatim: What Is a Photocopier?',
'description': 'md5:93603dada88ddbda9395632fdc5da260',
- 'timestamp': 1398631707, # FIXME
- 'upload_date': '20140427', # FIXME
+ 'timestamp': 1398646132,
+ 'upload_date': '20140428',
'creator': 'Brett Weiner',
'thumbnail': r're:https?://\w+\.nyt.com/images/.+\.jpg',
'duration': 419,
@@ -310,6 +312,8 @@ class NYTimesCookingIE(NYTimesBaseIE):
'ext': 'mp4',
'title': 'How to Make Mac and Cheese',
'description': 'md5:b8f2f33ec1fb7523b21367147c9594f1',
+ 'timestamp': 1522950315,
+ 'upload_date': '20180405',
'duration': 9.51,
'creator': 'Alison Roman',
'thumbnail': r're:https?://\w+\.nyt.com/images/.*\.jpg',
From 540b68298192874c75ad5ee4589bed64d02a7d55 Mon Sep 17 00:00:00 2001
From: Dmitry Meyer
Date: Fri, 9 Feb 2024 18:34:56 +0300
Subject: [PATCH 109/175] [ie/Boosty] Add extractor (#9144)
Closes #5900, Closes #8704
Authored by: un-def
---
yt_dlp/extractor/_extractors.py | 1 +
yt_dlp/extractor/boosty.py | 209 ++++++++++++++++++++++++++++++++
2 files changed, 210 insertions(+)
create mode 100644 yt_dlp/extractor/boosty.py
diff --git a/yt_dlp/extractor/_extractors.py b/yt_dlp/extractor/_extractors.py
index e7dd34c77bda..5d1dd60386ce 100644
--- a/yt_dlp/extractor/_extractors.py
+++ b/yt_dlp/extractor/_extractors.py
@@ -257,6 +257,7 @@
from .bloomberg import BloombergIE
from .bokecc import BokeCCIE
from .bongacams import BongaCamsIE
+from .boosty import BoostyIE
from .bostonglobe import BostonGlobeIE
from .box import BoxIE
from .boxcast import BoxCastVideoIE
diff --git a/yt_dlp/extractor/boosty.py b/yt_dlp/extractor/boosty.py
new file mode 100644
index 000000000000..fb14ca146761
--- /dev/null
+++ b/yt_dlp/extractor/boosty.py
@@ -0,0 +1,209 @@
+from .common import InfoExtractor
+from .youtube import YoutubeIE
+from ..utils import (
+ ExtractorError,
+ int_or_none,
+ qualities,
+ str_or_none,
+ url_or_none,
+)
+from ..utils.traversal import traverse_obj
+
+
+class BoostyIE(InfoExtractor):
+ _VALID_URL = r'https?://(?:www\.)?boosty\.to/(?P[^/#?]+)/posts/(?P[^/#?]+)'
+ _TESTS = [{
+ # single ok_video
+ 'url': 'https://boosty.to/kuplinov/posts/e55d050c-e3bb-4873-a7db-ac7a49b40c38',
+ 'info_dict': {
+ 'id': 'd7473824-352e-48e2-ae53-d4aa39459968',
+ 'title': 'phasma_3',
+ 'channel': 'Kuplinov',
+ 'channel_id': '7958701',
+ 'timestamp': 1655031975,
+ 'upload_date': '20220612',
+ 'release_timestamp': 1655049000,
+ 'release_date': '20220612',
+ 'modified_timestamp': 1668680993,
+ 'modified_date': '20221117',
+ 'tags': ['куплинов', 'phasmophobia'],
+ 'like_count': int,
+ 'ext': 'mp4',
+ 'duration': 105,
+ 'view_count': int,
+ 'thumbnail': r're:^https://i\.mycdn\.me/videoPreview\?',
+ },
+ }, {
+ # multiple ok_video
+ 'url': 'https://boosty.to/maddyson/posts/0c652798-3b35-471f-8b48-a76a0b28736f',
+ 'info_dict': {
+ 'id': '0c652798-3b35-471f-8b48-a76a0b28736f',
+ 'title': 'то что не пропустил юта6',
+ 'channel': 'Илья Давыдов',
+ 'channel_id': '6808257',
+ 'timestamp': 1694017040,
+ 'upload_date': '20230906',
+ 'release_timestamp': 1694017040,
+ 'release_date': '20230906',
+ 'modified_timestamp': 1694071178,
+ 'modified_date': '20230907',
+ 'like_count': int,
+ },
+ 'playlist_count': 3,
+ 'playlist': [{
+ 'info_dict': {
+ 'id': 'cc325a9f-a563-41c6-bf47-516c1b506c9a',
+ 'title': 'то что не пропустил юта6',
+ 'channel': 'Илья Давыдов',
+ 'channel_id': '6808257',
+ 'timestamp': 1694017040,
+ 'upload_date': '20230906',
+ 'release_timestamp': 1694017040,
+ 'release_date': '20230906',
+ 'modified_timestamp': 1694071178,
+ 'modified_date': '20230907',
+ 'like_count': int,
+ 'ext': 'mp4',
+ 'duration': 31204,
+ 'view_count': int,
+ 'thumbnail': r're:^https://i\.mycdn\.me/videoPreview\?',
+ },
+ }, {
+ 'info_dict': {
+ 'id': 'd07b0a72-9493-4512-b54e-55ce468fd4b7',
+ 'title': 'то что не пропустил юта6',
+ 'channel': 'Илья Давыдов',
+ 'channel_id': '6808257',
+ 'timestamp': 1694017040,
+ 'upload_date': '20230906',
+ 'release_timestamp': 1694017040,
+ 'release_date': '20230906',
+ 'modified_timestamp': 1694071178,
+ 'modified_date': '20230907',
+ 'like_count': int,
+ 'ext': 'mp4',
+ 'duration': 25704,
+ 'view_count': int,
+ 'thumbnail': r're:^https://i\.mycdn\.me/videoPreview\?',
+ },
+ }, {
+ 'info_dict': {
+ 'id': '4a3bba32-78c8-422a-9432-2791aff60b42',
+ 'title': 'то что не пропустил юта6',
+ 'channel': 'Илья Давыдов',
+ 'channel_id': '6808257',
+ 'timestamp': 1694017040,
+ 'upload_date': '20230906',
+ 'release_timestamp': 1694017040,
+ 'release_date': '20230906',
+ 'modified_timestamp': 1694071178,
+ 'modified_date': '20230907',
+ 'like_count': int,
+ 'ext': 'mp4',
+ 'duration': 31867,
+ 'view_count': int,
+ 'thumbnail': r're:^https://i\.mycdn\.me/videoPreview\?',
+ },
+ }],
+ }, {
+ # single external video (youtube)
+ 'url': 'https://boosty.to/denischuzhoy/posts/6094a487-bcec-4cf8-a453-43313b463c38',
+ 'info_dict': {
+ 'id': 'EXelTnve5lY',
+ 'title': 'Послание Президента Федеральному Собранию | Класс народа',
+ 'upload_date': '20210425',
+ 'channel': 'Денис Чужой',
+ 'tags': 'count:10',
+ 'like_count': int,
+ 'ext': 'mp4',
+ 'duration': 816,
+ 'view_count': int,
+ 'thumbnail': r're:^https://i\.ytimg\.com/',
+ 'age_limit': 0,
+ 'availability': 'public',
+ 'categories': list,
+ 'channel_follower_count': int,
+ 'channel_id': 'UCCzVNbWZfYpBfyofCCUD_0w',
+ 'channel_is_verified': bool,
+ 'channel_url': r're:^https://www\.youtube\.com/',
+ 'comment_count': int,
+ 'description': str,
+ 'heatmap': 'count:100',
+ 'live_status': str,
+ 'playable_in_embed': bool,
+ 'uploader': str,
+ 'uploader_id': str,
+ 'uploader_url': r're:^https://www\.youtube\.com/',
+ },
+ }]
+
+ _MP4_TYPES = ('tiny', 'lowest', 'low', 'medium', 'high', 'full_hd', 'quad_hd', 'ultra_hd')
+
+ def _extract_formats(self, player_urls, video_id):
+ formats = []
+ quality = qualities(self._MP4_TYPES)
+ for player_url in traverse_obj(player_urls, lambda _, v: url_or_none(v['url'])):
+ url = player_url['url']
+ format_type = player_url.get('type')
+ if format_type in ('hls', 'hls_live', 'live_ondemand_hls', 'live_playback_hls'):
+ formats.extend(self._extract_m3u8_formats(url, video_id, m3u8_id='hls', fatal=False))
+ elif format_type in ('dash', 'dash_live', 'live_playback_dash'):
+ formats.extend(self._extract_mpd_formats(url, video_id, mpd_id='dash', fatal=False))
+ elif format_type in self._MP4_TYPES:
+ formats.append({
+ 'url': url,
+ 'ext': 'mp4',
+ 'format_id': format_type,
+ 'quality': quality(format_type),
+ })
+ else:
+ self.report_warning(f'Unknown format type: {format_type!r}')
+ return formats
+
+ def _real_extract(self, url):
+ user, post_id = self._match_valid_url(url).group('user', 'post_id')
+ post = self._download_json(
+ f'https://api.boosty.to/v1/blog/{user}/post/{post_id}', post_id,
+ note='Downloading post data', errnote='Unable to download post data')
+
+ post_title = post.get('title')
+ if not post_title:
+ self.report_warning('Unable to extract post title. Falling back to parsing html page')
+ webpage = self._download_webpage(url, video_id=post_id)
+ post_title = self._og_search_title(webpage, default=None) or self._html_extract_title(webpage)
+
+ common_metadata = {
+ 'title': post_title,
+ **traverse_obj(post, {
+ 'channel': ('user', 'name', {str}),
+ 'channel_id': ('user', 'id', {str_or_none}),
+ 'timestamp': ('createdAt', {int_or_none}),
+ 'release_timestamp': ('publishTime', {int_or_none}),
+ 'modified_timestamp': ('updatedAt', {int_or_none}),
+ 'tags': ('tags', ..., 'title', {str}),
+ 'like_count': ('count', 'likes', {int_or_none}),
+ }),
+ }
+ entries = []
+ for item in traverse_obj(post, ('data', ..., {dict})):
+ item_type = item.get('type')
+ if item_type == 'video' and url_or_none(item.get('url')):
+ entries.append(self.url_result(item['url'], YoutubeIE))
+ elif item_type == 'ok_video':
+ video_id = item.get('id') or post_id
+ entries.append({
+ 'id': video_id,
+ 'formats': self._extract_formats(item.get('playerUrls'), video_id),
+ **common_metadata,
+ **traverse_obj(item, {
+ 'title': ('title', {str}),
+ 'duration': ('duration', {int_or_none}),
+ 'view_count': ('viewsCounter', {int_or_none}),
+ 'thumbnail': (('previewUrl', 'defaultPreview'), {url_or_none}),
+ }, get_all=False)})
+
+ if not entries:
+ raise ExtractorError('No videos found', expected=True)
+ if len(entries) == 1:
+ return entries[0]
+ return self.playlist_result(entries, post_id, post_title, **common_metadata)
From 882e3b753c79c7799ce135c3a5edb72494b576af Mon Sep 17 00:00:00 2001
From: "lauren n. liberda"
Date: Sat, 10 Feb 2024 00:11:34 +0100
Subject: [PATCH 110/175] [ie/tvp] Support livestreams (#8860)
Closes #8824
Authored by: selfisekai
---
yt_dlp/extractor/tvp.py | 21 +++++++++++++++++----
1 file changed, 17 insertions(+), 4 deletions(-)
diff --git a/yt_dlp/extractor/tvp.py b/yt_dlp/extractor/tvp.py
index 2aa0dd870a63..a8d00e243ad4 100644
--- a/yt_dlp/extractor/tvp.py
+++ b/yt_dlp/extractor/tvp.py
@@ -21,7 +21,7 @@
class TVPIE(InfoExtractor):
IE_NAME = 'tvp'
IE_DESC = 'Telewizja Polska'
- _VALID_URL = r'https?://(?:[^/]+\.)?(?:tvp(?:parlament)?\.(?:pl|info)|tvpworld\.com|swipeto\.pl)/(?:(?!\d+/)[^/]+/)*(?P\d+)'
+ _VALID_URL = r'https?://(?:[^/]+\.)?(?:tvp(?:parlament)?\.(?:pl|info)|tvpworld\.com|swipeto\.pl)/(?:(?!\d+/)[^/]+/)*(?P\d+)(?:[/?#]|$)'
_TESTS = [{
# TVPlayer 2 in js wrapper
@@ -514,7 +514,7 @@ def _parse_video(self, video, with_url=True):
class TVPVODVideoIE(TVPVODBaseIE):
IE_NAME = 'tvp:vod'
- _VALID_URL = r'https?://vod\.tvp\.pl/[a-z\d-]+,\d+/[a-z\d-]+(?\d+)(?:\?[^#]+)?(?:#.+)?$'
+ _VALID_URL = r'https?://vod\.tvp\.pl/(?P[a-z\d-]+,\d+)/[a-z\d-]+(?\d+)/?(?:[?#]|$)'
_TESTS = [{
'url': 'https://vod.tvp.pl/dla-dzieci,24/laboratorium-alchemika-odcinki,309338/odcinek-24,S01E24,311357',
@@ -560,12 +560,23 @@ class TVPVODVideoIE(TVPVODBaseIE):
'thumbnail': 're:https?://.+',
},
'params': {'skip_download': 'm3u8'},
+ }, {
+ 'url': 'https://vod.tvp.pl/live,1/tvp-world,399731',
+ 'info_dict': {
+ 'id': '399731',
+ 'ext': 'mp4',
+ 'title': r're:TVP WORLD \d{4}-\d{2}-\d{2} \d{2}:\d{2}',
+ 'live_status': 'is_live',
+ 'thumbnail': 're:https?://.+',
+ },
}]
def _real_extract(self, url):
- video_id = self._match_id(url)
+ category, video_id = self._match_valid_url(url).group('category', 'id')
- info_dict = self._parse_video(self._call_api(f'vods/{video_id}', video_id), with_url=False)
+ is_live = category == 'live,1'
+ entity = 'lives' if is_live else 'vods'
+ info_dict = self._parse_video(self._call_api(f'{entity}/{video_id}', video_id), with_url=False)
playlist = self._call_api(f'{video_id}/videos/playlist', video_id, query={'videoType': 'MOVIE'})
@@ -582,6 +593,8 @@ def _real_extract(self, url):
'ext': 'ttml',
})
+ info_dict['is_live'] = is_live
+
return info_dict
From a1b778428991b1779203bac243ef4e9b6baea90c Mon Sep 17 00:00:00 2001
From: bashonly
Date: Sun, 11 Feb 2024 14:58:18 +0100
Subject: [PATCH 111/175] [build] Move bundle scripts into `bundle` submodule
Authored by: bashonly
---
.github/workflows/build.yml | 20 ++++-----
.github/workflows/release-master.yml | 2 +-
.github/workflows/release-nightly.yml | 2 +-
README.md | 24 ++++++-----
bundle/__init__.py | 1 +
bundle/py2exe.py | 59 +++++++++++++++++++++++++++
pyinst.py => bundle/pyinstaller.py | 2 +-
pyproject.toml | 3 ++
setup.py | 56 +------------------------
9 files changed, 91 insertions(+), 78 deletions(-)
create mode 100644 bundle/__init__.py
create mode 100755 bundle/py2exe.py
rename pyinst.py => bundle/pyinstaller.py (98%)
mode change 100644 => 100755
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 036ce434894b..4b05e7cf93f8 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -144,9 +144,9 @@ jobs:
run: |
unset LD_LIBRARY_PATH # Harmful; set by setup-python
conda activate build
- python pyinst.py --onedir
+ python -m bundle.pyinstaller --onedir
(cd ./dist/yt-dlp_linux && zip -r ../yt-dlp_linux.zip .)
- python pyinst.py
+ python -m bundle.pyinstaller
mv ./dist/yt-dlp_linux ./yt-dlp_linux
mv ./dist/yt-dlp_linux.zip ./yt-dlp_linux.zip
@@ -211,7 +211,7 @@ jobs:
python3.8 -m pip install -U Pyinstaller secretstorage -r requirements.txt # Cached version may be out of date
python3.8 devscripts/update-version.py -c "${{ inputs.channel }}" -r "${{ needs.process.outputs.origin }}" "${{ inputs.version }}"
python3.8 devscripts/make_lazy_extractors.py
- python3.8 pyinst.py
+ python3.8 -m bundle.pyinstaller
if ${{ vars.UPDATE_TO_VERIFICATION && 'true' || 'false' }}; then
arch="${{ (matrix.architecture == 'armv7' && 'armv7l') || matrix.architecture }}"
@@ -250,9 +250,9 @@ jobs:
python3 devscripts/make_lazy_extractors.py
- name: Build
run: |
- python3 pyinst.py --target-architecture universal2 --onedir
+ python3 -m bundle.pyinstaller --target-architecture universal2 --onedir
(cd ./dist/yt-dlp_macos && zip -r ../yt-dlp_macos.zip .)
- python3 pyinst.py --target-architecture universal2
+ python3 -m bundle.pyinstaller --target-architecture universal2
- name: Verify --update-to
if: vars.UPDATE_TO_VERIFICATION
@@ -302,7 +302,7 @@ jobs:
python3 devscripts/make_lazy_extractors.py
- name: Build
run: |
- python3 pyinst.py
+ python3 -m bundle.pyinstaller
mv dist/yt-dlp_macos dist/yt-dlp_macos_legacy
- name: Verify --update-to
@@ -342,10 +342,10 @@ jobs:
python devscripts/make_lazy_extractors.py
- name: Build
run: |
- python setup.py py2exe
+ python -m bundle.py2exe
Move-Item ./dist/yt-dlp.exe ./dist/yt-dlp_min.exe
- python pyinst.py
- python pyinst.py --onedir
+ python -m bundle.pyinstaller
+ python -m bundle.pyinstaller --onedir
Compress-Archive -Path ./dist/yt-dlp/* -DestinationPath ./dist/yt-dlp_win.zip
- name: Verify --update-to
@@ -391,7 +391,7 @@ jobs:
python devscripts/make_lazy_extractors.py
- name: Build
run: |
- python pyinst.py
+ python -m bundle.pyinstaller
- name: Verify --update-to
if: vars.UPDATE_TO_VERIFICATION
diff --git a/.github/workflows/release-master.yml b/.github/workflows/release-master.yml
index 0664137a94d8..af14b053ec62 100644
--- a/.github/workflows/release-master.yml
+++ b/.github/workflows/release-master.yml
@@ -7,7 +7,7 @@ on:
- "yt_dlp/**.py"
- "!yt_dlp/version.py"
- "setup.py"
- - "pyinst.py"
+ - "bundle/*.py"
concurrency:
group: release-master
permissions:
diff --git a/.github/workflows/release-nightly.yml b/.github/workflows/release-nightly.yml
index 2e623a67c6f8..3f1418936aa8 100644
--- a/.github/workflows/release-nightly.yml
+++ b/.github/workflows/release-nightly.yml
@@ -18,7 +18,7 @@ jobs:
- name: Check for new commits
id: check_for_new_commits
run: |
- relevant_files=("yt_dlp/*.py" ':!yt_dlp/version.py' "setup.py" "pyinst.py")
+ relevant_files=("yt_dlp/*.py" ':!yt_dlp/version.py' "setup.py" "bundle/*.py")
echo "commit=$(git log --format=%H -1 --since="24 hours ago" -- "${relevant_files[@]}")" | tee "$GITHUB_OUTPUT"
release:
diff --git a/README.md b/README.md
index 7dc3bb2f6ca7..c74777d2f56e 100644
--- a/README.md
+++ b/README.md
@@ -321,19 +321,21 @@ If you do not have the necessary dependencies for a task you are attempting, yt-
## COMPILE
### Standalone PyInstaller Builds
-To build the standalone executable, you must have Python and `pyinstaller` (plus any of yt-dlp's [optional dependencies](#dependencies) if needed). Once you have all the necessary dependencies installed, simply run `pyinst.py`. The executable will be built for the same architecture (x86/ARM, 32/64 bit) as the Python used.
+To build the standalone executable, you must have Python and `pyinstaller` (plus any of yt-dlp's [optional dependencies](#dependencies) if needed). The executable will be built for the same architecture (x86/ARM, 32/64 bit) as the Python used. You can run the following commands:
- python3 -m pip install -U pyinstaller -r requirements.txt
- python3 devscripts/make_lazy_extractors.py
- python3 pyinst.py
+```
+python3 -m pip install -U pyinstaller -r requirements.txt
+python3 devscripts/make_lazy_extractors.py
+python3 -m bundle.pyinstaller
+```
On some systems, you may need to use `py` or `python` instead of `python3`.
-`pyinst.py` accepts any arguments that can be passed to `pyinstaller`, such as `--onefile/-F` or `--onedir/-D`, which is further [documented here](https://pyinstaller.org/en/stable/usage.html#what-to-generate).
+`bundle/pyinstaller.py` accepts any arguments that can be passed to `pyinstaller`, such as `--onefile/-F` or `--onedir/-D`, which is further [documented here](https://pyinstaller.org/en/stable/usage.html#what-to-generate).
**Note**: Pyinstaller versions below 4.4 [do not support](https://github.com/pyinstaller/pyinstaller#requirements-and-tested-platforms) Python installed from the Windows store without using a virtual environment.
-**Important**: Running `pyinstaller` directly **without** using `pyinst.py` is **not** officially supported. This may or may not work correctly.
+**Important**: Running `pyinstaller` directly **without** using `bundle/pyinstaller.py` is **not** officially supported. This may or may not work correctly.
### Platform-independent Binary (UNIX)
You will need the build tools `python` (3.8+), `zip`, `make` (GNU), `pandoc`\* and `pytest`\*.
@@ -346,11 +348,13 @@ You can also run `make yt-dlp` instead to compile only the binary without updati
While we provide the option to build with [py2exe](https://www.py2exe.org), it is recommended to build [using PyInstaller](#standalone-pyinstaller-builds) instead since the py2exe builds **cannot contain `pycryptodomex`/`certifi` and needs VC++14** on the target computer to run.
-If you wish to build it anyway, install Python and py2exe, and then simply run `setup.py py2exe`
+If you wish to build it anyway, install Python (if it is not already installed) and you can run the following commands:
- py -m pip install -U py2exe -r requirements.txt
- py devscripts/make_lazy_extractors.py
- py setup.py py2exe
+```
+py -m pip install -U py2exe -r requirements.txt
+py devscripts/make_lazy_extractors.py
+py -m bundle.py2exe
+```
### Related scripts
diff --git a/bundle/__init__.py b/bundle/__init__.py
new file mode 100644
index 000000000000..932b79829cf3
--- /dev/null
+++ b/bundle/__init__.py
@@ -0,0 +1 @@
+# Empty file
diff --git a/bundle/py2exe.py b/bundle/py2exe.py
new file mode 100755
index 000000000000..a7e4113f1fac
--- /dev/null
+++ b/bundle/py2exe.py
@@ -0,0 +1,59 @@
+#!/usr/bin/env python3
+
+# Allow execution from anywhere
+import os
+import sys
+
+sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
+
+import warnings
+
+from py2exe import freeze
+
+from devscripts.utils import read_version
+
+VERSION = read_version()
+
+
+def main():
+ warnings.warn(
+ 'py2exe builds do not support pycryptodomex and needs VC++14 to run. '
+ 'It is recommended to run "pyinst.py" to build using pyinstaller instead')
+
+ return freeze(
+ console=[{
+ 'script': './yt_dlp/__main__.py',
+ 'dest_base': 'yt-dlp',
+ 'icon_resources': [(1, 'devscripts/logo.ico')],
+ }],
+ version_info={
+ 'version': VERSION,
+ 'description': 'A youtube-dl fork with additional features and patches',
+ 'comments': 'Official repository: ',
+ 'product_name': 'yt-dlp',
+ 'product_version': VERSION,
+ },
+ options={
+ 'bundle_files': 0,
+ 'compressed': 1,
+ 'optimize': 2,
+ 'dist_dir': './dist',
+ 'excludes': [
+ # py2exe cannot import Crypto
+ 'Crypto',
+ 'Cryptodome',
+ # py2exe appears to confuse this with our socks library.
+ # We don't use pysocks and urllib3.contrib.socks would fail to import if tried.
+ 'urllib3.contrib.socks'
+ ],
+ 'dll_excludes': ['w9xpopen.exe', 'crypt32.dll'],
+ # Modules that are only imported dynamically must be added here
+ 'includes': ['yt_dlp.compat._legacy', 'yt_dlp.compat._deprecated',
+ 'yt_dlp.utils._legacy', 'yt_dlp.utils._deprecated'],
+ },
+ zipfile=None,
+ )
+
+
+if __name__ == '__main__':
+ main()
diff --git a/pyinst.py b/bundle/pyinstaller.py
old mode 100644
new mode 100755
similarity index 98%
rename from pyinst.py
rename to bundle/pyinstaller.py
index c36f6acd4f38..db9dbfde515e
--- a/pyinst.py
+++ b/bundle/pyinstaller.py
@@ -4,7 +4,7 @@
import os
import sys
-sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
+sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import platform
diff --git a/pyproject.toml b/pyproject.toml
index 97718ec431eb..626d9aa13368 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -3,3 +3,6 @@ build-backend = 'setuptools.build_meta'
# https://github.com/yt-dlp/yt-dlp/issues/5941
# https://github.com/pypa/distutils/issues/17
requires = ['setuptools > 50']
+
+[project.entry-points.pyinstaller40]
+hook-dirs = "yt_dlp.__pyinstaller:get_hook_dirs"
diff --git a/setup.py b/setup.py
index 3d9a69d10cce..fc5b504683a8 100644
--- a/setup.py
+++ b/setup.py
@@ -7,7 +7,6 @@
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import subprocess
-import warnings
try:
from setuptools import Command, find_packages, setup
@@ -39,46 +38,6 @@ def packages():
]
-def py2exe_params():
- warnings.warn(
- 'py2exe builds do not support pycryptodomex and needs VC++14 to run. '
- 'It is recommended to run "pyinst.py" to build using pyinstaller instead')
-
- return {
- 'console': [{
- 'script': './yt_dlp/__main__.py',
- 'dest_base': 'yt-dlp',
- 'icon_resources': [(1, 'devscripts/logo.ico')],
- }],
- 'version_info': {
- 'version': VERSION,
- 'description': DESCRIPTION,
- 'comments': LONG_DESCRIPTION.split('\n')[0],
- 'product_name': 'yt-dlp',
- 'product_version': VERSION,
- },
- 'options': {
- 'bundle_files': 0,
- 'compressed': 1,
- 'optimize': 2,
- 'dist_dir': './dist',
- 'excludes': [
- # py2exe cannot import Crypto
- 'Crypto',
- 'Cryptodome',
- # py2exe appears to confuse this with our socks library.
- # We don't use pysocks and urllib3.contrib.socks would fail to import if tried.
- 'urllib3.contrib.socks'
- ],
- 'dll_excludes': ['w9xpopen.exe', 'crypt32.dll'],
- # Modules that are only imported dynamically must be added here
- 'includes': ['yt_dlp.compat._legacy', 'yt_dlp.compat._deprecated',
- 'yt_dlp.utils._legacy', 'yt_dlp.utils._deprecated'],
- },
- 'zipfile': None,
- }
-
-
def build_params():
files_spec = [
('share/bash-completion/completions', ['completions/bash/yt-dlp']),
@@ -127,20 +86,7 @@ def run(self):
def main():
- if sys.argv[1:2] == ['py2exe']:
- params = py2exe_params()
- try:
- from py2exe import freeze
- except ImportError:
- import py2exe # noqa: F401
- warnings.warn('You are using an outdated version of py2exe. Support for this version will be removed in the future')
- params['console'][0].update(params.pop('version_info'))
- params['options'] = {'py2exe': params.pop('options')}
- else:
- return freeze(**params)
- else:
- params = build_params()
-
+ params = build_params()
setup(
name='yt-dlp', # package name (do not change/remove comment)
version=VERSION,
From 868d2f60a7cb59b410c8cbfb452cbdb072687b81 Mon Sep 17 00:00:00 2001
From: bashonly
Date: Sun, 11 Feb 2024 15:07:45 +0100
Subject: [PATCH 112/175] [build:Makefile] Add automated `CODE_FOLDERS` and
`CODE_FILES`
Authored by: bashonly
---
Makefile | 27 ++++++++++++---------------
1 file changed, 12 insertions(+), 15 deletions(-)
diff --git a/Makefile b/Makefile
index c85b24c13eda..296fc326036c 100644
--- a/Makefile
+++ b/Makefile
@@ -21,7 +21,7 @@ clean-test:
*.mp4 *.mpga *.oga *.ogg *.opus *.png *.sbv *.srt *.swf *.swp *.tt *.ttml *.url *.vtt *.wav *.webloc *.webm *.webp
clean-dist:
rm -rf yt-dlp.1.temp.md yt-dlp.1 README.txt MANIFEST build/ dist/ .coverage cover/ yt-dlp.tar.gz completions/ \
- yt_dlp/extractor/lazy_extractors.py *.spec CONTRIBUTING.md.tmp yt-dlp yt-dlp.exe yt_dlp.egg-info/ AUTHORS .mailmap
+ yt_dlp/extractor/lazy_extractors.py *.spec CONTRIBUTING.md.tmp yt-dlp yt-dlp.exe yt_dlp.egg-info/ AUTHORS
clean-cache:
find . \( \
-type d -name .pytest_cache -o -type d -name __pycache__ -o -name "*.pyc" -o -name "*.class" \
@@ -73,24 +73,24 @@ test:
offlinetest: codetest
$(PYTHON) -m pytest -k "not download"
-# XXX: This is hard to maintain
-CODE_FOLDERS = yt_dlp yt_dlp/downloader yt_dlp/extractor yt_dlp/postprocessor yt_dlp/compat yt_dlp/compat/urllib yt_dlp/utils yt_dlp/dependencies yt_dlp/networking
-yt-dlp: yt_dlp/*.py yt_dlp/*/*.py
+CODE_FOLDERS := $(shell find yt_dlp -type d -not -name '__*' -exec sh -c 'test -e "$$1"/__init__.py' sh {} \; -print)
+CODE_FILES := $(shell for f in $(CODE_FOLDERS); do echo "$$f" | awk '{gsub(/\/[^\/]+/,"/*"); print $$1"/*.py"}'; done | sort -u)
+yt-dlp: $(CODE_FILES)
mkdir -p zip
for d in $(CODE_FOLDERS) ; do \
mkdir -p zip/$$d ;\
cp -pPR $$d/*.py zip/$$d/ ;\
done
- touch -t 200001010101 zip/yt_dlp/*.py zip/yt_dlp/*/*.py
+ cd zip ; touch -t 200001010101 $(CODE_FILES)
mv zip/yt_dlp/__main__.py zip/
- cd zip ; zip -q ../yt-dlp yt_dlp/*.py yt_dlp/*/*.py __main__.py
+ cd zip ; zip -q ../yt-dlp $(CODE_FILES) __main__.py
rm -rf zip
echo '#!$(PYTHON)' > yt-dlp
cat yt-dlp.zip >> yt-dlp
rm yt-dlp.zip
chmod a+x yt-dlp
-README.md: yt_dlp/*.py yt_dlp/*/*.py devscripts/make_readme.py
+README.md: $(CODE_FILES) devscripts/make_readme.py
COLUMNS=80 $(PYTHON) yt_dlp/__main__.py --ignore-config --help | $(PYTHON) devscripts/make_readme.py
CONTRIBUTING.md: README.md devscripts/make_contributing.py
@@ -115,15 +115,15 @@ yt-dlp.1: README.md devscripts/prepare_manpage.py
pandoc -s -f $(MARKDOWN) -t man yt-dlp.1.temp.md -o yt-dlp.1
rm -f yt-dlp.1.temp.md
-completions/bash/yt-dlp: yt_dlp/*.py yt_dlp/*/*.py devscripts/bash-completion.in
+completions/bash/yt-dlp: $(CODE_FILES) devscripts/bash-completion.in
mkdir -p completions/bash
$(PYTHON) devscripts/bash-completion.py
-completions/zsh/_yt-dlp: yt_dlp/*.py yt_dlp/*/*.py devscripts/zsh-completion.in
+completions/zsh/_yt-dlp: $(CODE_FILES) devscripts/zsh-completion.in
mkdir -p completions/zsh
$(PYTHON) devscripts/zsh-completion.py
-completions/fish/yt-dlp.fish: yt_dlp/*.py yt_dlp/*/*.py devscripts/fish-completion.in
+completions/fish/yt-dlp.fish: $(CODE_FILES) devscripts/fish-completion.in
mkdir -p completions/fish
$(PYTHON) devscripts/fish-completion.py
@@ -148,8 +148,5 @@ yt-dlp.tar.gz: all
setup.py setup.cfg yt-dlp yt_dlp requirements.txt \
devscripts test
-AUTHORS: .mailmap
- git shortlog -s -n | cut -f2 | sort > AUTHORS
-
-.mailmap:
- git shortlog -s -e -n | awk '!(out[$$NF]++) { $$1="";sub(/^[ \t]+/,""); print}' > .mailmap
+AUTHORS:
+ git shortlog -s -n HEAD | cut -f2 | sort > AUTHORS
From 775cde82dc5b1dc64ab0539a92dd8c7ba6c0ad33 Mon Sep 17 00:00:00 2001
From: bashonly
Date: Sun, 11 Feb 2024 15:13:03 +0100
Subject: [PATCH 113/175] [build] Migrate to `pyproject.toml` and `hatchling`
Authored by: bashonly
---
.github/workflows/release-master.yml | 2 +-
.github/workflows/release-nightly.yml | 2 +-
.github/workflows/release.yml | 9 +-
MANIFEST.in | 10 --
Makefile | 11 +--
pyproject.toml | 120 +++++++++++++++++++++++-
setup.cfg | 4 -
setup.py | 129 --------------------------
8 files changed, 130 insertions(+), 157 deletions(-)
delete mode 100644 MANIFEST.in
delete mode 100644 setup.py
diff --git a/.github/workflows/release-master.yml b/.github/workflows/release-master.yml
index af14b053ec62..2430dc5f88b8 100644
--- a/.github/workflows/release-master.yml
+++ b/.github/workflows/release-master.yml
@@ -6,8 +6,8 @@ on:
paths:
- "yt_dlp/**.py"
- "!yt_dlp/version.py"
- - "setup.py"
- "bundle/*.py"
+ - "pyproject.toml"
concurrency:
group: release-master
permissions:
diff --git a/.github/workflows/release-nightly.yml b/.github/workflows/release-nightly.yml
index 3f1418936aa8..16d583846618 100644
--- a/.github/workflows/release-nightly.yml
+++ b/.github/workflows/release-nightly.yml
@@ -18,7 +18,7 @@ jobs:
- name: Check for new commits
id: check_for_new_commits
run: |
- relevant_files=("yt_dlp/*.py" ':!yt_dlp/version.py' "setup.py" "bundle/*.py")
+ relevant_files=("yt_dlp/*.py" ':!yt_dlp/version.py' "bundle/*.py" "pyproject.toml")
echo "commit=$(git log --format=%H -1 --since="24 hours ago" -- "${relevant_files[@]}")" | tee "$GITHUB_OUTPUT"
release:
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 69b5e315296e..d1508e5e6ce0 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -266,14 +266,19 @@ jobs:
run: |
python devscripts/update-version.py -c "${{ env.channel }}" -r "${{ env.target_repo }}" -s "${{ env.suffix }}" "${{ env.version }}"
python devscripts/make_lazy_extractors.py
- sed -i -E "s/(name=')[^']+(', # package name)/\1${{ env.pypi_project }}\2/" setup.py
+ sed -i -E '0,/(name = ")[^"]+(")/s//\1${{ env.pypi_project }}\2/' pyproject.toml
- name: Build
run: |
rm -rf dist/*
make pypi-files
+ printf '%s\n\n' \
+ 'Official repository: ' \
+ '**PS**: Some links in this document will not work since this is a copy of the README.md from Github' > ./README.md.new
+ cat ./README.md >> ./README.md.new && mv -f ./README.md.new ./README.md
python devscripts/set-variant.py pip -M "You installed yt-dlp with pip or using the wheel from PyPi; Use that to update"
- python setup.py sdist bdist_wheel
+ make clean-cache
+ python -m build --no-isolation .
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
diff --git a/MANIFEST.in b/MANIFEST.in
deleted file mode 100644
index bc2f056c05b7..000000000000
--- a/MANIFEST.in
+++ /dev/null
@@ -1,10 +0,0 @@
-include AUTHORS
-include Changelog.md
-include LICENSE
-include README.md
-include completions/*/*
-include supportedsites.md
-include yt-dlp.1
-include requirements.txt
-recursive-include devscripts *
-recursive-include test *
diff --git a/Makefile b/Makefile
index 296fc326036c..2f36c0cd1395 100644
--- a/Makefile
+++ b/Makefile
@@ -6,11 +6,11 @@ doc: README.md CONTRIBUTING.md issuetemplates supportedsites
ot: offlinetest
tar: yt-dlp.tar.gz
-# Keep this list in sync with MANIFEST.in
+# Keep this list in sync with pyproject.toml includes/artifacts
# intended use: when building a source distribution,
-# make pypi-files && python setup.py sdist
+# make pypi-files && python3 -m build -sn .
pypi-files: AUTHORS Changelog.md LICENSE README.md README.txt supportedsites \
- completions yt-dlp.1 requirements.txt setup.cfg devscripts/* test/*
+ completions yt-dlp.1 pyproject.toml setup.cfg devscripts/* test/*
.PHONY: all clean install test tar pypi-files completions ot offlinetest codetest supportedsites
@@ -144,9 +144,8 @@ yt-dlp.tar.gz: all
-- \
README.md supportedsites.md Changelog.md LICENSE \
CONTRIBUTING.md Collaborators.md CONTRIBUTORS AUTHORS \
- Makefile MANIFEST.in yt-dlp.1 README.txt completions \
- setup.py setup.cfg yt-dlp yt_dlp requirements.txt \
- devscripts test
+ Makefile yt-dlp.1 README.txt completions .gitignore \
+ setup.cfg yt-dlp yt_dlp pyproject.toml devscripts test
AUTHORS:
git shortlog -s -n HEAD | cut -f2 | sort > AUTHORS
diff --git a/pyproject.toml b/pyproject.toml
index 626d9aa13368..5ef013279a62 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,8 +1,120 @@
[build-system]
-build-backend = 'setuptools.build_meta'
-# https://github.com/yt-dlp/yt-dlp/issues/5941
-# https://github.com/pypa/distutils/issues/17
-requires = ['setuptools > 50']
+requires = ["hatchling"]
+build-backend = "hatchling.build"
+
+[project]
+name = "yt-dlp"
+maintainers = [
+ {name = "pukkandan", email = "pukkandan.ytdlp@gmail.com"},
+ {name = "Grub4K", email = "contact@grub4k.xyz"},
+ {name = "bashonly", email = "bashonly@protonmail.com"},
+]
+description = "A youtube-dl fork with additional features and patches"
+readme = "README.md"
+requires-python = ">=3.8"
+keywords = [
+ "youtube-dl",
+ "video-downloader",
+ "youtube-downloader",
+ "sponsorblock",
+ "youtube-dlc",
+ "yt-dlp",
+]
+license = {file = "LICENSE"}
+classifiers = [
+ "Topic :: Multimedia :: Video",
+ "Development Status :: 5 - Production/Stable",
+ "Environment :: Console",
+ "Programming Language :: Python",
+ "Programming Language :: Python :: 3 :: Only",
+ "Programming Language :: Python :: 3.8",
+ "Programming Language :: Python :: 3.9",
+ "Programming Language :: Python :: 3.10",
+ "Programming Language :: Python :: 3.11",
+ "Programming Language :: Python :: 3.12",
+ "Programming Language :: Python :: Implementation",
+ "Programming Language :: Python :: Implementation :: CPython",
+ "Programming Language :: Python :: Implementation :: PyPy",
+ "License :: OSI Approved :: The Unlicense (Unlicense)",
+ "Operating System :: OS Independent",
+]
+dynamic = ["version"]
+dependencies = [
+ "brotli; implementation_name=='cpython'",
+ "brotlicffi; implementation_name!='cpython'",
+ "certifi",
+ "mutagen",
+ "pycryptodomex",
+ "requests>=2.31.0,<3",
+ "urllib3>=1.26.17,<3",
+ "websockets>=12.0",
+]
+
+[project.optional-dependencies]
+secretstorage = [
+ "cffi",
+ "secretstorage",
+]
+build = [
+ "build",
+ "hatchling",
+ "pip",
+ "wheel",
+]
+dev = [
+ "flake8",
+ "isort",
+ "pytest",
+]
+pyinstaller = ["pyinstaller>=6.3"]
+py2exe = ["py2exe>=0.12"]
+
+[project.urls]
+Documentation = "https://github.com/yt-dlp/yt-dlp#readme"
+Repository = "https://github.com/yt-dlp/yt-dlp"
+Tracker = "https://github.com/yt-dlp/yt-dlp/issues"
+Funding = "https://github.com/yt-dlp/yt-dlp/blob/master/Collaborators.md#collaborators"
+
+[project.scripts]
+yt-dlp = "yt_dlp:main"
[project.entry-points.pyinstaller40]
hook-dirs = "yt_dlp.__pyinstaller:get_hook_dirs"
+
+[tool.hatch.build.targets.sdist]
+include = [
+ "/yt_dlp",
+ "/devscripts",
+ "/test",
+ "/.gitignore", # included by default, needed for auto-excludes
+ "/Changelog.md",
+ "/LICENSE", # included as license
+ "/pyproject.toml", # included by default
+ "/README.md", # included as readme
+ "/setup.cfg",
+ "/supportedsites.md",
+]
+exclude = ["/yt_dlp/__pyinstaller"]
+artifacts = [
+ "/yt_dlp/extractor/lazy_extractors.py",
+ "/completions",
+ "/AUTHORS", # included by default
+ "/README.txt",
+ "/yt-dlp.1",
+]
+
+[tool.hatch.build.targets.wheel]
+packages = ["yt_dlp"]
+exclude = ["/yt_dlp/__pyinstaller"]
+artifacts = ["/yt_dlp/extractor/lazy_extractors.py"]
+
+[tool.hatch.build.targets.wheel.shared-data]
+"completions/bash/yt-dlp" = "share/bash-completion/completions/yt-dlp"
+"completions/zsh/_yt-dlp" = "share/zsh/site-functions/_yt-dlp"
+"completions/fish/yt-dlp.fish" = "share/fish/vendor_completions.d/yt-dlp.fish"
+"README.txt" = "share/doc/yt_dlp/README.txt"
+"yt-dlp.1" = "share/man/man1/yt-dlp.1"
+
+[tool.hatch.version]
+path = "yt_dlp/version.py"
+pattern = "_pkg_version = '(?P[^']+)'"
diff --git a/setup.cfg b/setup.cfg
index a799f7293e60..aeb4cee58697 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,7 +1,3 @@
-[wheel]
-universal = true
-
-
[flake8]
exclude = build,venv,.tox,.git,.pytest_cache
ignore = E402,E501,E731,E741,W503
diff --git a/setup.py b/setup.py
deleted file mode 100644
index fc5b504683a8..000000000000
--- a/setup.py
+++ /dev/null
@@ -1,129 +0,0 @@
-#!/usr/bin/env python3
-
-# Allow execution from anywhere
-import os
-import sys
-
-sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
-
-import subprocess
-
-try:
- from setuptools import Command, find_packages, setup
- setuptools_available = True
-except ImportError:
- from distutils.core import Command, setup
- setuptools_available = False
-
-from devscripts.utils import read_file, read_version
-
-VERSION = read_version(varname='_pkg_version')
-
-DESCRIPTION = 'A youtube-dl fork with additional features and patches'
-
-LONG_DESCRIPTION = '\n\n'.join((
- 'Official repository: ',
- '**PS**: Some links in this document will not work since this is a copy of the README.md from Github',
- read_file('README.md')))
-
-REQUIREMENTS = read_file('requirements.txt').splitlines()
-
-
-def packages():
- if setuptools_available:
- return find_packages(exclude=('youtube_dl', 'youtube_dlc', 'test', 'ytdlp_plugins', 'devscripts'))
-
- return [
- 'yt_dlp', 'yt_dlp.extractor', 'yt_dlp.downloader', 'yt_dlp.postprocessor', 'yt_dlp.compat',
- ]
-
-
-def build_params():
- files_spec = [
- ('share/bash-completion/completions', ['completions/bash/yt-dlp']),
- ('share/zsh/site-functions', ['completions/zsh/_yt-dlp']),
- ('share/fish/vendor_completions.d', ['completions/fish/yt-dlp.fish']),
- ('share/doc/yt_dlp', ['README.txt']),
- ('share/man/man1', ['yt-dlp.1'])
- ]
- data_files = []
- for dirname, files in files_spec:
- resfiles = []
- for fn in files:
- if not os.path.exists(fn):
- warnings.warn(f'Skipping file {fn} since it is not present. Try running " make pypi-files " first')
- else:
- resfiles.append(fn)
- data_files.append((dirname, resfiles))
-
- params = {'data_files': data_files}
-
- if setuptools_available:
- params['entry_points'] = {
- 'console_scripts': ['yt-dlp = yt_dlp:main'],
- 'pyinstaller40': ['hook-dirs = yt_dlp.__pyinstaller:get_hook_dirs'],
- }
- else:
- params['scripts'] = ['yt-dlp']
- return params
-
-
-class build_lazy_extractors(Command):
- description = 'Build the extractor lazy loading module'
- user_options = []
-
- def initialize_options(self):
- pass
-
- def finalize_options(self):
- pass
-
- def run(self):
- if self.dry_run:
- print('Skipping build of lazy extractors in dry run mode')
- return
- subprocess.run([sys.executable, 'devscripts/make_lazy_extractors.py'])
-
-
-def main():
- params = build_params()
- setup(
- name='yt-dlp', # package name (do not change/remove comment)
- version=VERSION,
- maintainer='pukkandan',
- maintainer_email='pukkandan.ytdlp@gmail.com',
- description=DESCRIPTION,
- long_description=LONG_DESCRIPTION,
- long_description_content_type='text/markdown',
- url='https://github.com/yt-dlp/yt-dlp',
- packages=packages(),
- install_requires=REQUIREMENTS,
- python_requires='>=3.8',
- project_urls={
- 'Documentation': 'https://github.com/yt-dlp/yt-dlp#readme',
- 'Source': 'https://github.com/yt-dlp/yt-dlp',
- 'Tracker': 'https://github.com/yt-dlp/yt-dlp/issues',
- 'Funding': 'https://github.com/yt-dlp/yt-dlp/blob/master/Collaborators.md#collaborators',
- },
- classifiers=[
- 'Topic :: Multimedia :: Video',
- 'Development Status :: 5 - Production/Stable',
- 'Environment :: Console',
- 'Programming Language :: Python',
- 'Programming Language :: Python :: 3.8',
- 'Programming Language :: Python :: 3.9',
- 'Programming Language :: Python :: 3.10',
- 'Programming Language :: Python :: 3.11',
- 'Programming Language :: Python :: 3.12',
- 'Programming Language :: Python :: Implementation',
- 'Programming Language :: Python :: Implementation :: CPython',
- 'Programming Language :: Python :: Implementation :: PyPy',
- 'License :: Public Domain',
- 'Operating System :: OS Independent',
- ],
- cmdclass={'build_lazy_extractors': build_lazy_extractors},
- **params
- )
-
-
-main()
From fd647775e27e030ab17387c249e2ebeba68f8ff0 Mon Sep 17 00:00:00 2001
From: Simon Sawicki
Date: Sun, 11 Feb 2024 15:14:42 +0100
Subject: [PATCH 114/175] [devscripts] `tomlparse`: Add makeshift toml parser
Authored by: Grub4K
---
devscripts/tomlparse.py | 189 ++++++++++++++++++++++++++++++++++++++++
1 file changed, 189 insertions(+)
create mode 100755 devscripts/tomlparse.py
diff --git a/devscripts/tomlparse.py b/devscripts/tomlparse.py
new file mode 100755
index 000000000000..85ac4eef7893
--- /dev/null
+++ b/devscripts/tomlparse.py
@@ -0,0 +1,189 @@
+#!/usr/bin/env python3
+
+"""
+Simple parser for spec compliant toml files
+
+A simple toml parser for files that comply with the spec.
+Should only be used to parse `pyproject.toml` for `install_deps.py`.
+
+IMPORTANT: INVALID FILES OR MULTILINE STRINGS ARE NOT SUPPORTED!
+"""
+
+from __future__ import annotations
+
+import datetime
+import json
+import re
+
+WS = r'(?:[\ \t]*)'
+STRING_RE = re.compile(r'"(?:\\.|[^\\"\n])*"|\'[^\'\n]*\'')
+SINGLE_KEY_RE = re.compile(rf'{STRING_RE.pattern}|[A-Za-z0-9_-]+')
+KEY_RE = re.compile(rf'{WS}(?:{SINGLE_KEY_RE.pattern}){WS}(?:\.{WS}(?:{SINGLE_KEY_RE.pattern}){WS})*')
+EQUALS_RE = re.compile(rf'={WS}')
+WS_RE = re.compile(WS)
+
+_SUBTABLE = rf'(?P^\[(?P\[)?(?P{KEY_RE.pattern})\]\]?)'
+EXPRESSION_RE = re.compile(rf'^(?:{_SUBTABLE}|{KEY_RE.pattern}=)', re.MULTILINE)
+
+LIST_WS_RE = re.compile(rf'{WS}((#[^\n]*)?\n{WS})*')
+LEFTOVER_VALUE_RE = re.compile(r'[^,}\]\t\n#]+')
+
+
+def parse_key(value: str):
+ for match in SINGLE_KEY_RE.finditer(value):
+ if match[0][0] == '"':
+ yield json.loads(match[0])
+ elif match[0][0] == '\'':
+ yield match[0][1:-1]
+ else:
+ yield match[0]
+
+
+def get_target(root: dict, paths: list[str], is_list=False):
+ target = root
+
+ for index, key in enumerate(paths, 1):
+ use_list = is_list and index == len(paths)
+ result = target.get(key)
+ if result is None:
+ result = [] if use_list else {}
+ target[key] = result
+
+ if isinstance(result, dict):
+ target = result
+ elif use_list:
+ target = {}
+ result.append(target)
+ else:
+ target = result[-1]
+
+ assert isinstance(target, dict)
+ return target
+
+
+def parse_enclosed(data: str, index: int, end: str, ws_re: re.Pattern):
+ index += 1
+
+ if match := ws_re.match(data, index):
+ index = match.end()
+
+ while data[index] != end:
+ index = yield True, index
+
+ if match := ws_re.match(data, index):
+ index = match.end()
+
+ if data[index] == ',':
+ index += 1
+
+ if match := ws_re.match(data, index):
+ index = match.end()
+
+ assert data[index] == end
+ yield False, index + 1
+
+
+def parse_value(data: str, index: int):
+ if data[index] == '[':
+ result = []
+
+ indices = parse_enclosed(data, index, ']', LIST_WS_RE)
+ valid, index = next(indices)
+ while valid:
+ index, value = parse_value(data, index)
+ result.append(value)
+ valid, index = indices.send(index)
+
+ return index, result
+
+ if data[index] == '{':
+ result = {}
+
+ indices = parse_enclosed(data, index, '}', WS_RE)
+ valid, index = next(indices)
+ while valid:
+ valid, index = indices.send(parse_kv_pair(data, index, result))
+
+ return index, result
+
+ if match := STRING_RE.match(data, index):
+ return match.end(), json.loads(match[0]) if match[0][0] == '"' else match[0][1:-1]
+
+ match = LEFTOVER_VALUE_RE.match(data, index)
+ assert match
+ value = match[0].strip()
+ for func in [
+ int,
+ float,
+ datetime.time.fromisoformat,
+ datetime.date.fromisoformat,
+ datetime.datetime.fromisoformat,
+ {'true': True, 'false': False}.get,
+ ]:
+ try:
+ value = func(value)
+ break
+ except Exception:
+ pass
+
+ return match.end(), value
+
+
+def parse_kv_pair(data: str, index: int, target: dict):
+ match = KEY_RE.match(data, index)
+ if not match:
+ return None
+
+ *keys, key = parse_key(match[0])
+
+ match = EQUALS_RE.match(data, match.end())
+ assert match
+ index = match.end()
+
+ index, value = parse_value(data, index)
+ get_target(target, keys)[key] = value
+ return index
+
+
+def parse_toml(data: str):
+ root = {}
+ target = root
+
+ index = 0
+ while True:
+ match = EXPRESSION_RE.search(data, index)
+ if not match:
+ break
+
+ if match.group('subtable'):
+ index = match.end()
+ path, is_list = match.group('path', 'is_list')
+ target = get_target(root, list(parse_key(path)), bool(is_list))
+ continue
+
+ index = parse_kv_pair(data, match.start(), target)
+ assert index is not None
+
+ return root
+
+
+def main():
+ import argparse
+ from pathlib import Path
+
+ parser = argparse.ArgumentParser()
+ parser.add_argument('infile', type=Path, help='The TOML file to read as input')
+ args = parser.parse_args()
+
+ with args.infile.open('r', encoding='utf-8') as file:
+ data = file.read()
+
+ def default(obj):
+ if isinstance(obj, (datetime.date, datetime.time, datetime.datetime)):
+ return obj.isoformat()
+
+ print(json.dumps(parse_toml(data), default=default))
+
+
+if __name__ == '__main__':
+ main()
From b8a433aaca86b15cb9f1a451b0f69371d2fc22a9 Mon Sep 17 00:00:00 2001
From: bashonly
Date: Sun, 11 Feb 2024 15:17:08 +0100
Subject: [PATCH 115/175] [devscripts] `install_deps`: Add script and migrate
to it
Authored by: bashonly
---
.github/workflows/build.yml | 36 +++++++++--------
.github/workflows/core.yml | 2 +-
.github/workflows/download.yml | 4 +-
.github/workflows/quick-test.yml | 6 +--
.github/workflows/release.yml | 3 +-
README.md | 5 ++-
devscripts/install_deps.py | 66 ++++++++++++++++++++++++++++++++
requirements.txt | 8 ----
8 files changed, 95 insertions(+), 35 deletions(-)
create mode 100755 devscripts/install_deps.py
delete mode 100644 requirements.txt
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 4b05e7cf93f8..082164c9e8cb 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -121,16 +121,14 @@ jobs:
- name: Install Requirements
run: |
sudo apt -y install zip pandoc man sed
- reqs=$(mktemp)
- cat > "$reqs" << EOF
+ cat > ./requirements.txt << EOF
python=3.10.*
- pyinstaller
- cffi
brotli-python
- secretstorage
EOF
- sed -E '/^(brotli|secretstorage).*/d' requirements.txt >> "$reqs"
- mamba create -n build --file "$reqs"
+ python devscripts/install_deps.py --print \
+ --exclude brotli --exclude brotlicffi \
+ --include secretstorage --include pyinstaller >> ./requirements.txt
+ mamba create -n build --file ./requirements.txt
- name: Prepare
run: |
@@ -203,12 +201,13 @@ jobs:
apt update
apt -y install zlib1g-dev python3.8 python3.8-dev python3.8-distutils python3-pip
python3.8 -m pip install -U pip setuptools wheel
- # Cannot access requirements.txt from the repo directory at this stage
+ # Cannot access any files from the repo directory at this stage
python3.8 -m pip install -U Pyinstaller mutagen pycryptodomex websockets brotli certifi secretstorage
run: |
cd repo
- python3.8 -m pip install -U Pyinstaller secretstorage -r requirements.txt # Cached version may be out of date
+ python3.8 devscripts/install_deps.py -o --include build
+ python3.8 devscripts/install_deps.py --include pyinstaller --include secretstorage # Cached version may be out of date
python3.8 devscripts/update-version.py -c "${{ inputs.channel }}" -r "${{ needs.process.outputs.origin }}" "${{ inputs.version }}"
python3.8 devscripts/make_lazy_extractors.py
python3.8 -m bundle.pyinstaller
@@ -240,9 +239,10 @@ jobs:
- name: Install Requirements
run: |
brew install coreutils
- python3 -m pip install -U --user pip setuptools wheel
+ python3 devscripts/install_deps.py --user -o --include build
+ python3 devscripts/install_deps.py --print --include pyinstaller > requirements.txt
# We need to ignore wheels otherwise we break universal2 builds
- python3 -m pip install -U --user --no-binary :all: Pyinstaller -r requirements.txt
+ python3 -m pip install -U --user --no-binary :all: -r requirements.txt
- name: Prepare
run: |
@@ -293,8 +293,8 @@ jobs:
- name: Install Requirements
run: |
brew install coreutils
- python3 -m pip install -U --user pip setuptools wheel
- python3 -m pip install -U --user Pyinstaller -r requirements.txt
+ python3 devscripts/install_deps.py --user -o --include build
+ python3 devscripts/install_deps.py --user --include pyinstaller
- name: Prepare
run: |
@@ -333,8 +333,9 @@ jobs:
python-version: "3.8"
- name: Install Requirements
run: | # Custom pyinstaller built with https://github.com/yt-dlp/pyinstaller-builds
- python -m pip install -U pip setuptools wheel py2exe
- pip install -U "https://yt-dlp.github.io/Pyinstaller-Builds/x86_64/pyinstaller-5.8.0-py3-none-any.whl" -r requirements.txt
+ python devscripts/install_deps.py -o --include build
+ python devscripts/install_deps.py --include py2exe
+ python -m pip install -U "https://yt-dlp.github.io/Pyinstaller-Builds/x86_64/pyinstaller-5.8.0-py3-none-any.whl"
- name: Prepare
run: |
@@ -382,8 +383,9 @@ jobs:
architecture: "x86"
- name: Install Requirements
run: |
- python -m pip install -U pip setuptools wheel
- pip install -U "https://yt-dlp.github.io/Pyinstaller-Builds/i686/pyinstaller-5.8.0-py3-none-any.whl" -r requirements.txt
+ python devscripts/install_deps.py -o --include build
+ python devscripts/install_deps.py
+ python -m pip install -U "https://yt-dlp.github.io/Pyinstaller-Builds/i686/pyinstaller-5.8.0-py3-none-any.whl"
- name: Prepare
run: |
diff --git a/.github/workflows/core.yml b/.github/workflows/core.yml
index eaaf03dee481..f694c9bdd115 100644
--- a/.github/workflows/core.yml
+++ b/.github/workflows/core.yml
@@ -53,7 +53,7 @@ jobs:
with:
python-version: ${{ matrix.python-version }}
- name: Install test requirements
- run: pip install pytest -r requirements.txt
+ run: python3 ./devscripts/install_deps.py --include dev
- name: Run tests
continue-on-error: False
run: |
diff --git a/.github/workflows/download.yml b/.github/workflows/download.yml
index 9f47d671874e..84339d9700d6 100644
--- a/.github/workflows/download.yml
+++ b/.github/workflows/download.yml
@@ -15,7 +15,7 @@ jobs:
with:
python-version: 3.9
- name: Install test requirements
- run: pip install pytest -r requirements.txt
+ run: python3 ./devscripts/install_deps.py --include dev
- name: Run tests
continue-on-error: true
run: python3 ./devscripts/run_tests.py download
@@ -42,7 +42,7 @@ jobs:
with:
python-version: ${{ matrix.python-version }}
- name: Install test requirements
- run: pip install pytest -r requirements.txt
+ run: python3 ./devscripts/install_deps.py --include dev
- name: Run tests
continue-on-error: true
run: python3 ./devscripts/run_tests.py download
diff --git a/.github/workflows/quick-test.yml b/.github/workflows/quick-test.yml
index 84fca62d4ddb..4e9616926e8c 100644
--- a/.github/workflows/quick-test.yml
+++ b/.github/workflows/quick-test.yml
@@ -15,7 +15,7 @@ jobs:
with:
python-version: '3.8'
- name: Install test requirements
- run: pip install pytest -r requirements.txt
+ run: python3 ./devscripts/install_deps.py --include dev
- name: Run tests
run: |
python3 -m yt_dlp -v || true
@@ -28,8 +28,8 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-python@v4
- name: Install flake8
- run: pip install flake8
+ run: python3 ./devscripts/install_deps.py -o --include dev
- name: Make lazy extractors
- run: python devscripts/make_lazy_extractors.py
+ run: python3 ./devscripts/make_lazy_extractors.py
- name: Run flake8
run: flake8 .
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index d1508e5e6ce0..1653add4f09e 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -253,8 +253,7 @@ jobs:
- name: Install Requirements
run: |
sudo apt -y install pandoc man
- python -m pip install -U pip setuptools wheel twine
- python -m pip install -U -r requirements.txt
+ python devscripts/install_deps.py -o --include build
- name: Prepare
env:
diff --git a/README.md b/README.md
index c74777d2f56e..2fcb099176bc 100644
--- a/README.md
+++ b/README.md
@@ -324,7 +324,7 @@ If you do not have the necessary dependencies for a task you are attempting, yt-
To build the standalone executable, you must have Python and `pyinstaller` (plus any of yt-dlp's [optional dependencies](#dependencies) if needed). The executable will be built for the same architecture (x86/ARM, 32/64 bit) as the Python used. You can run the following commands:
```
-python3 -m pip install -U pyinstaller -r requirements.txt
+python3 devscripts/install_deps.py --include pyinstaller
python3 devscripts/make_lazy_extractors.py
python3 -m bundle.pyinstaller
```
@@ -351,13 +351,14 @@ While we provide the option to build with [py2exe](https://www.py2exe.org), it i
If you wish to build it anyway, install Python (if it is not already installed) and you can run the following commands:
```
-py -m pip install -U py2exe -r requirements.txt
+py devscripts/install_deps.py --include py2exe
py devscripts/make_lazy_extractors.py
py -m bundle.py2exe
```
### Related scripts
+* **`devscripts/install_deps.py`** - Install dependencies for yt-dlp.
* **`devscripts/update-version.py`** - Update the version number based on current date.
* **`devscripts/set-variant.py`** - Set the build variant of the executable.
* **`devscripts/make_changelog.py`** - Create a markdown changelog using short commit messages and update `CONTRIBUTORS` file.
diff --git a/devscripts/install_deps.py b/devscripts/install_deps.py
new file mode 100755
index 000000000000..715e5b044049
--- /dev/null
+++ b/devscripts/install_deps.py
@@ -0,0 +1,66 @@
+#!/usr/bin/env python3
+
+# Allow execution from anywhere
+import os
+import sys
+
+sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
+
+import argparse
+import re
+import subprocess
+
+from devscripts.tomlparse import parse_toml
+from devscripts.utils import read_file
+
+
+def parse_args():
+ parser = argparse.ArgumentParser(description='Install dependencies for yt-dlp')
+ parser.add_argument(
+ 'input', nargs='?', metavar='TOMLFILE', default='pyproject.toml', help='Input file (default: %(default)s)')
+ parser.add_argument(
+ '-e', '--exclude', metavar='REQUIREMENT', action='append', help='Exclude a required dependency')
+ parser.add_argument(
+ '-i', '--include', metavar='GROUP', action='append', help='Include an optional dependency group')
+ parser.add_argument(
+ '-o', '--only-optional', action='store_true', help='Only install optional dependencies')
+ parser.add_argument(
+ '-p', '--print', action='store_true', help='Only print a requirements.txt to stdout')
+ parser.add_argument(
+ '-u', '--user', action='store_true', help='Install with pip as --user')
+ return parser.parse_args()
+
+
+def main():
+ args = parse_args()
+ toml_data = parse_toml(read_file(args.input))
+ deps = toml_data['project']['dependencies']
+ targets = deps.copy() if not args.only_optional else []
+
+ for exclude in args.exclude or []:
+ for dep in deps:
+ simplified_dep = re.match(r'[\w-]+', dep)[0]
+ if dep in targets and (exclude.lower() == simplified_dep.lower() or exclude == dep):
+ targets.remove(dep)
+
+ optional_deps = toml_data['project']['optional-dependencies']
+ for include in args.include or []:
+ group = optional_deps.get(include)
+ if group:
+ targets.extend(group)
+
+ if args.print:
+ for target in targets:
+ print(target)
+ return
+
+ pip_args = [sys.executable, '-m', 'pip', 'install', '-U']
+ if args.user:
+ pip_args.append('--user')
+ pip_args.extend(targets)
+
+ return subprocess.call(pip_args)
+
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/requirements.txt b/requirements.txt
deleted file mode 100644
index 06ff82a80095..000000000000
--- a/requirements.txt
+++ /dev/null
@@ -1,8 +0,0 @@
-mutagen
-pycryptodomex
-brotli; implementation_name=='cpython'
-brotlicffi; implementation_name!='cpython'
-certifi
-requests>=2.31.0,<3
-urllib3>=1.26.17,<3
-websockets>=12.0
From 920397634d1e84e76d2cb897bd6d69ba0c6bd5ca Mon Sep 17 00:00:00 2001
From: bashonly
Date: Sun, 11 Feb 2024 15:24:41 +0100
Subject: [PATCH 116/175] [build] Fix `secretstorage` for ARM builds
Authored by: bashonly
---
.github/workflows/build.yml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 082164c9e8cb..0c2b0f684fbe 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -199,10 +199,10 @@ jobs:
dockerRunArgs: --volume "${PWD}/repo:/repo"
install: | # Installing Python 3.10 from the Deadsnakes repo raises errors
apt update
- apt -y install zlib1g-dev python3.8 python3.8-dev python3.8-distutils python3-pip
+ apt -y install zlib1g-dev libffi-dev python3.8 python3.8-dev python3.8-distutils python3-pip
python3.8 -m pip install -U pip setuptools wheel
# Cannot access any files from the repo directory at this stage
- python3.8 -m pip install -U Pyinstaller mutagen pycryptodomex websockets brotli certifi secretstorage
+ python3.8 -m pip install -U Pyinstaller mutagen pycryptodomex websockets brotli certifi secretstorage cffi
run: |
cd repo
From 867f637b95b342e1cb9f1dc3c6cf0ffe727187ce Mon Sep 17 00:00:00 2001
From: bashonly
Date: Sun, 11 Feb 2024 17:35:27 +0100
Subject: [PATCH 117/175] [cleanup] Build files cleanup
- Fix `AUTHORS` file by doing an unshallow checkout
- Update triggers for nightly/master release
Authored by: bashonly
---
.github/workflows/release-master.yml | 2 ++
.github/workflows/release-nightly.yml | 9 ++++++++-
.github/workflows/release.yml | 2 ++
3 files changed, 12 insertions(+), 1 deletion(-)
diff --git a/.github/workflows/release-master.yml b/.github/workflows/release-master.yml
index 2430dc5f88b8..a84547580b47 100644
--- a/.github/workflows/release-master.yml
+++ b/.github/workflows/release-master.yml
@@ -8,6 +8,8 @@ on:
- "!yt_dlp/version.py"
- "bundle/*.py"
- "pyproject.toml"
+ - "Makefile"
+ - ".github/workflows/build.yml"
concurrency:
group: release-master
permissions:
diff --git a/.github/workflows/release-nightly.yml b/.github/workflows/release-nightly.yml
index 16d583846618..f459a3a17e54 100644
--- a/.github/workflows/release-nightly.yml
+++ b/.github/workflows/release-nightly.yml
@@ -18,7 +18,14 @@ jobs:
- name: Check for new commits
id: check_for_new_commits
run: |
- relevant_files=("yt_dlp/*.py" ':!yt_dlp/version.py' "bundle/*.py" "pyproject.toml")
+ relevant_files=(
+ "yt_dlp/*.py"
+ ':!yt_dlp/version.py'
+ "bundle/*.py"
+ "pyproject.toml"
+ "Makefile"
+ ".github/workflows/build.yml"
+ )
echo "commit=$(git log --format=%H -1 --since="24 hours ago" -- "${relevant_files[@]}")" | tee "$GITHUB_OUTPUT"
release:
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 1653add4f09e..eded11a13556 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -246,6 +246,8 @@ jobs:
steps:
- uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
- uses: actions/setup-python@v4
with:
python-version: "3.10"
From b14e818b37f62e3224da157b3ad768b3f0815fcd Mon Sep 17 00:00:00 2001
From: bashonly
Date: Sun, 11 Feb 2024 15:47:16 +0100
Subject: [PATCH 118/175] [ci] Bump `actions/setup-python` to v5
Authored by: bashonly
---
.github/workflows/build.yml | 6 +++---
.github/workflows/core.yml | 2 +-
.github/workflows/download.yml | 4 ++--
.github/workflows/quick-test.yml | 4 ++--
.github/workflows/release.yml | 6 +++---
5 files changed, 11 insertions(+), 11 deletions(-)
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 0c2b0f684fbe..4d8e8bf380c9 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -107,7 +107,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- - uses: actions/setup-python@v4
+ - uses: actions/setup-python@v5
with:
python-version: "3.10"
- uses: conda-incubator/setup-miniconda@v2
@@ -328,7 +328,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- - uses: actions/setup-python@v4
+ - uses: actions/setup-python@v5
with: # 3.8 is used for Win7 support
python-version: "3.8"
- name: Install Requirements
@@ -377,7 +377,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- - uses: actions/setup-python@v4
+ - uses: actions/setup-python@v5
with:
python-version: "3.8"
architecture: "x86"
diff --git a/.github/workflows/core.yml b/.github/workflows/core.yml
index f694c9bdd115..ba8630630c5b 100644
--- a/.github/workflows/core.yml
+++ b/.github/workflows/core.yml
@@ -49,7 +49,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
- uses: actions/setup-python@v4
+ uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install test requirements
diff --git a/.github/workflows/download.yml b/.github/workflows/download.yml
index 84339d9700d6..7256804d9306 100644
--- a/.github/workflows/download.yml
+++ b/.github/workflows/download.yml
@@ -11,7 +11,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Set up Python
- uses: actions/setup-python@v4
+ uses: actions/setup-python@v5
with:
python-version: 3.9
- name: Install test requirements
@@ -38,7 +38,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
- uses: actions/setup-python@v4
+ uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install test requirements
diff --git a/.github/workflows/quick-test.yml b/.github/workflows/quick-test.yml
index 4e9616926e8c..3114e7bdd6c7 100644
--- a/.github/workflows/quick-test.yml
+++ b/.github/workflows/quick-test.yml
@@ -11,7 +11,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Set up Python 3.8
- uses: actions/setup-python@v4
+ uses: actions/setup-python@v5
with:
python-version: '3.8'
- name: Install test requirements
@@ -26,7 +26,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- - uses: actions/setup-python@v4
+ - uses: actions/setup-python@v5
- name: Install flake8
run: python3 ./devscripts/install_deps.py -o --include dev
- name: Make lazy extractors
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index eded11a13556..fac096be7d33 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -71,7 +71,7 @@ jobs:
with:
fetch-depth: 0
- - uses: actions/setup-python@v4
+ - uses: actions/setup-python@v5
with:
python-version: "3.10"
@@ -248,7 +248,7 @@ jobs:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- - uses: actions/setup-python@v4
+ - uses: actions/setup-python@v5
with:
python-version: "3.10"
@@ -297,7 +297,7 @@ jobs:
with:
fetch-depth: 0
- uses: actions/download-artifact@v3
- - uses: actions/setup-python@v4
+ - uses: actions/setup-python@v5
with:
python-version: "3.10"
From b0059f0413a6ba6ab0a3aec1f00188ce083cd8bf Mon Sep 17 00:00:00 2001
From: bashonly
Date: Sun, 11 Feb 2024 15:47:48 +0100
Subject: [PATCH 119/175] [build] Bump `conda-incubator/setup-miniconda` to v3
Authored by: bashonly
---
.github/workflows/build.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 4d8e8bf380c9..e8a97e3f43da 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -110,7 +110,7 @@ jobs:
- uses: actions/setup-python@v5
with:
python-version: "3.10"
- - uses: conda-incubator/setup-miniconda@v2
+ - uses: conda-incubator/setup-miniconda@v3
with:
miniforge-variant: Mambaforge
use-mamba: true
From 3876429d72afb35247f4b2531eb9b16cfc7e0968 Mon Sep 17 00:00:00 2001
From: bashonly
Date: Sun, 11 Feb 2024 15:48:09 +0100
Subject: [PATCH 120/175] [build] Bump `actions/upload-artifact` to v4 and
adjust workflows
Authored by: bashonly
---
.github/workflows/build.yml | 36 ++++++++++++++++++++++++++---------
.github/workflows/release.yml | 6 +++++-
2 files changed, 32 insertions(+), 10 deletions(-)
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index e8a97e3f43da..cd7ead7966df 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -162,13 +162,15 @@ jobs:
done
- name: Upload artifacts
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
with:
+ name: build-${{ github.job }}
path: |
yt-dlp
yt-dlp.tar.gz
yt-dlp_linux
yt-dlp_linux.zip
+ compression-level: 0
linux_arm:
needs: process
@@ -223,10 +225,12 @@ jobs:
fi
- name: Upload artifacts
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
with:
+ name: build-linux_${{ matrix.architecture }}
path: | # run-on-arch-action designates armv7l as armv7
repo/dist/yt-dlp_linux_${{ (matrix.architecture == 'armv7' && 'armv7l') || matrix.architecture }}
+ compression-level: 0
macos:
needs: process
@@ -265,11 +269,13 @@ jobs:
[[ "$version" != "$downgraded_version" ]]
- name: Upload artifacts
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
with:
+ name: build-${{ github.job }}
path: |
dist/yt-dlp_macos
dist/yt-dlp_macos.zip
+ compression-level: 0
macos_legacy:
needs: process
@@ -316,10 +322,12 @@ jobs:
[[ "$version" != "$downgraded_version" ]]
- name: Upload artifacts
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
with:
+ name: build-${{ github.job }}
path: |
dist/yt-dlp_macos_legacy
+ compression-level: 0
windows:
needs: process
@@ -363,12 +371,14 @@ jobs:
}
- name: Upload artifacts
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
with:
+ name: build-${{ github.job }}
path: |
dist/yt-dlp.exe
dist/yt-dlp_min.exe
dist/yt-dlp_win.zip
+ compression-level: 0
windows32:
needs: process
@@ -409,10 +419,12 @@ jobs:
}
- name: Upload artifacts
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
with:
+ name: build-${{ github.job }}
path: |
dist/yt-dlp_x86.exe
+ compression-level: 0
meta_files:
if: inputs.meta_files && always() && !cancelled()
@@ -426,7 +438,11 @@ jobs:
- windows32
runs-on: ubuntu-latest
steps:
- - uses: actions/download-artifact@v3
+ - uses: actions/download-artifact@v4
+ with:
+ path: artifact
+ pattern: build-*
+ merge-multiple: true
- name: Make SHA2-SUMS files
run: |
@@ -461,8 +477,10 @@ jobs:
done
- name: Upload artifacts
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
with:
+ name: build-${{ github.job }}
path: |
- SHA*SUMS*
_update_spec
+ SHA*SUMS*
+ compression-level: 0
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index fac096be7d33..f5c6a793e19d 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -296,7 +296,11 @@ jobs:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- - uses: actions/download-artifact@v3
+ - uses: actions/download-artifact@v4
+ with:
+ path: artifact
+ pattern: build-*
+ merge-multiple: true
- uses: actions/setup-python@v5
with:
python-version: "3.10"
From 1ed5ee2f045f717e814f84ba461dadc58e712266 Mon Sep 17 00:00:00 2001
From: sepro <4618135+seproDev@users.noreply.github.com>
Date: Tue, 13 Feb 2024 04:11:17 +0100
Subject: [PATCH 121/175] [ie/Ant1NewsGrEmbed] Fix extractor (#9191)
Authored by: seproDev
---
yt_dlp/extractor/antenna.py | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/yt_dlp/extractor/antenna.py b/yt_dlp/extractor/antenna.py
index c78717aa9ec3..17a4b6900dc2 100644
--- a/yt_dlp/extractor/antenna.py
+++ b/yt_dlp/extractor/antenna.py
@@ -78,14 +78,14 @@ class Ant1NewsGrArticleIE(AntennaBaseIE):
_TESTS = [{
'url': 'https://www.ant1news.gr/afieromata/article/549468/o-tzeims-mpont-sta-meteora-oi-apeiles-kai-o-xesikomos-ton-kalogeron',
- 'md5': '294f18331bb516539d72d85a82887dcc',
+ 'md5': '57eb8d12181f0fa2b14b0b138e1de9b6',
'info_dict': {
'id': '_xvg/m_cmbatw=',
'ext': 'mp4',
'title': 'md5:a93e8ecf2e4073bfdffcb38f59945411',
- 'timestamp': 1603092840,
- 'upload_date': '20201019',
- 'thumbnail': 'https://ant1media.azureedge.net/imgHandler/640/756206d2-d640-40e2-b201-3555abdfc0db.jpg',
+ 'timestamp': 1666166520,
+ 'upload_date': '20221019',
+ 'thumbnail': 'https://ant1media.azureedge.net/imgHandler/1920/756206d2-d640-40e2-b201-3555abdfc0db.jpg',
},
}, {
'url': 'https://ant1news.gr/Society/article/620286/symmoria-anilikon-dikigoros-thymaton-ithelan-na-toys-apoteleiosoyn',
@@ -117,7 +117,7 @@ class Ant1NewsGrEmbedIE(AntennaBaseIE):
_BASE_PLAYER_URL_RE = r'(?:https?:)?//(?:[a-zA-Z0-9\-]+\.)?(?:antenna|ant1news)\.gr/templates/pages/player'
_VALID_URL = rf'{_BASE_PLAYER_URL_RE}\?([^#]+&)?cid=(?P[^#&]+)'
_EMBED_REGEX = [rf'