2022-04-07 11:03:13 +00:00
import functools
2016-05-06 03:07:29 +00:00
2016-05-04 18:11:04 +00:00
from . common import InfoExtractor
2024-07-14 01:09:00 +00:00
from . . networking import Request
2016-05-04 18:11:04 +00:00
from . . utils import (
2022-04-07 11:03:13 +00:00
ExtractorError ,
OnDemandPagedList ,
2024-04-04 16:48:05 +00:00
UserNotLive ,
2017-04-10 19:05:53 +00:00
determine_ext ,
2024-04-04 16:48:05 +00:00
filter_dict ,
2016-05-04 18:11:04 +00:00
int_or_none ,
2024-04-13 16:40:53 +00:00
orderedSet ,
2022-01-07 12:12:29 +00:00
unified_timestamp ,
2018-07-21 12:08:28 +00:00
url_or_none ,
2018-04-01 15:47:39 +00:00
urlencode_postdata ,
2024-04-13 16:40:53 +00:00
urljoin ,
2016-05-04 18:11:04 +00:00
)
2024-04-04 16:48:05 +00:00
from . . utils . traversal import traverse_obj
2016-05-04 18:11:04 +00:00
2024-04-04 16:48:05 +00:00
class AfreecaTVBaseIE ( InfoExtractor ) :
_NETRC_MACHINE = ' afreecatv '
def _perform_login ( self , username , password ) :
login_form = {
' szWork ' : ' login ' ,
' szType ' : ' json ' ,
' szUid ' : username ,
' szPassword ' : password ,
' isSaveId ' : ' false ' ,
' szScriptVar ' : ' oLoginRet ' ,
' szAction ' : ' ' ,
}
response = self . _download_json (
2024-10-21 23:42:45 +00:00
' https://login.sooplive.co.kr/app/LoginAction.php ' , None ,
2024-04-04 16:48:05 +00:00
' Logging in ' , data = urlencode_postdata ( login_form ) )
_ERRORS = {
- 4 : ' Your account has been suspended due to a violation of our terms and policies. ' ,
2024-10-21 23:42:45 +00:00
- 5 : ' https://member.sooplive.co.kr/app/user_delete_progress.php ' ,
- 6 : ' https://login.sooplive.co.kr/membership/changeMember.php ' ,
- 8 : " Hello! Soop here. \n The username you have entered belongs to \n an account that requires a legal guardian ' s consent. \n If you wish to use our services without restriction, \n please make sure to go through the necessary verification process. " ,
- 9 : ' https://member.sooplive.co.kr/app/pop_login_block.php ' ,
- 11 : ' https://login.sooplive.co.kr/afreeca/second_login.php ' ,
- 12 : ' https://member.sooplive.co.kr/app/user_security.php ' ,
2024-04-04 16:48:05 +00:00
0 : ' The username does not exist or you have entered the wrong password. ' ,
- 1 : ' The username does not exist or you have entered the wrong password. ' ,
- 3 : ' You have entered your username/password incorrectly. ' ,
2024-10-21 23:42:45 +00:00
- 7 : ' You cannot use your Global Soop account to access Korean Soop. ' ,
2024-04-04 16:48:05 +00:00
- 10 : ' Sorry for the inconvenience. \n Your account has been blocked due to an unauthorized access. \n Please contact our Help Center for assistance. ' ,
- 32008 : ' You have failed to log in. Please contact our Help Center. ' ,
}
result = int_or_none ( response . get ( ' RESULT ' ) )
if result != 1 :
error = _ERRORS . get ( result , ' You have failed to log in. ' )
raise ExtractorError (
2024-06-11 23:09:58 +00:00
f ' Unable to login: { self . IE_NAME } said: { error } ' ,
2024-04-04 16:48:05 +00:00
expected = True )
2024-07-14 01:09:00 +00:00
def _call_api ( self , endpoint , display_id , data = None , headers = None , query = None ) :
return self . _download_json ( Request (
2024-10-21 23:42:45 +00:00
f ' https://api.m.sooplive.co.kr/ { endpoint } ' ,
2024-07-14 01:09:00 +00:00
data = data , headers = headers , query = query ,
extensions = { ' legacy_ssl ' : True } ) , display_id ,
' Downloading API JSON ' , ' Unable to download API JSON ' )
2024-11-14 22:09:11 +00:00
@staticmethod
def _fixup_thumb ( thumb_url ) :
if not url_or_none ( thumb_url ) :
return None
# Core would determine_ext as 'php' from the url, so we need to provide the real ext
# See: https://github.com/yt-dlp/yt-dlp/issues/11537
return [ { ' url ' : thumb_url , ' ext ' : ' jpg ' } ]
2024-04-04 16:48:05 +00:00
class AfreecaTVIE ( AfreecaTVBaseIE ) :
2024-10-21 23:42:45 +00:00
IE_NAME = ' soop '
IE_DESC = ' sooplive.co.kr '
_VALID_URL = r ' https?://vod \ .(?:sooplive \ .co \ .kr|afreecatv \ .com)/(?:PLAYER/STATION|player)/(?P<id> \ d+)/?(?:$|[?#&]) '
2016-05-06 03:08:43 +00:00
_TESTS = [ {
2024-10-21 23:42:45 +00:00
' url ' : ' https://vod.sooplive.co.kr/player/96753363 ' ,
2016-05-04 18:11:04 +00:00
' info_dict ' : {
2024-10-21 23:42:45 +00:00
' id ' : ' 20230108_9FF5BEE1_244432674_1 ' ,
2016-05-04 18:11:04 +00:00
' ext ' : ' mp4 ' ,
2024-10-21 23:42:45 +00:00
' uploader_id ' : ' rlantnghks ' ,
' uploader ' : ' 페이즈으 ' ,
' duration ' : 10840 ,
' thumbnail ' : r ' re:https?://videoimg \ .sooplive \ .co/.kr/.+ ' ,
' upload_date ' : ' 20230108 ' ,
' timestamp ' : 1673218805 ,
' title ' : ' 젠지 페이즈 ' ,
2017-03-05 16:31:44 +00:00
} ,
2024-10-21 23:42:45 +00:00
' params ' : {
' skip_download ' : True ,
2016-05-06 03:08:43 +00:00
} ,
2017-04-11 19:15:37 +00:00
} , {
# non standard key
2024-10-21 23:42:45 +00:00
' url ' : ' http://vod.sooplive.co.kr/PLAYER/STATION/20515605 ' ,
2017-04-11 19:15:37 +00:00
' info_dict ' : {
' id ' : ' 20170411_BE689A0E_190960999_1_2_h ' ,
' ext ' : ' mp4 ' ,
' title ' : ' 혼자사는여자집 ' ,
2024-10-21 23:42:45 +00:00
' thumbnail ' : r ' re:https?://(?:video|st)img \ .sooplive \ .co \ .kr/.+ ' ,
2017-04-11 19:15:37 +00:00
' uploader ' : ' ♥이슬이 ' ,
' uploader_id ' : ' dasl8121 ' ,
' upload_date ' : ' 20170411 ' ,
2024-04-06 17:23:16 +00:00
' timestamp ' : 1491929865 ,
2017-04-11 19:15:37 +00:00
' duration ' : 213 ,
} ,
' params ' : {
' skip_download ' : True ,
} ,
2017-10-01 20:28:25 +00:00
} , {
2023-06-14 19:01:18 +00:00
# adult content
2024-10-21 23:42:45 +00:00
' url ' : ' https://vod.sooplive.co.kr/player/97267690 ' ,
2017-10-01 20:28:25 +00:00
' info_dict ' : {
2018-04-01 17:00:45 +00:00
' id ' : ' 20180327_27901457_202289533_1 ' ,
2017-10-01 20:28:25 +00:00
' ext ' : ' mp4 ' ,
2018-04-01 17:00:45 +00:00
' title ' : ' [생]빨개요♥ (part 1) ' ,
2024-10-21 23:42:45 +00:00
' thumbnail ' : r ' re:https?://(?:video|st)img \ .sooplive \ .co \ .kr/.+ ' ,
2018-04-01 17:00:45 +00:00
' uploader ' : ' [SA]서아 ' ,
2017-10-01 20:28:25 +00:00
' uploader_id ' : ' bjdyrksu ' ,
2018-04-01 17:00:45 +00:00
' upload_date ' : ' 20180327 ' ,
' duration ' : 3601 ,
2017-10-01 20:28:25 +00:00
} ,
' params ' : {
' skip_download ' : True ,
} ,
2023-06-14 19:01:18 +00:00
' skip ' : ' The VOD does not exist ' ,
2024-04-06 17:23:16 +00:00
} , {
# adult content
2024-10-21 23:42:45 +00:00
' url ' : ' https://vod.sooplive.co.kr/player/70395877 ' ,
2024-04-06 17:23:16 +00:00
' only_matching ' : True ,
} , {
# subscribers only
2024-10-21 23:42:45 +00:00
' url ' : ' https://vod.sooplive.co.kr/player/104647403 ' ,
2024-04-06 17:23:16 +00:00
' only_matching ' : True ,
} , {
# private
2024-10-21 23:42:45 +00:00
' url ' : ' https://vod.sooplive.co.kr/player/81669846 ' ,
2024-04-06 17:23:16 +00:00
' only_matching ' : True ,
2016-05-06 03:08:43 +00:00
} ]
2016-05-04 18:11:04 +00:00
def _real_extract ( self , url ) :
video_id = self . _match_id ( url )
2024-07-14 01:09:00 +00:00
data = self . _call_api (
' station/video/a/view ' , video_id , headers = { ' Referer ' : url } ,
data = urlencode_postdata ( {
2017-10-01 20:28:25 +00:00
' nTitleNo ' : video_id ,
2024-04-06 17:23:16 +00:00
' nApiLevel ' : 10 ,
2024-07-14 01:09:00 +00:00
} ) ) [ ' data ' ]
2017-10-01 20:28:25 +00:00
2024-04-06 17:23:16 +00:00
error_code = traverse_obj ( data , ( ' code ' , { int } ) )
if error_code == - 6221 :
raise ExtractorError ( ' The VOD does not exist ' , expected = True )
elif error_code == - 6205 :
raise ExtractorError ( ' This VOD is private ' , expected = True )
2016-05-04 18:11:04 +00:00
2024-04-06 17:23:16 +00:00
common_info = traverse_obj ( data , {
' title ' : ( ' title ' , { str } ) ,
' uploader ' : ( ' writer_nick ' , { str } ) ,
' uploader_id ' : ( ' bj_id ' , { str } ) ,
2024-11-04 00:33:21 +00:00
' duration ' : ( ' total_file_duration ' , { int_or_none ( scale = 1000 ) } ) ,
2024-11-14 22:09:11 +00:00
' thumbnails ' : ( ' thumb ' , { self . _fixup_thumb } ) ,
2017-04-10 19:05:53 +00:00
} )
2024-04-06 17:23:16 +00:00
entries = [ ]
for file_num , file_element in enumerate (
traverse_obj ( data , ( ' files ' , lambda _ , v : url_or_none ( v [ ' file ' ] ) ) ) , start = 1 ) :
file_url = file_element [ ' file ' ]
if determine_ext ( file_url ) == ' m3u8 ' :
formats = self . _extract_m3u8_formats (
file_url , video_id , ' mp4 ' , m3u8_id = ' hls ' ,
note = f ' Downloading part { file_num } m3u8 information ' )
else :
formats = [ {
' url ' : file_url ,
' format_id ' : ' http ' ,
} ]
entries . append ( {
* * common_info ,
' id ' : file_element . get ( ' file_info_key ' ) or f ' { video_id } _ { file_num } ' ,
' title ' : f ' { common_info . get ( " title " ) or " Untitled " } (part { file_num } ) ' ,
' formats ' : formats ,
* * traverse_obj ( file_element , {
2024-11-04 00:33:21 +00:00
' duration ' : ( ' duration ' , { int_or_none ( scale = 1000 ) } ) ,
2024-04-06 17:23:16 +00:00
' timestamp ' : ( ' file_start ' , { unified_timestamp } ) ,
2024-06-11 23:09:58 +00:00
} ) ,
2017-04-10 19:05:53 +00:00
} )
2024-04-06 17:23:16 +00:00
if traverse_obj ( data , ( ' adult_status ' , { str } ) ) == ' notLogin ' :
if not entries :
self . raise_login_required (
' Only users older than 19 are able to watch this video ' , method = ' password ' )
self . report_warning (
' In accordance with local laws and regulations, underage users are '
' restricted from watching adult content. Only content suitable for all '
f ' ages will be downloaded. { self . _login_hint ( " password " ) } ' )
2016-05-04 18:11:04 +00:00
2024-04-06 17:23:16 +00:00
if not entries and traverse_obj ( data , ( ' sub_upload_type ' , { str } ) ) :
self . raise_login_required ( ' This VOD is for subscribers only ' , method = ' password ' )
if len ( entries ) == 1 :
return {
* * entries [ 0 ] ,
' title ' : common_info . get ( ' title ' ) ,
}
common_info [ ' timestamp ' ] = traverse_obj ( entries , ( . . . , ' timestamp ' ) , get_all = False )
2017-04-10 19:05:53 +00:00
2024-04-06 17:23:16 +00:00
return self . playlist_result ( entries , video_id , multi_video = True , * * common_info )
2022-01-07 12:12:29 +00:00
2024-06-30 22:00:33 +00:00
class AfreecaTVCatchStoryIE ( AfreecaTVBaseIE ) :
2024-10-21 23:42:45 +00:00
IE_NAME = ' soop:catchstory '
IE_DESC = ' sooplive.co.kr catch story '
_VALID_URL = r ' https?://vod \ .(?:sooplive \ .co \ .kr|afreecatv \ .com)/player/(?P<id> \ d+)/catchstory '
2024-06-30 22:00:33 +00:00
_TESTS = [ {
2024-10-21 23:42:45 +00:00
' url ' : ' https://vod.sooplive.co.kr/player/103247/catchstory ' ,
2024-06-30 22:00:33 +00:00
' info_dict ' : {
' id ' : ' 103247 ' ,
} ,
' playlist_count ' : 2 ,
} ]
def _real_extract ( self , url ) :
video_id = self . _match_id ( url )
2024-07-14 01:09:00 +00:00
data = self . _call_api (
' catchstory/a/view ' , video_id , headers = { ' Referer ' : url } ,
query = { ' aStoryListIdx ' : ' ' , ' nStoryIdx ' : video_id } )
2024-06-30 22:00:33 +00:00
return self . playlist_result ( self . _entries ( data ) , video_id )
2024-11-14 22:09:11 +00:00
def _entries ( self , data ) :
2024-06-30 22:00:33 +00:00
# 'files' is always a list with 1 element
yield from traverse_obj ( data , (
' data ' , lambda _ , v : v [ ' story_type ' ] == ' catch ' ,
' catch_list ' , lambda _ , v : v [ ' files ' ] [ 0 ] [ ' file ' ] , {
' id ' : ( ' files ' , 0 , ' file_info_key ' , { str } ) ,
' url ' : ( ' files ' , 0 , ' file ' , { url_or_none } ) ,
2024-11-04 00:33:21 +00:00
' duration ' : ( ' files ' , 0 , ' duration ' , { int_or_none ( scale = 1000 ) } ) ,
2024-06-30 22:00:33 +00:00
' title ' : ( ' title ' , { str } ) ,
' uploader ' : ( ' writer_nick ' , { str } ) ,
' uploader_id ' : ( ' writer_id ' , { str } ) ,
2024-11-14 22:09:11 +00:00
' thumbnails ' : ( ' thumb ' , { self . _fixup_thumb } ) ,
2024-06-30 22:00:33 +00:00
' timestamp ' : ( ' write_timestamp ' , { int_or_none } ) ,
} ) )
2024-04-04 16:48:05 +00:00
class AfreecaTVLiveIE ( AfreecaTVBaseIE ) :
2024-10-21 23:42:45 +00:00
IE_NAME = ' soop:live '
IE_DESC = ' sooplive.co.kr livestreams '
_VALID_URL = r ' https?://play \ .(?:sooplive \ .co \ .kr|afreecatv \ .com)/(?P<id>[^/?#]+)(?:/(?P<bno> \ d+))? '
2022-01-07 12:12:29 +00:00
_TESTS = [ {
2024-10-21 23:42:45 +00:00
' url ' : ' https://play.sooplive.co.kr/pyh3646/237852185 ' ,
2022-01-07 12:12:29 +00:00
' info_dict ' : {
' id ' : ' 237852185 ' ,
' ext ' : ' mp4 ' ,
' title ' : ' 【 우루과이 오늘은 무슨일이? 】 ' ,
' uploader ' : ' 박진우[JINU] ' ,
' uploader_id ' : ' pyh3646 ' ,
' timestamp ' : 1640661495 ,
' is_live ' : True ,
} ,
' skip ' : ' Livestream has ended ' ,
} , {
2024-10-21 23:42:45 +00:00
' url ' : ' https://play.sooplive.co.kr/pyh3646/237852185 ' ,
2022-01-07 12:12:29 +00:00
' only_matching ' : True ,
} , {
2024-10-21 23:42:45 +00:00
' url ' : ' https://play.sooplive.co.kr/pyh3646 ' ,
2022-01-07 12:12:29 +00:00
' only_matching ' : True ,
} ]
2024-10-21 23:42:45 +00:00
_LIVE_API_URL = ' https://live.sooplive.co.kr/afreeca/player_live_api.php '
2024-04-13 16:40:53 +00:00
_WORKING_CDNS = [
2024-10-21 23:42:45 +00:00
' gcp_cdn ' , # live-global-cdn-v02.sooplive.co.kr
' gs_cdn_pc_app ' , # pc-app.stream.sooplive.co.kr
' gs_cdn_mobile_web ' , # mobile-web.stream.sooplive.co.kr
' gs_cdn_pc_web ' , # pc-web.stream.sooplive.co.kr
2024-04-13 16:40:53 +00:00
]
_BAD_CDNS = [
' gs_cdn ' , # chromecast.afreeca.gscdn.com (cannot resolve)
2024-10-21 23:42:45 +00:00
' gs_cdn_chromecast ' , # chromecast.stream.sooplive.co.kr (HTTP Error 400)
' azure_cdn ' , # live-global-cdn-v01.sooplive.co.kr (cannot resolve)
' aws_cf ' , # live-global-cdn-v03.sooplive.co.kr (cannot resolve)
' kt_cdn ' , # kt.stream.sooplive.co.kr (HTTP Error 400)
2024-04-13 16:40:53 +00:00
]
def _extract_formats ( self , channel_info , broadcast_no , aid ) :
2024-10-21 23:42:45 +00:00
stream_base_url = channel_info . get ( ' RMD ' ) or ' https://livestream-manager.sooplive.co.kr '
2024-04-13 16:40:53 +00:00
# If user has not passed CDN IDs, try API-provided CDN ID followed by other working CDN IDs
default_cdn_ids = orderedSet ( [
* traverse_obj ( channel_info , ( ' CDN ' , { str } , all , lambda _ , v : v not in self . _BAD_CDNS ) ) ,
* self . _WORKING_CDNS ,
] )
cdn_ids = self . _configuration_arg ( ' cdn ' , default_cdn_ids )
for attempt , cdn_id in enumerate ( cdn_ids , start = 1 ) :
m3u8_url = traverse_obj ( self . _download_json (
urljoin ( stream_base_url , ' broad_stream_assign.html ' ) , broadcast_no ,
f ' Downloading { cdn_id } stream info ' , f ' Unable to download { cdn_id } stream info ' ,
fatal = False , query = {
' return_type ' : cdn_id ,
' broad_key ' : f ' { broadcast_no } -common-master-hls ' ,
} ) , ( ' view_url ' , { url_or_none } ) )
try :
return self . _extract_m3u8_formats (
m3u8_url , broadcast_no , ' mp4 ' , m3u8_id = ' hls ' , query = { ' aid ' : aid } ,
2024-10-21 23:42:45 +00:00
headers = { ' Referer ' : ' https://play.sooplive.co.kr/ ' } )
2024-04-13 16:40:53 +00:00
except ExtractorError as e :
if attempt == len ( cdn_ids ) :
raise
self . report_warning (
f ' { e . cause or e . msg } . Retrying... (attempt { attempt } of { len ( cdn_ids ) } ) ' )
2022-01-07 12:12:29 +00:00
def _real_extract ( self , url ) :
broadcaster_id , broadcast_no = self . _match_valid_url ( url ) . group ( ' id ' , ' bno ' )
2024-04-04 16:48:05 +00:00
channel_info = traverse_obj ( self . _download_json (
self . _LIVE_API_URL , broadcaster_id , data = urlencode_postdata ( { ' bid ' : broadcaster_id } ) ) ,
( ' CHANNEL ' , { dict } ) ) or { }
2022-01-07 12:12:29 +00:00
broadcaster_id = channel_info . get ( ' BJID ' ) or broadcaster_id
broadcast_no = channel_info . get ( ' BNO ' ) or broadcast_no
if not broadcast_no :
2024-10-21 23:42:45 +00:00
result = channel_info . get ( ' RESULT ' )
if result == 0 :
raise UserNotLive ( video_id = broadcaster_id )
elif result == - 6 :
self . raise_login_required (
' This channel is streaming for subscribers only ' , method = ' password ' )
raise ExtractorError ( ' Unable to extract broadcast number ' )
2024-04-04 16:48:05 +00:00
password = self . get_param ( ' videopassword ' )
if channel_info . get ( ' BPWD ' ) == ' Y ' and password is None :
2022-02-11 14:15:59 +00:00
raise ExtractorError (
' This livestream is protected by a password, use the --video-password option ' ,
expected = True )
2022-01-07 12:12:29 +00:00
2024-04-13 16:40:53 +00:00
token_info = traverse_obj ( self . _download_json (
2024-04-04 16:48:05 +00:00
self . _LIVE_API_URL , broadcast_no , ' Downloading access token for stream ' ,
' Unable to download access token for stream ' , data = urlencode_postdata ( filter_dict ( {
2022-02-11 14:15:59 +00:00
' bno ' : broadcast_no ,
' stream_type ' : ' common ' ,
' type ' : ' aid ' ,
2024-04-04 16:48:05 +00:00
' quality ' : ' master ' ,
' pwd ' : password ,
2024-04-13 16:40:53 +00:00
} ) ) ) , ( ' CHANNEL ' , { dict } ) ) or { }
aid = token_info . get ( ' AID ' )
if not aid :
result = token_info . get ( ' RESULT ' )
if result == 0 :
raise ExtractorError ( ' This livestream has ended ' , expected = True )
elif result == - 6 :
self . raise_login_required ( ' This livestream is for subscribers only ' , method = ' password ' )
raise ExtractorError ( ' Unable to extract access token ' )
formats = self . _extract_formats ( channel_info , broadcast_no , aid )
2024-04-04 16:48:05 +00:00
station_info = traverse_obj ( self . _download_json (
2024-10-21 23:42:45 +00:00
' https://st.sooplive.co.kr/api/get_station_status.php ' , broadcast_no ,
2024-04-04 16:48:05 +00:00
' Downloading channel metadata ' , ' Unable to download channel metadata ' ,
query = { ' szBjId ' : broadcaster_id } , fatal = False ) , { dict } ) or { }
2022-01-07 12:12:29 +00:00
return {
' id ' : broadcast_no ,
' title ' : channel_info . get ( ' TITLE ' ) or station_info . get ( ' station_title ' ) ,
' uploader ' : channel_info . get ( ' BJNICK ' ) or station_info . get ( ' station_name ' ) ,
' uploader_id ' : broadcaster_id ,
' timestamp ' : unified_timestamp ( station_info . get ( ' broad_start ' ) ) ,
' formats ' : formats ,
' is_live ' : True ,
2024-04-04 16:48:05 +00:00
' http_headers ' : { ' Referer ' : url } ,
2022-01-07 12:12:29 +00:00
}
2022-04-07 11:03:13 +00:00
2024-10-21 23:42:45 +00:00
class AfreecaTVUserIE ( AfreecaTVBaseIE ) :
IE_NAME = ' soop:user '
_VALID_URL = r ' https?://ch \ .(?:sooplive \ .co \ .kr|afreecatv \ .com)/(?P<id>[^/?#]+)/vods/?(?P<slug_type>[^/?#]+)? '
2022-04-07 11:03:13 +00:00
_TESTS = [ {
2024-10-21 23:42:45 +00:00
' url ' : ' https://ch.sooplive.co.kr/ryuryu24/vods/review ' ,
2022-04-07 11:03:13 +00:00
' info_dict ' : {
' _type ' : ' playlist ' ,
' id ' : ' ryuryu24 ' ,
' title ' : ' ryuryu24 - review ' ,
} ,
' playlist_count ' : 218 ,
} , {
2024-10-21 23:42:45 +00:00
' url ' : ' https://ch.sooplive.co.kr/parang1995/vods/highlight ' ,
2022-04-07 11:03:13 +00:00
' info_dict ' : {
' _type ' : ' playlist ' ,
' id ' : ' parang1995 ' ,
' title ' : ' parang1995 - highlight ' ,
} ,
' playlist_count ' : 997 ,
} , {
2024-10-21 23:42:45 +00:00
' url ' : ' https://ch.sooplive.co.kr/ryuryu24/vods ' ,
2022-04-07 11:03:13 +00:00
' info_dict ' : {
' _type ' : ' playlist ' ,
' id ' : ' ryuryu24 ' ,
' title ' : ' ryuryu24 - all ' ,
} ,
' playlist_count ' : 221 ,
} , {
2024-10-21 23:42:45 +00:00
' url ' : ' https://ch.sooplive.co.kr/ryuryu24/vods/balloonclip ' ,
2022-04-07 11:03:13 +00:00
' info_dict ' : {
' _type ' : ' playlist ' ,
' id ' : ' ryuryu24 ' ,
' title ' : ' ryuryu24 - balloonclip ' ,
} ,
' playlist_count ' : 0 ,
} ]
_PER_PAGE = 60
def _fetch_page ( self , user_id , user_type , page ) :
page + = 1
2024-10-21 23:42:45 +00:00
info = self . _download_json ( f ' https://chapi.sooplive.co.kr/api/ { user_id } /vods/ { user_type } ' , user_id ,
2022-04-07 11:03:13 +00:00
query = { ' page ' : page , ' per_page ' : self . _PER_PAGE , ' orderby ' : ' reg_date ' } ,
note = f ' Downloading { user_type } video page { page } ' )
for item in info [ ' data ' ] :
yield self . url_result (
2024-10-21 23:42:45 +00:00
f ' https://vod.sooplive.co.kr/player/ { item [ " title_no " ] } / ' , AfreecaTVIE , item [ ' title_no ' ] )
2022-04-07 11:03:13 +00:00
def _real_extract ( self , url ) :
user_id , user_type = self . _match_valid_url ( url ) . group ( ' id ' , ' slug_type ' )
user_type = user_type or ' all '
entries = OnDemandPagedList ( functools . partial ( self . _fetch_page , user_id , user_type ) , self . _PER_PAGE )
return self . playlist_result ( entries , user_id , f ' { user_id } - { user_type } ' )