forked from CrendKing/mpv-twitch-chat
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain.lua
More file actions
221 lines (182 loc) · 7 KB
/
Copy pathmain.lua
File metadata and controls
221 lines (182 loc) · 7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
--[[
License: https://github.com/CrendKing/mpv-twitch-chat/blob/master/LICENSE
Options:
show_name: Whether to show the commenter's name.
color: If show_name is enabled, color the commenter's name with its user color. Otherwise, color the whole message.
duration_multiplier: Each chat message's duration is calculated based on the density of the messages at the time after
applying this multiplier. Basically, if you want more messages simultaneously on screen, increase this number.
max_duration: Maximum duration in seconds of each chat message after applying the previous multiplier. This exists to prevent
messages to stay forever in "cold" segments.
fetch_aot: The chat data is downloaded in segments. This script uses timer to fetch new segments this many seconds before the
current segment is exhausted. Increase this number to avoid interruption if you have slower network to Twitch.
ignore_sub: Ignore subscription related messages.
--]]
local o = {
show_name = false,
color = true,
duration_multiplier = 10,
max_duration = 10,
fetch_aot = 1,
ignore_sub = true,
}
local options = require 'mp.options'
options.read_options(o)
if not mp.get_script_directory() then
mp.msg.error("This script requires to be placed in a script directory")
return
end
local utils = require "mp.utils"
package.path = utils.join_path(utils.join_path(mp.get_script_directory(), "json.lua"), "json.lua;") .. package.path
local json = require "json"
-- sid to be operated on
local chat_sid
-- request url for the chat data
local twitch_comments_url
-- next segment ID to fetch from Twitch
local twitch_cursor
-- two fifo segments for cycling the subtitle text
local curr_segment
local next_segment
-- SubRip sequence counter
local seq_counter
-- timer to fetch new segments of the chat data
local timer
local function load_twitch_chat(is_new_session)
if not chat_sid or not twitch_comments_url then
return
end
local request_url
if is_new_session then
local time_pos = mp.get_property_native("time-pos")
if not time_pos then
return
end
request_url = twitch_comments_url .. "?content_offset_seconds=" .. math.max(time_pos, 0)
next_segment = ""
seq_counter = 0
else
request_url = twitch_comments_url .. "?cursor=" .. twitch_cursor
end
local sp_ret = mp.command_native({
name = "subprocess",
capture_stdout = true,
args = {"curl", "-s", "-H", "Client-ID: phiay4sq36lfv9zu7cbqwz2ndnesfd8", request_url},
})
if sp_ret.status ~= 0 then
mp.msg.error("Error curl exit code: " .. sp_ret.status)
return
end
local resp_json = json.decode(sp_ret.stdout)
local comments = resp_json.comments
if not comments then
mp.msg.error("Failed to download comments JSON: " .. sp_ret.stdout)
return
end
twitch_cursor = resp_json._next
curr_segment = next_segment
next_segment = ""
local last_msg_offset = comments[#comments].content_offset_seconds
local segment_duration = last_msg_offset - comments[1].content_offset_seconds
local per_msg_duration = math.min(segment_duration * o.duration_multiplier / #comments, o.max_duration)
for i, curr_comment in ipairs(comments) do
if o.ignore_sub then
local notice_msg_id = curr_comment.message.user_notice_params["msg-id"]
if notice_msg_id == "sub" or notice_msg_id == "resub" then
goto continue
end
end
local msg_time_from = curr_comment.content_offset_seconds
local msg_time_from_ms = math.floor(msg_time_from * 1000) % 1000
local msg_time_from_sec = math.floor(msg_time_from) % 60
local msg_time_from_min = math.floor(msg_time_from / 60) % 60
local msg_time_from_hour = math.floor(msg_time_from / 3600)
local msg_time_to = msg_time_from + per_msg_duration
local msg_time_to_ms = math.floor(msg_time_to * 1000) % 1000
local msg_time_to_sec = math.floor(msg_time_to) % 60
local msg_time_to_min = math.floor(msg_time_to / 60) % 60
local msg_time_to_hour = math.floor(msg_time_to / 3600)
local msg_part_1, msg_part_2, msg_separator
if o.show_name then
msg_part_1 = curr_comment.commenter.display_name
msg_part_2 = curr_comment.message.body
msg_separator = ": "
else
msg_part_1 = curr_comment.message.body
msg_part_2 = ""
msg_separator = ""
end
if o.color then
if curr_comment.message.user_color then
msg_color = curr_comment.message.user_color
else
msg_color = string.format("#%06x", curr_comment.commenter._id % 16777216)
end
msg_part_1 = string.format("<font color=\"%s\">%s</font>", msg_color, msg_part_1)
end
local msg_line = msg_part_1 .. msg_separator .. msg_part_2
local subtitle = string.format([[%i
%i:%i:%i,%i --> %i:%i:%i,%i
%s
]],
seq_counter,
msg_time_from_hour, msg_time_from_min, msg_time_from_sec, msg_time_from_ms,
msg_time_to_hour, msg_time_to_min, msg_time_to_sec, msg_time_to_ms,
msg_line)
next_segment = next_segment .. subtitle
seq_counter = seq_counter + 1
::continue::
end
mp.command_native({"sub-remove", chat_sid})
mp.command_native({
name = "sub-add",
url = "memory://" .. curr_segment .. next_segment,
title = "Twitch Chat"
})
chat_sid = mp.get_property_native("sid")
return last_msg_offset
end
local function init()
twitch_comments_url = nil
end
local function timer_callback(is_new_session)
local last_msg_offset = load_twitch_chat(is_new_session)
if last_msg_offset then
local fetch_delay = last_msg_offset - mp.get_property_native("time-pos") - o.fetch_aot
timer = mp.add_timeout(fetch_delay, function()
timer_callback(false)
end)
end
end
local function handle_track_change(name, sid)
if not sid and timer then
timer:kill()
timer = nil
elseif sid and not timer then
if not twitch_comments_url then
local sub_filename = mp.get_property_native("current-tracks/sub/external-filename")
twitch_comments_url = sub_filename and sub_filename:match("https://api.twitch.tv/v5/videos/%d+/comments") or nil
end
if twitch_comments_url then
chat_sid = sid
timer_callback(true)
end
end
end
local function handle_seek()
if mp.get_property_native("sid") then
load_twitch_chat(true)
end
end
local function handle_pause(name, paused)
if timer then
if paused then
timer:stop()
else
timer:resume()
end
end
end
mp.register_event("start-file", init)
mp.observe_property("current-tracks/sub/id", "native", handle_track_change)
mp.register_event("seek", handle_seek)
mp.observe_property("pause", "native", handle_pause)