mirror of
https://github.com/yt-dlp/yt-dlp.git
synced 2024-11-25 02:45:12 +00:00
Merge branch 'yt-dlp:master' into ie-piramidetv
This commit is contained in:
commit
7297a8938e
21 changed files with 330 additions and 30 deletions
5
.github/ISSUE_TEMPLATE/1_broken_site.yml
vendored
5
.github/ISSUE_TEMPLATE/1_broken_site.yml
vendored
|
@ -77,3 +77,8 @@ body:
|
|||
render: shell
|
||||
validations:
|
||||
required: true
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
### NOTE: Due to a recent increase in malicious spam activity, this issue will be automatically locked until it is triaged by a maintainer.
|
||||
### If you receive any replies asking you download a file, do NOT follow the download links!
|
||||
|
|
|
@ -89,3 +89,8 @@ body:
|
|||
render: shell
|
||||
validations:
|
||||
required: true
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
### NOTE: Due to a recent increase in malicious spam activity, this issue will be automatically locked until it is triaged by a maintainer.
|
||||
### If you receive any replies asking you download a file, do NOT follow the download links!
|
||||
|
|
|
@ -85,3 +85,8 @@ body:
|
|||
render: shell
|
||||
validations:
|
||||
required: true
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
### NOTE: Due to a recent increase in malicious spam activity, this issue will be automatically locked until it is triaged by a maintainer.
|
||||
### If you receive any replies asking you download a file, do NOT follow the download links!
|
||||
|
|
5
.github/ISSUE_TEMPLATE/4_bug_report.yml
vendored
5
.github/ISSUE_TEMPLATE/4_bug_report.yml
vendored
|
@ -70,3 +70,8 @@ body:
|
|||
render: shell
|
||||
validations:
|
||||
required: true
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
### NOTE: Due to a recent increase in malicious spam activity, this issue will be automatically locked until it is triaged by a maintainer.
|
||||
### If you receive any replies asking you download a file, do NOT follow the download links!
|
||||
|
|
5
.github/ISSUE_TEMPLATE/5_feature_request.yml
vendored
5
.github/ISSUE_TEMPLATE/5_feature_request.yml
vendored
|
@ -64,3 +64,8 @@ body:
|
|||
[youtube] Extracting URL: https://www.youtube.com/watch?v=BaW_jenozKc
|
||||
<more lines>
|
||||
render: shell
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
### NOTE: Due to a recent increase in malicious spam activity, this issue will be automatically locked until it is triaged by a maintainer.
|
||||
### If you receive any replies asking you download a file, do NOT follow the download links!
|
||||
|
|
5
.github/ISSUE_TEMPLATE/6_question.yml
vendored
5
.github/ISSUE_TEMPLATE/6_question.yml
vendored
|
@ -70,3 +70,8 @@ body:
|
|||
[youtube] Extracting URL: https://www.youtube.com/watch?v=BaW_jenozKc
|
||||
<more lines>
|
||||
render: shell
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
### NOTE: Due to a recent increase in malicious spam activity, this issue will be automatically locked until it is triaged by a maintainer.
|
||||
### If you receive any replies asking you download a file, do NOT follow the download links!
|
||||
|
|
20
.github/workflows/antispam.yaml
vendored
Normal file
20
.github/workflows/antispam.yaml
vendored
Normal file
|
@ -0,0 +1,20 @@
|
|||
name: Anti-Spam
|
||||
on:
|
||||
issues:
|
||||
types: [opened]
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
lockdown:
|
||||
name: Issue Lockdown
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: "Lock new issue"
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
ISSUE_NUMBER: ${{ github.event.issue.number }}
|
||||
REPOSITORY: ${{ github.repository }}
|
||||
run: |
|
||||
gh issue lock "${ISSUE_NUMBER}" -r too_heated -R "${REPOSITORY}"
|
1
.github/workflows/core.yml
vendored
1
.github/workflows/core.yml
vendored
|
@ -55,6 +55,7 @@ jobs:
|
|||
- name: Install test requirements
|
||||
run: python3 ./devscripts/install_deps.py --include test --include curl-cffi
|
||||
- name: Run tests
|
||||
timeout-minutes: 15
|
||||
continue-on-error: False
|
||||
run: |
|
||||
python3 -m yt_dlp -v || true # Print debug head
|
||||
|
|
1
.github/workflows/quick-test.yml
vendored
1
.github/workflows/quick-test.yml
vendored
|
@ -17,6 +17,7 @@ jobs:
|
|||
- name: Install test requirements
|
||||
run: python3 ./devscripts/install_deps.py --include test
|
||||
- name: Run tests
|
||||
timeout-minutes: 15
|
||||
run: |
|
||||
python3 -m yt_dlp -v || true
|
||||
python3 ./devscripts/run_tests.py core
|
||||
|
|
|
@ -46,6 +46,11 @@
|
|||
render: shell
|
||||
validations:
|
||||
required: true
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
### NOTE: Due to a recent increase in malicious spam activity, this issue will be automatically locked until it is triaged by a maintainer.
|
||||
### If you receive any replies asking you download a file, do NOT follow the download links!
|
||||
'''.strip()
|
||||
|
||||
NO_SKIP = '''
|
||||
|
|
|
@ -49,7 +49,7 @@ dependencies = [
|
|||
"pycryptodomex",
|
||||
"requests>=2.32.2,<3",
|
||||
"urllib3>=1.26.17,<3",
|
||||
"websockets>=12.0",
|
||||
"websockets>=13.0",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
|
|
|
@ -88,7 +88,7 @@ def create_wss_websocket_server():
|
|||
certfn = os.path.join(TEST_DIR, 'testcert.pem')
|
||||
sslctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
|
||||
sslctx.load_cert_chain(certfn, None)
|
||||
return create_websocket_server(ssl_context=sslctx)
|
||||
return create_websocket_server(ssl=sslctx)
|
||||
|
||||
|
||||
MTLS_CERT_DIR = os.path.join(TEST_DIR, 'testdata', 'certificate')
|
||||
|
@ -103,7 +103,7 @@ def create_mtls_wss_websocket_server():
|
|||
sslctx.load_verify_locations(cafile=cacertfn)
|
||||
sslctx.load_cert_chain(certfn, None)
|
||||
|
||||
return create_websocket_server(ssl_context=sslctx)
|
||||
return create_websocket_server(ssl=sslctx)
|
||||
|
||||
|
||||
def create_legacy_wss_websocket_server():
|
||||
|
@ -112,7 +112,7 @@ def create_legacy_wss_websocket_server():
|
|||
sslctx.maximum_version = ssl.TLSVersion.TLSv1_2
|
||||
sslctx.set_ciphers('SHA1:AESCCM:aDSS:eNULL:aNULL')
|
||||
sslctx.load_cert_chain(certfn, None)
|
||||
return create_websocket_server(ssl_context=sslctx)
|
||||
return create_websocket_server(ssl=sslctx)
|
||||
|
||||
|
||||
def ws_validate_and_send(rh, req):
|
||||
|
@ -139,7 +139,7 @@ def setup_class(cls):
|
|||
cls.wss_thread, cls.wss_port = create_wss_websocket_server()
|
||||
cls.wss_base_url = f'wss://127.0.0.1:{cls.wss_port}'
|
||||
|
||||
cls.bad_wss_thread, cls.bad_wss_port = create_websocket_server(ssl_context=ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER))
|
||||
cls.bad_wss_thread, cls.bad_wss_port = create_websocket_server(ssl=ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER))
|
||||
cls.bad_wss_host = f'wss://127.0.0.1:{cls.bad_wss_port}'
|
||||
|
||||
cls.mtls_wss_thread, cls.mtls_wss_port = create_mtls_wss_websocket_server()
|
||||
|
|
|
@ -2316,6 +2316,7 @@
|
|||
VideomoreVideoIE,
|
||||
)
|
||||
from .videopress import VideoPressIE
|
||||
from .vidflex import VidflexIE
|
||||
from .vidio import (
|
||||
VidioIE,
|
||||
VidioLiveIE,
|
||||
|
|
|
@ -101,9 +101,10 @@ def _real_initialize(self):
|
|||
self._HEADERS['Authorization'] = f'Bearer {token}'
|
||||
|
||||
def _real_extract(self, url):
|
||||
video_id, event, type_, slug = self._match_valid_url(url).group('id', 'event', 'type', 'slug')
|
||||
webpage, urlh = self._download_webpage_handle(url, self._match_id(url))
|
||||
video_id, event, type_, slug = self._match_valid_url(urlh.url).group('id', 'event', 'type', 'slug')
|
||||
video_type = {'archive': 'archives', 'player': 'broadcasts'}[type_]
|
||||
webpage = self._download_webpage(url, video_id)
|
||||
|
||||
event_data = traverse_obj(
|
||||
self._search_nextjs_data(webpage, video_id, default={}),
|
||||
('props', 'pageProps', 'eventCMSData', {
|
||||
|
|
|
@ -3,7 +3,12 @@
|
|||
|
||||
|
||||
class EurosportIE(InfoExtractor):
|
||||
_VALID_URL = r'https?://www\.eurosport\.com/\w+/(?:[\w-]+/[\d-]+/)?[\w-]+_(?P<id>vid\d+)'
|
||||
_VALID_URL = r'''(?x)
|
||||
https?://(?:
|
||||
(?:(?:www|espanol)\.)?eurosport\.(?:com(?:\.tr)?|de|dk|es|fr|hu|it|nl|no|ro)|
|
||||
eurosport\.tvn24\.pl
|
||||
)/[\w-]+/(?:[\w-]+/[\d-]+/)?[\w.-]+_(?P<id>vid\d+)
|
||||
'''
|
||||
_TESTS = [{
|
||||
'url': 'https://www.eurosport.com/tennis/roland-garros/2022/highlights-rafael-nadal-brushes-aside-caper-ruud-to-win-record-extending-14th-french-open-title_vid1694147/video.shtml',
|
||||
'info_dict': {
|
||||
|
@ -70,6 +75,42 @@ class EurosportIE(InfoExtractor):
|
|||
'duration': 105.0,
|
||||
'upload_date': '20230518',
|
||||
},
|
||||
}, {
|
||||
'url': 'https://www.eurosport.de/radsport/vuelta-a-espana/2024/vuelta-a-espana-2024-wout-van-aert-und-co.-verzweifeln-an-mcnulty-zeitfahr-krimi-in-lissabon_vid2219478/video.shtml',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
'url': 'https://www.eurosport.dk/speedway/mikkel-michelsen-misser-finalen-i-cardiff-se-danskeren-i-semifinalen-her_vid2219363/video.shtml',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
'url': 'https://www.eurosport.nl/mixed-martial-arts/ufc/2022/ufc-305-respect-tussen-adesanya-en-du-plessis_vid2219650/video.shtml',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
'url': 'https://www.eurosport.es/ciclismo/la-vuelta-2024-carlos-rodriguez-olvida-la-crono-y-ya-espera-que-llegue-la-montana-no-me-encontre-nada-comodo_vid2219682/video.shtml',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
'url': 'https://www.eurosport.fr/football/supercoupe-d-europe/2024-2025/kylian-mbappe-vinicius-junior-eduardo-camavinga-touche.-extraits-de-l-entrainement-du-real-madrid-en-video_vid2216993/video.shtml',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
'url': 'https://www.eurosport.it/calcio/serie-a/2024-2025/samardzic-a-bergamo-per-le-visite-mediche-con-l-atalanta_vid2219680/video.shtml',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
'url': 'https://www.eurosport.hu/kerekpar/vuelta-a-espana/2024/dramai-harc-a-masodpercekert-meglepetesgyoztes-a-vuelta-nyitoszakaszan_vid2219481/video.shtml',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
'url': 'https://www.eurosport.no/golf/fedex-st-jude-championship/2024/ligger-pa-andreplass-sa-skjer-dette-drama_vid30000618/video.shtml',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
'url': 'https://www.eurosport.no/golf/fedex-st-jude-championship/2024/ligger-pa-andreplass-sa-skjer-dette-drama_vid2219531/video.shtml',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
'url': 'https://www.eurosport.ro/tenis/western-southern-open-2/2024/rezumatul-partidei-dintre-zverev-si-shelton-de-la-cincinnati_vid2219657/video.shtml',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
'url': 'https://www.eurosport.com.tr/hentbol/olympic-games-paris-2024/2024/paris-2024-denmark-ile-germany-olimpiyatlarin-onemli-anlari_vid2215836/video.shtml',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
'url': 'https://eurosport.tvn24.pl/kolarstwo/tour-de-france-kobiet/2024/kasia-niewiadoma-przed-ostatnim-8.-etapem-tour-de-france-kobiet_vid2219765/video.shtml',
|
||||
'only_matching': True,
|
||||
}]
|
||||
|
||||
_TOKEN = None
|
||||
|
@ -77,6 +118,7 @@ class EurosportIE(InfoExtractor):
|
|||
# actually defined in https://netsport.eurosport.io/?variables={"databaseId":<databaseId>,"playoutType":"VDP"}&extensions={"persistedQuery":{"version":1 ..
|
||||
# but this method require to get sha256 hash
|
||||
_GEO_COUNTRIES = ['DE', 'NL', 'EU', 'IT', 'FR'] # Not complete list but it should work
|
||||
_GEO_BYPASS = False
|
||||
|
||||
def _real_initialize(self):
|
||||
if EurosportIE._TOKEN is None:
|
||||
|
@ -98,13 +140,13 @@ def _real_extract(self, url):
|
|||
for stream_type in json_data['attributes']['streaming']:
|
||||
if stream_type == 'hls':
|
||||
fmts, subs = self._extract_m3u8_formats_and_subtitles(
|
||||
traverse_obj(json_data, ('attributes', 'streaming', stream_type, 'url')), display_id, ext='mp4')
|
||||
traverse_obj(json_data, ('attributes', 'streaming', stream_type, 'url')), display_id, ext='mp4', fatal=False)
|
||||
elif stream_type == 'dash':
|
||||
fmts, subs = self._extract_mpd_formats_and_subtitles(
|
||||
traverse_obj(json_data, ('attributes', 'streaming', stream_type, 'url')), display_id)
|
||||
traverse_obj(json_data, ('attributes', 'streaming', stream_type, 'url')), display_id, fatal=False)
|
||||
elif stream_type == 'mss':
|
||||
fmts, subs = self._extract_ism_formats_and_subtitles(
|
||||
traverse_obj(json_data, ('attributes', 'streaming', stream_type, 'url')), display_id)
|
||||
traverse_obj(json_data, ('attributes', 'streaming', stream_type, 'url')), display_id, fatal=False)
|
||||
|
||||
formats.extend(fmts)
|
||||
self._merge_subtitles(subs, target=subtitles)
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
from ..utils import (
|
||||
ExtractorError,
|
||||
clean_html,
|
||||
join_nonempty,
|
||||
time_seconds,
|
||||
try_call,
|
||||
unified_timestamp,
|
||||
|
@ -167,7 +168,7 @@ def _extract_performers(self, prog):
|
|||
|
||||
|
||||
class RadikoIE(RadikoBaseIE):
|
||||
_VALID_URL = r'https?://(?:www\.)?radiko\.jp/#!/ts/(?P<station>[A-Z0-9-]+)/(?P<id>\d+)'
|
||||
_VALID_URL = r'https?://(?:www\.)?radiko\.jp/#!/ts/(?P<station>[A-Z0-9-]+)/(?P<timestring>\d+)'
|
||||
|
||||
_TESTS = [{
|
||||
# QRR (文化放送) station provides <desc>
|
||||
|
@ -183,8 +184,9 @@ class RadikoIE(RadikoBaseIE):
|
|||
}]
|
||||
|
||||
def _real_extract(self, url):
|
||||
station, video_id = self._match_valid_url(url).groups()
|
||||
vid_int = unified_timestamp(video_id, False)
|
||||
station, timestring = self._match_valid_url(url).group('station', 'timestring')
|
||||
video_id = join_nonempty(station, timestring)
|
||||
vid_int = unified_timestamp(timestring, False)
|
||||
prog, station_program, ft, radio_begin, radio_end = self._find_program(video_id, station, vid_int)
|
||||
|
||||
auth_token, area_id = self._auth_client()
|
||||
|
@ -207,7 +209,7 @@ def _real_extract(self, url):
|
|||
'ft': radio_begin,
|
||||
'end_at': radio_end,
|
||||
'to': radio_end,
|
||||
'seek': video_id,
|
||||
'seek': timestring,
|
||||
},
|
||||
),
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
determine_ext,
|
||||
int_or_none,
|
||||
parse_qs,
|
||||
traverse_obj,
|
||||
try_get,
|
||||
unified_timestamp,
|
||||
url_or_none,
|
||||
|
@ -80,6 +81,8 @@ def _extract_formats(self, options, video_id):
|
|||
'url': format_url,
|
||||
'format_id': format_id,
|
||||
})
|
||||
for hls_url in traverse_obj(options, ('live_streams', 'hls', ..., 'url', {url_or_none})):
|
||||
formats.extend(self._extract_m3u8_formats(hls_url, video_id, ext='mp4', fatal=False))
|
||||
return formats
|
||||
|
||||
def _download_and_extract_formats(self, video_id, query=None):
|
||||
|
@ -90,7 +93,7 @@ def _download_and_extract_formats(self, video_id, query=None):
|
|||
class RutubeIE(RutubeBaseIE):
|
||||
IE_NAME = 'rutube'
|
||||
IE_DESC = 'Rutube videos'
|
||||
_VALID_URL = r'https?://rutube\.ru/(?:video(?:/private)?|(?:play/)?embed)/(?P<id>[\da-z]{32})'
|
||||
_VALID_URL = r'https?://rutube\.ru/(?:(?:live/)?video(?:/private)?|(?:play/)?embed)/(?P<id>[\da-z]{32})'
|
||||
_EMBED_REGEX = [r'<iframe[^>]+?src=(["\'])(?P<url>(?:https?:)?//rutube\.ru/(?:play/)?embed/[\da-z]{32}.*?)\1']
|
||||
|
||||
_TESTS = [{
|
||||
|
@ -164,6 +167,29 @@ class RutubeIE(RutubeBaseIE):
|
|||
'uploader': 'Стас Быков',
|
||||
},
|
||||
'expected_warnings': ['Unable to download f4m'],
|
||||
}, {
|
||||
'url': 'https://rutube.ru/live/video/c58f502c7bb34a8fcdd976b221fca292/',
|
||||
'info_dict': {
|
||||
'id': 'c58f502c7bb34a8fcdd976b221fca292',
|
||||
'ext': 'mp4',
|
||||
'categories': ['Телепередачи'],
|
||||
'description': '',
|
||||
'thumbnail': 'http://pic.rutubelist.ru/video/14/19/14190807c0c48b40361aca93ad0867c7.jpg',
|
||||
'live_status': 'is_live',
|
||||
'age_limit': 0,
|
||||
'uploader_id': '23460655',
|
||||
'timestamp': 1652972968,
|
||||
'view_count': int,
|
||||
'upload_date': '20220519',
|
||||
'title': r're:Первый канал. Прямой эфир \d{4}-\d{2}-\d{2} \d{2}:\d{2}$',
|
||||
'uploader': 'Первый канал',
|
||||
},
|
||||
}, {
|
||||
'url': 'https://rutube.ru/video/5ab908fccfac5bb43ef2b1e4182256b0/',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
'url': 'https://rutube.ru/live/video/private/c58f502c7bb34a8fcdd976b221fca292/',
|
||||
'only_matching': True,
|
||||
}]
|
||||
|
||||
@classmethod
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
class TVN24IE(InfoExtractor):
|
||||
_WORKING = False
|
||||
_VALID_URL = r'https?://(?:(?:[^/]+)\.)?tvn24(?:bis)?\.pl/(?:[^/]+/)*(?P<id>[^/]+)'
|
||||
_VALID_URL = r'https?://(?:(?!eurosport)[^/]+\.)?tvn24(?:bis)?\.pl/(?:[^/?#]+/)*(?P<id>[^/?#]+)'
|
||||
_TESTS = [{
|
||||
'url': 'http://www.tvn24.pl/wiadomosci-z-kraju,3/oredzie-artura-andrusa,702428.html',
|
||||
'md5': 'fbdec753d7bc29d96036808275f2130c',
|
||||
|
|
|
@ -1764,7 +1764,7 @@ class TwitterSpacesIE(TwitterBaseIE):
|
|||
'release_timestamp': 1659904215,
|
||||
'release_date': '20220807',
|
||||
},
|
||||
'params': {'skip_download': 'm3u8'},
|
||||
'skip': 'No longer available',
|
||||
}, {
|
||||
# post_live/TimedOut but downloadable
|
||||
'url': 'https://twitter.com/i/spaces/1vAxRAVQWONJl',
|
||||
|
@ -1780,6 +1780,8 @@ class TwitterSpacesIE(TwitterBaseIE):
|
|||
'upload_date': '20230413',
|
||||
'release_timestamp': 1681839000,
|
||||
'release_date': '20230418',
|
||||
'protocol': 'm3u8', # ffmpeg is forced
|
||||
'container': 'm4a_dash', # audio-only format fixup is applied
|
||||
},
|
||||
'params': {'skip_download': 'm3u8'},
|
||||
}, {
|
||||
|
@ -1790,11 +1792,31 @@ class TwitterSpacesIE(TwitterBaseIE):
|
|||
'ext': 'm4a',
|
||||
'title': 'あ',
|
||||
'description': 'Twitter Space participated by nobody yet',
|
||||
'uploader': '息根とめる🔪Twitchで復活',
|
||||
'uploader': '息根とめる',
|
||||
'uploader_id': 'tomeru_ikinone',
|
||||
'live_status': 'was_live',
|
||||
'timestamp': 1685617198,
|
||||
'upload_date': '20230601',
|
||||
'protocol': 'm3u8', # ffmpeg is forced
|
||||
'container': 'm4a_dash', # audio-only format fixup is applied
|
||||
},
|
||||
'params': {'skip_download': 'm3u8'},
|
||||
}, {
|
||||
# Video Space
|
||||
'url': 'https://x.com/i/spaces/1DXGydznBYWKM',
|
||||
'info_dict': {
|
||||
'id': '1DXGydznBYWKM',
|
||||
'ext': 'mp4',
|
||||
'title': 'America and Israel’s “special relationship”',
|
||||
'description': 'Twitter Space participated by nobody yet',
|
||||
'uploader': 'Candace Owens',
|
||||
'uploader_id': 'RealCandaceO',
|
||||
'live_status': 'was_live',
|
||||
'timestamp': 1723931351,
|
||||
'upload_date': '20240817',
|
||||
'release_timestamp': 1723932000,
|
||||
'release_date': '20240817',
|
||||
'protocol': 'm3u8_native', # not ffmpeg, detected as video space
|
||||
},
|
||||
'params': {'skip_download': 'm3u8'},
|
||||
}]
|
||||
|
@ -1854,13 +1876,17 @@ def _real_extract(self, url):
|
|||
source = traverse_obj(
|
||||
self._call_api(f'live_video_stream/status/{metadata["media_key"]}', metadata['media_key']),
|
||||
('source', ('noRedirectPlaybackUrl', 'location'), {url_or_none}), get_all=False)
|
||||
formats = self._extract_m3u8_formats( # XXX: Some Spaces need ffmpeg as downloader
|
||||
source, metadata['media_key'], 'm4a', entry_protocol='m3u8', live=is_live,
|
||||
headers=headers, fatal=False) if source else []
|
||||
for fmt in formats:
|
||||
fmt.update({'vcodec': 'none', 'acodec': 'aac'})
|
||||
if not is_live:
|
||||
fmt['container'] = 'm4a_dash'
|
||||
is_audio_space = source and 'audio-space' in source
|
||||
formats = self._extract_m3u8_formats(
|
||||
source, metadata['media_key'], 'm4a' if is_audio_space else 'mp4',
|
||||
# XXX: Some audio-only Spaces need ffmpeg as downloader
|
||||
entry_protocol='m3u8' if is_audio_space else 'm3u8_native',
|
||||
live=is_live, headers=headers, fatal=False) if source else []
|
||||
if is_audio_space:
|
||||
for fmt in formats:
|
||||
fmt.update({'vcodec': 'none', 'acodec': 'aac'})
|
||||
if not is_live:
|
||||
fmt['container'] = 'm4a_dash'
|
||||
|
||||
participants = ', '.join(traverse_obj(
|
||||
space_data, ('participants', 'speakers', ..., 'display_name'))) or 'nobody yet'
|
||||
|
|
148
yt_dlp/extractor/vidflex.py
Normal file
148
yt_dlp/extractor/vidflex.py
Normal file
|
@ -0,0 +1,148 @@
|
|||
import base64
|
||||
import json
|
||||
|
||||
from .common import InfoExtractor
|
||||
from ..utils import (
|
||||
int_or_none,
|
||||
join_nonempty,
|
||||
mimetype2ext,
|
||||
url_or_none,
|
||||
)
|
||||
from ..utils.traversal import traverse_obj
|
||||
|
||||
|
||||
class VidflexIE(InfoExtractor):
|
||||
_DOMAINS_RE = [
|
||||
r'[^.]+\.vidflex\.tv',
|
||||
r'(?:www\.)?acactv\.ca',
|
||||
r'(?:www\.)?albertalacrossetv\.com',
|
||||
r'(?:www\.)?cjfltv\.com',
|
||||
r'(?:www\.)?figureitoutbaseball\.com',
|
||||
r'(?:www\.)?ocaalive\.com',
|
||||
r'(?:www\.)?pegasussports\.tv',
|
||||
r'(?:www\.)?praxisseries\.ca',
|
||||
r'(?:www\.)?silenticetv\.com',
|
||||
r'(?:www\.)?tuffhedemantv\.com',
|
||||
r'(?:www\.)?watchfuntv\.com',
|
||||
r'live\.ofsaa\.on\.ca',
|
||||
r'tv\.procoro\.ca',
|
||||
r'tv\.realcastmedia\.net',
|
||||
r'tv\.fringetheatre\.ca',
|
||||
r'video\.haisla\.ca',
|
||||
r'video\.hockeycanada\.ca',
|
||||
r'video\.huuayaht\.org',
|
||||
r'video\.turningpointensemble\.ca',
|
||||
r'videos\.livingworks\.net',
|
||||
r'videos\.telusworldofscienceedmonton\.ca',
|
||||
r'watch\.binghamtonbulldogs\.com',
|
||||
r'watch\.rekindle\.tv',
|
||||
r'watch\.wpca\.com',
|
||||
]
|
||||
_VALID_URL = rf'https?://(?:{"|".join(_DOMAINS_RE)})/[a-z]{{2}}(?:-[a-z]{{2}})?/c/[\w-]+\.(?P<id>\d+)'
|
||||
_TESTS = [{
|
||||
'url': 'https://video.hockeycanada.ca/en/c/nwt-micd-up-with-jamie-lee-rattray.107486',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
# m3u8 + https
|
||||
'url': 'https://video.hockeycanada.ca/en-us/c/nwt-micd-up-with-jamie-lee-rattray.107486',
|
||||
'info_dict': {
|
||||
'id': '107486',
|
||||
'title': 'NWT: Mic’d up with Jamie Lee Rattray',
|
||||
'ext': 'mp4',
|
||||
'duration': 115,
|
||||
'timestamp': 1634310409,
|
||||
'upload_date': '20211015',
|
||||
'tags': ['English', '2021', "National Women's Team"],
|
||||
'description': 'md5:efb1cf6165b48cc3f5555c4262dd5b23',
|
||||
'thumbnail': r're:^https?://wpmedia01-a\.akamaihd\.net/en/asset/public/image/.+',
|
||||
},
|
||||
'params': {'skip_download': True},
|
||||
}, {
|
||||
'url': 'https://video.hockeycanada.ca/en/c/mwc-remembering-the-wild-ride-in-riga.112307',
|
||||
'info_dict': {
|
||||
'id': '112307',
|
||||
'title': 'MWC: Remembering the wild ride in Riga',
|
||||
'ext': 'mp4',
|
||||
'duration': 322,
|
||||
'timestamp': 1716235607,
|
||||
'upload_date': '20240520',
|
||||
'tags': ['English', '2024', "National Men's Team", 'IIHF World Championship', 'Fan'],
|
||||
'description': r're:.+Canada’s National Men’s Team.+',
|
||||
'thumbnail': r're:^https?://wpmedia01-a\.akamaihd\.net/en/asset/public/image/.+',
|
||||
},
|
||||
'params': {'skip_download': True},
|
||||
}, {
|
||||
# the same video in French
|
||||
'url': 'https://video.hockeycanada.ca/fr/c/cmm-retour-sur-un-parcours-endiable-a-riga.112304',
|
||||
'info_dict': {
|
||||
'id': '112304',
|
||||
'title': 'CMM : Retour sur un parcours endiablé à Riga',
|
||||
'ext': 'mp4',
|
||||
'duration': 322,
|
||||
'timestamp': 1716235545,
|
||||
'upload_date': '20240520',
|
||||
'tags': ['French', '2024', "National Men's Team", 'IIHF World Championship', 'Fan'],
|
||||
'description': 'md5:cf825222882a3dab1cd62cffcf3b4d1f',
|
||||
'thumbnail': r're:^https?://wpmedia01-a\.akamaihd\.net/en/asset/public/image/.+',
|
||||
},
|
||||
'params': {'skip_download': True},
|
||||
}, {
|
||||
'url': 'https://myfbcgreenville.vidflex.tv/en/c/may-12th-2024.658',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
'url': 'https://www.figureitoutbaseball.com/en/c/fiob-podcast-14-dan-bertolini-ncaa-d1-head-coach-recorded-11-29-2018.1367',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
'url': 'https://videos.telusworldofscienceedmonton.ca/en/c/the-aurora-project-timelapse-4.577',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
'url': 'https://www.tuffhedemantv.com/en/c/2022-tuff-hedeman-tour-hobbs-nm-january-22.227',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
'url': 'https://www.albertalacrossetv.com/en/c/up-floor-ground-balls-one-more.3449',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
'url': 'https://www.silenticetv.com/en/c/jp-unlocked-day-in-the-life-of-langley-ha-15u.5197',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
'url': 'https://jphl.vidflex.tv/en/c/jp-unlocked-day-in-the-life-of-langley-ha-15u.5197',
|
||||
'only_matching': True,
|
||||
}]
|
||||
|
||||
def _real_extract(self, url):
|
||||
video_id = self._match_id(url)
|
||||
webpage = self._download_webpage(url, video_id)
|
||||
|
||||
data_url = self._html_search_regex(
|
||||
r'content_api:\s*(["\'])(?P<url>https?://(?:(?!\1).)+)\1', webpage, 'content api url', group='url')
|
||||
media_config = traverse_obj(
|
||||
self._download_json(data_url, video_id),
|
||||
('config', {base64.b64decode}, {bytes.decode}, {json.loads}, {dict}))
|
||||
|
||||
return {
|
||||
'id': video_id,
|
||||
'formats': list(self._yield_formats(media_config, video_id)),
|
||||
**self._search_json_ld(
|
||||
webpage.replace('/*<![CDATA[*/', '').replace('/*]]>*/', ''), video_id),
|
||||
}
|
||||
|
||||
def _yield_formats(self, media_config, video_id):
|
||||
for media_source in traverse_obj(media_config, ('media', 'source', lambda _, v: url_or_none(v['src']))):
|
||||
media_url = media_source['src']
|
||||
media_type = mimetype2ext(media_source.get('type'))
|
||||
|
||||
if media_type == 'm3u8':
|
||||
yield from self._extract_m3u8_formats(media_url, video_id, fatal=False, m3u8_id='hls')
|
||||
elif media_type == 'mp4':
|
||||
bitrate = self._search_regex(r'_(\d+)k\.mp4', media_url, 'bitrate', default=None)
|
||||
yield {
|
||||
'format_id': join_nonempty('http', bitrate),
|
||||
'url': media_url,
|
||||
'ext': 'mp4',
|
||||
'tbr': int_or_none(bitrate),
|
||||
}
|
||||
else:
|
||||
yield {
|
||||
'url': media_url,
|
||||
'ext': media_type,
|
||||
}
|
|
@ -47,10 +47,7 @@
|
|||
# 2: "AttributeError: 'ClientConnection' object has no attribute 'recv_events_exc'. Did you mean: 'recv_events'?"
|
||||
import websockets.sync.connection # isort: split
|
||||
with contextlib.suppress(Exception):
|
||||
# > 12.0
|
||||
websockets.sync.connection.Connection.recv_exc = None
|
||||
# 12.0
|
||||
websockets.sync.connection.Connection.recv_events_exc = None
|
||||
|
||||
|
||||
class WebsocketsResponseAdapter(WebSocketResponse):
|
||||
|
@ -162,7 +159,7 @@ def _send(self, request):
|
|||
additional_headers=headers,
|
||||
open_timeout=timeout,
|
||||
user_agent_header=None,
|
||||
ssl_context=ssl_ctx if wsuri.secure else None,
|
||||
ssl=ssl_ctx if wsuri.secure else None,
|
||||
close_timeout=0, # not ideal, but prevents yt-dlp hanging
|
||||
)
|
||||
return WebsocketsResponseAdapter(conn, url=request.url)
|
||||
|
|
Loading…
Reference in a new issue