Introduction
Introduction Statistics Contact Development Disclaimer Help
jellyfin.lua - mpv-jellyfin - MPV script for adding an interface for Jellyfin.
git clone git://jay.scot/mpv-jellyfin
Log
Files
Refs
README
LICENSE
---
jellyfin.lua (7977B)
---
1 local opt = require 'mp.options'
2 local utils = require 'mp.utils'
3 local msg = require 'mp.msg'
4
5 local options = {
6 url = "",
7 username = "",
8 password = "",
9 image_path = "",
10 hide_spoilers = "on",
11 show_by_default = ""
12 }
13 opt.read_options(options, mp.get_script_name())
14
15 local overlay = mp.create_osd_overlay("ass-events")
16 local meta_overlay = mp.create_osd_overlay("ass-events")
17 local shown = false
18 local user_id = ""
19 local api_key = ""
20
21 local parent_id = {"", "", "", ""}
22 local selection = {1, 1, 1, 1}
23 local list_start = {1, 1, 1, 1}
24 local layer = 1
25
26 local items = {}
27 local ow, oh, op = 0, 0, 0
28 local video_id = ""
29 local async = nil
30
31 local toggle_overlay -- function
32
33 local function send_request(method, url)
34 if #api_key > 0 then
35 local request = mp.command_native({
36 name = "subprocess",
37 capture_stdout = true,
38 capture_stderr = true,
39 playback_only = false,
40 args = {"curl", "-X", method, url}
41 })
42 return utils.parse_json(request.stdout)
43 end
44 return nil
45 end
46
47 local function line_break(str, flags, space)
48 if str == nil then return "" end
49 local text = flags
50 local n = 0
51 for i = 1, #str do
52 local c = str:sub(i, i)
53 if (c == ' ' and i-n > space) or c == '\n' then
54 text = text..str:sub(n, i-1).."\n"..flags
55 n = i+1
56 end
57 end
58 text = text..str:sub(n, -1)
59 return text
60 end
61
62 local function update_list()
63 overlay.data = ""
64 local magic_num = 29 -- const
65 if selection[layer] - list_start[layer] > magic_num then
66 list_start[layer] = selection[layer] - magic_num
67 elseif selection[layer] - list_start[layer] < 0 then
68 list_start[layer] = selection[layer]
69 end
70 for i=list_start[layer],list_start[layer]+magic_num do
71 if i > #items then break end
72 local index = ""
73 if items[i].IndexNumber and items[i].IsFolder == false t…
74 index = items[i].IndexNumber..". "
75 else
76 -- nothing
77 end
78 if i == selection[layer] then
79 overlay.data = overlay.data.."{\\fs16}{\\c&HFF&}…
80 else
81 overlay.data = overlay.data.."{\\fs16}"..index..…
82 end
83 end
84 overlay:update()
85 end
86
87 local scale = 2 -- const
88
89 local function show_image(success, result, error, userdata)
90 if success == true and shown == true then
91 mp.command_native({
92 name = "overlay-add",
93 id = 0,
94 x = math.floor(ow/2.5),
95 y = 10,
96 file = userdata[3],
97 offset = 0,
98 fmt = "bgra",
99 w = userdata[1],
100 h = userdata[2],
101 stride = userdata[1]*4,
102 dw = userdata[1]*scale,
103 dh = userdata[2]*scale
104 })
105 end
106 end
107
108 local function update_image(item)
109 local width = math.floor(ow/(3*scale))
110 local height = 0
111 local filepath = ""
112 mp.commandv("overlay-remove", "0")
113 if item.ImageTags.Primary ~= nil then
114 height = math.floor(width/item.PrimaryImageAspectRatio)
115 filepath = options.image_path.."/"..item.Id.."_"..width.…
116 if async ~= nil then mp.abort_async_command(async) end
117 async = mp.command_native_async({
118 name = "subprocess",
119 playback_only = false,
120 args = { "mpv", options.url.."/Items/"..item.Id.…
121 }, function(success, result, error) show_image(success, …
122 end
123 end
124
125 local function update_metadata(item)
126 meta_overlay.data = ""
127 local name = line_break(item.Name, "{\\a7}{\\fs24}", 30)
128 meta_overlay.data = meta_overlay.data..name.."\n"
129 local year = ""
130 if item.ProductionYear then year = item.ProductionYear end
131 local time = ""
132 if item.RunTimeTicks then time = " "..math.floor(item.RunTimeT…
133 local rating = ""
134 if item.CommunityRating then rating = " "..item.CommunityRatin…
135 local hidden = ""
136 local watched = ""
137 if item.UserData.Played == false then
138 if options.hide_spoilers ~= "off" then hidden = "{\\bord…
139 else
140 watched = " Watched"
141 end
142 local favourite = ""
143 if item.UserData.IsFavorite == true then
144 favourite = " Favorite"
145 end
146 meta_overlay.data = meta_overlay.data.."{\\a7}{\\fs16}"..year..t…
147 local tagline = line_break(item.Taglines[1], "{\\a7}{\\fs20}", 3…
148 meta_overlay.data = meta_overlay.data..tagline.."\n"
149 local description = line_break(item.Overview, "{\\a7}{\\fs16}"..…
150 meta_overlay.data = meta_overlay.data..description
151 meta_overlay:update()
152 end
153
154 local function update_data()
155 update_list()
156 local item = items[selection[layer]]
157 update_image(item)
158 update_metadata(item)
159 end
160
161 local function update_overlay()
162 overlay.data = "{\\fs16}Loading..."
163 overlay:update()
164 local url = options.url.."/Items?api_key="..api_key.."&userID=".…
165 if layer == 2 then
166 url = url.."&sortBy=SortName"
167 else
168 -- nothing
169 end
170 items = send_request("GET", url).Items
171 ow, oh, op = mp.get_osd_size()
172 update_data()
173 end
174
175 local function width_change(name, data)
176 if shown then update_overlay() end
177 end
178
179 local function play_video()
180 toggle_overlay()
181 mp.commandv("loadfile", options.url.."/Videos/"..video_id.."/str…
182 mp.set_property("force-media-title", items[selection[layer]].Nam…
183 end
184
185 local function key_up()
186 if #items > 1 then
187 selection[layer] = selection[layer] - 1
188 if selection[layer] == 0 then selection[layer] = #items …
189 update_data()
190 end
191 end
192
193 local function key_right()
194 if items[selection[layer]].IsFolder == false then
195 video_id = items[selection[layer]].Id
196 play_video()
197 else
198 layer = layer + 1 -- shouldn't get too big
199 parent_id[layer] = items[selection[layer-1]].Id
200 selection[layer] = 1
201 update_overlay()
202 end
203 end
204
205 local function key_down()
206 if #items > 1 then
207 selection[layer] = selection[layer] + 1
208 if selection[layer] > #items then selection[layer] = 1 e…
209 update_data()
210 end
211 end
212
213 local function key_left()
214 if layer == 1 then return end
215 layer = layer - 1
216 update_overlay()
217 end
218
219 local function connect()
220 local request = mp.command_native({
221 name = "subprocess",
222 capture_stdout = true,
223 capture_stderr = true,
224 playback_only = false,
225 args = {"curl", options.url.."/Users/AuthenticateByName"…
226 })
227 local result = utils.parse_json(request.stdout)
228 user_id = result.User.Id
229 api_key = result.AccessToken
230 end
231
232 toggle_overlay = function()
233 if shown then
234 mp.remove_key_binding("jup")
235 mp.remove_key_binding("jright")
236 mp.remove_key_binding("jdown")
237 mp.remove_key_binding("jleft")
238 mp.commandv("overlay-remove", "0")
239 overlay:remove()
240 meta_overlay:remove()
241 else
242 mp.add_forced_key_binding("UP", "jup", key_up, { repeata…
243 mp.add_forced_key_binding("RIGHT", "jright", key_right)
244 mp.add_forced_key_binding("DOWN", "jdown", key_down, { r…
245 mp.add_forced_key_binding("LEFT", "jleft", key_left)
246 if #api_key <= 0 then connect() end
247 if #items == 0 then
248 update_overlay()
249 else
250 update_data()
251 end
252 end
253 shown = not shown
254 end
255
256 local function check_percent()
257 local pos = mp.get_property_number("percent-pos")
258 if pos then
259 if pos > 95 and #video_id > 0 then
260 send_request("POST", options.url.."/Users/"..use…
261 items[selection[layer]].UserData.Played = true
262 video_id = ""
263 end
264 end
265 end
266
267 local function unpause()
268 mp.set_property_bool("pause", false)
269 end
270
271 os.execute("mkdir -p "..options.image_path)
272 mp.add_periodic_timer(1, check_percent)
273 mp.add_key_binding("Ctrl+j", "jf", toggle_overlay)
274 mp.observe_property("osd-width", "number", width_change)
275 mp.register_event("end-file", unpause)
276 if options.show_by_default == "on" then toggle_overlay() end
You are viewing proxied material from jay.scot. The copyright of proxied material belongs to its original authors. Any comments or complaints in relation to proxied material should be directed to the original authors of the content concerned. Please see the disclaimer for more details.