mirror of
https://github.com/yt-dlp/yt-dlp.git
synced 2026-06-22 10:44:37 +00:00
Compare commits
No commits in common. "65b17ffbb9f59efd5f0895d9d73dce80572b1f1e" and "d664768a01bc351ddb1f53985c0c493d64aef480" have entirely different histories.
65b17ffbb9
...
d664768a01
@ -187,7 +187,7 @@ class TwitchBaseIE(InfoExtractor):
|
|||||||
'url': thumbnail,
|
'url': thumbnail,
|
||||||
}] if thumbnail else None
|
}] if thumbnail else None
|
||||||
|
|
||||||
def _extract_twitch_m3u8_formats(self, path, video_id, token, signature, live_from_start=False):
|
def _extract_twitch_m3u8_formats(self, path, video_id, token, signature):
|
||||||
formats = self._extract_m3u8_formats(
|
formats = self._extract_m3u8_formats(
|
||||||
f'{self._USHER_BASE}/{path}/{video_id}.m3u8', video_id, 'mp4', query={
|
f'{self._USHER_BASE}/{path}/{video_id}.m3u8', video_id, 'mp4', query={
|
||||||
'allow_source': 'true',
|
'allow_source': 'true',
|
||||||
@ -204,10 +204,9 @@ class TwitchBaseIE(InfoExtractor):
|
|||||||
for fmt in formats:
|
for fmt in formats:
|
||||||
if fmt.get('vcodec') and fmt['vcodec'].startswith('av01'):
|
if fmt.get('vcodec') and fmt['vcodec'].startswith('av01'):
|
||||||
# mpegts does not yet have proper support for av1
|
# mpegts does not yet have proper support for av1
|
||||||
fmt.setdefault('downloader_options', {}).update({'ffmpeg_args_out': ['-f', 'mp4']})
|
fmt['downloader_options'] = {'ffmpeg_args_out': ['-f', 'mp4']}
|
||||||
if live_from_start:
|
if self.get_param('live_from_start'):
|
||||||
fmt.setdefault('downloader_options', {}).update({'ffmpeg_args': ['-live_start_index', '0']})
|
fmt['downloader_options'] = {'ffmpeg_args': ['-live_start_index', '0']}
|
||||||
fmt['is_from_start'] = True
|
|
||||||
|
|
||||||
return formats
|
return formats
|
||||||
|
|
||||||
@ -553,8 +552,7 @@ class TwitchVodIE(TwitchBaseIE):
|
|||||||
access_token = self._download_access_token(vod_id, 'video', 'id')
|
access_token = self._download_access_token(vod_id, 'video', 'id')
|
||||||
|
|
||||||
formats = self._extract_twitch_m3u8_formats(
|
formats = self._extract_twitch_m3u8_formats(
|
||||||
'vod', vod_id, access_token['value'], access_token['signature'],
|
'vod', vod_id, access_token['value'], access_token['signature'])
|
||||||
live_from_start=self.get_param('live_from_start'))
|
|
||||||
formats.extend(self._extract_storyboard(vod_id, video.get('storyboard'), info.get('duration')))
|
formats.extend(self._extract_storyboard(vod_id, video.get('storyboard'), info.get('duration')))
|
||||||
|
|
||||||
self._prefer_source(formats)
|
self._prefer_source(formats)
|
||||||
@ -637,10 +635,6 @@ class TwitchPlaylistBaseIE(TwitchBaseIE):
|
|||||||
_PAGE_LIMIT = 100
|
_PAGE_LIMIT = 100
|
||||||
|
|
||||||
def _entries(self, channel_name, *args):
|
def _entries(self, channel_name, *args):
|
||||||
"""
|
|
||||||
Subclasses must define _make_variables() and _extract_entry(),
|
|
||||||
as well as set _OPERATION_NAME, _ENTRY_KIND, _EDGE_KIND, and _NODE_KIND
|
|
||||||
"""
|
|
||||||
cursor = None
|
cursor = None
|
||||||
variables_common = self._make_variables(channel_name, *args)
|
variables_common = self._make_variables(channel_name, *args)
|
||||||
entries_key = f'{self._ENTRY_KIND}s'
|
entries_key = f'{self._ENTRY_KIND}s'
|
||||||
@ -680,22 +674,7 @@ class TwitchPlaylistBaseIE(TwitchBaseIE):
|
|||||||
break
|
break
|
||||||
|
|
||||||
|
|
||||||
class TwitchVideosBaseIE(TwitchPlaylistBaseIE):
|
class TwitchVideosIE(TwitchPlaylistBaseIE):
|
||||||
_OPERATION_NAME = 'FilterableVideoTower_Videos'
|
|
||||||
_ENTRY_KIND = 'video'
|
|
||||||
_EDGE_KIND = 'VideoEdge'
|
|
||||||
_NODE_KIND = 'Video'
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _make_variables(channel_name, broadcast_type, sort):
|
|
||||||
return {
|
|
||||||
'channelOwnerLogin': channel_name,
|
|
||||||
'broadcastType': broadcast_type,
|
|
||||||
'videoSort': sort.upper(),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class TwitchVideosIE(TwitchVideosBaseIE):
|
|
||||||
_VALID_URL = r'https?://(?:(?:www|go|m)\.)?twitch\.tv/(?P<id>[^/]+)/(?:videos|profile)'
|
_VALID_URL = r'https?://(?:(?:www|go|m)\.)?twitch\.tv/(?P<id>[^/]+)/(?:videos|profile)'
|
||||||
|
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
@ -774,6 +753,11 @@ class TwitchVideosIE(TwitchVideosBaseIE):
|
|||||||
'views': 'Popular',
|
'views': 'Popular',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_OPERATION_NAME = 'FilterableVideoTower_Videos'
|
||||||
|
_ENTRY_KIND = 'video'
|
||||||
|
_EDGE_KIND = 'VideoEdge'
|
||||||
|
_NODE_KIND = 'Video'
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def suitable(cls, url):
|
def suitable(cls, url):
|
||||||
return (False
|
return (False
|
||||||
@ -782,6 +766,14 @@ class TwitchVideosIE(TwitchVideosBaseIE):
|
|||||||
TwitchVideosCollectionsIE))
|
TwitchVideosCollectionsIE))
|
||||||
else super().suitable(url))
|
else super().suitable(url))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _make_variables(channel_name, broadcast_type, sort):
|
||||||
|
return {
|
||||||
|
'channelOwnerLogin': channel_name,
|
||||||
|
'broadcastType': broadcast_type,
|
||||||
|
'videoSort': sort.upper(),
|
||||||
|
}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _extract_entry(node):
|
def _extract_entry(node):
|
||||||
return _make_video_result(node)
|
return _make_video_result(node)
|
||||||
@ -929,7 +921,7 @@ class TwitchVideosCollectionsIE(TwitchPlaylistBaseIE):
|
|||||||
playlist_title=f'{channel_name} - Collections')
|
playlist_title=f'{channel_name} - Collections')
|
||||||
|
|
||||||
|
|
||||||
class TwitchStreamIE(TwitchVideosBaseIE):
|
class TwitchStreamIE(TwitchBaseIE):
|
||||||
IE_NAME = 'twitch:stream'
|
IE_NAME = 'twitch:stream'
|
||||||
_VALID_URL = r'''(?x)
|
_VALID_URL = r'''(?x)
|
||||||
https?://
|
https?://
|
||||||
@ -992,7 +984,6 @@ class TwitchStreamIE(TwitchVideosBaseIE):
|
|||||||
'skip_download': 'Livestream',
|
'skip_download': 'Livestream',
|
||||||
},
|
},
|
||||||
}]
|
}]
|
||||||
_PAGE_LIMIT = 1
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def suitable(cls, url):
|
def suitable(cls, url):
|
||||||
@ -1006,20 +997,6 @@ class TwitchStreamIE(TwitchVideosBaseIE):
|
|||||||
TwitchClipsIE))
|
TwitchClipsIE))
|
||||||
else super().suitable(url))
|
else super().suitable(url))
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _extract_entry(node):
|
|
||||||
if not isinstance(node, dict) or not node.get('id'):
|
|
||||||
return None
|
|
||||||
video_id = node['id']
|
|
||||||
return {
|
|
||||||
'_type': 'url',
|
|
||||||
'ie_key': TwitchVodIE.ie_key(),
|
|
||||||
'id': 'v' + video_id,
|
|
||||||
'url': f'https://www.twitch.tv/videos/{video_id}',
|
|
||||||
'title': node.get('title'),
|
|
||||||
'timestamp': unified_timestamp(node.get('publishedAt')) or 0,
|
|
||||||
}
|
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
channel_name = self._match_id(url).lower()
|
channel_name = self._match_id(url).lower()
|
||||||
|
|
||||||
@ -1054,25 +1031,23 @@ class TwitchStreamIE(TwitchVideosBaseIE):
|
|||||||
if not stream:
|
if not stream:
|
||||||
raise UserNotLive(video_id=channel_name)
|
raise UserNotLive(video_id=channel_name)
|
||||||
|
|
||||||
timestamp = unified_timestamp(stream.get('createdAt'))
|
|
||||||
|
|
||||||
if self.get_param('live_from_start'):
|
|
||||||
self.to_screen(f'{channel_name}: Extracting VOD to download live from start')
|
|
||||||
entry = next(self._entries(channel_name, None, 'time'), None)
|
|
||||||
if entry and entry.pop('timestamp') >= (timestamp or float('inf')):
|
|
||||||
return entry
|
|
||||||
self.report_warning(
|
|
||||||
'Unable to extract the VOD associated with this livestream', video_id=channel_name)
|
|
||||||
|
|
||||||
access_token = self._download_access_token(
|
access_token = self._download_access_token(
|
||||||
channel_name, 'stream', 'channelName')
|
channel_name, 'stream', 'channelName')
|
||||||
|
|
||||||
stream_id = stream.get('id') or channel_name
|
stream_id = stream.get('id')
|
||||||
|
if self.get_param('live_from_start'):
|
||||||
|
if stream_id:
|
||||||
|
return self.url_result(f'https://www.twitch.tv/videos/{stream_id}', TwitchVodIE, stream_id)
|
||||||
|
self.report_warning('Unable to extract stream ID; cannot download live from start')
|
||||||
|
if not stream_id:
|
||||||
|
stream_id = channel_name
|
||||||
|
|
||||||
formats = self._extract_twitch_m3u8_formats(
|
formats = self._extract_twitch_m3u8_formats(
|
||||||
'api/channel/hls', channel_name, access_token['value'], access_token['signature'])
|
'api/channel/hls', channel_name, access_token['value'], access_token['signature'])
|
||||||
self._prefer_source(formats)
|
self._prefer_source(formats)
|
||||||
|
|
||||||
view_count = stream.get('viewers')
|
view_count = stream.get('viewers')
|
||||||
|
timestamp = unified_timestamp(stream.get('createdAt'))
|
||||||
|
|
||||||
sq_user = try_get(gql, lambda x: x[1]['data']['user'], dict) or {}
|
sq_user = try_get(gql, lambda x: x[1]['data']['user'], dict) or {}
|
||||||
uploader = sq_user.get('displayName')
|
uploader = sq_user.get('displayName')
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user