1 from __future__
import division
, unicode_literals
5 from .fragment
import FragmentFD
6 from ..compat
import compat_urllib_error
11 from ..extractor
.youtube
import YoutubeBaseInfoExtractor
as YT_BaseIE
14 class YoutubeLiveChatReplayFD(FragmentFD
):
15 """ Downloads YouTube live chat replays fragment by fragment """
17 FD_NAME
= 'youtube_live_chat_replay'
19 def real_download(self
, filename
, info_dict
):
20 video_id
= info_dict
['video_id']
21 self
.to_screen('[%s] Downloading live chat' % self
.FD_NAME
)
23 fragment_retries
= self
.params
.get('fragment_retries', 0)
24 test
= self
.params
.get('test', False)
32 ie
= YT_BaseIE(self
.ydl
)
34 def dl_fragment(url
, data
=None, headers
=None):
35 http_headers
= info_dict
.get('http_headers', {})
37 http_headers
= http_headers
.copy()
38 http_headers
.update(headers
)
39 return self
._download
_fragment
(ctx
, url
, info_dict
, http_headers
, data
)
41 def download_and_parse_fragment(url
, frag_index
, request_data
):
43 while count
<= fragment_retries
:
45 success
, raw_fragment
= dl_fragment(url
, request_data
, {'content-type': 'application/json'}
)
47 return False, None, None
49 data
= ie
._extract
_yt
_initial
_data
(video_id
, raw_fragment
.decode('utf-8', 'replace'))
50 except RegexNotFoundError
:
53 data
= json
.loads(raw_fragment
)
54 live_chat_continuation
= try_get(
56 lambda x
: x
['continuationContents']['liveChatContinuation'], dict) or {}
57 offset
= continuation_id
= None
58 processed_fragment
= bytearray()
59 for action
in live_chat_continuation
.get('actions', []):
60 if 'replayChatItemAction' in action
:
61 replay_chat_item_action
= action
['replayChatItemAction']
62 offset
= int(replay_chat_item_action
['videoOffsetTimeMsec'])
63 processed_fragment
.extend(
64 json
.dumps(action
, ensure_ascii
=False).encode('utf-8') + b
'\n')
65 if offset
is not None:
66 continuation_id
= try_get(
67 live_chat_continuation
,
68 lambda x
: x
['continuations'][0]['liveChatReplayContinuationData']['continuation'])
69 self
._append
_fragment
(ctx
, processed_fragment
)
71 return True, continuation_id
, offset
72 except compat_urllib_error
.HTTPError
as err
:
74 if count
<= fragment_retries
:
75 self
.report_retry_fragment(err
, frag_index
, count
, fragment_retries
)
76 if count
> fragment_retries
:
77 self
.report_error('giving up after %s fragment retries' % fragment_retries
)
78 return False, None, None
80 self
._prepare
_and
_start
_frag
_download
(ctx
)
82 success
, raw_fragment
= dl_fragment(info_dict
['url'])
86 data
= ie
._extract
_yt
_initial
_data
(video_id
, raw_fragment
.decode('utf-8', 'replace'))
87 except RegexNotFoundError
:
89 continuation_id
= try_get(
91 lambda x
: x
['contents']['twoColumnWatchNextResults']['conversationBar']['liveChatRenderer']['continuations'][0]['reloadContinuationData']['continuation'])
92 # no data yet but required to call _append_fragment
93 self
._append
_fragment
(ctx
, b
'')
95 ytcfg
= ie
._extract
_ytcfg
(video_id
, raw_fragment
.decode('utf-8', 'replace'))
99 api_key
= try_get(ytcfg
, lambda x
: x
['INNERTUBE_API_KEY'])
100 innertube_context
= try_get(ytcfg
, lambda x
: x
['INNERTUBE_CONTEXT'])
101 if not api_key
or not innertube_context
:
103 url
= 'https://www.youtube.com/youtubei/v1/live_chat/get_live_chat_replay?key=' + api_key
105 frag_index
= offset
= 0
106 while continuation_id
is not None:
109 'context': innertube_context
,
110 'continuation': continuation_id
,
113 request_data
['currentPlayerState'] = {'playerOffsetMs': str(max(offset - 5000, 0))}
114 success
, continuation_id
, offset
= download_and_parse_fragment(
115 url
, frag_index
, json
.dumps(request_data
, ensure_ascii
=False).encode('utf-8') + b
'\n')
121 self
._finish
_frag
_download
(ctx
)