mirror of
https://github.com/yt-dlp/yt-dlp.git
synced 2026-06-29 14:14:54 +00:00
Compare commits
No commits in common. "29e257037862f3b2ad65e6e8d2972f9ed89389e3" and "36b29bb3532e008a2aaf3d36d1c6fc3944137930" have entirely different histories.
29e2570378
...
36b29bb353
@ -84,7 +84,6 @@ from .agora import (
|
|||||||
)
|
)
|
||||||
from .airtv import AirTVIE
|
from .airtv import AirTVIE
|
||||||
from .aitube import AitubeKZVideoIE
|
from .aitube import AitubeKZVideoIE
|
||||||
from .alibaba import AlibabaIE
|
|
||||||
from .aliexpress import AliExpressLiveIE
|
from .aliexpress import AliExpressLiveIE
|
||||||
from .aljazeera import AlJazeeraIE
|
from .aljazeera import AlJazeeraIE
|
||||||
from .allocine import AllocineIE
|
from .allocine import AllocineIE
|
||||||
|
|||||||
@ -1,42 +0,0 @@
|
|||||||
from .common import InfoExtractor
|
|
||||||
from ..utils import int_or_none, str_or_none, url_or_none
|
|
||||||
from ..utils.traversal import traverse_obj
|
|
||||||
|
|
||||||
|
|
||||||
class AlibabaIE(InfoExtractor):
|
|
||||||
_VALID_URL = r'https?://(?:www\.)?alibaba\.com/product-detail/[\w-]+_(?P<id>\d+)\.html'
|
|
||||||
_TESTS = [{
|
|
||||||
'url': 'https://www.alibaba.com/product-detail/Kids-Entertainment-Bouncer-Bouncy-Castle-Waterslide_1601271126969.html',
|
|
||||||
'info_dict': {
|
|
||||||
'id': '6000280444270',
|
|
||||||
'display_id': '1601271126969',
|
|
||||||
'ext': 'mp4',
|
|
||||||
'title': 'Kids Entertainment Bouncer Bouncy Castle Waterslide Juex Gonflables Commercial Inflatable Tropical Water Slide',
|
|
||||||
'duration': 30,
|
|
||||||
'thumbnail': 'https://sc04.alicdn.com/kf/Hc5bb391974454af18c7a4f91cbe4062bg.jpg_120x120.jpg',
|
|
||||||
},
|
|
||||||
}]
|
|
||||||
|
|
||||||
def _real_extract(self, url):
|
|
||||||
display_id = self._match_id(url)
|
|
||||||
webpage = self._download_webpage(url, display_id)
|
|
||||||
product_data = self._search_json(
|
|
||||||
r'window\.detailData\s*=', webpage, 'detail data', display_id)['globalData']['product']
|
|
||||||
|
|
||||||
return {
|
|
||||||
**traverse_obj(product_data, ('mediaItems', lambda _, v: v['type'] == 'video' and v['videoId'], any, {
|
|
||||||
'id': ('videoId', {int}, {str_or_none}),
|
|
||||||
'duration': ('duration', {int_or_none}),
|
|
||||||
'thumbnail': ('videoCoverUrl', {url_or_none}),
|
|
||||||
'formats': ('videoUrl', lambda _, v: url_or_none(v['videoUrl']), {
|
|
||||||
'url': 'videoUrl',
|
|
||||||
'format_id': ('definition', {str_or_none}),
|
|
||||||
'tbr': ('bitrate', {int_or_none}),
|
|
||||||
'width': ('width', {int_or_none}),
|
|
||||||
'height': ('height', {int_or_none}),
|
|
||||||
'filesize': ('length', {int_or_none}),
|
|
||||||
}),
|
|
||||||
})),
|
|
||||||
'title': traverse_obj(product_data, ('subject', {str})),
|
|
||||||
'display_id': display_id,
|
|
||||||
}
|
|
||||||
@ -8,11 +8,10 @@ from ..utils import (
|
|||||||
|
|
||||||
|
|
||||||
class SportDeutschlandIE(InfoExtractor):
|
class SportDeutschlandIE(InfoExtractor):
|
||||||
IE_NAME = 'sporteurope'
|
_VALID_URL = r'https?://(?:player\.)?sportdeutschland\.tv/(?P<id>(?:[^/?#]+/)?[^?#/&]+)'
|
||||||
_VALID_URL = r'https?://(?:player\.)?sporteurope\.tv/(?P<id>(?:[^/?#]+/)?[^?#/&]+)'
|
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
# Single-part video, direct link
|
# Single-part video, direct link
|
||||||
'url': 'https://sporteurope.tv/rostock-griffins/gfl2-rostock-griffins-vs-elmshorn-fighting-pirates',
|
'url': 'https://sportdeutschland.tv/rostock-griffins/gfl2-rostock-griffins-vs-elmshorn-fighting-pirates',
|
||||||
'md5': '35c11a19395c938cdd076b93bda54cde',
|
'md5': '35c11a19395c938cdd076b93bda54cde',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '9f27a97d-1544-4d0b-aa03-48d92d17a03a',
|
'id': '9f27a97d-1544-4d0b-aa03-48d92d17a03a',
|
||||||
@ -20,9 +19,9 @@ class SportDeutschlandIE(InfoExtractor):
|
|||||||
'title': 'GFL2: Rostock Griffins vs. Elmshorn Fighting Pirates',
|
'title': 'GFL2: Rostock Griffins vs. Elmshorn Fighting Pirates',
|
||||||
'display_id': 'rostock-griffins/gfl2-rostock-griffins-vs-elmshorn-fighting-pirates',
|
'display_id': 'rostock-griffins/gfl2-rostock-griffins-vs-elmshorn-fighting-pirates',
|
||||||
'channel': 'Rostock Griffins',
|
'channel': 'Rostock Griffins',
|
||||||
'channel_url': 'https://sporteurope.tv/rostock-griffins',
|
'channel_url': 'https://sportdeutschland.tv/rostock-griffins',
|
||||||
'live_status': 'was_live',
|
'live_status': 'was_live',
|
||||||
'description': r're:Video-Livestream des Spiels Rostock Griffins vs\. Elmshorn Fighting Pirates.+',
|
'description': 'md5:60cb00067e55dafa27b0933a43d72862',
|
||||||
'channel_id': '9635f21c-3f67-4584-9ce4-796e9a47276b',
|
'channel_id': '9635f21c-3f67-4584-9ce4-796e9a47276b',
|
||||||
'timestamp': 1749913117,
|
'timestamp': 1749913117,
|
||||||
'upload_date': '20250614',
|
'upload_date': '20250614',
|
||||||
@ -30,16 +29,16 @@ class SportDeutschlandIE(InfoExtractor):
|
|||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
# Single-part video, embedded player link
|
# Single-part video, embedded player link
|
||||||
'url': 'https://player.sporteurope.tv/9e9619c4-7d77-43c4-926d-49fb57dc06dc',
|
'url': 'https://player.sportdeutschland.tv/9e9619c4-7d77-43c4-926d-49fb57dc06dc',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '9f27a97d-1544-4d0b-aa03-48d92d17a03a',
|
'id': '9f27a97d-1544-4d0b-aa03-48d92d17a03a',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'GFL2: Rostock Griffins vs. Elmshorn Fighting Pirates',
|
'title': 'GFL2: Rostock Griffins vs. Elmshorn Fighting Pirates',
|
||||||
'display_id': '9e9619c4-7d77-43c4-926d-49fb57dc06dc',
|
'display_id': '9e9619c4-7d77-43c4-926d-49fb57dc06dc',
|
||||||
'channel': 'Rostock Griffins',
|
'channel': 'Rostock Griffins',
|
||||||
'channel_url': 'https://sporteurope.tv/rostock-griffins',
|
'channel_url': 'https://sportdeutschland.tv/rostock-griffins',
|
||||||
'live_status': 'was_live',
|
'live_status': 'was_live',
|
||||||
'description': r're:Video-Livestream des Spiels Rostock Griffins vs\. Elmshorn Fighting Pirates.+',
|
'description': 'md5:60cb00067e55dafa27b0933a43d72862',
|
||||||
'channel_id': '9635f21c-3f67-4584-9ce4-796e9a47276b',
|
'channel_id': '9635f21c-3f67-4584-9ce4-796e9a47276b',
|
||||||
'timestamp': 1749913117,
|
'timestamp': 1749913117,
|
||||||
'upload_date': '20250614',
|
'upload_date': '20250614',
|
||||||
@ -48,7 +47,7 @@ class SportDeutschlandIE(InfoExtractor):
|
|||||||
'params': {'skip_download': True},
|
'params': {'skip_download': True},
|
||||||
}, {
|
}, {
|
||||||
# Multi-part video
|
# Multi-part video
|
||||||
'url': 'https://sporteurope.tv/rhine-ruhr-2025-fisu-world-university-games/volleyball-w-japan-vs-brasilien-halbfinale-2',
|
'url': 'https://sportdeutschland.tv/rhine-ruhr-2025-fisu-world-university-games/volleyball-w-japan-vs-brasilien-halbfinale-2',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '9f63d737-2444-4e3a-a1ea-840df73fd481',
|
'id': '9f63d737-2444-4e3a-a1ea-840df73fd481',
|
||||||
'display_id': 'rhine-ruhr-2025-fisu-world-university-games/volleyball-w-japan-vs-brasilien-halbfinale-2',
|
'display_id': 'rhine-ruhr-2025-fisu-world-university-games/volleyball-w-japan-vs-brasilien-halbfinale-2',
|
||||||
@ -56,7 +55,7 @@ class SportDeutschlandIE(InfoExtractor):
|
|||||||
'description': 'md5:0a17da15e48a687e6019639c3452572b',
|
'description': 'md5:0a17da15e48a687e6019639c3452572b',
|
||||||
'channel': 'Rhine-Ruhr 2025 FISU World University Games',
|
'channel': 'Rhine-Ruhr 2025 FISU World University Games',
|
||||||
'channel_id': '9f5216be-a49d-470b-9a30-4fe9df993334',
|
'channel_id': '9f5216be-a49d-470b-9a30-4fe9df993334',
|
||||||
'channel_url': 'https://sporteurope.tv/rhine-ruhr-2025-fisu-world-university-games',
|
'channel_url': 'https://sportdeutschland.tv/rhine-ruhr-2025-fisu-world-university-games',
|
||||||
'live_status': 'was_live',
|
'live_status': 'was_live',
|
||||||
},
|
},
|
||||||
'playlist_count': 2,
|
'playlist_count': 2,
|
||||||
@ -67,7 +66,7 @@ class SportDeutschlandIE(InfoExtractor):
|
|||||||
'title': 'Volleyball w: Japan vs. Braslien - Halbfinale 2 Part 1',
|
'title': 'Volleyball w: Japan vs. Braslien - Halbfinale 2 Part 1',
|
||||||
'channel': 'Rhine-Ruhr 2025 FISU World University Games',
|
'channel': 'Rhine-Ruhr 2025 FISU World University Games',
|
||||||
'channel_id': '9f5216be-a49d-470b-9a30-4fe9df993334',
|
'channel_id': '9f5216be-a49d-470b-9a30-4fe9df993334',
|
||||||
'channel_url': 'https://sporteurope.tv/rhine-ruhr-2025-fisu-world-university-games',
|
'channel_url': 'https://sportdeutschland.tv/rhine-ruhr-2025-fisu-world-university-games',
|
||||||
'duration': 14773.0,
|
'duration': 14773.0,
|
||||||
'timestamp': 1753085197,
|
'timestamp': 1753085197,
|
||||||
'upload_date': '20250721',
|
'upload_date': '20250721',
|
||||||
@ -80,17 +79,16 @@ class SportDeutschlandIE(InfoExtractor):
|
|||||||
'title': 'Volleyball w: Japan vs. Braslien - Halbfinale 2 Part 2',
|
'title': 'Volleyball w: Japan vs. Braslien - Halbfinale 2 Part 2',
|
||||||
'channel': 'Rhine-Ruhr 2025 FISU World University Games',
|
'channel': 'Rhine-Ruhr 2025 FISU World University Games',
|
||||||
'channel_id': '9f5216be-a49d-470b-9a30-4fe9df993334',
|
'channel_id': '9f5216be-a49d-470b-9a30-4fe9df993334',
|
||||||
'channel_url': 'https://sporteurope.tv/rhine-ruhr-2025-fisu-world-university-games',
|
'channel_url': 'https://sportdeutschland.tv/rhine-ruhr-2025-fisu-world-university-games',
|
||||||
'duration': 14773.0,
|
'duration': 14773.0,
|
||||||
'timestamp': 1753128421,
|
'timestamp': 1753128421,
|
||||||
'upload_date': '20250721',
|
'upload_date': '20250721',
|
||||||
'live_status': 'was_live',
|
'live_status': 'was_live',
|
||||||
},
|
},
|
||||||
}],
|
}],
|
||||||
'skip': '404 Not Found',
|
|
||||||
}, {
|
}, {
|
||||||
# Livestream
|
# Livestream
|
||||||
'url': 'https://sporteurope.tv/dtb/gymnastik-international-tag-1',
|
'url': 'https://sportdeutschland.tv/dtb/gymnastik-international-tag-1',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '95d71b8a-370a-4b87-ad16-94680da18528',
|
'id': '95d71b8a-370a-4b87-ad16-94680da18528',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
@ -98,7 +96,7 @@ class SportDeutschlandIE(InfoExtractor):
|
|||||||
'display_id': 'dtb/gymnastik-international-tag-1',
|
'display_id': 'dtb/gymnastik-international-tag-1',
|
||||||
'channel_id': '936ecef1-2f4a-4e08-be2f-68073cb7ecab',
|
'channel_id': '936ecef1-2f4a-4e08-be2f-68073cb7ecab',
|
||||||
'channel': 'Deutscher Turner-Bund',
|
'channel': 'Deutscher Turner-Bund',
|
||||||
'channel_url': 'https://sporteurope.tv/dtb',
|
'channel_url': 'https://sportdeutschland.tv/dtb',
|
||||||
'description': 'md5:07a885dde5838a6f0796ee21dc3b0c52',
|
'description': 'md5:07a885dde5838a6f0796ee21dc3b0c52',
|
||||||
'live_status': 'is_live',
|
'live_status': 'is_live',
|
||||||
},
|
},
|
||||||
@ -108,9 +106,9 @@ class SportDeutschlandIE(InfoExtractor):
|
|||||||
def _process_video(self, asset_id, video):
|
def _process_video(self, asset_id, video):
|
||||||
is_live = video['type'] == 'mux_live'
|
is_live = video['type'] == 'mux_live'
|
||||||
token = self._download_json(
|
token = self._download_json(
|
||||||
f'https://api.sporteurope.tv/api/web/personal/asset-token/{asset_id}',
|
f'https://api.sportdeutschland.tv/api/web/personal/asset-token/{asset_id}',
|
||||||
video['id'], query={'type': video['type'], 'playback_id': video['src']},
|
video['id'], query={'type': video['type'], 'playback_id': video['src']},
|
||||||
headers={'Referer': 'https://sporteurope.tv/'})['token']
|
headers={'Referer': 'https://sportdeutschland.tv/'})['token']
|
||||||
formats, subtitles = self._extract_m3u8_formats_and_subtitles(
|
formats, subtitles = self._extract_m3u8_formats_and_subtitles(
|
||||||
f'https://stream.mux.com/{video["src"]}.m3u8?token={token}', video['id'], live=is_live)
|
f'https://stream.mux.com/{video["src"]}.m3u8?token={token}', video['id'], live=is_live)
|
||||||
|
|
||||||
@ -128,7 +126,7 @@ class SportDeutschlandIE(InfoExtractor):
|
|||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
display_id = self._match_id(url)
|
display_id = self._match_id(url)
|
||||||
meta = self._download_json(
|
meta = self._download_json(
|
||||||
f'https://api.sporteurope.tv/api/stateless/frontend/assets/{display_id}',
|
f'https://api.sportdeutschland.tv/api/stateless/frontend/assets/{display_id}',
|
||||||
display_id, query={'access_token': 'true'})
|
display_id, query={'access_token': 'true'})
|
||||||
|
|
||||||
info = {
|
info = {
|
||||||
@ -141,7 +139,7 @@ class SportDeutschlandIE(InfoExtractor):
|
|||||||
'channel_id': ('profile', 'id'),
|
'channel_id': ('profile', 'id'),
|
||||||
'is_live': 'currently_live',
|
'is_live': 'currently_live',
|
||||||
'was_live': 'was_live',
|
'was_live': 'was_live',
|
||||||
'channel_url': ('profile', 'slug', {lambda x: f'https://sporteurope.tv/{x}'}),
|
'channel_url': ('profile', 'slug', {lambda x: f'https://sportdeutschland.tv/{x}'}),
|
||||||
}, get_all=False),
|
}, get_all=False),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
|
import base64
|
||||||
|
import codecs
|
||||||
import itertools
|
import itertools
|
||||||
import re
|
import re
|
||||||
import urllib.parse
|
import string
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
@ -14,6 +16,7 @@ from ..utils import (
|
|||||||
join_nonempty,
|
join_nonempty,
|
||||||
parse_duration,
|
parse_duration,
|
||||||
str_or_none,
|
str_or_none,
|
||||||
|
try_call,
|
||||||
try_get,
|
try_get,
|
||||||
unified_strdate,
|
unified_strdate,
|
||||||
url_or_none,
|
url_or_none,
|
||||||
@ -29,7 +32,7 @@ class _ByteGenerator:
|
|||||||
try:
|
try:
|
||||||
self._algorithm = getattr(self, f'_algo{algo_id}')
|
self._algorithm = getattr(self, f'_algo{algo_id}')
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
raise ExtractorError(f'Unknown algorithm ID "{algo_id}"')
|
raise ExtractorError(f'Unknown algorithm ID: {algo_id}')
|
||||||
self._s = to_signed_32(seed)
|
self._s = to_signed_32(seed)
|
||||||
|
|
||||||
def _algo1(self, s):
|
def _algo1(self, s):
|
||||||
@ -213,28 +216,32 @@ class XHamsterIE(InfoExtractor):
|
|||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
}]
|
}]
|
||||||
|
|
||||||
|
_XOR_KEY = b'xh7999'
|
||||||
|
|
||||||
def _decipher_format_url(self, format_url, format_id):
|
def _decipher_format_url(self, format_url, format_id):
|
||||||
parsed_url = urllib.parse.urlparse(format_url)
|
if all(char in string.hexdigits for char in format_url):
|
||||||
|
byte_data = bytes.fromhex(format_url)
|
||||||
hex_string, path_remainder = self._search_regex(
|
seed = int.from_bytes(byte_data[1:5], byteorder='little', signed=True)
|
||||||
r'^/(?P<hex>[0-9a-fA-F]{12,})(?P<rem>[/,].+)$', parsed_url.path, 'url components',
|
|
||||||
default=(None, None), group=('hex', 'rem'))
|
|
||||||
if not hex_string:
|
|
||||||
self.report_warning(f'Skipping format "{format_id}": unsupported URL format')
|
|
||||||
return None
|
|
||||||
|
|
||||||
byte_data = bytes.fromhex(hex_string)
|
|
||||||
seed = int.from_bytes(byte_data[1:5], byteorder='little', signed=True)
|
|
||||||
|
|
||||||
try:
|
|
||||||
byte_gen = _ByteGenerator(byte_data[0], seed)
|
byte_gen = _ByteGenerator(byte_data[0], seed)
|
||||||
except ExtractorError as e:
|
return bytearray(byte ^ next(byte_gen) for byte in byte_data[5:]).decode('latin-1')
|
||||||
self.report_warning(f'Skipping format "{format_id}": {e.msg}')
|
|
||||||
|
cipher_type, _, ciphertext = try_call(
|
||||||
|
lambda: base64.b64decode(format_url).decode().partition('_')) or [None] * 3
|
||||||
|
|
||||||
|
if not cipher_type or not ciphertext:
|
||||||
|
self.report_warning(f'Skipping format "{format_id}": failed to decipher URL')
|
||||||
return None
|
return None
|
||||||
|
|
||||||
deciphered = bytearray(byte ^ next(byte_gen) for byte in byte_data[5:]).decode('latin-1')
|
if cipher_type == 'xor':
|
||||||
|
return bytes(
|
||||||
|
a ^ b for a, b in
|
||||||
|
zip(ciphertext.encode(), itertools.cycle(self._XOR_KEY))).decode()
|
||||||
|
|
||||||
return parsed_url._replace(path=f'/{deciphered}{path_remainder}').geturl()
|
if cipher_type == 'rot13':
|
||||||
|
return codecs.decode(ciphertext, cipher_type)
|
||||||
|
|
||||||
|
self.report_warning(f'Skipping format "{format_id}": unsupported cipher type "{cipher_type}"')
|
||||||
|
return None
|
||||||
|
|
||||||
def _fixup_formats(self, formats):
|
def _fixup_formats(self, formats):
|
||||||
for f in formats:
|
for f in formats:
|
||||||
@ -357,11 +364,8 @@ class XHamsterIE(InfoExtractor):
|
|||||||
'height': get_height(quality),
|
'height': get_height(quality),
|
||||||
'filesize': format_sizes.get(quality),
|
'filesize': format_sizes.get(quality),
|
||||||
'http_headers': {
|
'http_headers': {
|
||||||
'Referer': urlh.url,
|
'Referer': standard_url,
|
||||||
},
|
},
|
||||||
# HTTP formats return "Wrong key" error even when deciphered by site JS
|
|
||||||
# TODO: Remove this when resolved on the site's end
|
|
||||||
'__needs_testing': True,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
categories_list = video.get('categories')
|
categories_list = video.get('categories')
|
||||||
@ -398,8 +402,7 @@ class XHamsterIE(InfoExtractor):
|
|||||||
'age_limit': age_limit if age_limit is not None else 18,
|
'age_limit': age_limit if age_limit is not None else 18,
|
||||||
'categories': categories,
|
'categories': categories,
|
||||||
'formats': self._fixup_formats(formats),
|
'formats': self._fixup_formats(formats),
|
||||||
# TODO: Revert to ('res', 'proto', 'tbr') when HTTP formats problem is resolved
|
'_format_sort_fields': ('res', 'proto', 'tbr'),
|
||||||
'_format_sort_fields': ('res', 'proto:m3u8', 'tbr'),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Old layout fallback
|
# Old layout fallback
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user