3 from .common
import InfoExtractor
16 from ..utils
.traversal
import traverse_obj
19 class KukuluLiveIE(InfoExtractor
):
20 _VALID_URL
= r
'https?://live\.erinn\.biz/live\.php\?h(?P<id>\d+)'
22 'url': 'https://live.erinn.biz/live.php?h675134569',
23 'md5': 'e380fa6a47fc703d91cea913ab44ec2e',
28 'description': 'テストも兼ねたプロセカ配信。',
29 'timestamp': 1702689148,
30 'upload_date': '20231216',
31 'thumbnail': r
're:^https?://.*',
34 'url': 'https://live.erinn.biz/live.php?h102338092',
35 'md5': 'dcf5167a934b1c60333461e13a81a6e2',
39 'title': 'Among Usで遊びます!!',
40 'description': 'VTuberになりましたねんねこ㌨ですよろしくお願いします',
41 'timestamp': 1704603118,
42 'upload_date': '20240107',
43 'thumbnail': r
're:^https?://.*',
46 'url': 'https://live.erinn.biz/live.php?h878049531',
47 'only_matching': True,
50 def _get_quality_meta(self
, video_id
, desc
, code
, force_h264
=None):
51 desc
+= ' (force_h264)' if force_h264
else ''
52 qs
= self
._download
_webpage
(
53 'https://live.erinn.biz/live.player.fplayer.php', video_id
,
54 f
'Downloading {desc} quality metadata', f
'Unable to download {desc} quality metadata',
57 'action': f
'get{code}liveByAjax',
58 'force_h264': force_h264
,
60 return urllib
.parse
.parse_qs(qs
)
62 def _add_quality_formats(self
, formats
, quality_meta
):
63 vcodec
= traverse_obj(quality_meta
, ('vcodec', 0, {str}
))
64 quality
= traverse_obj(quality_meta
, ('now_quality', 0, {str}
))
65 quality_priority
= qualities(('low', 'h264', 'high'))(quality
)
66 if traverse_obj(quality_meta
, ('hlsaddr', 0, {url_or_none}
)):
69 'url': quality_meta
['hlsaddr'][0],
72 'quality': quality_priority
,
74 if traverse_obj(quality_meta
, ('hlsaddr_audioonly', 0, {url_or_none}
)):
76 'format_id': join_nonempty(quality
, 'audioonly'),
77 'url': quality_meta
['hlsaddr_audioonly'][0],
80 'quality': quality_priority
,
83 def _real_extract(self
, url
):
84 video_id
= self
._match
_id
(url
)
85 html
= self
._download
_webpage
(url
, video_id
)
87 if '>タイムシフトが見つかりませんでした。<' in html
:
88 raise ExtractorError('This stream has expired', expected
=True)
91 get_element_by_id('livetitle', html
.replace('<SPAN', '<span').replace('SPAN>', 'span>')))
92 description
= self
._html
_search
_meta
('Description', html
)
93 thumbnail
= self
._html
_search
_meta
(['og:image', 'twitter:image'], html
)
95 if self
._search
_regex
(r
'(var\s+timeshift\s*=\s*false)', html
, 'is livestream', default
=False):
97 for (desc
, code
) in [('high', 'Z'), ('low', 'ForceLow')]:
98 quality_meta
= self
._get
_quality
_meta
(video_id
, desc
, code
)
99 self
._add
_quality
_formats
(formats
, quality_meta
)
100 if desc
== 'high' and traverse_obj(quality_meta
, ('vcodec', 0)) == 'HEVC':
101 self
._add
_quality
_formats
(
102 formats
, self
._get
_quality
_meta
(video_id
, desc
, code
, force_h264
='1'))
107 'description': description
,
108 'thumbnail': thumbnail
,
114 player_html
= self
._download
_webpage
(
115 'https://live.erinn.biz/live.timeshift.fplayer.php', video_id
,
116 'Downloading player html', 'Unable to download player html', query
={'hash': video_id
})
118 sources
= traverse_obj(self
._search
_json
(
119 r
'var\s+fplayer_source\s*=', player_html
, 'stream data', video_id
,
120 contains_pattern
=r
'\[(?s:.+)\]', transform_source
=js_to_json
), lambda _
, v
: v
['file'])
122 def entries(segments
, playlist
=True):
123 for i
, segment
in enumerate(segments
, 1):
125 'id': f
'{video_id}_{i}' if playlist
else video_id
,
126 'title': f
'{title} (Part {i})' if playlist
else title
,
127 'description': description
,
128 'timestamp': traverse_obj(segment
, ('time_start', {int_or_none}
)),
129 'thumbnail': thumbnail
,
131 'url': urljoin('https://live.erinn.biz', segment
['file']),
133 'protocol': 'm3u8_native',
137 if len(sources
) == 1:
138 return next(entries(sources
, playlist
=False))
140 return self
.playlist_result(entries(sources
), video_id
, title
, description
, multi_video
=True)