55from ..networking .exceptions import HTTPError
66from ..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
122133class 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