logo

youtube-dl

[mirror] Download/Watch videos from video hostersgit clone https://hacktivis.me/git/mirror/youtube-dl.git

line.py (8423B)


  1. # coding: utf-8
  2. from __future__ import unicode_literals
  3. import re
  4. from .common import InfoExtractor
  5. from ..compat import compat_str
  6. from ..utils import (
  7. ExtractorError,
  8. int_or_none,
  9. js_to_json,
  10. str_or_none,
  11. )
  12. class LineTVIE(InfoExtractor):
  13. _VALID_URL = r'https?://tv\.line\.me/v/(?P<id>\d+)_[^/]+-(?P<segment>ep\d+-\d+)'
  14. _TESTS = [{
  15. 'url': 'https://tv.line.me/v/793123_goodbye-mrblack-ep1-1/list/69246',
  16. 'info_dict': {
  17. 'id': '793123_ep1-1',
  18. 'ext': 'mp4',
  19. 'title': 'Goodbye Mr.Black | EP.1-1',
  20. 'thumbnail': r're:^https?://.*\.jpg$',
  21. 'duration': 998.509,
  22. 'view_count': int,
  23. },
  24. }, {
  25. 'url': 'https://tv.line.me/v/2587507_%E6%B4%BE%E9%81%A3%E5%A5%B3%E9%86%ABx-ep1-02/list/185245',
  26. 'only_matching': True,
  27. }]
  28. def _real_extract(self, url):
  29. series_id, segment = re.match(self._VALID_URL, url).groups()
  30. video_id = '%s_%s' % (series_id, segment)
  31. webpage = self._download_webpage(url, video_id)
  32. player_params = self._parse_json(self._search_regex(
  33. r'naver\.WebPlayer\(({[^}]+})\)', webpage, 'player parameters'),
  34. video_id, transform_source=js_to_json)
  35. video_info = self._download_json(
  36. 'https://global-nvapis.line.me/linetv/rmcnmv/vod_play_videoInfo.json',
  37. video_id, query={
  38. 'videoId': player_params['videoId'],
  39. 'key': player_params['key'],
  40. })
  41. stream = video_info['streams'][0]
  42. extra_query = '?__gda__=' + stream['key']['value']
  43. formats = self._extract_m3u8_formats(
  44. stream['source'] + extra_query, video_id, ext='mp4',
  45. entry_protocol='m3u8_native', m3u8_id='hls')
  46. for a_format in formats:
  47. a_format['url'] += extra_query
  48. duration = None
  49. for video in video_info.get('videos', {}).get('list', []):
  50. encoding_option = video.get('encodingOption', {})
  51. abr = video['bitrate']['audio']
  52. vbr = video['bitrate']['video']
  53. tbr = abr + vbr
  54. formats.append({
  55. 'url': video['source'],
  56. 'format_id': 'http-%d' % int(tbr),
  57. 'height': encoding_option.get('height'),
  58. 'width': encoding_option.get('width'),
  59. 'abr': abr,
  60. 'vbr': vbr,
  61. 'filesize': video.get('size'),
  62. })
  63. if video.get('duration') and duration is None:
  64. duration = video['duration']
  65. self._sort_formats(formats)
  66. if not formats[0].get('width'):
  67. formats[0]['vcodec'] = 'none'
  68. title = self._og_search_title(webpage)
  69. # like_count requires an additional API request https://tv.line.me/api/likeit/getCount
  70. return {
  71. 'id': video_id,
  72. 'title': title,
  73. 'formats': formats,
  74. 'extra_param_to_segment_url': extra_query[1:],
  75. 'duration': duration,
  76. 'thumbnails': [{'url': thumbnail['source']}
  77. for thumbnail in video_info.get('thumbnails', {}).get('list', [])],
  78. 'view_count': video_info.get('meta', {}).get('count'),
  79. }
  80. class LineLiveBaseIE(InfoExtractor):
  81. _API_BASE_URL = 'https://live-api.line-apps.com/web/v4.0/channel/'
  82. def _parse_broadcast_item(self, item):
  83. broadcast_id = compat_str(item['id'])
  84. title = item['title']
  85. is_live = item.get('isBroadcastingNow')
  86. thumbnails = []
  87. for thumbnail_id, thumbnail_url in (item.get('thumbnailURLs') or {}).items():
  88. if not thumbnail_url:
  89. continue
  90. thumbnails.append({
  91. 'id': thumbnail_id,
  92. 'url': thumbnail_url,
  93. })
  94. channel = item.get('channel') or {}
  95. channel_id = str_or_none(channel.get('id'))
  96. return {
  97. 'id': broadcast_id,
  98. 'title': self._live_title(title) if is_live else title,
  99. 'thumbnails': thumbnails,
  100. 'timestamp': int_or_none(item.get('createdAt')),
  101. 'channel': channel.get('name'),
  102. 'channel_id': channel_id,
  103. 'channel_url': 'https://live.line.me/channels/' + channel_id if channel_id else None,
  104. 'duration': int_or_none(item.get('archiveDuration')),
  105. 'view_count': int_or_none(item.get('viewerCount')),
  106. 'comment_count': int_or_none(item.get('chatCount')),
  107. 'is_live': is_live,
  108. }
  109. class LineLiveIE(LineLiveBaseIE):
  110. _VALID_URL = r'https?://live\.line\.me/channels/(?P<channel_id>\d+)/broadcast/(?P<id>\d+)'
  111. _TESTS = [{
  112. 'url': 'https://live.line.me/channels/4867368/broadcast/16331360',
  113. 'md5': 'bc931f26bf1d4f971e3b0982b3fab4a3',
  114. 'info_dict': {
  115. 'id': '16331360',
  116. 'title': '振りコピ講座😙😙😙',
  117. 'ext': 'mp4',
  118. 'timestamp': 1617095132,
  119. 'upload_date': '20210330',
  120. 'channel': '白川ゆめか',
  121. 'channel_id': '4867368',
  122. 'view_count': int,
  123. 'comment_count': int,
  124. 'is_live': False,
  125. }
  126. }, {
  127. # archiveStatus == 'DELETED'
  128. 'url': 'https://live.line.me/channels/4778159/broadcast/16378488',
  129. 'only_matching': True,
  130. }]
  131. def _real_extract(self, url):
  132. channel_id, broadcast_id = re.match(self._VALID_URL, url).groups()
  133. broadcast = self._download_json(
  134. self._API_BASE_URL + '%s/broadcast/%s' % (channel_id, broadcast_id),
  135. broadcast_id)
  136. item = broadcast['item']
  137. info = self._parse_broadcast_item(item)
  138. protocol = 'm3u8' if info['is_live'] else 'm3u8_native'
  139. formats = []
  140. for k, v in (broadcast.get(('live' if info['is_live'] else 'archived') + 'HLSURLs') or {}).items():
  141. if not v:
  142. continue
  143. if k == 'abr':
  144. formats.extend(self._extract_m3u8_formats(
  145. v, broadcast_id, 'mp4', protocol,
  146. m3u8_id='hls', fatal=False))
  147. continue
  148. f = {
  149. 'ext': 'mp4',
  150. 'format_id': 'hls-' + k,
  151. 'protocol': protocol,
  152. 'url': v,
  153. }
  154. if not k.isdigit():
  155. f['vcodec'] = 'none'
  156. formats.append(f)
  157. if not formats:
  158. archive_status = item.get('archiveStatus')
  159. if archive_status != 'ARCHIVED':
  160. raise ExtractorError('this video has been ' + archive_status.lower(), expected=True)
  161. self._sort_formats(formats)
  162. info['formats'] = formats
  163. return info
  164. class LineLiveChannelIE(LineLiveBaseIE):
  165. _VALID_URL = r'https?://live\.line\.me/channels/(?P<id>\d+)(?!/broadcast/\d+)(?:[/?&#]|$)'
  166. _TEST = {
  167. 'url': 'https://live.line.me/channels/5893542',
  168. 'info_dict': {
  169. 'id': '5893542',
  170. 'title': 'いくらちゃん',
  171. 'description': 'md5:c3a4af801f43b2fac0b02294976580be',
  172. },
  173. 'playlist_mincount': 29
  174. }
  175. def _archived_broadcasts_entries(self, archived_broadcasts, channel_id):
  176. while True:
  177. for row in (archived_broadcasts.get('rows') or []):
  178. share_url = str_or_none(row.get('shareURL'))
  179. if not share_url:
  180. continue
  181. info = self._parse_broadcast_item(row)
  182. info.update({
  183. '_type': 'url',
  184. 'url': share_url,
  185. 'ie_key': LineLiveIE.ie_key(),
  186. })
  187. yield info
  188. if not archived_broadcasts.get('hasNextPage'):
  189. return
  190. archived_broadcasts = self._download_json(
  191. self._API_BASE_URL + channel_id + '/archived_broadcasts',
  192. channel_id, query={
  193. 'lastId': info['id'],
  194. })
  195. def _real_extract(self, url):
  196. channel_id = self._match_id(url)
  197. channel = self._download_json(self._API_BASE_URL + channel_id, channel_id)
  198. return self.playlist_result(
  199. self._archived_broadcasts_entries(channel.get('archivedBroadcasts') or {}, channel_id),
  200. channel_id, channel.get('title'), channel.get('information'))