mirror of
https://github.com/yt-dlp/yt-dlp.git
synced 2026-07-06 18:29:14 +00:00
[ie/streaks] Fix extractor (#16413)
* Add `api_key` extractor-arg * Fix SSAI detection * Add hooks for custom Streaks playback API requests Authored by: doe1080
This commit is contained in:
parent
161dd9fd05
commit
26654a359d
@ -1969,6 +1969,9 @@ The following extractors use this feature:
|
|||||||
#### sonylivseries
|
#### sonylivseries
|
||||||
* `sort_order`: Episode sort order for series extraction - one of `asc` (ascending, oldest first) or `desc` (descending, newest first). Default is `asc`
|
* `sort_order`: Episode sort order for series extraction - one of `asc` (ascending, oldest first) or `desc` (descending, newest first). Default is `asc`
|
||||||
|
|
||||||
|
#### streaks
|
||||||
|
* `api_key`: API key for the `X-Streaks-Api-Key` header
|
||||||
|
|
||||||
#### tver
|
#### tver
|
||||||
* `backend`: Backend API to use for extraction - one of `streaks` (default) or `brightcove` (deprecated)
|
* `backend`: Backend API to use for extraction - one of `streaks` (default) or `brightcove` (deprecated)
|
||||||
|
|
||||||
|
|||||||
@ -5,12 +5,13 @@ from .common import InfoExtractor
|
|||||||
from ..networking.exceptions import HTTPError
|
from ..networking.exceptions import HTTPError
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
ExtractorError,
|
ExtractorError,
|
||||||
|
clean_html,
|
||||||
filter_dict,
|
filter_dict,
|
||||||
float_or_none,
|
float_or_none,
|
||||||
|
int_or_none,
|
||||||
join_nonempty,
|
join_nonempty,
|
||||||
mimetype2ext,
|
mimetype2ext,
|
||||||
parse_iso8601,
|
parse_iso8601,
|
||||||
unsmuggle_url,
|
|
||||||
update_url_query,
|
update_url_query,
|
||||||
url_or_none,
|
url_or_none,
|
||||||
)
|
)
|
||||||
@ -22,22 +23,28 @@ class StreaksBaseIE(InfoExtractor):
|
|||||||
_GEO_BYPASS = False
|
_GEO_BYPASS = False
|
||||||
_GEO_COUNTRIES = ['JP']
|
_GEO_COUNTRIES = ['JP']
|
||||||
|
|
||||||
def _extract_from_streaks_api(self, project_id, media_id, headers=None, query=None, ssai=False, live_from_start=False):
|
def _streaks_playback_api_url(self, project_id, media_id):
|
||||||
try:
|
return self._API_URL_TEMPLATE.format('playback', project_id, media_id, '')
|
||||||
response = self._download_json(
|
|
||||||
self._API_URL_TEMPLATE.format('playback', project_id, media_id, ''),
|
def _download_streaks_playback_json(self, project_id, media_id, headers=None):
|
||||||
|
return self._download_json(
|
||||||
|
self._streaks_playback_api_url(project_id, media_id),
|
||||||
media_id, 'Downloading STREAKS playback API JSON', headers={
|
media_id, 'Downloading STREAKS playback API JSON', headers={
|
||||||
'Accept': 'application/json',
|
'Accept': 'application/json',
|
||||||
'Origin': 'https://players.streaks.jp',
|
'Origin': 'https://players.streaks.jp',
|
||||||
**self.geo_verification_headers(),
|
**self.geo_verification_headers(),
|
||||||
**(headers or {}),
|
**(headers or {}),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
def _extract_from_streaks_api(self, project_id, media_id, headers=None, query=None, live_from_start=False):
|
||||||
|
try:
|
||||||
|
response = self._download_streaks_playback_json(project_id, media_id, headers=headers)
|
||||||
except ExtractorError as e:
|
except ExtractorError as e:
|
||||||
if isinstance(e.cause, HTTPError) and e.cause.status in (403, 404):
|
if isinstance(e.cause, HTTPError) and e.cause.status in (403, 404):
|
||||||
error = self._parse_json(e.cause.response.read().decode(), media_id, fatal=False)
|
error = self._parse_json(e.cause.response.read().decode(), media_id, fatal=False)
|
||||||
message = traverse_obj(error, ('message', {str}))
|
message = traverse_obj(error, ('message', {clean_html}, filter))
|
||||||
code = traverse_obj(error, ('code', {str}))
|
code = traverse_obj(error, ('code', {clean_html}, filter))
|
||||||
error_id = traverse_obj(error, ('id', {int}))
|
error_id = traverse_obj(error, ('id', {int_or_none}))
|
||||||
if code == 'REQUEST_FAILED':
|
if code == 'REQUEST_FAILED':
|
||||||
if error_id == 124:
|
if error_id == 124:
|
||||||
self.raise_geo_restricted(countries=self._GEO_COUNTRIES)
|
self.raise_geo_restricted(countries=self._GEO_COUNTRIES)
|
||||||
@ -59,8 +66,12 @@ class StreaksBaseIE(InfoExtractor):
|
|||||||
|
|
||||||
formats, subtitles = [], {}
|
formats, subtitles = [], {}
|
||||||
drm_formats = False
|
drm_formats = False
|
||||||
|
sources = response['sources']
|
||||||
|
ssai = traverse_obj(sources, (..., 'ssai', {dict}, any))
|
||||||
|
|
||||||
for source in traverse_obj(response, ('sources', lambda _, v: v['src'])):
|
for source in traverse_obj(sources, (
|
||||||
|
lambda _, v: url_or_none(v['src']),
|
||||||
|
)):
|
||||||
if source.get('key_systems'):
|
if source.get('key_systems'):
|
||||||
drm_formats = True
|
drm_formats = True
|
||||||
continue
|
continue
|
||||||
@ -108,12 +119,12 @@ class StreaksBaseIE(InfoExtractor):
|
|||||||
'subtitles': subtitles,
|
'subtitles': subtitles,
|
||||||
'uploader_id': project_id,
|
'uploader_id': project_id,
|
||||||
**traverse_obj(response, {
|
**traverse_obj(response, {
|
||||||
'title': ('name', {str}),
|
'title': ('name', {clean_html}, filter),
|
||||||
'description': ('description', {str}, filter),
|
'description': ('description', {clean_html}, filter),
|
||||||
'duration': ('duration', {float_or_none}),
|
'duration': ('duration', {float_or_none}),
|
||||||
'modified_timestamp': ('updated_at', {parse_iso8601}),
|
'modified_timestamp': ('updated_at', {parse_iso8601}),
|
||||||
'tags': ('tags', ..., {str}),
|
'tags': ('tags', ..., {clean_html}, filter, all, filter),
|
||||||
'thumbnails': (('poster', 'thumbnail'), 'src', {'url': {url_or_none}}),
|
'thumbnail': (('thumbnail', 'poster'), 'src', {url_or_none}, any),
|
||||||
'timestamp': ('created_at', {parse_iso8601}),
|
'timestamp': ('created_at', {parse_iso8601}),
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
@ -121,26 +132,27 @@ class StreaksBaseIE(InfoExtractor):
|
|||||||
|
|
||||||
class StreaksIE(StreaksBaseIE):
|
class StreaksIE(StreaksBaseIE):
|
||||||
_VALID_URL = [
|
_VALID_URL = [
|
||||||
r'https?://players\.streaks\.jp/(?P<project_id>[\w-]+)/[\da-f]+/index\.html\?(?:[^#]+&)?m=(?P<id>(?:ref:)?[\w-]+)',
|
r'https?://players\.streaks\.jp/(?P<project_id>[\w-]+)/(?P<api_key>[\da-f]+)/index\.html\?(?:[^#]+&)?m=(?P<id>(?:ref:)?[\w-]+)',
|
||||||
r'https?://playback\.api\.streaks\.jp/v1/projects/(?P<project_id>[\w-]+)/medias/(?P<id>(?:ref:)?[\w-]+)',
|
r'https?://playback\.api\.streaks\.jp/v1/projects/(?P<project_id>[\w-]+)/medias/(?P<id>(?:ref:)?[\w-]+)',
|
||||||
]
|
]
|
||||||
_EMBED_REGEX = [rf'<iframe\s+[^>]*\bsrc\s*=\s*["\'](?P<url>{_VALID_URL[0]})']
|
_EMBED_REGEX = [rf'<iframe\s+[^>]*\bsrc\s*=\s*["\'](?P<url>{_VALID_URL[0]})']
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'https://players.streaks.jp/tipness/08155cd19dc14c12bebefb69b92eafcc/index.html?m=dbdf2df35b4d483ebaeeaeb38c594647',
|
# https://online.tipness.co.jp/contents/9a064492-a62b-5d03-4668-6b40d6325b1e
|
||||||
|
'url': 'https://players.streaks.jp/tipness/08155cd19dc14c12bebefb69b92eafcc/index.html?m=ba2c253508914d9ea061a5f26bc58b20',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'dbdf2df35b4d483ebaeeaeb38c594647',
|
'id': 'ba2c253508914d9ea061a5f26bc58b20',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': '3shunenCM_edit.mp4',
|
'title': 'tarun_suimin.mp4',
|
||||||
'display_id': 'dbdf2df35b4d483ebaeeaeb38c594647',
|
'duration': 265.344,
|
||||||
'duration': 47.533,
|
|
||||||
'live_status': 'not_live',
|
'live_status': 'not_live',
|
||||||
'modified_date': '20230726',
|
'modified_date': '20230908',
|
||||||
'modified_timestamp': 1690356180,
|
'modified_timestamp': 1694146842,
|
||||||
'timestamp': 1690355996,
|
'timestamp': 1694145352,
|
||||||
'upload_date': '20230726',
|
'upload_date': '20230908',
|
||||||
'uploader_id': 'tipness',
|
'uploader_id': 'tipness',
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
|
# https://www.ktv.jp/mycoffeetime/
|
||||||
'url': 'https://players.streaks.jp/ktv-web/0298e8964c164ab384c07ef6e08c444b/index.html?m=ref:mycoffeetime_250317',
|
'url': 'https://players.streaks.jp/ktv-web/0298e8964c164ab384c07ef6e08c444b/index.html?m=ref:mycoffeetime_250317',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'dccdc079e3fd41f88b0c8435e2d453ab',
|
'id': 'dccdc079e3fd41f88b0c8435e2d453ab',
|
||||||
@ -157,22 +169,24 @@ class StreaksIE(StreaksBaseIE):
|
|||||||
'uploader_id': 'ktv-web',
|
'uploader_id': 'ktv-web',
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
'url': 'https://playback.api.streaks.jp/v1/projects/ktv-web/medias/b5411938e1e5435dac71edf829dd4813',
|
# https://www.ktv.jp/news/articles/?id=28105
|
||||||
|
'url': 'https://playback.api.streaks.jp/v1/projects/ktv-news/medias/714171c4c53c409bb41e1572997ebfcf',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'b5411938e1e5435dac71edf829dd4813',
|
'id': '714171c4c53c409bb41e1572997ebfcf',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'KANTELE_SYUSEi_0630',
|
'title': '28105.mp4',
|
||||||
'display_id': 'b5411938e1e5435dac71edf829dd4813',
|
'duration': 49.984,
|
||||||
'live_status': 'not_live',
|
'live_status': 'not_live',
|
||||||
'modified_date': '20250122',
|
'modified_date': '20260702',
|
||||||
'modified_timestamp': 1737522999,
|
'modified_timestamp': 1783028559,
|
||||||
'thumbnail': r're:https?://.+\.jpg',
|
'thumbnail': r're:https?://.+\.jpg',
|
||||||
'timestamp': 1735205137,
|
'timestamp': 1783028407,
|
||||||
'upload_date': '20241226',
|
'upload_date': '20260702',
|
||||||
'uploader_id': 'ktv-web',
|
'uploader_id': 'ktv-news',
|
||||||
},
|
},
|
||||||
|
'params': {'extractor_args': {'streaks': {'api_key': ['0ff2ccfb6381401582d6ee60e3cb66a1']}}},
|
||||||
}, {
|
}, {
|
||||||
# TVer Olympics: website already down, but api remains accessible
|
# https://tver.jp/olympic/paris2024/live/FBLMTEAM11------------SFNL000100--/
|
||||||
'url': 'https://playback.api.streaks.jp/v1/projects/tver-olympic/medias/ref:sp_240806_1748_dvr',
|
'url': 'https://playback.api.streaks.jp/v1/projects/tver-olympic/medias/ref:sp_240806_1748_dvr',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'c10f7345adb648cf804d7578ab93b2e3',
|
'id': 'c10f7345adb648cf804d7578ab93b2e3',
|
||||||
@ -187,8 +201,10 @@ class StreaksIE(StreaksBaseIE):
|
|||||||
'upload_date': '20240804',
|
'upload_date': '20240804',
|
||||||
'uploader_id': 'tver-olympic',
|
'uploader_id': 'tver-olympic',
|
||||||
},
|
},
|
||||||
|
'params': {'extractor_args': {'streaks': {'api_key': ['e09168c4383d4b18949067022558f071']}}},
|
||||||
|
'skip': 'Invalid URL',
|
||||||
}, {
|
}, {
|
||||||
# TBS FREE: 24-hour stream
|
# https://cu.tbs.co.jp/simul/simul-02
|
||||||
'url': 'https://playback.api.streaks.jp/v1/projects/tbs/medias/ref:simul-02',
|
'url': 'https://playback.api.streaks.jp/v1/projects/tbs/medias/ref:simul-02',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'c4e83a7b48f4409a96adacec674b4e22',
|
'id': 'c4e83a7b48f4409a96adacec674b4e22',
|
||||||
@ -202,12 +218,14 @@ class StreaksIE(StreaksBaseIE):
|
|||||||
'upload_date': '20240117',
|
'upload_date': '20240117',
|
||||||
'uploader_id': 'tbs',
|
'uploader_id': 'tbs',
|
||||||
},
|
},
|
||||||
|
'skip': 'Invalid URL',
|
||||||
}, {
|
}, {
|
||||||
# DRM protected
|
# DRM protected
|
||||||
'url': 'https://players.streaks.jp/sp-jbc/a12d7ee0f40c49d6a0a2bff520639677/index.html?m=5f89c62f37ee4a68be8e6e3b1396c7d8',
|
'url': 'https://players.streaks.jp/sp-jbc/a12d7ee0f40c49d6a0a2bff520639677/index.html?m=5f89c62f37ee4a68be8e6e3b1396c7d8',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
}]
|
}]
|
||||||
_WEBPAGE_TESTS = [{
|
_WEBPAGE_TESTS = [{
|
||||||
|
# https://players.streaks.jp/play/719af2a1d2d544e89bcad3456eeae5d9/index.html?m=2d975178293140dc8074a7fc536a7604
|
||||||
'url': 'https://event.play.jp/playnext2023/',
|
'url': 'https://event.play.jp/playnext2023/',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '2d975178293140dc8074a7fc536a7604',
|
'id': '2d975178293140dc8074a7fc536a7604',
|
||||||
@ -222,6 +240,7 @@ class StreaksIE(StreaksBaseIE):
|
|||||||
'modified_date': '20250213',
|
'modified_date': '20250213',
|
||||||
'live_status': 'not_live',
|
'live_status': 'not_live',
|
||||||
},
|
},
|
||||||
|
'params': {'nocheckcertificate': True},
|
||||||
}, {
|
}, {
|
||||||
'url': 'https://wowshop.jp/Page/special/cooking_goods/?bid=wowshop&srsltid=AfmBOor_phUNoPEE_UCPiGGSCMrJE5T2US397smvsbrSdLqUxwON0el4',
|
'url': 'https://wowshop.jp/Page/special/cooking_goods/?bid=wowshop&srsltid=AfmBOor_phUNoPEE_UCPiGGSCMrJE5T2US397smvsbrSdLqUxwON0el4',
|
||||||
'playlist_mincount': 2,
|
'playlist_mincount': 2,
|
||||||
@ -232,13 +251,15 @@ class StreaksIE(StreaksBaseIE):
|
|||||||
'age_limit': 0,
|
'age_limit': 0,
|
||||||
'thumbnail': 'https://wowshop.jp/Page/special/cooking_goods/images/ogp.jpg',
|
'thumbnail': 'https://wowshop.jp/Page/special/cooking_goods/images/ogp.jpg',
|
||||||
},
|
},
|
||||||
|
'skip': 'Invalid URL',
|
||||||
}]
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
url, smuggled_data = unsmuggle_url(url, {})
|
mobj = self._match_valid_url(url).groupdict()
|
||||||
project_id, media_id = self._match_valid_url(url).group('project_id', 'id')
|
project_id, media_id = mobj['project_id'], mobj['id']
|
||||||
|
api_key = mobj.get('api_key') or self._configuration_arg('api_key', [None])[0]
|
||||||
|
|
||||||
return self._extract_from_streaks_api(
|
return self._extract_from_streaks_api(
|
||||||
project_id, media_id, headers=filter_dict({
|
project_id, media_id, headers=filter_dict({
|
||||||
'X-Streaks-Api-Key': smuggled_data.get('api_key'),
|
'X-Streaks-Api-Key': api_key,
|
||||||
}))
|
}))
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user