initial commit
This commit is contained in:
402
main.lua
Normal file
402
main.lua
Normal file
@ -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)
|
Reference in New Issue
Block a user