Skip to content

Commit 26654a3

Browse files
authored
[ie/streaks] Fix extractor (#16413)
* Add `api_key` extractor-arg * Fix SSAI detection * Add hooks for custom Streaks playback API requests Authored by: doe1080
1 parent 161dd9f commit 26654a3

2 files changed

Lines changed: 66 additions & 42 deletions

File tree

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1969,6 +1969,9 @@ The following extractors use this feature:
19691969
#### sonylivseries
19701970
* `sort_order`: Episode sort order for series extraction - one of `asc` (ascending, oldest first) or `desc` (descending, newest first). Default is `asc`
19711971

1972+
#### streaks
1973+
* `api_key`: API key for the `X-Streaks-Api-Key` header
1974+
19721975
#### tver
19731976
* `backend`: Backend API to use for extraction - one of `streaks` (default) or `brightcove` (deprecated)
19741977

yt_dlp/extractor/streaks.py

Lines changed: 63 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,13 @@
55
from ..networking.exceptions import HTTPError
66
from ..utils import (
77
ExtractorError,
8+
clean_html,
89
filter_dict,
910
float_or_none,
11+
int_or_none,
1012
join_nonempty,
1113
mimetype2ext,
1214
parse_iso8601,
13-
unsmuggle_url,
1415
update_url_query,
1516
url_or_none,
1617
)
@@ -22,22 +23,28 @@ class StreaksBaseIE(InfoExtractor):
2223
_GEO_BYPASS = False
2324
_GEO_COUNTRIES = ['JP']
2425

25-
def _extract_from_streaks_api(self, project_id, media_id, headers=None, query=None, ssai=False, live_from_start=False):
26+
def _streaks_playback_api_url(self, project_id, media_id):
27+
return self._API_URL_TEMPLATE.format('playback', project_id, media_id, '')
28+
29+
def _download_streaks_playback_json(self, project_id, media_id, headers=None):
30+
return self._download_json(
31+
self._streaks_playback_api_url(project_id, media_id),
32+
media_id, 'Downloading STREAKS playback API JSON', headers={
33+
'Accept': 'application/json',
34+
'Origin': 'https://players.streaks.jp',
35+
**self.geo_verification_headers(),
36+
**(headers or {}),
37+
})
38+
39+
def _extract_from_streaks_api(self, project_id, media_id, headers=None, query=None, live_from_start=False):
2640
try:
27-
response = self._download_json(
28-
self._API_URL_TEMPLATE.format('playback', project_id, media_id, ''),
29-
media_id, 'Downloading STREAKS playback API JSON', headers={
30-
'Accept': 'application/json',
31-
'Origin': 'https://players.streaks.jp',
32-
**self.geo_verification_headers(),
33-
**(headers or {}),
34-
})
41+
response = self._download_streaks_playback_json(project_id, media_id, headers=headers)
3542
except ExtractorError as e:
3643
if isinstance(e.cause, HTTPError) and e.cause.status in (403, 404):
3744
error = self._parse_json(e.cause.response.read().decode(), media_id, fatal=False)
38-
message = traverse_obj(error, ('message', {str}))
39-
code = traverse_obj(error, ('code', {str}))
40-
error_id = traverse_obj(error, ('id', {int}))
45+
message = traverse_obj(error, ('message', {clean_html}, filter))
46+
code = traverse_obj(error, ('code', {clean_html}, filter))
47+
error_id = traverse_obj(error, ('id', {int_or_none}))
4148
if code == 'REQUEST_FAILED':
4249
if error_id == 124:
4350
self.raise_geo_restricted(countries=self._GEO_COUNTRIES)
@@ -59,8 +66,12 @@ def _extract_from_streaks_api(self, project_id, media_id, headers=None, query=No
5966

6067
formats, subtitles = [], {}
6168
drm_formats = False
69+
sources = response['sources']
70+
ssai = traverse_obj(sources, (..., 'ssai', {dict}, any))
6271

63-
for source in traverse_obj(response, ('sources', lambda _, v: v['src'])):
72+
for source in traverse_obj(sources, (
73+
lambda _, v: url_or_none(v['src']),
74+
)):
6475
if source.get('key_systems'):
6576
drm_formats = True
6677
continue
@@ -108,39 +119,40 @@ def _extract_from_streaks_api(self, project_id, media_id, headers=None, query=No
108119
'subtitles': subtitles,
109120
'uploader_id': project_id,
110121
**traverse_obj(response, {
111-
'title': ('name', {str}),
112-
'description': ('description', {str}, filter),
122+
'title': ('name', {clean_html}, filter),
123+
'description': ('description', {clean_html}, filter),
113124
'duration': ('duration', {float_or_none}),
114125
'modified_timestamp': ('updated_at', {parse_iso8601}),
115-
'tags': ('tags', ..., {str}),
116-
'thumbnails': (('poster', 'thumbnail'), 'src', {'url': {url_or_none}}),
126+
'tags': ('tags', ..., {clean_html}, filter, all, filter),
127+
'thumbnail': (('thumbnail', 'poster'), 'src', {url_or_none}, any),
117128
'timestamp': ('created_at', {parse_iso8601}),
118129
}),
119130
}
120131

121132

122133
class StreaksIE(StreaksBaseIE):
123134
_VALID_URL = [
124-
r'https?://players\.streaks\.jp/(?P<project_id>[\w-]+)/[\da-f]+/index\.html\?(?:[^#]+&)?m=(?P<id>(?:ref:)?[\w-]+)',
135+
r'https?://players\.streaks\.jp/(?P<project_id>[\w-]+)/(?P<api_key>[\da-f]+)/index\.html\?(?:[^#]+&)?m=(?P<id>(?:ref:)?[\w-]+)',
125136
r'https?://playback\.api\.streaks\.jp/v1/projects/(?P<project_id>[\w-]+)/medias/(?P<id>(?:ref:)?[\w-]+)',
126137
]
127138
_EMBED_REGEX = [rf'<iframe\s+[^>]*\bsrc\s*=\s*["\'](?P<url>{_VALID_URL[0]})']
128139
_TESTS = [{
129-
'url': 'https://players.streaks.jp/tipness/08155cd19dc14c12bebefb69b92eafcc/index.html?m=dbdf2df35b4d483ebaeeaeb38c594647',
140+
# https://online.tipness.co.jp/contents/9a064492-a62b-5d03-4668-6b40d6325b1e
141+
'url': 'https://players.streaks.jp/tipness/08155cd19dc14c12bebefb69b92eafcc/index.html?m=ba2c253508914d9ea061a5f26bc58b20',
130142
'info_dict': {
131-
'id': 'dbdf2df35b4d483ebaeeaeb38c594647',
143+
'id': 'ba2c253508914d9ea061a5f26bc58b20',
132144
'ext': 'mp4',
133-
'title': '3shunenCM_edit.mp4',
134-
'display_id': 'dbdf2df35b4d483ebaeeaeb38c594647',
135-
'duration': 47.533,
145+
'title': 'tarun_suimin.mp4',
146+
'duration': 265.344,
136147
'live_status': 'not_live',
137-
'modified_date': '20230726',
138-
'modified_timestamp': 1690356180,
139-
'timestamp': 1690355996,
140-
'upload_date': '20230726',
148+
'modified_date': '20230908',
149+
'modified_timestamp': 1694146842,
150+
'timestamp': 1694145352,
151+
'upload_date': '20230908',
141152
'uploader_id': 'tipness',
142153
},
143154
}, {
155+
# https://www.ktv.jp/mycoffeetime/
144156
'url': 'https://players.streaks.jp/ktv-web/0298e8964c164ab384c07ef6e08c444b/index.html?m=ref:mycoffeetime_250317',
145157
'info_dict': {
146158
'id': 'dccdc079e3fd41f88b0c8435e2d453ab',
@@ -157,22 +169,24 @@ class StreaksIE(StreaksBaseIE):
157169
'uploader_id': 'ktv-web',
158170
},
159171
}, {
160-
'url': 'https://playback.api.streaks.jp/v1/projects/ktv-web/medias/b5411938e1e5435dac71edf829dd4813',
172+
# https://www.ktv.jp/news/articles/?id=28105
173+
'url': 'https://playback.api.streaks.jp/v1/projects/ktv-news/medias/714171c4c53c409bb41e1572997ebfcf',
161174
'info_dict': {
162-
'id': 'b5411938e1e5435dac71edf829dd4813',
175+
'id': '714171c4c53c409bb41e1572997ebfcf',
163176
'ext': 'mp4',
164-
'title': 'KANTELE_SYUSEi_0630',
165-
'display_id': 'b5411938e1e5435dac71edf829dd4813',
177+
'title': '28105.mp4',
178+
'duration': 49.984,
166179
'live_status': 'not_live',
167-
'modified_date': '20250122',
168-
'modified_timestamp': 1737522999,
180+
'modified_date': '20260702',
181+
'modified_timestamp': 1783028559,
169182
'thumbnail': r're:https?://.+\.jpg',
170-
'timestamp': 1735205137,
171-
'upload_date': '20241226',
172-
'uploader_id': 'ktv-web',
183+
'timestamp': 1783028407,
184+
'upload_date': '20260702',
185+
'uploader_id': 'ktv-news',
173186
},
187+
'params': {'extractor_args': {'streaks': {'api_key': ['0ff2ccfb6381401582d6ee60e3cb66a1']}}},
174188
}, {
175-
# TVer Olympics: website already down, but api remains accessible
189+
# https://tver.jp/olympic/paris2024/live/FBLMTEAM11------------SFNL000100--/
176190
'url': 'https://playback.api.streaks.jp/v1/projects/tver-olympic/medias/ref:sp_240806_1748_dvr',
177191
'info_dict': {
178192
'id': 'c10f7345adb648cf804d7578ab93b2e3',
@@ -187,8 +201,10 @@ class StreaksIE(StreaksBaseIE):
187201
'upload_date': '20240804',
188202
'uploader_id': 'tver-olympic',
189203
},
204+
'params': {'extractor_args': {'streaks': {'api_key': ['e09168c4383d4b18949067022558f071']}}},
205+
'skip': 'Invalid URL',
190206
}, {
191-
# TBS FREE: 24-hour stream
207+
# https://cu.tbs.co.jp/simul/simul-02
192208
'url': 'https://playback.api.streaks.jp/v1/projects/tbs/medias/ref:simul-02',
193209
'info_dict': {
194210
'id': 'c4e83a7b48f4409a96adacec674b4e22',
@@ -202,12 +218,14 @@ class StreaksIE(StreaksBaseIE):
202218
'upload_date': '20240117',
203219
'uploader_id': 'tbs',
204220
},
221+
'skip': 'Invalid URL',
205222
}, {
206223
# DRM protected
207224
'url': 'https://players.streaks.jp/sp-jbc/a12d7ee0f40c49d6a0a2bff520639677/index.html?m=5f89c62f37ee4a68be8e6e3b1396c7d8',
208225
'only_matching': True,
209226
}]
210227
_WEBPAGE_TESTS = [{
228+
# https://players.streaks.jp/play/719af2a1d2d544e89bcad3456eeae5d9/index.html?m=2d975178293140dc8074a7fc536a7604
211229
'url': 'https://event.play.jp/playnext2023/',
212230
'info_dict': {
213231
'id': '2d975178293140dc8074a7fc536a7604',
@@ -222,6 +240,7 @@ class StreaksIE(StreaksBaseIE):
222240
'modified_date': '20250213',
223241
'live_status': 'not_live',
224242
},
243+
'params': {'nocheckcertificate': True},
225244
}, {
226245
'url': 'https://wowshop.jp/Page/special/cooking_goods/?bid=wowshop&srsltid=AfmBOor_phUNoPEE_UCPiGGSCMrJE5T2US397smvsbrSdLqUxwON0el4',
227246
'playlist_mincount': 2,
@@ -232,13 +251,15 @@ class StreaksIE(StreaksBaseIE):
232251
'age_limit': 0,
233252
'thumbnail': 'https://wowshop.jp/Page/special/cooking_goods/images/ogp.jpg',
234253
},
254+
'skip': 'Invalid URL',
235255
}]
236256

237257
def _real_extract(self, url):
238-
url, smuggled_data = unsmuggle_url(url, {})
239-
project_id, media_id = self._match_valid_url(url).group('project_id', 'id')
258+
mobj = self._match_valid_url(url).groupdict()
259+
project_id, media_id = mobj['project_id'], mobj['id']
260+
api_key = mobj.get('api_key') or self._configuration_arg('api_key', [None])[0]
240261

241262
return self._extract_from_streaks_api(
242263
project_id, media_id, headers=filter_dict({
243-
'X-Streaks-Api-Key': smuggled_data.get('api_key'),
264+
'X-Streaks-Api-Key': api_key,
244265
}))

0 commit comments

Comments
 (0)