diff options
author | Squibid <me@zacharyscheiman.com> | 2024-08-03 22:42:15 -0400 |
---|---|---|
committer | Squibid <me@zacharyscheiman.com> | 2024-08-03 22:42:15 -0400 |
commit | d400da457ea15c58319a9f200f49c6502787e17c (patch) | |
tree | 2685cc46ab516ef3c086ca8fd8aa526b11a27618 /main.lua | |
download | jellies-and-jams-d400da457ea15c58319a9f200f49c6502787e17c.tar.gz jellies-and-jams-d400da457ea15c58319a9f200f49c6502787e17c.tar.bz2 jellies-and-jams-d400da457ea15c58319a9f200f49c6502787e17c.zip |
initial commit
Diffstat (limited to 'main.lua')
-rw-r--r-- | main.lua | 402 |
1 files changed, 402 insertions, 0 deletions
diff --git a/main.lua b/main.lua new file mode 100644 index 0000000..b12807f --- /dev/null +++ b/main.lua @@ -0,0 +1,402 @@ +local mp = require('mp') +local msg = require('mp.msg') +local utils = require('mp.utils') + +local jf = require("utils.jellyfin") + +-- require mpv scroll list library +package.path = mp.command_native({"expand-path", "~~/script-modules/?.lua;"}) + ..package.path +local lok, list = pcall(require, "scroll-list") +if not lok then + msg.info("Install: https://github.com/CogentRedTester/mpv-scroll-list") + return 1 +end + +-- make an instance of the list +local original_open = list.open +function list:open() + original_open(self) +end + +local settings = { + url = '', + username = '', + password = '', + header = { + prefix = "jellyfin/", + separator = "/", + contents = { + { "watched", " ", "favorite", " ", "runtime" }, + { "stars", " ", "rating", " ", "release" } + } + }, + client = { + name = 'Jellyfin Lua Client', + device = 'Mpv', + version = '1.1' + } +} + +--- table containing all info about the runtime +---@type table +local rt = { + authenticated = false, -- are we connected to the server + user = {}, + + library = jf.library(), -- current library + title = jf.library(), -- current movie or show + season = jf.library(), -- current season of show + + video = jf.video(), -- current video (movie/episode) + nextup = jf.video(), -- next video + + -- our menu to render + menu = { + items = {} -- contains table of strings to display (with ass styling) + } +} + +--- draw list to screen +---@param items table list of elements to draw to the screen +local function redraw_list(items) + -- clear out the render list + list.list = {} + + -- style each line + for i = 1, #items do + local item = {} + item.style = "" + + if items[i].UserData then + -- if the user has started watching it make it blue + if items[i].UserData.PlayedPercentage then + if items[i].UserData.PlayedPercentage > 0 then + item.style = [[{\c&Hdca400&}]]..item.style + end + -- if the user has finished watching it make it green + elseif items[i].UserData.Played then + item.style = [[{\c&H33ff66&}]]..item.style + end + end + item.style = [[{\c&Hffffff&}]]..item.style + item.ass = rt.menu.items[i].Name + list:insert(item) + end + + list:update() +end + +function list:format_header_string() + ---@type string + local header + + -- shorten the selected item + local sel = rt.menu.items[list.selected] + + -- setup topheading + if not rt.library.id then + header = settings.header.prefix + elseif not rt.title.id then + header = settings.header.prefix..rt.library.name..settings.header.separator + elseif not rt.season.id then + header = settings.header.prefix..rt.library.name..settings.header.separator + ..rt.title.name + else + header = settings.header.prefix..rt.library.name..settings.header.separator + ..rt.title.name..settings.header.separator..rt.season.name + end + + -- generate x subheadings based on user config + for i, v in pairs(settings.header.contents) do + -- if no selected item exit + if not rt.menu.items or not sel then + goto finish + end + + -- if this isn't the first line or the last line add a newline symbol and + -- reset colors + if i == 1 or i <= #settings.header.contents then + header = header..[[\N\h{\q2\fs20\c&Hffffff&}]] + end + + for _, c in pairs(v) do + if not sel or not sel.UserData then + goto continue + end + + if type(c) == "function" then + header = header..c(rt.menu.items, list.selected) + elseif c == "watched" then + if sel.UserData.PlayedPercentage then + if sel.UserData.PlayedPercentage > 0 then + header = header..[[{\c&Hdca400&}]].. + math.floor(sel.UserData.PlayedPercentage)..[[%{\c&Hffffff&}]] + end + elseif sel.UserData.Played then + header = header..[[{\c&H33ff66&}✔️{\c&Hffffff&}]] + elseif sel.UserData.UnplayedItemCount then + header = header..[[{\c&Hdca400&}]]..sel.UserData.UnplayedItemCount.. + [[{\c&Hffffff&}]] + else + header = header..[[{\c&H444444&}✔️{\c&Hffffff&}]] + end + + elseif c == "favorite" then + if sel.UserData.IsFavorite then + header = header..[[{\c&H0000ff&}♥️{\c&Hffffff&}]] + else + header = header..[[{\c&H444444&}♥️{\c&Hffffff&}]] + end + + elseif c == "runtime" then + if sel.RunTimeTicks then + ---@type number + local hour + local min = math.floor(sel.RunTimeTicks / 600000000) + + if min >= 60 then + hour = math.floor(min / 60) + min = min - math.floor(min / 60) * 60 + header = header..hour.."h "..min.."m" + else + header = header..min.."m" + end + end + + elseif c == "stars" then + if sel.CommunityRating then + local stars = math.floor(sel.CommunityRating * 10 + 0.5) / 10 + + header = header..[[{\c&H00ffff}★]]..stars..[[{\c&Hffffff&}]] + end + + elseif c == "rating" then + if sel.OfficialRating then + header = header..[[{\c&Hffffff}[]]..sel.OfficialRating.. + [[]{\c&Hffffff&}]] + end + + elseif c == "release" then + if sel.ProductionYear then + header = header..sel.ProductionYear + end + + elseif c == "entries" then + header = header..#rt.menu.items + else + header = header..c + end + end + ::continue:: + end + + ::finish:: + return header +end + +--- change position in jellyfin +local function enter() + local sel = rt.menu.items[list.selected] + if not sel then + return + end + + if sel.IsFolder == false then + msg.trace("selected a video") + + rt.video.id = sel.Id + + -- start playing the movie + jf.play() + else + if not rt.library.id then + msg.trace("selected a library") + + rt.library.id = sel.Id + rt.library.name = sel.Name + rt.library.pos = list.selected + elseif not rt.title.id then + msg.trace("selected a movie") + + rt.title.id = sel.Id + rt.title.name = sel.Name + rt.title.pos = list.selected + elseif not rt.season.id then + msg.trace("selected a season") + + rt.season.id = sel.Id + rt.season.name = sel.Name + rt.season.pos = list.selected + end + + list.selected = 1 + end +end + +--- helper to addkeys to the list view +---@param keys string keybind +---@param name string action name +---@param fn function callback +---@param opts? table options +local function addkey(keys, name, fn, opts) + opts = opts or { repeatable = true } + local i = 1 + for key in keys:gmatch("%S+") do + table.insert(list.keybinds, { key, name..i, fn, opts }) + i = i + 1 + end +end + +--- wrapper to safely open the list +local function openlist() + -- setup jellyfin api on first open + if type(jf.setup) ~= "boolean" then + jf.setup(list, rt, settings) + + -- mark jellyfin api as setup by replacing function with a boolean + jf.setup = true + end + + -- authenticate with the server based on users settings + rt.authenticated = jf.authenticate() + if not rt.authenticated then + return + end + + -- refresh the list + rt.menu.items = jf.getpos() + redraw_list(rt.menu.items) + + -- open the list + list:open() +end + +-- wrapper to safely close the list +local function closelist() + rt.authenticated = false + list:close() +end + +-- register script message for users so they may define keybinds +mp.register_script_message("jnj-bind-open", function(key) + mp.add_key_binding(key, 'JNJ-BIND-OPEN', openlist) +end) + +mp.register_script_message("jnj-bind-close", function(key) + addkey(key, 'close', closelist) +end) + +mp.register_script_message("jnj-bind-toggle", function(key) + mp.add_key_binding(key, 'JNJ-BIND-TOGGLE', function() + if list.hidden then + openlist() + else + closelist() + end + end) +end) + +mp.register_script_message("jnj-bind-enter", function(key) + addkey(key, 'enter', function() + enter() + + local items = jf.getpos() + if not items then + return + end + + rt.menu.items = items + redraw_list(rt.menu.items) + end) +end) + +mp.register_script_message("jnj-bind-leave", function(key) + addkey(key, 'leave', function() + if not rt.library.id then + -- do nothing we're already in the root + elseif not rt.title.id then + rt.library.id = nil + list.selected = rt.library.pos + elseif not rt.season.id then + rt.title.id = nil + list.selected = rt.title.pos + else + rt.season.id = nil + list.selected = rt.season.pos + end + + local items = jf.getpos() + if not items then + return + end + + rt.menu.items = items + redraw_list(rt.menu.items) + end) +end) + +mp.register_script_message("jnj-bind-down", function(key) + addkey(key, 'down', function() + list:scroll_down() + end) +end) + +mp.register_script_message("jnj-bind-up", function(key) + addkey(key, 'up', function() + list:scroll_up() + end) +end) + + +mp.register_script_message("jnj-bind-pagedown", function(key) + addkey(key, 'pageup', function() + list:move_pagedown() + end) +end) + +mp.register_script_message("jnj-bind-pageup", function(key) + addkey(key, 'pagedown', function() + list:move_pageup() + end) +end) + +mp.register_script_message("jnj-bind-top", function(key) + addkey(key, 'top', function() + list:move_begin() + end) +end) + +mp.register_script_message("jnj-bind-bottom", function(key) + addkey(key, 'bottom', function() + list:move_end() + end) +end) + +mp.register_script_message("jnj-bind-toggle-played", function(key) + addkey(key, 'bottom', function() + if rt.menu.items[list.selected] and rt.menu.items[list.selected].Id and + not rt.menu.items[list.selected].isFolder then + jf.setplayed(not rt.menu.items[list.selected].UserData.Played) + redraw_list(rt.menu.items) + end + end) +end) + +mp.register_script_message("jnj-bind-toggle-favorite", function(key) + addkey(key, 'bottom', function() + if rt.menu.items[list.selected] and rt.menu.items[list.selected].Id and + not rt.menu.items[list.selected].isFolder then + jf.setfavorite(not rt.menu.items[list.selected].UserData.IsFavorite) + redraw_list(rt.menu.items) + end + end) +end) + +-- add script message for users to provide their settings via json +mp.register_script_message("jnj-set-settings", function(json) + local user_settings = utils.parse_json(json) + for i, v in pairs(user_settings) do + settings[i] = v + end +end) |