diff options
| author | TheSiahxyz <164138827+TheSiahxyz@users.noreply.github.com> | 2026-03-14 00:07:41 +0900 |
|---|---|---|
| committer | TheSiahxyz <164138827+TheSiahxyz@users.noreply.github.com> | 2026-03-14 00:07:41 +0900 |
| commit | 5e954f3dfb4304341c1a50a1f9310f58dcf39682 (patch) | |
| tree | f4e5c1fdc9ee24b662fd9f4f43da4621a012c29a | |
| parent | a13bf1ecb918bcf80ecb8bb9663ac7a84d576243 (diff) | |
updates
| -rw-r--r-- | ar/.config/mpv/script-modules/scroll-list.lua | 457 | ||||
| -rw-r--r-- | ar/.config/mpv/script-modules/sha1.lua | 331 | ||||
| -rw-r--r-- | ar/.config/mpv/script-modules/user-input-module.lua | 156 | ||||
| -rw-r--r-- | ar/.config/mpv/script-modules/utf8_data.lua | 3712 | ||||
| -rw-r--r-- | ar/.config/mpv/scripts/SimpleBookmark.lua | 3021 | ||||
| -rw-r--r-- | ar/.config/mpv/scripts/SmartCopyPaste_II.lua | 2 | ||||
| -rw-r--r-- | ar/.config/mpv/scripts/gallery-thumbgen.lua | 2 | ||||
| -rw-r--r-- | ar/.config/mpv/scripts/mdmenu.lua | 6 | ||||
| -rw-r--r-- | ar/.config/mpv/scripts/navigator.lua | 1 | ||||
| -rw-r--r-- | ar/.config/mpv/scripts/osc.lua | 5415 | ||||
| -rw-r--r-- | ar/.config/mpv/scripts/subtitle-search.lua | 1095 | ||||
| -rw-r--r-- | ar/.config/mpv/scripts/thumbfast.lua | 1603 |
12 files changed, 8578 insertions, 7223 deletions
diff --git a/ar/.config/mpv/script-modules/scroll-list.lua b/ar/.config/mpv/script-modules/scroll-list.lua index 5d8f9fa..c1088f1 100644 --- a/ar/.config/mpv/script-modules/scroll-list.lua +++ b/ar/.config/mpv/script-modules/scroll-list.lua @@ -1,293 +1,384 @@ -local mp = require 'mp' +local mp = require("mp") local scroll_list = { - global_style = [[]], - header_style = [[{\q2\fs35\c&00ccff&}]], - list_style = [[{\q2\fs25\c&Hffffff&}]], - wrapper_style = [[{\c&00ccff&\fs16}]], - cursor_style = [[{\c&00ccff&}]], - selected_style = [[{\c&Hfce788&}]], - - cursor = [[➤\h]], - indent = [[\h\h\h\h]], - - num_entries = 16, - wrap = false, - empty_text = "no entries" + global_style = [[]], + header_style = [[{\q2\fs35\c&00ccff&}]], + list_style = [[{\q2\fs25\c&Hffffff&}]], + wrapper_style = [[{\c&00ccff&\fs16}]], + cursor_style = [[{\c&00ccff&}]], + selected_style = [[{\c&Hfce788&}]], + + cursor = [[➤\h]], + indent = [[\h\h\h\h]], + + num_entries = 16, + wrap = false, + empty_text = "no entries", } --formats strings for ass handling --this function is based on a similar function from https://github.com/mpv-player/mpv/blob/master/player/lua/console.lua#L110 function scroll_list.ass_escape(str, replace_newline) - if replace_newline == true then replace_newline = "\\\239\187\191n" end - - --escape the invalid single characters - str = str:gsub('[\\{}\n]', { - -- There is no escape for '\' in ASS (I think?) but '\' is used verbatim if - -- it isn't followed by a recognised character, so add a zero-width - -- non-breaking space - ['\\'] = '\\\239\187\191', - ['{'] = '\\{', - ['}'] = '\\}', - -- Precede newlines with a ZWNBSP to prevent ASS's weird collapsing of - -- consecutive newlines - ['\n'] = '\239\187\191\\N', - }) - - -- Turn leading spaces into hard spaces to prevent ASS from stripping them - str = str:gsub('\\N ', '\\N\\h') - str = str:gsub('^ ', '\\h') - - if replace_newline then - str = str:gsub("\\N", replace_newline) - end - return str + if replace_newline == true then + replace_newline = "\\\239\187\191n" + end + + --escape the invalid single characters + str = str:gsub("[\\{}\n]", { + -- There is no escape for '\' in ASS (I think?) but '\' is used verbatim if + -- it isn't followed by a recognised character, so add a zero-width + -- non-breaking space + ["\\"] = "\\\239\187\191", + ["{"] = "\\{", + ["}"] = "\\}", + -- Precede newlines with a ZWNBSP to prevent ASS's weird collapsing of + -- consecutive newlines + ["\n"] = "\239\187\191\\N", + }) + + -- Turn leading spaces into hard spaces to prevent ASS from stripping them + str = str:gsub("\\N ", "\\N\\h") + str = str:gsub("^ ", "\\h") + + if replace_newline then + str = str:gsub("\\N", replace_newline) + end + return str end --format and return the header string function scroll_list:format_header_string(str) - return str + return str end --appends the entered text to the overlay function scroll_list:append(text) - if text == nil then return end - self.ass.data = self.ass.data .. text - end + if text == nil then + return + end + self.ass.data = self.ass.data .. text +end --appends a newline character to the osd function scroll_list:newline() - self.ass.data = self.ass.data .. '\\N' + self.ass.data = self.ass.data .. "\\N" end --re-parses the list into an ass string --if the list is closed then it flags an update on the next open function scroll_list:update() - if self.hidden then self.flag_update = true - else self:update_ass() end + if self.hidden then + self.flag_update = true + else + self:update_ass() + end end --prints the header to the overlay function scroll_list:format_header() - self:append(self.header_style) - self:append(self:format_header_string(self.header)) - self:newline() + self:append(self.header_style) + self:append(self:format_header_string(self.header)) + self:newline() end --formats each line of the list and prints it to the overlay function scroll_list:format_line(index, item) - self:append(self.list_style) + self:append(self.list_style) - if index == self.selected then self:append(self.cursor_style..self.cursor..self.selected_style) - else self:append(self.indent) end + if index == self.selected then + self:append(self.cursor_style .. self.cursor .. self.selected_style) + else + self:append(self.indent) + end - self:append(item.style) - self:append(item.ass) - self:newline() + self:append(item.style) + self:append(item.ass) + self:newline() end --refreshes the ass text using the contents of the list function scroll_list:update_ass() - self.ass.data = self.global_style - self:format_header() - - if #self.list < 1 then - self:append(self.empty_text) - self.ass:update() - return - end - - local start = 1 - local finish = start+self.num_entries-1 - - --handling cursor positioning - local mid = math.ceil(self.num_entries/2)+1 - if self.selected+mid > finish then - local offset = self.selected - finish + mid - - --if we've overshot the end of the list then undo some of the offset - if finish + offset > #self.list then - offset = offset - ((finish+offset) - #self.list) - end - - start = start + offset - finish = finish + offset - end - - --making sure that we don't overstep the boundaries - if start < 1 then start = 1 end - local overflow = finish < #self.list - --this is necessary when the number of items in the dir is less than the max - if not overflow then finish = #self.list end - - --adding a header to show there are items above in the list - if start > 1 then self:append(self.wrapper_style..(start-1)..' item(s) above\\N\\N') end - - for i=start, finish do - self:format_line(i, self.list[i]) - end - - if overflow then self:append('\\N'..self.wrapper_style..#self.list-finish..' item(s) remaining') end - self.ass:update() + self.ass.data = self.global_style + self:format_header() + + if #self.list < 1 then + self:append(self.empty_text) + self.ass:update() + return + end + + local start = 1 + local finish = start + self.num_entries - 1 + + --handling cursor positioning + local mid = math.ceil(self.num_entries / 2) + 1 + if self.selected + mid > finish then + local offset = self.selected - finish + mid + + --if we've overshot the end of the list then undo some of the offset + if finish + offset > #self.list then + offset = offset - ((finish + offset) - #self.list) + end + + start = start + offset + finish = finish + offset + end + + --making sure that we don't overstep the boundaries + if start < 1 then + start = 1 + end + local overflow = finish < #self.list + --this is necessary when the number of items in the dir is less than the max + if not overflow then + finish = #self.list + end + + --adding a header to show there are items above in the list + if start > 1 then + self:append(self.wrapper_style .. (start - 1) .. " item(s) above\\N\\N") + end + + for i = start, finish do + self:format_line(i, self.list[i]) + end + + if overflow then + self:append("\\N" .. self.wrapper_style .. #self.list - finish .. " item(s) remaining") + end + self.ass:update() end --moves the selector down the list function scroll_list:scroll_down() - if self.selected < #self.list then - self.selected = self.selected + 1 - self:update_ass() - elseif self.wrap then - self.selected = 1 - self:update_ass() - end + if self.selected < #self.list then + self.selected = self.selected + 1 + self:update_ass() + elseif self.wrap then + self.selected = 1 + self:update_ass() + end end --moves the selector up the list function scroll_list:scroll_up() - if self.selected > 1 then - self.selected = self.selected - 1 - self:update_ass() - elseif self.wrap then - self.selected = #self.list - self:update_ass() - end + if self.selected > 1 then + self.selected = self.selected - 1 + self:update_ass() + elseif self.wrap then + self.selected = #self.list + self:update_ass() + end end --moves the selector to the list next page function scroll_list:move_pagedown() - if #self.list > self.num_entries then - self.selected = self.selected + self.num_entries - if self.selected > #self.list then self.selected = #self.list end - self:update_ass() - end + if #self.list > self.num_entries then + self.selected = self.selected + self.num_entries + if self.selected > #self.list then + self.selected = #self.list + end + self:update_ass() + end end --moves the selector to the list previous page function scroll_list:move_pageup() - if #self.list > self.num_entries then - self.selected = self.selected - self.num_entries - if self.selected < 1 then self.selected = 1 end - self:update_ass() - end + if #self.list > self.num_entries then + self.selected = self.selected - self.num_entries + if self.selected < 1 then + self.selected = 1 + end + self:update_ass() + end end --moves the selector to the list begin function scroll_list:move_begin() - if #self.list > 1 then - self.selected = 1 - self:update_ass() - end + if #self.list > 1 then + self.selected = 1 + self:update_ass() + end end --moves the selector to the list end function scroll_list:move_end() - if #self.list > 1 then - self.selected = #self.list - self:update_ass() - end + if #self.list > 1 then + self.selected = #self.list + self:update_ass() + end end --adds the forced keybinds function scroll_list:add_keybinds() - for _,v in ipairs(self.keybinds) do - mp.add_forced_key_binding(v[1], 'dynamic/'..self.ass.id..'/'..v[2], v[3], v[4]) - end + for _, v in ipairs(self.keybinds) do + mp.add_forced_key_binding(v[1], "dynamic/" .. self.ass.id .. "/" .. v[2], v[3], v[4]) + end end --removes the forced keybinds function scroll_list:remove_keybinds() - for _,v in ipairs(self.keybinds) do - mp.remove_key_binding('dynamic/'..self.ass.id..'/'..v[2]) - end + for _, v in ipairs(self.keybinds) do + mp.remove_key_binding("dynamic/" .. self.ass.id .. "/" .. v[2]) + end end --opens the list and sets the hidden flag function scroll_list:open_list() - self.hidden = false - if not self.flag_update then self.ass:update() - else self.flag_update = false ; self:update_ass() end + self.hidden = false + if not self.flag_update then + self.ass:update() + else + self.flag_update = false + self:update_ass() + end end --closes the list and sets the hidden flag function scroll_list:close_list() - self.hidden = true - self.ass:remove() + self.hidden = true + self.ass:remove() end --modifiable function that opens the list function scroll_list:open() - if self.hidden then self:add_keybinds() end - self:open_list() + if self.hidden then + self:add_keybinds() + end + self:open_list() end --modifiable function that closes the list function scroll_list:close() - self:remove_keybinds() - self:close_list() + self:remove_keybinds() + self:close_list() end --toggles the list function scroll_list:toggle() - if self.hidden then self:open() - else self:close() end + if self.hidden then + self:open() + else + self:close() + end end --clears the list in-place function scroll_list:clear() - local i = 1 - while self.list[i] do - self.list[i] = nil - i = i + 1 - end + local i = 1 + while self.list[i] do + self.list[i] = nil + i = i + 1 + end end --added alias for ipairs(list.list) for lua 5.1 function scroll_list:ipairs() - return ipairs(self.list) + return ipairs(self.list) end --append item to the end of the list function scroll_list:insert(item) - self.list[#self.list + 1] = item + self.list[#self.list + 1] = item end local metatable = { - __index = function(t, key) - if scroll_list[key] ~= nil then return scroll_list[key] - elseif key == "__current" then return t.list[t.selected] - elseif type(key) == "number" then return t.list[key] end - end, - __newindex = function(t, key, value) - if type(key) == "number" then rawset(t.list, key, value) - else rawset(t, key, value) end - end, - __scroll_list = scroll_list, - __len = function(t) return #t.list end, - __ipairs = function(t) return ipairs(t.list) end + __index = function(t, key) + if scroll_list[key] ~= nil then + return scroll_list[key] + elseif key == "__current" then + return t.list[t.selected] + elseif type(key) == "number" then + return t.list[key] + end + end, + __newindex = function(t, key, value) + if type(key) == "number" then + rawset(t.list, key, value) + else + rawset(t, key, value) + end + end, + __scroll_list = scroll_list, + __len = function(t) + return #t.list + end, + __ipairs = function(t) + return ipairs(t.list) + end, } --creates a new list object function scroll_list:new() - local vars - vars = { - ass = mp.create_osd_overlay('ass-events'), - hidden = true, - flag_update = true, - - header = "header \\N ----------------------------------------------", - list = {}, - selected = 1, - - keybinds = { - {'DOWN', 'scroll_down', function() vars:scroll_down() end, {repeatable = true}}, - {'UP', 'scroll_up', function() vars:scroll_up() end, {repeatable = true}}, - {'PGDWN', 'move_pagedown', function() vars:move_pagedown() end, {}}, - {'PGUP', 'move_pageup', function() vars:move_pageup() end, {}}, - {'HOME', 'move_begin', function() vars:move_begin() end, {}}, - {'END', 'move_end', function() vars:move_end() end, {}}, - {'ESC', 'close_browser', function() vars:close() end, {}} - } - } - return setmetatable(vars, metatable) + local vars + vars = { + ass = mp.create_osd_overlay("ass-events"), + hidden = true, + flag_update = true, + + header = "header \\N ----------------------------------------------", + list = {}, + selected = 1, + + keybinds = { + { + "DOWN", + "scroll_down", + function() + vars:scroll_down() + end, + { repeatable = true }, + }, + { + "UP", + "scroll_up", + function() + vars:scroll_up() + end, + { repeatable = true }, + }, + { + "PGDWN", + "move_pagedown", + function() + vars:move_pagedown() + end, + {}, + }, + { + "PGUP", + "move_pageup", + function() + vars:move_pageup() + end, + {}, + }, + { + "HOME", + "move_begin", + function() + vars:move_begin() + end, + {}, + }, + { + "END", + "move_end", + function() + vars:move_end() + end, + {}, + }, + { + "ESC", + "close_browser", + function() + vars:close() + end, + {}, + }, + }, + } + return setmetatable(vars, metatable) end return scroll_list:new() diff --git a/ar/.config/mpv/script-modules/sha1.lua b/ar/.config/mpv/script-modules/sha1.lua index 6b19396..bc98a9b 100644 --- a/ar/.config/mpv/script-modules/sha1.lua +++ b/ar/.config/mpv/script-modules/sha1.lua @@ -61,110 +61,114 @@ local floor, modf = math.floor, math.modf local char, format, rep = string.char, string.format, string.rep -- merge 4 bytes to an 32 bit word -local function bytes_to_w32(a, b, c, d) return a * 0x1000000 + b * 0x10000 + c * 0x100 + d end +local function bytes_to_w32(a, b, c, d) + return a * 0x1000000 + b * 0x10000 + c * 0x100 + d +end -- split a 32 bit word into four 8 bit numbers local function w32_to_bytes(i) - return floor(i / 0x1000000) % 0x100, floor(i / 0x10000) % 0x100, floor(i / 0x100) % 0x100, i % 0x100 + return floor(i / 0x1000000) % 0x100, floor(i / 0x10000) % 0x100, floor(i / 0x100) % 0x100, i % 0x100 end -- shift the bits of a 32 bit word. Don't use negative values for "bits" local function w32_rot(bits, a) - local b2 = 2 ^ (32 - bits) - local a, b = modf(a / b2) - return a + b * b2 * (2 ^ (bits)) + local b2 = 2 ^ (32 - bits) + local a, b = modf(a / b2) + return a + b * b2 * (2 ^ bits) end -- caching function for functions that accept 2 arguments, both of values between -- 0 and 255. The function to be cached is passed, all values are calculated -- during loading and a function is returned that returns the cached values (only) local function cache2arg(fn) - if not cfg_caching then return fn end - local lut = {} - for i = 0, 0xffff do - local a, b = floor(i / 0x100), i % 0x100 - lut[i] = fn(a, b) - end - return function(a, b) - return lut[a * 0x100 + b] - end + if not cfg_caching then + return fn + end + local lut = {} + for i = 0, 0xffff do + local a, b = floor(i / 0x100), i % 0x100 + lut[i] = fn(a, b) + end + return function(a, b) + return lut[a * 0x100 + b] + end end -- splits an 8-bit number into 8 bits, returning all 8 bits as booleans local function byte_to_bits(b) - local b = function(n) - local b = floor(b / n) - return b % 2 == 1 - end - return b(1), b(2), b(4), b(8), b(16), b(32), b(64), b(128) + local b = function(n) + local b = floor(b / n) + return b % 2 == 1 + end + return b(1), b(2), b(4), b(8), b(16), b(32), b(64), b(128) end -- builds an 8bit number from 8 booleans local function bits_to_byte(a, b, c, d, e, f, g, h) - local function n(b, x) return b and x or 0 end + local function n(b, x) + return b and x or 0 + end - return n(a, 1) + n(b, 2) + n(c, 4) + n(d, 8) + n(e, 16) + n(f, 32) + n(g, 64) + n(h, 128) + return n(a, 1) + n(b, 2) + n(c, 4) + n(d, 8) + n(e, 16) + n(f, 32) + n(g, 64) + n(h, 128) end -- debug function for visualizing bits in a string local function bits_to_string(a, b, c, d, e, f, g, h) - local function x(b) return b and "1" or "0" end + local function x(b) + return b and "1" or "0" + end - return ("%s%s%s%s %s%s%s%s"):format(x(a), x(b), x(c), x(d), x(e), x(f), x(g), x(h)) + return ("%s%s%s%s %s%s%s%s"):format(x(a), x(b), x(c), x(d), x(e), x(f), x(g), x(h)) end -- debug function for converting a 8-bit number as bit string local function byte_to_bit_string(b) - return bits_to_string(byte_to_bits(b)) + return bits_to_string(byte_to_bits(b)) end -- debug function for converting a 32 bit number as bit string local function w32_to_bit_string(a) - if type(a) == "string" then return a end - local aa, ab, ac, ad = w32_to_bytes(a) - local s = byte_to_bit_string - return ("%s %s %s %s"):format(s(aa):reverse(), s(ab):reverse(), s(ac):reverse(), s(ad):reverse()):reverse() + if type(a) == "string" then + return a + end + local aa, ab, ac, ad = w32_to_bytes(a) + local s = byte_to_bit_string + return ("%s %s %s %s"):format(s(aa):reverse(), s(ab):reverse(), s(ac):reverse(), s(ad):reverse()):reverse() end -- bitwise "and" function for 2 8bit number local band = cache2arg(function(a, b) - local A, B, C, D, E, F, G, H = byte_to_bits(b) - local a, b, c, d, e, f, g, h = byte_to_bits(a) - return bits_to_byte( - A and a, B and b, C and c, D and d, - E and e, F and f, G and g, H and h) + local A, B, C, D, E, F, G, H = byte_to_bits(b) + local a, b, c, d, e, f, g, h = byte_to_bits(a) + return bits_to_byte(A and a, B and b, C and c, D and d, E and e, F and f, G and g, H and h) end) -- bitwise "or" function for 2 8bit numbers local bor = cache2arg(function(a, b) - local A, B, C, D, E, F, G, H = byte_to_bits(b) - local a, b, c, d, e, f, g, h = byte_to_bits(a) - return bits_to_byte( - A or a, B or b, C or c, D or d, - E or e, F or f, G or g, H or h) + local A, B, C, D, E, F, G, H = byte_to_bits(b) + local a, b, c, d, e, f, g, h = byte_to_bits(a) + return bits_to_byte(A or a, B or b, C or c, D or d, E or e, F or f, G or g, H or h) end) -- bitwise "xor" function for 2 8bit numbers local bxor = cache2arg(function(a, b) - local A, B, C, D, E, F, G, H = byte_to_bits(b) - local a, b, c, d, e, f, g, h = byte_to_bits(a) - return bits_to_byte( - A ~= a, B ~= b, C ~= c, D ~= d, - E ~= e, F ~= f, G ~= g, H ~= h) + local A, B, C, D, E, F, G, H = byte_to_bits(b) + local a, b, c, d, e, f, g, h = byte_to_bits(a) + return bits_to_byte(A ~= a, B ~= b, C ~= c, D ~= d, E ~= e, F ~= f, G ~= g, H ~= h) end) -- bitwise complement for one 8bit number local function bnot(x) - return 255 - (x % 256) + return 255 - (x % 256) end -- creates a function to combine to 32bit numbers using an 8bit combination function local function w32_comb(fn) - return function(a, b) - local aa, ab, ac, ad = w32_to_bytes(a) - local ba, bb, bc, bd = w32_to_bytes(b) - return bytes_to_w32(fn(aa, ba), fn(ab, bb), fn(ac, bc), fn(ad, bd)) - end + return function(a, b) + local aa, ab, ac, ad = w32_to_bytes(a) + local ba, bb, bc, bd = w32_to_bytes(b) + return bytes_to_w32(fn(aa, ba), fn(ab, bb), fn(ac, bc), fn(ad, bd)) + end end -- create functions for and, xor and or, all for 2 32bit numbers @@ -174,132 +178,139 @@ local w32_or = w32_comb(bor) -- xor function that may receive a variable number of arguments local function w32_xor_n(a, ...) - local aa, ab, ac, ad = w32_to_bytes(a) - for i = 1, select('#', ...) do - local ba, bb, bc, bd = w32_to_bytes(select(i, ...)) - aa, ab, ac, ad = bxor(aa, ba), bxor(ab, bb), bxor(ac, bc), bxor(ad, bd) - end - return bytes_to_w32(aa, ab, ac, ad) + local aa, ab, ac, ad = w32_to_bytes(a) + for i = 1, select("#", ...) do + local ba, bb, bc, bd = w32_to_bytes(select(i, ...)) + aa, ab, ac, ad = bxor(aa, ba), bxor(ab, bb), bxor(ac, bc), bxor(ad, bd) + end + return bytes_to_w32(aa, ab, ac, ad) end -- combining 3 32bit numbers through binary "or" operation local function w32_or3(a, b, c) - local aa, ab, ac, ad = w32_to_bytes(a) - local ba, bb, bc, bd = w32_to_bytes(b) - local ca, cb, cc, cd = w32_to_bytes(c) - return bytes_to_w32( - bor(aa, bor(ba, ca)), bor(ab, bor(bb, cb)), bor(ac, bor(bc, cc)), bor(ad, bor(bd, cd)) - ) + local aa, ab, ac, ad = w32_to_bytes(a) + local ba, bb, bc, bd = w32_to_bytes(b) + local ca, cb, cc, cd = w32_to_bytes(c) + return bytes_to_w32(bor(aa, bor(ba, ca)), bor(ab, bor(bb, cb)), bor(ac, bor(bc, cc)), bor(ad, bor(bd, cd))) end -- binary complement for 32bit numbers local function w32_not(a) - return 4294967295 - (a % 4294967296) + return 4294967295 - (a % 4294967296) end -- adding 2 32bit numbers, cutting off the remainder on 33th bit -local function w32_add(a, b) return (a + b) % 4294967296 end +local function w32_add(a, b) + return (a + b) % 4294967296 +end -- adding n 32bit numbers, cutting off the remainder (again) local function w32_add_n(a, ...) - for i = 1, select('#', ...) do - a = (a + select(i, ...)) % 4294967296 - end - return a + for i = 1, select("#", ...) do + a = (a + select(i, ...)) % 4294967296 + end + return a end -- converting the number to a hexadecimal string -local function w32_to_hexstring(w) return format("%08x", w) end +local function w32_to_hexstring(w) + return format("%08x", w) +end -- calculating the SHA1 for some text function sha1.hex(msg) - local H0, H1, H2, H3, H4 = 0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0 - local msg_len_in_bits = #msg * 8 - - local first_append = char(0x80) -- append a '1' bit plus seven '0' bits - - local non_zero_message_bytes = #msg + 1 + 8 -- the +1 is the appended bit 1, the +8 are for the final appended length - local current_mod = non_zero_message_bytes % 64 - local second_append = current_mod > 0 and rep(char(0), 64 - current_mod) or "" - - -- now to append the length as a 64-bit number. - local B1, R1 = modf(msg_len_in_bits / 0x01000000) - local B2, R2 = modf(0x01000000 * R1 / 0x00010000) - local B3, R3 = modf(0x00010000 * R2 / 0x00000100) - local B4 = 0x00000100 * R3 - - local L64 = char(0) .. char(0) .. char(0) .. char(0) -- high 32 bits - .. char(B1) .. char(B2) .. char(B3) .. char(B4) -- low 32 bits - - msg = msg .. first_append .. second_append .. L64 - - assert(#msg % 64 == 0) - - local chunks = #msg / 64 - - local W = {} - local start, A, B, C, D, E, f, K, TEMP - local chunk = 0 - - while chunk < chunks do - -- - -- break chunk up into W[0] through W[15] - -- - start, chunk = chunk * 64 + 1, chunk + 1 - - for t = 0, 15 do - W[t] = bytes_to_w32(msg:byte(start, start + 3)) - start = start + 4 - end - - -- - -- build W[16] through W[79] - -- - for t = 16, 79 do - -- For t = 16 to 79 let Wt = S1(Wt-3 XOR Wt-8 XOR Wt-14 XOR Wt-16). - W[t] = w32_rot(1, w32_xor_n(W[t - 3], W[t - 8], W[t - 14], W[t - 16])) - end - - A, B, C, D, E = H0, H1, H2, H3, H4 - - for t = 0, 79 do - if t <= 19 then - -- (B AND C) OR ((NOT B) AND D) - f = w32_or(w32_and(B, C), w32_and(w32_not(B), D)) - K = 0x5A827999 - elseif t <= 39 then - -- B XOR C XOR D - f = w32_xor_n(B, C, D) - K = 0x6ED9EBA1 - elseif t <= 59 then - -- (B AND C) OR (B AND D) OR (C AND D - f = w32_or3(w32_and(B, C), w32_and(B, D), w32_and(C, D)) - K = 0x8F1BBCDC - else - -- B XOR C XOR D - f = w32_xor_n(B, C, D) - K = 0xCA62C1D6 - end - - -- TEMP = S5(A) + ft(B,C,D) + E + Wt + Kt; - A, B, C, D, E = w32_add_n(w32_rot(5, A), f, E, W[t], K), - A, w32_rot(30, B), C, D - end - -- Let H0 = H0 + A, H1 = H1 + B, H2 = H2 + C, H3 = H3 + D, H4 = H4 + E. - H0, H1, H2, H3, H4 = w32_add(H0, A), w32_add(H1, B), w32_add(H2, C), w32_add(H3, D), w32_add(H4, E) - end - local f = w32_to_hexstring - return f(H0) .. f(H1) .. f(H2) .. f(H3) .. f(H4) + local H0, H1, H2, H3, H4 = 0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0 + local msg_len_in_bits = #msg * 8 + + local first_append = char(0x80) -- append a '1' bit plus seven '0' bits + + local non_zero_message_bytes = #msg + 1 + 8 -- the +1 is the appended bit 1, the +8 are for the final appended length + local current_mod = non_zero_message_bytes % 64 + local second_append = current_mod > 0 and rep(char(0), 64 - current_mod) or "" + + -- now to append the length as a 64-bit number. + local B1, R1 = modf(msg_len_in_bits / 0x01000000) + local B2, R2 = modf(0x01000000 * R1 / 0x00010000) + local B3, R3 = modf(0x00010000 * R2 / 0x00000100) + local B4 = 0x00000100 * R3 + + local L64 = char(0) + .. char(0) + .. char(0) + .. char(0) -- high 32 bits + .. char(B1) + .. char(B2) + .. char(B3) + .. char(B4) -- low 32 bits + + msg = msg .. first_append .. second_append .. L64 + + assert(#msg % 64 == 0) + + local chunks = #msg / 64 + + local W = {} + local start, A, B, C, D, E, f, K, TEMP + local chunk = 0 + + while chunk < chunks do + -- + -- break chunk up into W[0] through W[15] + -- + start, chunk = chunk * 64 + 1, chunk + 1 + + for t = 0, 15 do + W[t] = bytes_to_w32(msg:byte(start, start + 3)) + start = start + 4 + end + + -- + -- build W[16] through W[79] + -- + for t = 16, 79 do + -- For t = 16 to 79 let Wt = S1(Wt-3 XOR Wt-8 XOR Wt-14 XOR Wt-16). + W[t] = w32_rot(1, w32_xor_n(W[t - 3], W[t - 8], W[t - 14], W[t - 16])) + end + + A, B, C, D, E = H0, H1, H2, H3, H4 + + for t = 0, 79 do + if t <= 19 then + -- (B AND C) OR ((NOT B) AND D) + f = w32_or(w32_and(B, C), w32_and(w32_not(B), D)) + K = 0x5A827999 + elseif t <= 39 then + -- B XOR C XOR D + f = w32_xor_n(B, C, D) + K = 0x6ED9EBA1 + elseif t <= 59 then + -- (B AND C) OR (B AND D) OR (C AND D + f = w32_or3(w32_and(B, C), w32_and(B, D), w32_and(C, D)) + K = 0x8F1BBCDC + else + -- B XOR C XOR D + f = w32_xor_n(B, C, D) + K = 0xCA62C1D6 + end + + -- TEMP = S5(A) + ft(B,C,D) + E + Wt + Kt; + A, B, C, D, E = w32_add_n(w32_rot(5, A), f, E, W[t], K), A, w32_rot(30, B), C, D + end + -- Let H0 = H0 + A, H1 = H1 + B, H2 = H2 + C, H3 = H3 + D, H4 = H4 + E. + H0, H1, H2, H3, H4 = w32_add(H0, A), w32_add(H1, B), w32_add(H2, C), w32_add(H3, D), w32_add(H4, E) + end + local f = w32_to_hexstring + return f(H0) .. f(H1) .. f(H2) .. f(H3) .. f(H4) end local function hex_to_binary(hex) - return hex:gsub('..', function(hexval) - return string.char(tonumber(hexval, 16)) - end) + return hex:gsub("..", function(hexval) + return string.char(tonumber(hexval, 16)) + end) end function sha1.bin(msg) - return hex_to_binary(sha1.hex(msg)) + return hex_to_binary(sha1.hex(msg)) end local xor_with_0x5c = {} @@ -307,28 +318,28 @@ local xor_with_0x36 = {} -- building the lookuptables ahead of time (instead of littering the source code -- with precalculated values) for i = 0, 0xff do - xor_with_0x5c[char(i)] = char(bxor(i, 0x5c)) - xor_with_0x36[char(i)] = char(bxor(i, 0x36)) + xor_with_0x5c[char(i)] = char(bxor(i, 0x5c)) + xor_with_0x36[char(i)] = char(bxor(i, 0x36)) end local blocksize = 64 -- 512 bits function sha1.hmacHex(key, text) - assert(type(key) == 'string', "key passed to hmacHex should be a string") - assert(type(text) == 'string', "text passed to hmacHex should be a string") + assert(type(key) == "string", "key passed to hmacHex should be a string") + assert(type(text) == "string", "text passed to hmacHex should be a string") - if #key > blocksize then - key = sha1.bin(key) - end + if #key > blocksize then + key = sha1.bin(key) + end - local key_xord_with_0x36 = key:gsub('.', xor_with_0x36) .. string.rep(string.char(0x36), blocksize - #key) - local key_xord_with_0x5c = key:gsub('.', xor_with_0x5c) .. string.rep(string.char(0x5c), blocksize - #key) + local key_xord_with_0x36 = key:gsub(".", xor_with_0x36) .. string.rep(string.char(0x36), blocksize - #key) + local key_xord_with_0x5c = key:gsub(".", xor_with_0x5c) .. string.rep(string.char(0x5c), blocksize - #key) - return sha1.hex(key_xord_with_0x5c .. sha1.bin(key_xord_with_0x36 .. text)) + return sha1.hex(key_xord_with_0x5c .. sha1.bin(key_xord_with_0x36 .. text)) end function sha1.hmacBin(key, text) - return hex_to_binary(sha1.hmacHex(key, text)) + return hex_to_binary(sha1.hmacHex(key, text)) end return sha1 diff --git a/ar/.config/mpv/script-modules/user-input-module.lua b/ar/.config/mpv/script-modules/user-input-module.lua index f15d5c4..6740d13 100644 --- a/ar/.config/mpv/script-modules/user-input-module.lua +++ b/ar/.config/mpv/script-modules/user-input-module.lua @@ -12,115 +12,119 @@ local API_VERSION = "0.1.0" -local mp = require 'mp' -local msg = require "mp.msg" -local utils = require 'mp.utils' +local mp = require("mp") +local msg = require("mp.msg") +local utils = require("mp.utils") local mod = {} local name = mp.get_script_name() local counter = 1 local function pack(...) - local t = {...} - t.n = select("#", ...) - return t + local t = { ... } + t.n = select("#", ...) + return t end local request_mt = {} -- ensures the option tables are correctly formatted based on the input local function format_options(options, response_string) - return { - response = response_string, - version = API_VERSION, - id = name..'/'..(options.id or ""), - source = name, - request_text = ("[%s] %s"):format(options.source or name, options.request_text or options.text or "requesting user input:"), - default_input = options.default_input, - cursor_pos = tonumber(options.cursor_pos), - queueable = options.queueable and true, - replace = options.replace and true - } + return { + response = response_string, + version = API_VERSION, + id = name .. "/" .. (options.id or ""), + source = name, + request_text = ("[%s] %s"):format( + options.source or name, + options.request_text or options.text or "requesting user input:" + ), + default_input = options.default_input, + cursor_pos = tonumber(options.cursor_pos), + queueable = options.queueable and true, + replace = options.replace and true, + } end -- cancels the request function request_mt:cancel() - assert(self.uid, "request object missing UID") - mp.commandv("script-message-to", "user_input", "cancel-user-input/uid", self.uid) + assert(self.uid, "request object missing UID") + mp.commandv("script-message-to", "user_input", "cancel-user-input/uid", self.uid) end -- updates the options for the request function request_mt:update(options) - assert(self.uid, "request object missing UID") - options = utils.format_json( format_options(options) ) - mp.commandv("script-message-to", "user_input", "update-user-input/uid", self.uid, options) + assert(self.uid, "request object missing UID") + options = utils.format_json(format_options(options)) + mp.commandv("script-message-to", "user_input", "update-user-input/uid", self.uid, options) end -- sends a request to ask the user for input using formatted options provided -- creates a script message to recieve the response and call fn function mod.get_user_input(fn, options, ...) - options = options or {} - local response_string = name.."/__user_input_request/"..counter - counter = counter + 1 - - local request = { - uid = response_string, - passthrough_args = pack(...), - callback = fn, - pending = true - } - - -- create a callback for user-input to respond to - mp.register_script_message(response_string, function(response) - mp.unregister_script_message(response_string) - request.pending = false - - response = utils.parse_json(response) - request.callback(response.line, response.err, unpack(request.passthrough_args, 1, request.passthrough_args.n)) - end) - - -- send the input command - options = utils.format_json( format_options(options, response_string) ) - mp.commandv("script-message-to", "user_input", "request-user-input", options) - - return setmetatable(request, { __index = request_mt }) + options = options or {} + local response_string = name .. "/__user_input_request/" .. counter + counter = counter + 1 + + local request = { + uid = response_string, + passthrough_args = pack(...), + callback = fn, + pending = true, + } + + -- create a callback for user-input to respond to + mp.register_script_message(response_string, function(response) + mp.unregister_script_message(response_string) + request.pending = false + + response = utils.parse_json(response) + request.callback(response.line, response.err, unpack(request.passthrough_args, 1, request.passthrough_args.n)) + end) + + -- send the input command + options = utils.format_json(format_options(options, response_string)) + mp.commandv("script-message-to", "user_input", "request-user-input", options) + + return setmetatable(request, { __index = request_mt }) end -- runs the request synchronously using coroutines -- takes the option table and an optional coroutine resume function function mod.get_user_input_co(options, co_resume) - local co, main = coroutine.running() - assert(not main and co, "get_user_input_co must be run from within a coroutine") - - local uid = {} - local request = mod.get_user_input(function(line, err) - if co_resume then - co_resume(uid, line, err) - else - local success, er = coroutine.resume(co, uid, line, err) - if not success then - msg.warn(debug.traceback(co)) - msg.error(er) - end - end - end, options) - - -- if the uid was not sent then the coroutine was resumed by the user. - -- we will treat this as a cancellation request - local success, line, err = coroutine.yield(request) - if success ~= uid then - request:cancel() - request.callback = function() end - return nil, "cancelled" - end - - return line, err + local co, main = coroutine.running() + assert(not main and co, "get_user_input_co must be run from within a coroutine") + + local uid = {} + local request = mod.get_user_input(function(line, err) + if co_resume then + co_resume(uid, line, err) + else + local success, er = coroutine.resume(co, uid, line, err) + if not success then + msg.warn(debug.traceback(co)) + msg.error(er) + end + end + end, options) + + -- if the uid was not sent then the coroutine was resumed by the user. + -- we will treat this as a cancellation request + local success, line, err = coroutine.yield(request) + if success ~= uid then + request:cancel() + request.callback = function() end + return nil, "cancelled" + end + + return line, err end -- sends a request to cancel all input requests with the given id function mod.cancel_user_input(id) - id = name .. '/' .. (id or "") - mp.commandv("script-message-to", "user_input", "cancel-user-input/id", id) + id = name .. "/" .. (id or "") + mp.commandv("script-message-to", "user_input", "cancel-user-input/id", id) end -return mod
\ No newline at end of file +return mod + diff --git a/ar/.config/mpv/script-modules/utf8_data.lua b/ar/.config/mpv/script-modules/utf8_data.lua index bec6b9e..1c42fe1 100644 --- a/ar/.config/mpv/script-modules/utf8_data.lua +++ b/ar/.config/mpv/script-modules/utf8_data.lua @@ -1,1865 +1,1863 @@ utf8_lc_uc = { - ["a"] = "A", - ["b"] = "B", - ["c"] = "C", - ["d"] = "D", - ["e"] = "E", - ["f"] = "F", - ["g"] = "G", - ["h"] = "H", - ["i"] = "I", - ["j"] = "J", - ["k"] = "K", - ["l"] = "L", - ["m"] = "M", - ["n"] = "N", - ["o"] = "O", - ["p"] = "P", - ["q"] = "Q", - ["r"] = "R", - ["s"] = "S", - ["t"] = "T", - ["u"] = "U", - ["v"] = "V", - ["w"] = "W", - ["x"] = "X", - ["y"] = "Y", - ["z"] = "Z", - ["µ"] = "Μ", - ["à"] = "À", - ["á"] = "Á", - ["â"] = "Â", - ["ã"] = "Ã", - ["ä"] = "Ä", - ["å"] = "Å", - ["æ"] = "Æ", - ["ç"] = "Ç", - ["è"] = "È", - ["é"] = "É", - ["ê"] = "Ê", - ["ë"] = "Ë", - ["ì"] = "Ì", - ["í"] = "Í", - ["î"] = "Î", - ["ï"] = "Ï", - ["ð"] = "Ð", - ["ñ"] = "Ñ", - ["ò"] = "Ò", - ["ó"] = "Ó", - ["ô"] = "Ô", - ["õ"] = "Õ", - ["ö"] = "Ö", - ["ø"] = "Ø", - ["ù"] = "Ù", - ["ú"] = "Ú", - ["û"] = "Û", - ["ü"] = "Ü", - ["ý"] = "Ý", - ["þ"] = "Þ", - ["ÿ"] = "Ÿ", - ["ā"] = "Ā", - ["ă"] = "Ă", - ["ą"] = "Ą", - ["ć"] = "Ć", - ["ĉ"] = "Ĉ", - ["ċ"] = "Ċ", - ["č"] = "Č", - ["ď"] = "Ď", - ["đ"] = "Đ", - ["ē"] = "Ē", - ["ĕ"] = "Ĕ", - ["ė"] = "Ė", - ["ę"] = "Ę", - ["ě"] = "Ě", - ["ĝ"] = "Ĝ", - ["ğ"] = "Ğ", - ["ġ"] = "Ġ", - ["ģ"] = "Ģ", - ["ĥ"] = "Ĥ", - ["ħ"] = "Ħ", - ["ĩ"] = "Ĩ", - ["ī"] = "Ī", - ["ĭ"] = "Ĭ", - ["į"] = "Į", - ["ı"] = "I", - ["ij"] = "IJ", - ["ĵ"] = "Ĵ", - ["ķ"] = "Ķ", - ["ĺ"] = "Ĺ", - ["ļ"] = "Ļ", - ["ľ"] = "Ľ", - ["ŀ"] = "Ŀ", - ["ł"] = "Ł", - ["ń"] = "Ń", - ["ņ"] = "Ņ", - ["ň"] = "Ň", - ["ŋ"] = "Ŋ", - ["ō"] = "Ō", - ["ŏ"] = "Ŏ", - ["ő"] = "Ő", - ["œ"] = "Œ", - ["ŕ"] = "Ŕ", - ["ŗ"] = "Ŗ", - ["ř"] = "Ř", - ["ś"] = "Ś", - ["ŝ"] = "Ŝ", - ["ş"] = "Ş", - ["š"] = "Š", - ["ţ"] = "Ţ", - ["ť"] = "Ť", - ["ŧ"] = "Ŧ", - ["ũ"] = "Ũ", - ["ū"] = "Ū", - ["ŭ"] = "Ŭ", - ["ů"] = "Ů", - ["ű"] = "Ű", - ["ų"] = "Ų", - ["ŵ"] = "Ŵ", - ["ŷ"] = "Ŷ", - ["ź"] = "Ź", - ["ż"] = "Ż", - ["ž"] = "Ž", - ["ſ"] = "S", - ["ƀ"] = "Ƀ", - ["ƃ"] = "Ƃ", - ["ƅ"] = "Ƅ", - ["ƈ"] = "Ƈ", - ["ƌ"] = "Ƌ", - ["ƒ"] = "Ƒ", - ["ƕ"] = "Ƕ", - ["ƙ"] = "Ƙ", - ["ƚ"] = "Ƚ", - ["ƞ"] = "Ƞ", - ["ơ"] = "Ơ", - ["ƣ"] = "Ƣ", - ["ƥ"] = "Ƥ", - ["ƨ"] = "Ƨ", - ["ƭ"] = "Ƭ", - ["ư"] = "Ư", - ["ƴ"] = "Ƴ", - ["ƶ"] = "Ƶ", - ["ƹ"] = "Ƹ", - ["ƽ"] = "Ƽ", - ["ƿ"] = "Ƿ", - ["Dž"] = "DŽ", - ["dž"] = "DŽ", - ["Lj"] = "LJ", - ["lj"] = "LJ", - ["Nj"] = "NJ", - ["nj"] = "NJ", - ["ǎ"] = "Ǎ", - ["ǐ"] = "Ǐ", - ["ǒ"] = "Ǒ", - ["ǔ"] = "Ǔ", - ["ǖ"] = "Ǖ", - ["ǘ"] = "Ǘ", - ["ǚ"] = "Ǚ", - ["ǜ"] = "Ǜ", - ["ǝ"] = "Ǝ", - ["ǟ"] = "Ǟ", - ["ǡ"] = "Ǡ", - ["ǣ"] = "Ǣ", - ["ǥ"] = "Ǥ", - ["ǧ"] = "Ǧ", - ["ǩ"] = "Ǩ", - ["ǫ"] = "Ǫ", - ["ǭ"] = "Ǭ", - ["ǯ"] = "Ǯ", - ["Dz"] = "DZ", - ["dz"] = "DZ", - ["ǵ"] = "Ǵ", - ["ǹ"] = "Ǹ", - ["ǻ"] = "Ǻ", - ["ǽ"] = "Ǽ", - ["ǿ"] = "Ǿ", - ["ȁ"] = "Ȁ", - ["ȃ"] = "Ȃ", - ["ȅ"] = "Ȅ", - ["ȇ"] = "Ȇ", - ["ȉ"] = "Ȉ", - ["ȋ"] = "Ȋ", - ["ȍ"] = "Ȍ", - ["ȏ"] = "Ȏ", - ["ȑ"] = "Ȑ", - ["ȓ"] = "Ȓ", - ["ȕ"] = "Ȕ", - ["ȗ"] = "Ȗ", - ["ș"] = "Ș", - ["ț"] = "Ț", - ["ȝ"] = "Ȝ", - ["ȟ"] = "Ȟ", - ["ȣ"] = "Ȣ", - ["ȥ"] = "Ȥ", - ["ȧ"] = "Ȧ", - ["ȩ"] = "Ȩ", - ["ȫ"] = "Ȫ", - ["ȭ"] = "Ȭ", - ["ȯ"] = "Ȯ", - ["ȱ"] = "Ȱ", - ["ȳ"] = "Ȳ", - ["ȼ"] = "Ȼ", - ["ɂ"] = "Ɂ", - ["ɇ"] = "Ɇ", - ["ɉ"] = "Ɉ", - ["ɋ"] = "Ɋ", - ["ɍ"] = "Ɍ", - ["ɏ"] = "Ɏ", - ["ɓ"] = "Ɓ", - ["ɔ"] = "Ɔ", - ["ɖ"] = "Ɖ", - ["ɗ"] = "Ɗ", - ["ə"] = "Ə", - ["ɛ"] = "Ɛ", - ["ɠ"] = "Ɠ", - ["ɣ"] = "Ɣ", - ["ɨ"] = "Ɨ", - ["ɩ"] = "Ɩ", - ["ɫ"] = "Ɫ", - ["ɯ"] = "Ɯ", - ["ɲ"] = "Ɲ", - ["ɵ"] = "Ɵ", - ["ɽ"] = "Ɽ", - ["ʀ"] = "Ʀ", - ["ʃ"] = "Ʃ", - ["ʈ"] = "Ʈ", - ["ʉ"] = "Ʉ", - ["ʊ"] = "Ʊ", - ["ʋ"] = "Ʋ", - ["ʌ"] = "Ʌ", - ["ʒ"] = "Ʒ", - ["ͅ"] = "Ι", - ["ͻ"] = "Ͻ", - ["ͼ"] = "Ͼ", - ["ͽ"] = "Ͽ", - ["ά"] = "Ά", - ["έ"] = "Έ", - ["ή"] = "Ή", - ["ί"] = "Ί", - ["α"] = "Α", - ["β"] = "Β", - ["γ"] = "Γ", - ["δ"] = "Δ", - ["ε"] = "Ε", - ["ζ"] = "Ζ", - ["η"] = "Η", - ["θ"] = "Θ", - ["ι"] = "Ι", - ["κ"] = "Κ", - ["λ"] = "Λ", - ["μ"] = "Μ", - ["ν"] = "Ν", - ["ξ"] = "Ξ", - ["ο"] = "Ο", - ["π"] = "Π", - ["ρ"] = "Ρ", - ["ς"] = "Σ", - ["σ"] = "Σ", - ["τ"] = "Τ", - ["υ"] = "Υ", - ["φ"] = "Φ", - ["χ"] = "Χ", - ["ψ"] = "Ψ", - ["ω"] = "Ω", - ["ϊ"] = "Ϊ", - ["ϋ"] = "Ϋ", - ["ό"] = "Ό", - ["ύ"] = "Ύ", - ["ώ"] = "Ώ", - ["ϐ"] = "Β", - ["ϑ"] = "Θ", - ["ϕ"] = "Φ", - ["ϖ"] = "Π", - ["ϙ"] = "Ϙ", - ["ϛ"] = "Ϛ", - ["ϝ"] = "Ϝ", - ["ϟ"] = "Ϟ", - ["ϡ"] = "Ϡ", - ["ϣ"] = "Ϣ", - ["ϥ"] = "Ϥ", - ["ϧ"] = "Ϧ", - ["ϩ"] = "Ϩ", - ["ϫ"] = "Ϫ", - ["ϭ"] = "Ϭ", - ["ϯ"] = "Ϯ", - ["ϰ"] = "Κ", - ["ϱ"] = "Ρ", - ["ϲ"] = "Ϲ", - ["ϵ"] = "Ε", - ["ϸ"] = "Ϸ", - ["ϻ"] = "Ϻ", - ["а"] = "А", - ["б"] = "Б", - ["в"] = "В", - ["г"] = "Г", - ["д"] = "Д", - ["е"] = "Е", - ["ж"] = "Ж", - ["з"] = "З", - ["и"] = "И", - ["й"] = "Й", - ["к"] = "К", - ["л"] = "Л", - ["м"] = "М", - ["н"] = "Н", - ["о"] = "О", - ["п"] = "П", - ["р"] = "Р", - ["с"] = "С", - ["т"] = "Т", - ["у"] = "У", - ["ф"] = "Ф", - ["х"] = "Х", - ["ц"] = "Ц", - ["ч"] = "Ч", - ["ш"] = "Ш", - ["щ"] = "Щ", - ["ъ"] = "Ъ", - ["ы"] = "Ы", - ["ь"] = "Ь", - ["э"] = "Э", - ["ю"] = "Ю", - ["я"] = "Я", - ["ѐ"] = "Ѐ", - ["ё"] = "Ё", - ["ђ"] = "Ђ", - ["ѓ"] = "Ѓ", - ["є"] = "Є", - ["ѕ"] = "Ѕ", - ["і"] = "І", - ["ї"] = "Ї", - ["ј"] = "Ј", - ["љ"] = "Љ", - ["њ"] = "Њ", - ["ћ"] = "Ћ", - ["ќ"] = "Ќ", - ["ѝ"] = "Ѝ", - ["ў"] = "Ў", - ["џ"] = "Џ", - ["ѡ"] = "Ѡ", - ["ѣ"] = "Ѣ", - ["ѥ"] = "Ѥ", - ["ѧ"] = "Ѧ", - ["ѩ"] = "Ѩ", - ["ѫ"] = "Ѫ", - ["ѭ"] = "Ѭ", - ["ѯ"] = "Ѯ", - ["ѱ"] = "Ѱ", - ["ѳ"] = "Ѳ", - ["ѵ"] = "Ѵ", - ["ѷ"] = "Ѷ", - ["ѹ"] = "Ѹ", - ["ѻ"] = "Ѻ", - ["ѽ"] = "Ѽ", - ["ѿ"] = "Ѿ", - ["ҁ"] = "Ҁ", - ["ҋ"] = "Ҋ", - ["ҍ"] = "Ҍ", - ["ҏ"] = "Ҏ", - ["ґ"] = "Ґ", - ["ғ"] = "Ғ", - ["ҕ"] = "Ҕ", - ["җ"] = "Җ", - ["ҙ"] = "Ҙ", - ["қ"] = "Қ", - ["ҝ"] = "Ҝ", - ["ҟ"] = "Ҟ", - ["ҡ"] = "Ҡ", - ["ң"] = "Ң", - ["ҥ"] = "Ҥ", - ["ҧ"] = "Ҧ", - ["ҩ"] = "Ҩ", - ["ҫ"] = "Ҫ", - ["ҭ"] = "Ҭ", - ["ү"] = "Ү", - ["ұ"] = "Ұ", - ["ҳ"] = "Ҳ", - ["ҵ"] = "Ҵ", - ["ҷ"] = "Ҷ", - ["ҹ"] = "Ҹ", - ["һ"] = "Һ", - ["ҽ"] = "Ҽ", - ["ҿ"] = "Ҿ", - ["ӂ"] = "Ӂ", - ["ӄ"] = "Ӄ", - ["ӆ"] = "Ӆ", - ["ӈ"] = "Ӈ", - ["ӊ"] = "Ӊ", - ["ӌ"] = "Ӌ", - ["ӎ"] = "Ӎ", - ["ӏ"] = "Ӏ", - ["ӑ"] = "Ӑ", - ["ӓ"] = "Ӓ", - ["ӕ"] = "Ӕ", - ["ӗ"] = "Ӗ", - ["ә"] = "Ә", - ["ӛ"] = "Ӛ", - ["ӝ"] = "Ӝ", - ["ӟ"] = "Ӟ", - ["ӡ"] = "Ӡ", - ["ӣ"] = "Ӣ", - ["ӥ"] = "Ӥ", - ["ӧ"] = "Ӧ", - ["ө"] = "Ө", - ["ӫ"] = "Ӫ", - ["ӭ"] = "Ӭ", - ["ӯ"] = "Ӯ", - ["ӱ"] = "Ӱ", - ["ӳ"] = "Ӳ", - ["ӵ"] = "Ӵ", - ["ӷ"] = "Ӷ", - ["ӹ"] = "Ӹ", - ["ӻ"] = "Ӻ", - ["ӽ"] = "Ӽ", - ["ӿ"] = "Ӿ", - ["ԁ"] = "Ԁ", - ["ԃ"] = "Ԃ", - ["ԅ"] = "Ԅ", - ["ԇ"] = "Ԇ", - ["ԉ"] = "Ԉ", - ["ԋ"] = "Ԋ", - ["ԍ"] = "Ԍ", - ["ԏ"] = "Ԏ", - ["ԑ"] = "Ԑ", - ["ԓ"] = "Ԓ", - ["ա"] = "Ա", - ["բ"] = "Բ", - ["գ"] = "Գ", - ["դ"] = "Դ", - ["ե"] = "Ե", - ["զ"] = "Զ", - ["է"] = "Է", - ["ը"] = "Ը", - ["թ"] = "Թ", - ["ժ"] = "Ժ", - ["ի"] = "Ի", - ["լ"] = "Լ", - ["խ"] = "Խ", - ["ծ"] = "Ծ", - ["կ"] = "Կ", - ["հ"] = "Հ", - ["ձ"] = "Ձ", - ["ղ"] = "Ղ", - ["ճ"] = "Ճ", - ["մ"] = "Մ", - ["յ"] = "Յ", - ["ն"] = "Ն", - ["շ"] = "Շ", - ["ո"] = "Ո", - ["չ"] = "Չ", - ["պ"] = "Պ", - ["ջ"] = "Ջ", - ["ռ"] = "Ռ", - ["ս"] = "Ս", - ["վ"] = "Վ", - ["տ"] = "Տ", - ["ր"] = "Ր", - ["ց"] = "Ց", - ["ւ"] = "Ւ", - ["փ"] = "Փ", - ["ք"] = "Ք", - ["օ"] = "Օ", - ["ֆ"] = "Ֆ", - ["ᵽ"] = "Ᵽ", - ["ḁ"] = "Ḁ", - ["ḃ"] = "Ḃ", - ["ḅ"] = "Ḅ", - ["ḇ"] = "Ḇ", - ["ḉ"] = "Ḉ", - ["ḋ"] = "Ḋ", - ["ḍ"] = "Ḍ", - ["ḏ"] = "Ḏ", - ["ḑ"] = "Ḑ", - ["ḓ"] = "Ḓ", - ["ḕ"] = "Ḕ", - ["ḗ"] = "Ḗ", - ["ḙ"] = "Ḙ", - ["ḛ"] = "Ḛ", - ["ḝ"] = "Ḝ", - ["ḟ"] = "Ḟ", - ["ḡ"] = "Ḡ", - ["ḣ"] = "Ḣ", - ["ḥ"] = "Ḥ", - ["ḧ"] = "Ḧ", - ["ḩ"] = "Ḩ", - ["ḫ"] = "Ḫ", - ["ḭ"] = "Ḭ", - ["ḯ"] = "Ḯ", - ["ḱ"] = "Ḱ", - ["ḳ"] = "Ḳ", - ["ḵ"] = "Ḵ", - ["ḷ"] = "Ḷ", - ["ḹ"] = "Ḹ", - ["ḻ"] = "Ḻ", - ["ḽ"] = "Ḽ", - ["ḿ"] = "Ḿ", - ["ṁ"] = "Ṁ", - ["ṃ"] = "Ṃ", - ["ṅ"] = "Ṅ", - ["ṇ"] = "Ṇ", - ["ṉ"] = "Ṉ", - ["ṋ"] = "Ṋ", - ["ṍ"] = "Ṍ", - ["ṏ"] = "Ṏ", - ["ṑ"] = "Ṑ", - ["ṓ"] = "Ṓ", - ["ṕ"] = "Ṕ", - ["ṗ"] = "Ṗ", - ["ṙ"] = "Ṙ", - ["ṛ"] = "Ṛ", - ["ṝ"] = "Ṝ", - ["ṟ"] = "Ṟ", - ["ṡ"] = "Ṡ", - ["ṣ"] = "Ṣ", - ["ṥ"] = "Ṥ", - ["ṧ"] = "Ṧ", - ["ṩ"] = "Ṩ", - ["ṫ"] = "Ṫ", - ["ṭ"] = "Ṭ", - ["ṯ"] = "Ṯ", - ["ṱ"] = "Ṱ", - ["ṳ"] = "Ṳ", - ["ṵ"] = "Ṵ", - ["ṷ"] = "Ṷ", - ["ṹ"] = "Ṹ", - ["ṻ"] = "Ṻ", - ["ṽ"] = "Ṽ", - ["ṿ"] = "Ṿ", - ["ẁ"] = "Ẁ", - ["ẃ"] = "Ẃ", - ["ẅ"] = "Ẅ", - ["ẇ"] = "Ẇ", - ["ẉ"] = "Ẉ", - ["ẋ"] = "Ẋ", - ["ẍ"] = "Ẍ", - ["ẏ"] = "Ẏ", - ["ẑ"] = "Ẑ", - ["ẓ"] = "Ẓ", - ["ẕ"] = "Ẕ", - ["ẛ"] = "Ṡ", - ["ạ"] = "Ạ", - ["ả"] = "Ả", - ["ấ"] = "Ấ", - ["ầ"] = "Ầ", - ["ẩ"] = "Ẩ", - ["ẫ"] = "Ẫ", - ["ậ"] = "Ậ", - ["ắ"] = "Ắ", - ["ằ"] = "Ằ", - ["ẳ"] = "Ẳ", - ["ẵ"] = "Ẵ", - ["ặ"] = "Ặ", - ["ẹ"] = "Ẹ", - ["ẻ"] = "Ẻ", - ["ẽ"] = "Ẽ", - ["ế"] = "Ế", - ["ề"] = "Ề", - ["ể"] = "Ể", - ["ễ"] = "Ễ", - ["ệ"] = "Ệ", - ["ỉ"] = "Ỉ", - ["ị"] = "Ị", - ["ọ"] = "Ọ", - ["ỏ"] = "Ỏ", - ["ố"] = "Ố", - ["ồ"] = "Ồ", - ["ổ"] = "Ổ", - ["ỗ"] = "Ỗ", - ["ộ"] = "Ộ", - ["ớ"] = "Ớ", - ["ờ"] = "Ờ", - ["ở"] = "Ở", - ["ỡ"] = "Ỡ", - ["ợ"] = "Ợ", - ["ụ"] = "Ụ", - ["ủ"] = "Ủ", - ["ứ"] = "Ứ", - ["ừ"] = "Ừ", - ["ử"] = "Ử", - ["ữ"] = "Ữ", - ["ự"] = "Ự", - ["ỳ"] = "Ỳ", - ["ỵ"] = "Ỵ", - ["ỷ"] = "Ỷ", - ["ỹ"] = "Ỹ", - ["ἀ"] = "Ἀ", - ["ἁ"] = "Ἁ", - ["ἂ"] = "Ἂ", - ["ἃ"] = "Ἃ", - ["ἄ"] = "Ἄ", - ["ἅ"] = "Ἅ", - ["ἆ"] = "Ἆ", - ["ἇ"] = "Ἇ", - ["ἐ"] = "Ἐ", - ["ἑ"] = "Ἑ", - ["ἒ"] = "Ἒ", - ["ἓ"] = "Ἓ", - ["ἔ"] = "Ἔ", - ["ἕ"] = "Ἕ", - ["ἠ"] = "Ἠ", - ["ἡ"] = "Ἡ", - ["ἢ"] = "Ἢ", - ["ἣ"] = "Ἣ", - ["ἤ"] = "Ἤ", - ["ἥ"] = "Ἥ", - ["ἦ"] = "Ἦ", - ["ἧ"] = "Ἧ", - ["ἰ"] = "Ἰ", - ["ἱ"] = "Ἱ", - ["ἲ"] = "Ἲ", - ["ἳ"] = "Ἳ", - ["ἴ"] = "Ἴ", - ["ἵ"] = "Ἵ", - ["ἶ"] = "Ἶ", - ["ἷ"] = "Ἷ", - ["ὀ"] = "Ὀ", - ["ὁ"] = "Ὁ", - ["ὂ"] = "Ὂ", - ["ὃ"] = "Ὃ", - ["ὄ"] = "Ὄ", - ["ὅ"] = "Ὅ", - ["ὑ"] = "Ὑ", - ["ὓ"] = "Ὓ", - ["ὕ"] = "Ὕ", - ["ὗ"] = "Ὗ", - ["ὠ"] = "Ὠ", - ["ὡ"] = "Ὡ", - ["ὢ"] = "Ὢ", - ["ὣ"] = "Ὣ", - ["ὤ"] = "Ὤ", - ["ὥ"] = "Ὥ", - ["ὦ"] = "Ὦ", - ["ὧ"] = "Ὧ", - ["ὰ"] = "Ὰ", - ["ά"] = "Ά", - ["ὲ"] = "Ὲ", - ["έ"] = "Έ", - ["ὴ"] = "Ὴ", - ["ή"] = "Ή", - ["ὶ"] = "Ὶ", - ["ί"] = "Ί", - ["ὸ"] = "Ὸ", - ["ό"] = "Ό", - ["ὺ"] = "Ὺ", - ["ύ"] = "Ύ", - ["ὼ"] = "Ὼ", - ["ώ"] = "Ώ", - ["ᾀ"] = "ᾈ", - ["ᾁ"] = "ᾉ", - ["ᾂ"] = "ᾊ", - ["ᾃ"] = "ᾋ", - ["ᾄ"] = "ᾌ", - ["ᾅ"] = "ᾍ", - ["ᾆ"] = "ᾎ", - ["ᾇ"] = "ᾏ", - ["ᾐ"] = "ᾘ", - ["ᾑ"] = "ᾙ", - ["ᾒ"] = "ᾚ", - ["ᾓ"] = "ᾛ", - ["ᾔ"] = "ᾜ", - ["ᾕ"] = "ᾝ", - ["ᾖ"] = "ᾞ", - ["ᾗ"] = "ᾟ", - ["ᾠ"] = "ᾨ", - ["ᾡ"] = "ᾩ", - ["ᾢ"] = "ᾪ", - ["ᾣ"] = "ᾫ", - ["ᾤ"] = "ᾬ", - ["ᾥ"] = "ᾭ", - ["ᾦ"] = "ᾮ", - ["ᾧ"] = "ᾯ", - ["ᾰ"] = "Ᾰ", - ["ᾱ"] = "Ᾱ", - ["ᾳ"] = "ᾼ", - ["ι"] = "Ι", - ["ῃ"] = "ῌ", - ["ῐ"] = "Ῐ", - ["ῑ"] = "Ῑ", - ["ῠ"] = "Ῠ", - ["ῡ"] = "Ῡ", - ["ῥ"] = "Ῥ", - ["ῳ"] = "ῼ", - ["ⅎ"] = "Ⅎ", - ["ⅰ"] = "Ⅰ", - ["ⅱ"] = "Ⅱ", - ["ⅲ"] = "Ⅲ", - ["ⅳ"] = "Ⅳ", - ["ⅴ"] = "Ⅴ", - ["ⅵ"] = "Ⅵ", - ["ⅶ"] = "Ⅶ", - ["ⅷ"] = "Ⅷ", - ["ⅸ"] = "Ⅸ", - ["ⅹ"] = "Ⅹ", - ["ⅺ"] = "Ⅺ", - ["ⅻ"] = "Ⅻ", - ["ⅼ"] = "Ⅼ", - ["ⅽ"] = "Ⅽ", - ["ⅾ"] = "Ⅾ", - ["ⅿ"] = "Ⅿ", - ["ↄ"] = "Ↄ", - ["ⓐ"] = "Ⓐ", - ["ⓑ"] = "Ⓑ", - ["ⓒ"] = "Ⓒ", - ["ⓓ"] = "Ⓓ", - ["ⓔ"] = "Ⓔ", - ["ⓕ"] = "Ⓕ", - ["ⓖ"] = "Ⓖ", - ["ⓗ"] = "Ⓗ", - ["ⓘ"] = "Ⓘ", - ["ⓙ"] = "Ⓙ", - ["ⓚ"] = "Ⓚ", - ["ⓛ"] = "Ⓛ", - ["ⓜ"] = "Ⓜ", - ["ⓝ"] = "Ⓝ", - ["ⓞ"] = "Ⓞ", - ["ⓟ"] = "Ⓟ", - ["ⓠ"] = "Ⓠ", - ["ⓡ"] = "Ⓡ", - ["ⓢ"] = "Ⓢ", - ["ⓣ"] = "Ⓣ", - ["ⓤ"] = "Ⓤ", - ["ⓥ"] = "Ⓥ", - ["ⓦ"] = "Ⓦ", - ["ⓧ"] = "Ⓧ", - ["ⓨ"] = "Ⓨ", - ["ⓩ"] = "Ⓩ", - ["ⰰ"] = "Ⰰ", - ["ⰱ"] = "Ⰱ", - ["ⰲ"] = "Ⰲ", - ["ⰳ"] = "Ⰳ", - ["ⰴ"] = "Ⰴ", - ["ⰵ"] = "Ⰵ", - ["ⰶ"] = "Ⰶ", - ["ⰷ"] = "Ⰷ", - ["ⰸ"] = "Ⰸ", - ["ⰹ"] = "Ⰹ", - ["ⰺ"] = "Ⰺ", - ["ⰻ"] = "Ⰻ", - ["ⰼ"] = "Ⰼ", - ["ⰽ"] = "Ⰽ", - ["ⰾ"] = "Ⰾ", - ["ⰿ"] = "Ⰿ", - ["ⱀ"] = "Ⱀ", - ["ⱁ"] = "Ⱁ", - ["ⱂ"] = "Ⱂ", - ["ⱃ"] = "Ⱃ", - ["ⱄ"] = "Ⱄ", - ["ⱅ"] = "Ⱅ", - ["ⱆ"] = "Ⱆ", - ["ⱇ"] = "Ⱇ", - ["ⱈ"] = "Ⱈ", - ["ⱉ"] = "Ⱉ", - ["ⱊ"] = "Ⱊ", - ["ⱋ"] = "Ⱋ", - ["ⱌ"] = "Ⱌ", - ["ⱍ"] = "Ⱍ", - ["ⱎ"] = "Ⱎ", - ["ⱏ"] = "Ⱏ", - ["ⱐ"] = "Ⱐ", - ["ⱑ"] = "Ⱑ", - ["ⱒ"] = "Ⱒ", - ["ⱓ"] = "Ⱓ", - ["ⱔ"] = "Ⱔ", - ["ⱕ"] = "Ⱕ", - ["ⱖ"] = "Ⱖ", - ["ⱗ"] = "Ⱗ", - ["ⱘ"] = "Ⱘ", - ["ⱙ"] = "Ⱙ", - ["ⱚ"] = "Ⱚ", - ["ⱛ"] = "Ⱛ", - ["ⱜ"] = "Ⱜ", - ["ⱝ"] = "Ⱝ", - ["ⱞ"] = "Ⱞ", - ["ⱡ"] = "Ⱡ", - ["ⱥ"] = "Ⱥ", - ["ⱦ"] = "Ⱦ", - ["ⱨ"] = "Ⱨ", - ["ⱪ"] = "Ⱪ", - ["ⱬ"] = "Ⱬ", - ["ⱶ"] = "Ⱶ", - ["ⲁ"] = "Ⲁ", - ["ⲃ"] = "Ⲃ", - ["ⲅ"] = "Ⲅ", - ["ⲇ"] = "Ⲇ", - ["ⲉ"] = "Ⲉ", - ["ⲋ"] = "Ⲋ", - ["ⲍ"] = "Ⲍ", - ["ⲏ"] = "Ⲏ", - ["ⲑ"] = "Ⲑ", - ["ⲓ"] = "Ⲓ", - ["ⲕ"] = "Ⲕ", - ["ⲗ"] = "Ⲗ", - ["ⲙ"] = "Ⲙ", - ["ⲛ"] = "Ⲛ", - ["ⲝ"] = "Ⲝ", - ["ⲟ"] = "Ⲟ", - ["ⲡ"] = "Ⲡ", - ["ⲣ"] = "Ⲣ", - ["ⲥ"] = "Ⲥ", - ["ⲧ"] = "Ⲧ", - ["ⲩ"] = "Ⲩ", - ["ⲫ"] = "Ⲫ", - ["ⲭ"] = "Ⲭ", - ["ⲯ"] = "Ⲯ", - ["ⲱ"] = "Ⲱ", - ["ⲳ"] = "Ⲳ", - ["ⲵ"] = "Ⲵ", - ["ⲷ"] = "Ⲷ", - ["ⲹ"] = "Ⲹ", - ["ⲻ"] = "Ⲻ", - ["ⲽ"] = "Ⲽ", - ["ⲿ"] = "Ⲿ", - ["ⳁ"] = "Ⳁ", - ["ⳃ"] = "Ⳃ", - ["ⳅ"] = "Ⳅ", - ["ⳇ"] = "Ⳇ", - ["ⳉ"] = "Ⳉ", - ["ⳋ"] = "Ⳋ", - ["ⳍ"] = "Ⳍ", - ["ⳏ"] = "Ⳏ", - ["ⳑ"] = "Ⳑ", - ["ⳓ"] = "Ⳓ", - ["ⳕ"] = "Ⳕ", - ["ⳗ"] = "Ⳗ", - ["ⳙ"] = "Ⳙ", - ["ⳛ"] = "Ⳛ", - ["ⳝ"] = "Ⳝ", - ["ⳟ"] = "Ⳟ", - ["ⳡ"] = "Ⳡ", - ["ⳣ"] = "Ⳣ", - ["ⴀ"] = "Ⴀ", - ["ⴁ"] = "Ⴁ", - ["ⴂ"] = "Ⴂ", - ["ⴃ"] = "Ⴃ", - ["ⴄ"] = "Ⴄ", - ["ⴅ"] = "Ⴅ", - ["ⴆ"] = "Ⴆ", - ["ⴇ"] = "Ⴇ", - ["ⴈ"] = "Ⴈ", - ["ⴉ"] = "Ⴉ", - ["ⴊ"] = "Ⴊ", - ["ⴋ"] = "Ⴋ", - ["ⴌ"] = "Ⴌ", - ["ⴍ"] = "Ⴍ", - ["ⴎ"] = "Ⴎ", - ["ⴏ"] = "Ⴏ", - ["ⴐ"] = "Ⴐ", - ["ⴑ"] = "Ⴑ", - ["ⴒ"] = "Ⴒ", - ["ⴓ"] = "Ⴓ", - ["ⴔ"] = "Ⴔ", - ["ⴕ"] = "Ⴕ", - ["ⴖ"] = "Ⴖ", - ["ⴗ"] = "Ⴗ", - ["ⴘ"] = "Ⴘ", - ["ⴙ"] = "Ⴙ", - ["ⴚ"] = "Ⴚ", - ["ⴛ"] = "Ⴛ", - ["ⴜ"] = "Ⴜ", - ["ⴝ"] = "Ⴝ", - ["ⴞ"] = "Ⴞ", - ["ⴟ"] = "Ⴟ", - ["ⴠ"] = "Ⴠ", - ["ⴡ"] = "Ⴡ", - ["ⴢ"] = "Ⴢ", - ["ⴣ"] = "Ⴣ", - ["ⴤ"] = "Ⴤ", - ["ⴥ"] = "Ⴥ", - ["a"] = "A", - ["b"] = "B", - ["c"] = "C", - ["d"] = "D", - ["e"] = "E", - ["f"] = "F", - ["g"] = "G", - ["h"] = "H", - ["i"] = "I", - ["j"] = "J", - ["k"] = "K", - ["l"] = "L", - ["m"] = "M", - ["n"] = "N", - ["o"] = "O", - ["p"] = "P", - ["q"] = "Q", - ["r"] = "R", - ["s"] = "S", - ["t"] = "T", - ["u"] = "U", - ["v"] = "V", - ["w"] = "W", - ["x"] = "X", - ["y"] = "Y", - ["z"] = "Z", - ["𐐨"] = "𐐀", - ["𐐩"] = "𐐁", - ["𐐪"] = "𐐂", - ["𐐫"] = "𐐃", - ["𐐬"] = "𐐄", - ["𐐭"] = "𐐅", - ["𐐮"] = "𐐆", - ["𐐯"] = "𐐇", - ["𐐰"] = "𐐈", - ["𐐱"] = "𐐉", - ["𐐲"] = "𐐊", - ["𐐳"] = "𐐋", - ["𐐴"] = "𐐌", - ["𐐵"] = "𐐍", - ["𐐶"] = "𐐎", - ["𐐷"] = "𐐏", - ["𐐸"] = "𐐐", - ["𐐹"] = "𐐑", - ["𐐺"] = "𐐒", - ["𐐻"] = "𐐓", - ["𐐼"] = "𐐔", - ["𐐽"] = "𐐕", - ["𐐾"] = "𐐖", - ["𐐿"] = "𐐗", - ["𐑀"] = "𐐘", - ["𐑁"] = "𐐙", - ["𐑂"] = "𐐚", - ["𐑃"] = "𐐛", - ["𐑄"] = "𐐜", - ["𐑅"] = "𐐝", - ["𐑆"] = "𐐞", - ["𐑇"] = "𐐟", - ["𐑈"] = "𐐠", - ["𐑉"] = "𐐡", - ["𐑊"] = "𐐢", - ["𐑋"] = "𐐣", - ["𐑌"] = "𐐤", - ["𐑍"] = "𐐥", - ["𐑎"] = "𐐦", - ["𐑏"] = "𐐧", + ["a"] = "A", + ["b"] = "B", + ["c"] = "C", + ["d"] = "D", + ["e"] = "E", + ["f"] = "F", + ["g"] = "G", + ["h"] = "H", + ["i"] = "I", + ["j"] = "J", + ["k"] = "K", + ["l"] = "L", + ["m"] = "M", + ["n"] = "N", + ["o"] = "O", + ["p"] = "P", + ["q"] = "Q", + ["r"] = "R", + ["s"] = "S", + ["t"] = "T", + ["u"] = "U", + ["v"] = "V", + ["w"] = "W", + ["x"] = "X", + ["y"] = "Y", + ["z"] = "Z", + ["µ"] = "Μ", + ["à"] = "À", + ["á"] = "Á", + ["â"] = "Â", + ["ã"] = "Ã", + ["ä"] = "Ä", + ["å"] = "Å", + ["æ"] = "Æ", + ["ç"] = "Ç", + ["è"] = "È", + ["é"] = "É", + ["ê"] = "Ê", + ["ë"] = "Ë", + ["ì"] = "Ì", + ["í"] = "Í", + ["î"] = "Î", + ["ï"] = "Ï", + ["ð"] = "Ð", + ["ñ"] = "Ñ", + ["ò"] = "Ò", + ["ó"] = "Ó", + ["ô"] = "Ô", + ["õ"] = "Õ", + ["ö"] = "Ö", + ["ø"] = "Ø", + ["ù"] = "Ù", + ["ú"] = "Ú", + ["û"] = "Û", + ["ü"] = "Ü", + ["ý"] = "Ý", + ["þ"] = "Þ", + ["ÿ"] = "Ÿ", + ["ā"] = "Ā", + ["ă"] = "Ă", + ["ą"] = "Ą", + ["ć"] = "Ć", + ["ĉ"] = "Ĉ", + ["ċ"] = "Ċ", + ["č"] = "Č", + ["ď"] = "Ď", + ["đ"] = "Đ", + ["ē"] = "Ē", + ["ĕ"] = "Ĕ", + ["ė"] = "Ė", + ["ę"] = "Ę", + ["ě"] = "Ě", + ["ĝ"] = "Ĝ", + ["ğ"] = "Ğ", + ["ġ"] = "Ġ", + ["ģ"] = "Ģ", + ["ĥ"] = "Ĥ", + ["ħ"] = "Ħ", + ["ĩ"] = "Ĩ", + ["ī"] = "Ī", + ["ĭ"] = "Ĭ", + ["į"] = "Į", + ["ı"] = "I", + ["ij"] = "IJ", + ["ĵ"] = "Ĵ", + ["ķ"] = "Ķ", + ["ĺ"] = "Ĺ", + ["ļ"] = "Ļ", + ["ľ"] = "Ľ", + ["ŀ"] = "Ŀ", + ["ł"] = "Ł", + ["ń"] = "Ń", + ["ņ"] = "Ņ", + ["ň"] = "Ň", + ["ŋ"] = "Ŋ", + ["ō"] = "Ō", + ["ŏ"] = "Ŏ", + ["ő"] = "Ő", + ["œ"] = "Œ", + ["ŕ"] = "Ŕ", + ["ŗ"] = "Ŗ", + ["ř"] = "Ř", + ["ś"] = "Ś", + ["ŝ"] = "Ŝ", + ["ş"] = "Ş", + ["š"] = "Š", + ["ţ"] = "Ţ", + ["ť"] = "Ť", + ["ŧ"] = "Ŧ", + ["ũ"] = "Ũ", + ["ū"] = "Ū", + ["ŭ"] = "Ŭ", + ["ů"] = "Ů", + ["ű"] = "Ű", + ["ų"] = "Ų", + ["ŵ"] = "Ŵ", + ["ŷ"] = "Ŷ", + ["ź"] = "Ź", + ["ż"] = "Ż", + ["ž"] = "Ž", + ["ſ"] = "S", + ["ƀ"] = "Ƀ", + ["ƃ"] = "Ƃ", + ["ƅ"] = "Ƅ", + ["ƈ"] = "Ƈ", + ["ƌ"] = "Ƌ", + ["ƒ"] = "Ƒ", + ["ƕ"] = "Ƕ", + ["ƙ"] = "Ƙ", + ["ƚ"] = "Ƚ", + ["ƞ"] = "Ƞ", + ["ơ"] = "Ơ", + ["ƣ"] = "Ƣ", + ["ƥ"] = "Ƥ", + ["ƨ"] = "Ƨ", + ["ƭ"] = "Ƭ", + ["ư"] = "Ư", + ["ƴ"] = "Ƴ", + ["ƶ"] = "Ƶ", + ["ƹ"] = "Ƹ", + ["ƽ"] = "Ƽ", + ["ƿ"] = "Ƿ", + ["Dž"] = "DŽ", + ["dž"] = "DŽ", + ["Lj"] = "LJ", + ["lj"] = "LJ", + ["Nj"] = "NJ", + ["nj"] = "NJ", + ["ǎ"] = "Ǎ", + ["ǐ"] = "Ǐ", + ["ǒ"] = "Ǒ", + ["ǔ"] = "Ǔ", + ["ǖ"] = "Ǖ", + ["ǘ"] = "Ǘ", + ["ǚ"] = "Ǚ", + ["ǜ"] = "Ǜ", + ["ǝ"] = "Ǝ", + ["ǟ"] = "Ǟ", + ["ǡ"] = "Ǡ", + ["ǣ"] = "Ǣ", + ["ǥ"] = "Ǥ", + ["ǧ"] = "Ǧ", + ["ǩ"] = "Ǩ", + ["ǫ"] = "Ǫ", + ["ǭ"] = "Ǭ", + ["ǯ"] = "Ǯ", + ["Dz"] = "DZ", + ["dz"] = "DZ", + ["ǵ"] = "Ǵ", + ["ǹ"] = "Ǹ", + ["ǻ"] = "Ǻ", + ["ǽ"] = "Ǽ", + ["ǿ"] = "Ǿ", + ["ȁ"] = "Ȁ", + ["ȃ"] = "Ȃ", + ["ȅ"] = "Ȅ", + ["ȇ"] = "Ȇ", + ["ȉ"] = "Ȉ", + ["ȋ"] = "Ȋ", + ["ȍ"] = "Ȍ", + ["ȏ"] = "Ȏ", + ["ȑ"] = "Ȑ", + ["ȓ"] = "Ȓ", + ["ȕ"] = "Ȕ", + ["ȗ"] = "Ȗ", + ["ș"] = "Ș", + ["ț"] = "Ț", + ["ȝ"] = "Ȝ", + ["ȟ"] = "Ȟ", + ["ȣ"] = "Ȣ", + ["ȥ"] = "Ȥ", + ["ȧ"] = "Ȧ", + ["ȩ"] = "Ȩ", + ["ȫ"] = "Ȫ", + ["ȭ"] = "Ȭ", + ["ȯ"] = "Ȯ", + ["ȱ"] = "Ȱ", + ["ȳ"] = "Ȳ", + ["ȼ"] = "Ȼ", + ["ɂ"] = "Ɂ", + ["ɇ"] = "Ɇ", + ["ɉ"] = "Ɉ", + ["ɋ"] = "Ɋ", + ["ɍ"] = "Ɍ", + ["ɏ"] = "Ɏ", + ["ɓ"] = "Ɓ", + ["ɔ"] = "Ɔ", + ["ɖ"] = "Ɖ", + ["ɗ"] = "Ɗ", + ["ə"] = "Ə", + ["ɛ"] = "Ɛ", + ["ɠ"] = "Ɠ", + ["ɣ"] = "Ɣ", + ["ɨ"] = "Ɨ", + ["ɩ"] = "Ɩ", + ["ɫ"] = "Ɫ", + ["ɯ"] = "Ɯ", + ["ɲ"] = "Ɲ", + ["ɵ"] = "Ɵ", + ["ɽ"] = "Ɽ", + ["ʀ"] = "Ʀ", + ["ʃ"] = "Ʃ", + ["ʈ"] = "Ʈ", + ["ʉ"] = "Ʉ", + ["ʊ"] = "Ʊ", + ["ʋ"] = "Ʋ", + ["ʌ"] = "Ʌ", + ["ʒ"] = "Ʒ", + ["ͅ"] = "Ι", + ["ͻ"] = "Ͻ", + ["ͼ"] = "Ͼ", + ["ͽ"] = "Ͽ", + ["ά"] = "Ά", + ["έ"] = "Έ", + ["ή"] = "Ή", + ["ί"] = "Ί", + ["α"] = "Α", + ["β"] = "Β", + ["γ"] = "Γ", + ["δ"] = "Δ", + ["ε"] = "Ε", + ["ζ"] = "Ζ", + ["η"] = "Η", + ["θ"] = "Θ", + ["ι"] = "Ι", + ["κ"] = "Κ", + ["λ"] = "Λ", + ["μ"] = "Μ", + ["ν"] = "Ν", + ["ξ"] = "Ξ", + ["ο"] = "Ο", + ["π"] = "Π", + ["ρ"] = "Ρ", + ["ς"] = "Σ", + ["σ"] = "Σ", + ["τ"] = "Τ", + ["υ"] = "Υ", + ["φ"] = "Φ", + ["χ"] = "Χ", + ["ψ"] = "Ψ", + ["ω"] = "Ω", + ["ϊ"] = "Ϊ", + ["ϋ"] = "Ϋ", + ["ό"] = "Ό", + ["ύ"] = "Ύ", + ["ώ"] = "Ώ", + ["ϐ"] = "Β", + ["ϑ"] = "Θ", + ["ϕ"] = "Φ", + ["ϖ"] = "Π", + ["ϙ"] = "Ϙ", + ["ϛ"] = "Ϛ", + ["ϝ"] = "Ϝ", + ["ϟ"] = "Ϟ", + ["ϡ"] = "Ϡ", + ["ϣ"] = "Ϣ", + ["ϥ"] = "Ϥ", + ["ϧ"] = "Ϧ", + ["ϩ"] = "Ϩ", + ["ϫ"] = "Ϫ", + ["ϭ"] = "Ϭ", + ["ϯ"] = "Ϯ", + ["ϰ"] = "Κ", + ["ϱ"] = "Ρ", + ["ϲ"] = "Ϲ", + ["ϵ"] = "Ε", + ["ϸ"] = "Ϸ", + ["ϻ"] = "Ϻ", + ["а"] = "А", + ["б"] = "Б", + ["в"] = "В", + ["г"] = "Г", + ["д"] = "Д", + ["е"] = "Е", + ["ж"] = "Ж", + ["з"] = "З", + ["и"] = "И", + ["й"] = "Й", + ["к"] = "К", + ["л"] = "Л", + ["м"] = "М", + ["н"] = "Н", + ["о"] = "О", + ["п"] = "П", + ["р"] = "Р", + ["с"] = "С", + ["т"] = "Т", + ["у"] = "У", + ["ф"] = "Ф", + ["х"] = "Х", + ["ц"] = "Ц", + ["ч"] = "Ч", + ["ш"] = "Ш", + ["щ"] = "Щ", + ["ъ"] = "Ъ", + ["ы"] = "Ы", + ["ь"] = "Ь", + ["э"] = "Э", + ["ю"] = "Ю", + ["я"] = "Я", + ["ѐ"] = "Ѐ", + ["ё"] = "Ё", + ["ђ"] = "Ђ", + ["ѓ"] = "Ѓ", + ["є"] = "Є", + ["ѕ"] = "Ѕ", + ["і"] = "І", + ["ї"] = "Ї", + ["ј"] = "Ј", + ["љ"] = "Љ", + ["њ"] = "Њ", + ["ћ"] = "Ћ", + ["ќ"] = "Ќ", + ["ѝ"] = "Ѝ", + ["ў"] = "Ў", + ["џ"] = "Џ", + ["ѡ"] = "Ѡ", + ["ѣ"] = "Ѣ", + ["ѥ"] = "Ѥ", + ["ѧ"] = "Ѧ", + ["ѩ"] = "Ѩ", + ["ѫ"] = "Ѫ", + ["ѭ"] = "Ѭ", + ["ѯ"] = "Ѯ", + ["ѱ"] = "Ѱ", + ["ѳ"] = "Ѳ", + ["ѵ"] = "Ѵ", + ["ѷ"] = "Ѷ", + ["ѹ"] = "Ѹ", + ["ѻ"] = "Ѻ", + ["ѽ"] = "Ѽ", + ["ѿ"] = "Ѿ", + ["ҁ"] = "Ҁ", + ["ҋ"] = "Ҋ", + ["ҍ"] = "Ҍ", + ["ҏ"] = "Ҏ", + ["ґ"] = "Ґ", + ["ғ"] = "Ғ", + ["ҕ"] = "Ҕ", + ["җ"] = "Җ", + ["ҙ"] = "Ҙ", + ["қ"] = "Қ", + ["ҝ"] = "Ҝ", + ["ҟ"] = "Ҟ", + ["ҡ"] = "Ҡ", + ["ң"] = "Ң", + ["ҥ"] = "Ҥ", + ["ҧ"] = "Ҧ", + ["ҩ"] = "Ҩ", + ["ҫ"] = "Ҫ", + ["ҭ"] = "Ҭ", + ["ү"] = "Ү", + ["ұ"] = "Ұ", + ["ҳ"] = "Ҳ", + ["ҵ"] = "Ҵ", + ["ҷ"] = "Ҷ", + ["ҹ"] = "Ҹ", + ["һ"] = "Һ", + ["ҽ"] = "Ҽ", + ["ҿ"] = "Ҿ", + ["ӂ"] = "Ӂ", + ["ӄ"] = "Ӄ", + ["ӆ"] = "Ӆ", + ["ӈ"] = "Ӈ", + ["ӊ"] = "Ӊ", + ["ӌ"] = "Ӌ", + ["ӎ"] = "Ӎ", + ["ӏ"] = "Ӏ", + ["ӑ"] = "Ӑ", + ["ӓ"] = "Ӓ", + ["ӕ"] = "Ӕ", + ["ӗ"] = "Ӗ", + ["ә"] = "Ә", + ["ӛ"] = "Ӛ", + ["ӝ"] = "Ӝ", + ["ӟ"] = "Ӟ", + ["ӡ"] = "Ӡ", + ["ӣ"] = "Ӣ", + ["ӥ"] = "Ӥ", + ["ӧ"] = "Ӧ", + ["ө"] = "Ө", + ["ӫ"] = "Ӫ", + ["ӭ"] = "Ӭ", + ["ӯ"] = "Ӯ", + ["ӱ"] = "Ӱ", + ["ӳ"] = "Ӳ", + ["ӵ"] = "Ӵ", + ["ӷ"] = "Ӷ", + ["ӹ"] = "Ӹ", + ["ӻ"] = "Ӻ", + ["ӽ"] = "Ӽ", + ["ӿ"] = "Ӿ", + ["ԁ"] = "Ԁ", + ["ԃ"] = "Ԃ", + ["ԅ"] = "Ԅ", + ["ԇ"] = "Ԇ", + ["ԉ"] = "Ԉ", + ["ԋ"] = "Ԋ", + ["ԍ"] = "Ԍ", + ["ԏ"] = "Ԏ", + ["ԑ"] = "Ԑ", + ["ԓ"] = "Ԓ", + ["ա"] = "Ա", + ["բ"] = "Բ", + ["գ"] = "Գ", + ["դ"] = "Դ", + ["ե"] = "Ե", + ["զ"] = "Զ", + ["է"] = "Է", + ["ը"] = "Ը", + ["թ"] = "Թ", + ["ժ"] = "Ժ", + ["ի"] = "Ի", + ["լ"] = "Լ", + ["խ"] = "Խ", + ["ծ"] = "Ծ", + ["կ"] = "Կ", + ["հ"] = "Հ", + ["ձ"] = "Ձ", + ["ղ"] = "Ղ", + ["ճ"] = "Ճ", + ["մ"] = "Մ", + ["յ"] = "Յ", + ["ն"] = "Ն", + ["շ"] = "Շ", + ["ո"] = "Ո", + ["չ"] = "Չ", + ["պ"] = "Պ", + ["ջ"] = "Ջ", + ["ռ"] = "Ռ", + ["ս"] = "Ս", + ["վ"] = "Վ", + ["տ"] = "Տ", + ["ր"] = "Ր", + ["ց"] = "Ց", + ["ւ"] = "Ւ", + ["փ"] = "Փ", + ["ք"] = "Ք", + ["օ"] = "Օ", + ["ֆ"] = "Ֆ", + ["ᵽ"] = "Ᵽ", + ["ḁ"] = "Ḁ", + ["ḃ"] = "Ḃ", + ["ḅ"] = "Ḅ", + ["ḇ"] = "Ḇ", + ["ḉ"] = "Ḉ", + ["ḋ"] = "Ḋ", + ["ḍ"] = "Ḍ", + ["ḏ"] = "Ḏ", + ["ḑ"] = "Ḑ", + ["ḓ"] = "Ḓ", + ["ḕ"] = "Ḕ", + ["ḗ"] = "Ḗ", + ["ḙ"] = "Ḙ", + ["ḛ"] = "Ḛ", + ["ḝ"] = "Ḝ", + ["ḟ"] = "Ḟ", + ["ḡ"] = "Ḡ", + ["ḣ"] = "Ḣ", + ["ḥ"] = "Ḥ", + ["ḧ"] = "Ḧ", + ["ḩ"] = "Ḩ", + ["ḫ"] = "Ḫ", + ["ḭ"] = "Ḭ", + ["ḯ"] = "Ḯ", + ["ḱ"] = "Ḱ", + ["ḳ"] = "Ḳ", + ["ḵ"] = "Ḵ", + ["ḷ"] = "Ḷ", + ["ḹ"] = "Ḹ", + ["ḻ"] = "Ḻ", + ["ḽ"] = "Ḽ", + ["ḿ"] = "Ḿ", + ["ṁ"] = "Ṁ", + ["ṃ"] = "Ṃ", + ["ṅ"] = "Ṅ", + ["ṇ"] = "Ṇ", + ["ṉ"] = "Ṉ", + ["ṋ"] = "Ṋ", + ["ṍ"] = "Ṍ", + ["ṏ"] = "Ṏ", + ["ṑ"] = "Ṑ", + ["ṓ"] = "Ṓ", + ["ṕ"] = "Ṕ", + ["ṗ"] = "Ṗ", + ["ṙ"] = "Ṙ", + ["ṛ"] = "Ṛ", + ["ṝ"] = "Ṝ", + ["ṟ"] = "Ṟ", + ["ṡ"] = "Ṡ", + ["ṣ"] = "Ṣ", + ["ṥ"] = "Ṥ", + ["ṧ"] = "Ṧ", + ["ṩ"] = "Ṩ", + ["ṫ"] = "Ṫ", + ["ṭ"] = "Ṭ", + ["ṯ"] = "Ṯ", + ["ṱ"] = "Ṱ", + ["ṳ"] = "Ṳ", + ["ṵ"] = "Ṵ", + ["ṷ"] = "Ṷ", + ["ṹ"] = "Ṹ", + ["ṻ"] = "Ṻ", + ["ṽ"] = "Ṽ", + ["ṿ"] = "Ṿ", + ["ẁ"] = "Ẁ", + ["ẃ"] = "Ẃ", + ["ẅ"] = "Ẅ", + ["ẇ"] = "Ẇ", + ["ẉ"] = "Ẉ", + ["ẋ"] = "Ẋ", + ["ẍ"] = "Ẍ", + ["ẏ"] = "Ẏ", + ["ẑ"] = "Ẑ", + ["ẓ"] = "Ẓ", + ["ẕ"] = "Ẕ", + ["ẛ"] = "Ṡ", + ["ạ"] = "Ạ", + ["ả"] = "Ả", + ["ấ"] = "Ấ", + ["ầ"] = "Ầ", + ["ẩ"] = "Ẩ", + ["ẫ"] = "Ẫ", + ["ậ"] = "Ậ", + ["ắ"] = "Ắ", + ["ằ"] = "Ằ", + ["ẳ"] = "Ẳ", + ["ẵ"] = "Ẵ", + ["ặ"] = "Ặ", + ["ẹ"] = "Ẹ", + ["ẻ"] = "Ẻ", + ["ẽ"] = "Ẽ", + ["ế"] = "Ế", + ["ề"] = "Ề", + ["ể"] = "Ể", + ["ễ"] = "Ễ", + ["ệ"] = "Ệ", + ["ỉ"] = "Ỉ", + ["ị"] = "Ị", + ["ọ"] = "Ọ", + ["ỏ"] = "Ỏ", + ["ố"] = "Ố", + ["ồ"] = "Ồ", + ["ổ"] = "Ổ", + ["ỗ"] = "Ỗ", + ["ộ"] = "Ộ", + ["ớ"] = "Ớ", + ["ờ"] = "Ờ", + ["ở"] = "Ở", + ["ỡ"] = "Ỡ", + ["ợ"] = "Ợ", + ["ụ"] = "Ụ", + ["ủ"] = "Ủ", + ["ứ"] = "Ứ", + ["ừ"] = "Ừ", + ["ử"] = "Ử", + ["ữ"] = "Ữ", + ["ự"] = "Ự", + ["ỳ"] = "Ỳ", + ["ỵ"] = "Ỵ", + ["ỷ"] = "Ỷ", + ["ỹ"] = "Ỹ", + ["ἀ"] = "Ἀ", + ["ἁ"] = "Ἁ", + ["ἂ"] = "Ἂ", + ["ἃ"] = "Ἃ", + ["ἄ"] = "Ἄ", + ["ἅ"] = "Ἅ", + ["ἆ"] = "Ἆ", + ["ἇ"] = "Ἇ", + ["ἐ"] = "Ἐ", + ["ἑ"] = "Ἑ", + ["ἒ"] = "Ἒ", + ["ἓ"] = "Ἓ", + ["ἔ"] = "Ἔ", + ["ἕ"] = "Ἕ", + ["ἠ"] = "Ἠ", + ["ἡ"] = "Ἡ", + ["ἢ"] = "Ἢ", + ["ἣ"] = "Ἣ", + ["ἤ"] = "Ἤ", + ["ἥ"] = "Ἥ", + ["ἦ"] = "Ἦ", + ["ἧ"] = "Ἧ", + ["ἰ"] = "Ἰ", + ["ἱ"] = "Ἱ", + ["ἲ"] = "Ἲ", + ["ἳ"] = "Ἳ", + ["ἴ"] = "Ἴ", + ["ἵ"] = "Ἵ", + ["ἶ"] = "Ἶ", + ["ἷ"] = "Ἷ", + ["ὀ"] = "Ὀ", + ["ὁ"] = "Ὁ", + ["ὂ"] = "Ὂ", + ["ὃ"] = "Ὃ", + ["ὄ"] = "Ὄ", + ["ὅ"] = "Ὅ", + ["ὑ"] = "Ὑ", + ["ὓ"] = "Ὓ", + ["ὕ"] = "Ὕ", + ["ὗ"] = "Ὗ", + ["ὠ"] = "Ὠ", + ["ὡ"] = "Ὡ", + ["ὢ"] = "Ὢ", + ["ὣ"] = "Ὣ", + ["ὤ"] = "Ὤ", + ["ὥ"] = "Ὥ", + ["ὦ"] = "Ὦ", + ["ὧ"] = "Ὧ", + ["ὰ"] = "Ὰ", + ["ά"] = "Ά", + ["ὲ"] = "Ὲ", + ["έ"] = "Έ", + ["ὴ"] = "Ὴ", + ["ή"] = "Ή", + ["ὶ"] = "Ὶ", + ["ί"] = "Ί", + ["ὸ"] = "Ὸ", + ["ό"] = "Ό", + ["ὺ"] = "Ὺ", + ["ύ"] = "Ύ", + ["ὼ"] = "Ὼ", + ["ώ"] = "Ώ", + ["ᾀ"] = "ᾈ", + ["ᾁ"] = "ᾉ", + ["ᾂ"] = "ᾊ", + ["ᾃ"] = "ᾋ", + ["ᾄ"] = "ᾌ", + ["ᾅ"] = "ᾍ", + ["ᾆ"] = "ᾎ", + ["ᾇ"] = "ᾏ", + ["ᾐ"] = "ᾘ", + ["ᾑ"] = "ᾙ", + ["ᾒ"] = "ᾚ", + ["ᾓ"] = "ᾛ", + ["ᾔ"] = "ᾜ", + ["ᾕ"] = "ᾝ", + ["ᾖ"] = "ᾞ", + ["ᾗ"] = "ᾟ", + ["ᾠ"] = "ᾨ", + ["ᾡ"] = "ᾩ", + ["ᾢ"] = "ᾪ", + ["ᾣ"] = "ᾫ", + ["ᾤ"] = "ᾬ", + ["ᾥ"] = "ᾭ", + ["ᾦ"] = "ᾮ", + ["ᾧ"] = "ᾯ", + ["ᾰ"] = "Ᾰ", + ["ᾱ"] = "Ᾱ", + ["ᾳ"] = "ᾼ", + ["ι"] = "Ι", + ["ῃ"] = "ῌ", + ["ῐ"] = "Ῐ", + ["ῑ"] = "Ῑ", + ["ῠ"] = "Ῠ", + ["ῡ"] = "Ῡ", + ["ῥ"] = "Ῥ", + ["ῳ"] = "ῼ", + ["ⅎ"] = "Ⅎ", + ["ⅰ"] = "Ⅰ", + ["ⅱ"] = "Ⅱ", + ["ⅲ"] = "Ⅲ", + ["ⅳ"] = "Ⅳ", + ["ⅴ"] = "Ⅴ", + ["ⅵ"] = "Ⅵ", + ["ⅶ"] = "Ⅶ", + ["ⅷ"] = "Ⅷ", + ["ⅸ"] = "Ⅸ", + ["ⅹ"] = "Ⅹ", + ["ⅺ"] = "Ⅺ", + ["ⅻ"] = "Ⅻ", + ["ⅼ"] = "Ⅼ", + ["ⅽ"] = "Ⅽ", + ["ⅾ"] = "Ⅾ", + ["ⅿ"] = "Ⅿ", + ["ↄ"] = "Ↄ", + ["ⓐ"] = "Ⓐ", + ["ⓑ"] = "Ⓑ", + ["ⓒ"] = "Ⓒ", + ["ⓓ"] = "Ⓓ", + ["ⓔ"] = "Ⓔ", + ["ⓕ"] = "Ⓕ", + ["ⓖ"] = "Ⓖ", + ["ⓗ"] = "Ⓗ", + ["ⓘ"] = "Ⓘ", + ["ⓙ"] = "Ⓙ", + ["ⓚ"] = "Ⓚ", + ["ⓛ"] = "Ⓛ", + ["ⓜ"] = "Ⓜ", + ["ⓝ"] = "Ⓝ", + ["ⓞ"] = "Ⓞ", + ["ⓟ"] = "Ⓟ", + ["ⓠ"] = "Ⓠ", + ["ⓡ"] = "Ⓡ", + ["ⓢ"] = "Ⓢ", + ["ⓣ"] = "Ⓣ", + ["ⓤ"] = "Ⓤ", + ["ⓥ"] = "Ⓥ", + ["ⓦ"] = "Ⓦ", + ["ⓧ"] = "Ⓧ", + ["ⓨ"] = "Ⓨ", + ["ⓩ"] = "Ⓩ", + ["ⰰ"] = "Ⰰ", + ["ⰱ"] = "Ⰱ", + ["ⰲ"] = "Ⰲ", + ["ⰳ"] = "Ⰳ", + ["ⰴ"] = "Ⰴ", + ["ⰵ"] = "Ⰵ", + ["ⰶ"] = "Ⰶ", + ["ⰷ"] = "Ⰷ", + ["ⰸ"] = "Ⰸ", + ["ⰹ"] = "Ⰹ", + ["ⰺ"] = "Ⰺ", + ["ⰻ"] = "Ⰻ", + ["ⰼ"] = "Ⰼ", + ["ⰽ"] = "Ⰽ", + ["ⰾ"] = "Ⰾ", + ["ⰿ"] = "Ⰿ", + ["ⱀ"] = "Ⱀ", + ["ⱁ"] = "Ⱁ", + ["ⱂ"] = "Ⱂ", + ["ⱃ"] = "Ⱃ", + ["ⱄ"] = "Ⱄ", + ["ⱅ"] = "Ⱅ", + ["ⱆ"] = "Ⱆ", + ["ⱇ"] = "Ⱇ", + ["ⱈ"] = "Ⱈ", + ["ⱉ"] = "Ⱉ", + ["ⱊ"] = "Ⱊ", + ["ⱋ"] = "Ⱋ", + ["ⱌ"] = "Ⱌ", + ["ⱍ"] = "Ⱍ", + ["ⱎ"] = "Ⱎ", + ["ⱏ"] = "Ⱏ", + ["ⱐ"] = "Ⱐ", + ["ⱑ"] = "Ⱑ", + ["ⱒ"] = "Ⱒ", + ["ⱓ"] = "Ⱓ", + ["ⱔ"] = "Ⱔ", + ["ⱕ"] = "Ⱕ", + ["ⱖ"] = "Ⱖ", + ["ⱗ"] = "Ⱗ", + ["ⱘ"] = "Ⱘ", + ["ⱙ"] = "Ⱙ", + ["ⱚ"] = "Ⱚ", + ["ⱛ"] = "Ⱛ", + ["ⱜ"] = "Ⱜ", + ["ⱝ"] = "Ⱝ", + ["ⱞ"] = "Ⱞ", + ["ⱡ"] = "Ⱡ", + ["ⱥ"] = "Ⱥ", + ["ⱦ"] = "Ⱦ", + ["ⱨ"] = "Ⱨ", + ["ⱪ"] = "Ⱪ", + ["ⱬ"] = "Ⱬ", + ["ⱶ"] = "Ⱶ", + ["ⲁ"] = "Ⲁ", + ["ⲃ"] = "Ⲃ", + ["ⲅ"] = "Ⲅ", + ["ⲇ"] = "Ⲇ", + ["ⲉ"] = "Ⲉ", + ["ⲋ"] = "Ⲋ", + ["ⲍ"] = "Ⲍ", + ["ⲏ"] = "Ⲏ", + ["ⲑ"] = "Ⲑ", + ["ⲓ"] = "Ⲓ", + ["ⲕ"] = "Ⲕ", + ["ⲗ"] = "Ⲗ", + ["ⲙ"] = "Ⲙ", + ["ⲛ"] = "Ⲛ", + ["ⲝ"] = "Ⲝ", + ["ⲟ"] = "Ⲟ", + ["ⲡ"] = "Ⲡ", + ["ⲣ"] = "Ⲣ", + ["ⲥ"] = "Ⲥ", + ["ⲧ"] = "Ⲧ", + ["ⲩ"] = "Ⲩ", + ["ⲫ"] = "Ⲫ", + ["ⲭ"] = "Ⲭ", + ["ⲯ"] = "Ⲯ", + ["ⲱ"] = "Ⲱ", + ["ⲳ"] = "Ⲳ", + ["ⲵ"] = "Ⲵ", + ["ⲷ"] = "Ⲷ", + ["ⲹ"] = "Ⲹ", + ["ⲻ"] = "Ⲻ", + ["ⲽ"] = "Ⲽ", + ["ⲿ"] = "Ⲿ", + ["ⳁ"] = "Ⳁ", + ["ⳃ"] = "Ⳃ", + ["ⳅ"] = "Ⳅ", + ["ⳇ"] = "Ⳇ", + ["ⳉ"] = "Ⳉ", + ["ⳋ"] = "Ⳋ", + ["ⳍ"] = "Ⳍ", + ["ⳏ"] = "Ⳏ", + ["ⳑ"] = "Ⳑ", + ["ⳓ"] = "Ⳓ", + ["ⳕ"] = "Ⳕ", + ["ⳗ"] = "Ⳗ", + ["ⳙ"] = "Ⳙ", + ["ⳛ"] = "Ⳛ", + ["ⳝ"] = "Ⳝ", + ["ⳟ"] = "Ⳟ", + ["ⳡ"] = "Ⳡ", + ["ⳣ"] = "Ⳣ", + ["ⴀ"] = "Ⴀ", + ["ⴁ"] = "Ⴁ", + ["ⴂ"] = "Ⴂ", + ["ⴃ"] = "Ⴃ", + ["ⴄ"] = "Ⴄ", + ["ⴅ"] = "Ⴅ", + ["ⴆ"] = "Ⴆ", + ["ⴇ"] = "Ⴇ", + ["ⴈ"] = "Ⴈ", + ["ⴉ"] = "Ⴉ", + ["ⴊ"] = "Ⴊ", + ["ⴋ"] = "Ⴋ", + ["ⴌ"] = "Ⴌ", + ["ⴍ"] = "Ⴍ", + ["ⴎ"] = "Ⴎ", + ["ⴏ"] = "Ⴏ", + ["ⴐ"] = "Ⴐ", + ["ⴑ"] = "Ⴑ", + ["ⴒ"] = "Ⴒ", + ["ⴓ"] = "Ⴓ", + ["ⴔ"] = "Ⴔ", + ["ⴕ"] = "Ⴕ", + ["ⴖ"] = "Ⴖ", + ["ⴗ"] = "Ⴗ", + ["ⴘ"] = "Ⴘ", + ["ⴙ"] = "Ⴙ", + ["ⴚ"] = "Ⴚ", + ["ⴛ"] = "Ⴛ", + ["ⴜ"] = "Ⴜ", + ["ⴝ"] = "Ⴝ", + ["ⴞ"] = "Ⴞ", + ["ⴟ"] = "Ⴟ", + ["ⴠ"] = "Ⴠ", + ["ⴡ"] = "Ⴡ", + ["ⴢ"] = "Ⴢ", + ["ⴣ"] = "Ⴣ", + ["ⴤ"] = "Ⴤ", + ["ⴥ"] = "Ⴥ", + ["a"] = "A", + ["b"] = "B", + ["c"] = "C", + ["d"] = "D", + ["e"] = "E", + ["f"] = "F", + ["g"] = "G", + ["h"] = "H", + ["i"] = "I", + ["j"] = "J", + ["k"] = "K", + ["l"] = "L", + ["m"] = "M", + ["n"] = "N", + ["o"] = "O", + ["p"] = "P", + ["q"] = "Q", + ["r"] = "R", + ["s"] = "S", + ["t"] = "T", + ["u"] = "U", + ["v"] = "V", + ["w"] = "W", + ["x"] = "X", + ["y"] = "Y", + ["z"] = "Z", + ["𐐨"] = "𐐀", + ["𐐩"] = "𐐁", + ["𐐪"] = "𐐂", + ["𐐫"] = "𐐃", + ["𐐬"] = "𐐄", + ["𐐭"] = "𐐅", + ["𐐮"] = "𐐆", + ["𐐯"] = "𐐇", + ["𐐰"] = "𐐈", + ["𐐱"] = "𐐉", + ["𐐲"] = "𐐊", + ["𐐳"] = "𐐋", + ["𐐴"] = "𐐌", + ["𐐵"] = "𐐍", + ["𐐶"] = "𐐎", + ["𐐷"] = "𐐏", + ["𐐸"] = "𐐐", + ["𐐹"] = "𐐑", + ["𐐺"] = "𐐒", + ["𐐻"] = "𐐓", + ["𐐼"] = "𐐔", + ["𐐽"] = "𐐕", + ["𐐾"] = "𐐖", + ["𐐿"] = "𐐗", + ["𐑀"] = "𐐘", + ["𐑁"] = "𐐙", + ["𐑂"] = "𐐚", + ["𐑃"] = "𐐛", + ["𐑄"] = "𐐜", + ["𐑅"] = "𐐝", + ["𐑆"] = "𐐞", + ["𐑇"] = "𐐟", + ["𐑈"] = "𐐠", + ["𐑉"] = "𐐡", + ["𐑊"] = "𐐢", + ["𐑋"] = "𐐣", + ["𐑌"] = "𐐤", + ["𐑍"] = "𐐥", + ["𐑎"] = "𐐦", + ["𐑏"] = "𐐧", } - utf8_uc_lc = { - ["A"] = "a", - ["B"] = "b", - ["C"] = "c", - ["D"] = "d", - ["E"] = "e", - ["F"] = "f", - ["G"] = "g", - ["H"] = "h", - ["I"] = "i", - ["J"] = "j", - ["K"] = "k", - ["L"] = "l", - ["M"] = "m", - ["N"] = "n", - ["O"] = "o", - ["P"] = "p", - ["Q"] = "q", - ["R"] = "r", - ["S"] = "s", - ["T"] = "t", - ["U"] = "u", - ["V"] = "v", - ["W"] = "w", - ["X"] = "x", - ["Y"] = "y", - ["Z"] = "z", - ["À"] = "à", - ["Á"] = "á", - ["Â"] = "â", - ["Ã"] = "ã", - ["Ä"] = "ä", - ["Å"] = "å", - ["Æ"] = "æ", - ["Ç"] = "ç", - ["È"] = "è", - ["É"] = "é", - ["Ê"] = "ê", - ["Ë"] = "ë", - ["Ì"] = "ì", - ["Í"] = "í", - ["Î"] = "î", - ["Ï"] = "ï", - ["Ð"] = "ð", - ["Ñ"] = "ñ", - ["Ò"] = "ò", - ["Ó"] = "ó", - ["Ô"] = "ô", - ["Õ"] = "õ", - ["Ö"] = "ö", - ["Ø"] = "ø", - ["Ù"] = "ù", - ["Ú"] = "ú", - ["Û"] = "û", - ["Ü"] = "ü", - ["Ý"] = "ý", - ["Þ"] = "þ", - ["Ā"] = "ā", - ["Ă"] = "ă", - ["Ą"] = "ą", - ["Ć"] = "ć", - ["Ĉ"] = "ĉ", - ["Ċ"] = "ċ", - ["Č"] = "č", - ["Ď"] = "ď", - ["Đ"] = "đ", - ["Ē"] = "ē", - ["Ĕ"] = "ĕ", - ["Ė"] = "ė", - ["Ę"] = "ę", - ["Ě"] = "ě", - ["Ĝ"] = "ĝ", - ["Ğ"] = "ğ", - ["Ġ"] = "ġ", - ["Ģ"] = "ģ", - ["Ĥ"] = "ĥ", - ["Ħ"] = "ħ", - ["Ĩ"] = "ĩ", - ["Ī"] = "ī", - ["Ĭ"] = "ĭ", - ["Į"] = "į", - ["İ"] = "i", - ["IJ"] = "ij", - ["Ĵ"] = "ĵ", - ["Ķ"] = "ķ", - ["Ĺ"] = "ĺ", - ["Ļ"] = "ļ", - ["Ľ"] = "ľ", - ["Ŀ"] = "ŀ", - ["Ł"] = "ł", - ["Ń"] = "ń", - ["Ņ"] = "ņ", - ["Ň"] = "ň", - ["Ŋ"] = "ŋ", - ["Ō"] = "ō", - ["Ŏ"] = "ŏ", - ["Ő"] = "ő", - ["Œ"] = "œ", - ["Ŕ"] = "ŕ", - ["Ŗ"] = "ŗ", - ["Ř"] = "ř", - ["Ś"] = "ś", - ["Ŝ"] = "ŝ", - ["Ş"] = "ş", - ["Š"] = "š", - ["Ţ"] = "ţ", - ["Ť"] = "ť", - ["Ŧ"] = "ŧ", - ["Ũ"] = "ũ", - ["Ū"] = "ū", - ["Ŭ"] = "ŭ", - ["Ů"] = "ů", - ["Ű"] = "ű", - ["Ų"] = "ų", - ["Ŵ"] = "ŵ", - ["Ŷ"] = "ŷ", - ["Ÿ"] = "ÿ", - ["Ź"] = "ź", - ["Ż"] = "ż", - ["Ž"] = "ž", - ["Ɓ"] = "ɓ", - ["Ƃ"] = "ƃ", - ["Ƅ"] = "ƅ", - ["Ɔ"] = "ɔ", - ["Ƈ"] = "ƈ", - ["Ɖ"] = "ɖ", - ["Ɗ"] = "ɗ", - ["Ƌ"] = "ƌ", - ["Ǝ"] = "ǝ", - ["Ə"] = "ə", - ["Ɛ"] = "ɛ", - ["Ƒ"] = "ƒ", - ["Ɠ"] = "ɠ", - ["Ɣ"] = "ɣ", - ["Ɩ"] = "ɩ", - ["Ɨ"] = "ɨ", - ["Ƙ"] = "ƙ", - ["Ɯ"] = "ɯ", - ["Ɲ"] = "ɲ", - ["Ɵ"] = "ɵ", - ["Ơ"] = "ơ", - ["Ƣ"] = "ƣ", - ["Ƥ"] = "ƥ", - ["Ʀ"] = "ʀ", - ["Ƨ"] = "ƨ", - ["Ʃ"] = "ʃ", - ["Ƭ"] = "ƭ", - ["Ʈ"] = "ʈ", - ["Ư"] = "ư", - ["Ʊ"] = "ʊ", - ["Ʋ"] = "ʋ", - ["Ƴ"] = "ƴ", - ["Ƶ"] = "ƶ", - ["Ʒ"] = "ʒ", - ["Ƹ"] = "ƹ", - ["Ƽ"] = "ƽ", - ["DŽ"] = "dž", - ["Dž"] = "dž", - ["LJ"] = "lj", - ["Lj"] = "lj", - ["NJ"] = "nj", - ["Nj"] = "nj", - ["Ǎ"] = "ǎ", - ["Ǐ"] = "ǐ", - ["Ǒ"] = "ǒ", - ["Ǔ"] = "ǔ", - ["Ǖ"] = "ǖ", - ["Ǘ"] = "ǘ", - ["Ǚ"] = "ǚ", - ["Ǜ"] = "ǜ", - ["Ǟ"] = "ǟ", - ["Ǡ"] = "ǡ", - ["Ǣ"] = "ǣ", - ["Ǥ"] = "ǥ", - ["Ǧ"] = "ǧ", - ["Ǩ"] = "ǩ", - ["Ǫ"] = "ǫ", - ["Ǭ"] = "ǭ", - ["Ǯ"] = "ǯ", - ["DZ"] = "dz", - ["Dz"] = "dz", - ["Ǵ"] = "ǵ", - ["Ƕ"] = "ƕ", - ["Ƿ"] = "ƿ", - ["Ǹ"] = "ǹ", - ["Ǻ"] = "ǻ", - ["Ǽ"] = "ǽ", - ["Ǿ"] = "ǿ", - ["Ȁ"] = "ȁ", - ["Ȃ"] = "ȃ", - ["Ȅ"] = "ȅ", - ["Ȇ"] = "ȇ", - ["Ȉ"] = "ȉ", - ["Ȋ"] = "ȋ", - ["Ȍ"] = "ȍ", - ["Ȏ"] = "ȏ", - ["Ȑ"] = "ȑ", - ["Ȓ"] = "ȓ", - ["Ȕ"] = "ȕ", - ["Ȗ"] = "ȗ", - ["Ș"] = "ș", - ["Ț"] = "ț", - ["Ȝ"] = "ȝ", - ["Ȟ"] = "ȟ", - ["Ƞ"] = "ƞ", - ["Ȣ"] = "ȣ", - ["Ȥ"] = "ȥ", - ["Ȧ"] = "ȧ", - ["Ȩ"] = "ȩ", - ["Ȫ"] = "ȫ", - ["Ȭ"] = "ȭ", - ["Ȯ"] = "ȯ", - ["Ȱ"] = "ȱ", - ["Ȳ"] = "ȳ", - ["Ⱥ"] = "ⱥ", - ["Ȼ"] = "ȼ", - ["Ƚ"] = "ƚ", - ["Ⱦ"] = "ⱦ", - ["Ɂ"] = "ɂ", - ["Ƀ"] = "ƀ", - ["Ʉ"] = "ʉ", - ["Ʌ"] = "ʌ", - ["Ɇ"] = "ɇ", - ["Ɉ"] = "ɉ", - ["Ɋ"] = "ɋ", - ["Ɍ"] = "ɍ", - ["Ɏ"] = "ɏ", - ["Ά"] = "ά", - ["Έ"] = "έ", - ["Ή"] = "ή", - ["Ί"] = "ί", - ["Ό"] = "ό", - ["Ύ"] = "ύ", - ["Ώ"] = "ώ", - ["Α"] = "α", - ["Β"] = "β", - ["Γ"] = "γ", - ["Δ"] = "δ", - ["Ε"] = "ε", - ["Ζ"] = "ζ", - ["Η"] = "η", - ["Θ"] = "θ", - ["Ι"] = "ι", - ["Κ"] = "κ", - ["Λ"] = "λ", - ["Μ"] = "μ", - ["Ν"] = "ν", - ["Ξ"] = "ξ", - ["Ο"] = "ο", - ["Π"] = "π", - ["Ρ"] = "ρ", - ["Σ"] = "σ", - ["Τ"] = "τ", - ["Υ"] = "υ", - ["Φ"] = "φ", - ["Χ"] = "χ", - ["Ψ"] = "ψ", - ["Ω"] = "ω", - ["Ϊ"] = "ϊ", - ["Ϋ"] = "ϋ", - ["Ϙ"] = "ϙ", - ["Ϛ"] = "ϛ", - ["Ϝ"] = "ϝ", - ["Ϟ"] = "ϟ", - ["Ϡ"] = "ϡ", - ["Ϣ"] = "ϣ", - ["Ϥ"] = "ϥ", - ["Ϧ"] = "ϧ", - ["Ϩ"] = "ϩ", - ["Ϫ"] = "ϫ", - ["Ϭ"] = "ϭ", - ["Ϯ"] = "ϯ", - ["ϴ"] = "θ", - ["Ϸ"] = "ϸ", - ["Ϲ"] = "ϲ", - ["Ϻ"] = "ϻ", - ["Ͻ"] = "ͻ", - ["Ͼ"] = "ͼ", - ["Ͽ"] = "ͽ", - ["Ѐ"] = "ѐ", - ["Ё"] = "ё", - ["Ђ"] = "ђ", - ["Ѓ"] = "ѓ", - ["Є"] = "є", - ["Ѕ"] = "ѕ", - ["І"] = "і", - ["Ї"] = "ї", - ["Ј"] = "ј", - ["Љ"] = "љ", - ["Њ"] = "њ", - ["Ћ"] = "ћ", - ["Ќ"] = "ќ", - ["Ѝ"] = "ѝ", - ["Ў"] = "ў", - ["Џ"] = "џ", - ["А"] = "а", - ["Б"] = "б", - ["В"] = "в", - ["Г"] = "г", - ["Д"] = "д", - ["Е"] = "е", - ["Ж"] = "ж", - ["З"] = "з", - ["И"] = "и", - ["Й"] = "й", - ["К"] = "к", - ["Л"] = "л", - ["М"] = "м", - ["Н"] = "н", - ["О"] = "о", - ["П"] = "п", - ["Р"] = "р", - ["С"] = "с", - ["Т"] = "т", - ["У"] = "у", - ["Ф"] = "ф", - ["Х"] = "х", - ["Ц"] = "ц", - ["Ч"] = "ч", - ["Ш"] = "ш", - ["Щ"] = "щ", - ["Ъ"] = "ъ", - ["Ы"] = "ы", - ["Ь"] = "ь", - ["Э"] = "э", - ["Ю"] = "ю", - ["Я"] = "я", - ["Ѡ"] = "ѡ", - ["Ѣ"] = "ѣ", - ["Ѥ"] = "ѥ", - ["Ѧ"] = "ѧ", - ["Ѩ"] = "ѩ", - ["Ѫ"] = "ѫ", - ["Ѭ"] = "ѭ", - ["Ѯ"] = "ѯ", - ["Ѱ"] = "ѱ", - ["Ѳ"] = "ѳ", - ["Ѵ"] = "ѵ", - ["Ѷ"] = "ѷ", - ["Ѹ"] = "ѹ", - ["Ѻ"] = "ѻ", - ["Ѽ"] = "ѽ", - ["Ѿ"] = "ѿ", - ["Ҁ"] = "ҁ", - ["Ҋ"] = "ҋ", - ["Ҍ"] = "ҍ", - ["Ҏ"] = "ҏ", - ["Ґ"] = "ґ", - ["Ғ"] = "ғ", - ["Ҕ"] = "ҕ", - ["Җ"] = "җ", - ["Ҙ"] = "ҙ", - ["Қ"] = "қ", - ["Ҝ"] = "ҝ", - ["Ҟ"] = "ҟ", - ["Ҡ"] = "ҡ", - ["Ң"] = "ң", - ["Ҥ"] = "ҥ", - ["Ҧ"] = "ҧ", - ["Ҩ"] = "ҩ", - ["Ҫ"] = "ҫ", - ["Ҭ"] = "ҭ", - ["Ү"] = "ү", - ["Ұ"] = "ұ", - ["Ҳ"] = "ҳ", - ["Ҵ"] = "ҵ", - ["Ҷ"] = "ҷ", - ["Ҹ"] = "ҹ", - ["Һ"] = "һ", - ["Ҽ"] = "ҽ", - ["Ҿ"] = "ҿ", - ["Ӏ"] = "ӏ", - ["Ӂ"] = "ӂ", - ["Ӄ"] = "ӄ", - ["Ӆ"] = "ӆ", - ["Ӈ"] = "ӈ", - ["Ӊ"] = "ӊ", - ["Ӌ"] = "ӌ", - ["Ӎ"] = "ӎ", - ["Ӑ"] = "ӑ", - ["Ӓ"] = "ӓ", - ["Ӕ"] = "ӕ", - ["Ӗ"] = "ӗ", - ["Ә"] = "ә", - ["Ӛ"] = "ӛ", - ["Ӝ"] = "ӝ", - ["Ӟ"] = "ӟ", - ["Ӡ"] = "ӡ", - ["Ӣ"] = "ӣ", - ["Ӥ"] = "ӥ", - ["Ӧ"] = "ӧ", - ["Ө"] = "ө", - ["Ӫ"] = "ӫ", - ["Ӭ"] = "ӭ", - ["Ӯ"] = "ӯ", - ["Ӱ"] = "ӱ", - ["Ӳ"] = "ӳ", - ["Ӵ"] = "ӵ", - ["Ӷ"] = "ӷ", - ["Ӹ"] = "ӹ", - ["Ӻ"] = "ӻ", - ["Ӽ"] = "ӽ", - ["Ӿ"] = "ӿ", - ["Ԁ"] = "ԁ", - ["Ԃ"] = "ԃ", - ["Ԅ"] = "ԅ", - ["Ԇ"] = "ԇ", - ["Ԉ"] = "ԉ", - ["Ԋ"] = "ԋ", - ["Ԍ"] = "ԍ", - ["Ԏ"] = "ԏ", - ["Ԑ"] = "ԑ", - ["Ԓ"] = "ԓ", - ["Ա"] = "ա", - ["Բ"] = "բ", - ["Գ"] = "գ", - ["Դ"] = "դ", - ["Ե"] = "ե", - ["Զ"] = "զ", - ["Է"] = "է", - ["Ը"] = "ը", - ["Թ"] = "թ", - ["Ժ"] = "ժ", - ["Ի"] = "ի", - ["Լ"] = "լ", - ["Խ"] = "խ", - ["Ծ"] = "ծ", - ["Կ"] = "կ", - ["Հ"] = "հ", - ["Ձ"] = "ձ", - ["Ղ"] = "ղ", - ["Ճ"] = "ճ", - ["Մ"] = "մ", - ["Յ"] = "յ", - ["Ն"] = "ն", - ["Շ"] = "շ", - ["Ո"] = "ո", - ["Չ"] = "չ", - ["Պ"] = "պ", - ["Ջ"] = "ջ", - ["Ռ"] = "ռ", - ["Ս"] = "ս", - ["Վ"] = "վ", - ["Տ"] = "տ", - ["Ր"] = "ր", - ["Ց"] = "ց", - ["Ւ"] = "ւ", - ["Փ"] = "փ", - ["Ք"] = "ք", - ["Օ"] = "օ", - ["Ֆ"] = "ֆ", - ["Ⴀ"] = "ⴀ", - ["Ⴁ"] = "ⴁ", - ["Ⴂ"] = "ⴂ", - ["Ⴃ"] = "ⴃ", - ["Ⴄ"] = "ⴄ", - ["Ⴅ"] = "ⴅ", - ["Ⴆ"] = "ⴆ", - ["Ⴇ"] = "ⴇ", - ["Ⴈ"] = "ⴈ", - ["Ⴉ"] = "ⴉ", - ["Ⴊ"] = "ⴊ", - ["Ⴋ"] = "ⴋ", - ["Ⴌ"] = "ⴌ", - ["Ⴍ"] = "ⴍ", - ["Ⴎ"] = "ⴎ", - ["Ⴏ"] = "ⴏ", - ["Ⴐ"] = "ⴐ", - ["Ⴑ"] = "ⴑ", - ["Ⴒ"] = "ⴒ", - ["Ⴓ"] = "ⴓ", - ["Ⴔ"] = "ⴔ", - ["Ⴕ"] = "ⴕ", - ["Ⴖ"] = "ⴖ", - ["Ⴗ"] = "ⴗ", - ["Ⴘ"] = "ⴘ", - ["Ⴙ"] = "ⴙ", - ["Ⴚ"] = "ⴚ", - ["Ⴛ"] = "ⴛ", - ["Ⴜ"] = "ⴜ", - ["Ⴝ"] = "ⴝ", - ["Ⴞ"] = "ⴞ", - ["Ⴟ"] = "ⴟ", - ["Ⴠ"] = "ⴠ", - ["Ⴡ"] = "ⴡ", - ["Ⴢ"] = "ⴢ", - ["Ⴣ"] = "ⴣ", - ["Ⴤ"] = "ⴤ", - ["Ⴥ"] = "ⴥ", - ["Ḁ"] = "ḁ", - ["Ḃ"] = "ḃ", - ["Ḅ"] = "ḅ", - ["Ḇ"] = "ḇ", - ["Ḉ"] = "ḉ", - ["Ḋ"] = "ḋ", - ["Ḍ"] = "ḍ", - ["Ḏ"] = "ḏ", - ["Ḑ"] = "ḑ", - ["Ḓ"] = "ḓ", - ["Ḕ"] = "ḕ", - ["Ḗ"] = "ḗ", - ["Ḙ"] = "ḙ", - ["Ḛ"] = "ḛ", - ["Ḝ"] = "ḝ", - ["Ḟ"] = "ḟ", - ["Ḡ"] = "ḡ", - ["Ḣ"] = "ḣ", - ["Ḥ"] = "ḥ", - ["Ḧ"] = "ḧ", - ["Ḩ"] = "ḩ", - ["Ḫ"] = "ḫ", - ["Ḭ"] = "ḭ", - ["Ḯ"] = "ḯ", - ["Ḱ"] = "ḱ", - ["Ḳ"] = "ḳ", - ["Ḵ"] = "ḵ", - ["Ḷ"] = "ḷ", - ["Ḹ"] = "ḹ", - ["Ḻ"] = "ḻ", - ["Ḽ"] = "ḽ", - ["Ḿ"] = "ḿ", - ["Ṁ"] = "ṁ", - ["Ṃ"] = "ṃ", - ["Ṅ"] = "ṅ", - ["Ṇ"] = "ṇ", - ["Ṉ"] = "ṉ", - ["Ṋ"] = "ṋ", - ["Ṍ"] = "ṍ", - ["Ṏ"] = "ṏ", - ["Ṑ"] = "ṑ", - ["Ṓ"] = "ṓ", - ["Ṕ"] = "ṕ", - ["Ṗ"] = "ṗ", - ["Ṙ"] = "ṙ", - ["Ṛ"] = "ṛ", - ["Ṝ"] = "ṝ", - ["Ṟ"] = "ṟ", - ["Ṡ"] = "ṡ", - ["Ṣ"] = "ṣ", - ["Ṥ"] = "ṥ", - ["Ṧ"] = "ṧ", - ["Ṩ"] = "ṩ", - ["Ṫ"] = "ṫ", - ["Ṭ"] = "ṭ", - ["Ṯ"] = "ṯ", - ["Ṱ"] = "ṱ", - ["Ṳ"] = "ṳ", - ["Ṵ"] = "ṵ", - ["Ṷ"] = "ṷ", - ["Ṹ"] = "ṹ", - ["Ṻ"] = "ṻ", - ["Ṽ"] = "ṽ", - ["Ṿ"] = "ṿ", - ["Ẁ"] = "ẁ", - ["Ẃ"] = "ẃ", - ["Ẅ"] = "ẅ", - ["Ẇ"] = "ẇ", - ["Ẉ"] = "ẉ", - ["Ẋ"] = "ẋ", - ["Ẍ"] = "ẍ", - ["Ẏ"] = "ẏ", - ["Ẑ"] = "ẑ", - ["Ẓ"] = "ẓ", - ["Ẕ"] = "ẕ", - ["Ạ"] = "ạ", - ["Ả"] = "ả", - ["Ấ"] = "ấ", - ["Ầ"] = "ầ", - ["Ẩ"] = "ẩ", - ["Ẫ"] = "ẫ", - ["Ậ"] = "ậ", - ["Ắ"] = "ắ", - ["Ằ"] = "ằ", - ["Ẳ"] = "ẳ", - ["Ẵ"] = "ẵ", - ["Ặ"] = "ặ", - ["Ẹ"] = "ẹ", - ["Ẻ"] = "ẻ", - ["Ẽ"] = "ẽ", - ["Ế"] = "ế", - ["Ề"] = "ề", - ["Ể"] = "ể", - ["Ễ"] = "ễ", - ["Ệ"] = "ệ", - ["Ỉ"] = "ỉ", - ["Ị"] = "ị", - ["Ọ"] = "ọ", - ["Ỏ"] = "ỏ", - ["Ố"] = "ố", - ["Ồ"] = "ồ", - ["Ổ"] = "ổ", - ["Ỗ"] = "ỗ", - ["Ộ"] = "ộ", - ["Ớ"] = "ớ", - ["Ờ"] = "ờ", - ["Ở"] = "ở", - ["Ỡ"] = "ỡ", - ["Ợ"] = "ợ", - ["Ụ"] = "ụ", - ["Ủ"] = "ủ", - ["Ứ"] = "ứ", - ["Ừ"] = "ừ", - ["Ử"] = "ử", - ["Ữ"] = "ữ", - ["Ự"] = "ự", - ["Ỳ"] = "ỳ", - ["Ỵ"] = "ỵ", - ["Ỷ"] = "ỷ", - ["Ỹ"] = "ỹ", - ["Ἀ"] = "ἀ", - ["Ἁ"] = "ἁ", - ["Ἂ"] = "ἂ", - ["Ἃ"] = "ἃ", - ["Ἄ"] = "ἄ", - ["Ἅ"] = "ἅ", - ["Ἆ"] = "ἆ", - ["Ἇ"] = "ἇ", - ["Ἐ"] = "ἐ", - ["Ἑ"] = "ἑ", - ["Ἒ"] = "ἒ", - ["Ἓ"] = "ἓ", - ["Ἔ"] = "ἔ", - ["Ἕ"] = "ἕ", - ["Ἠ"] = "ἠ", - ["Ἡ"] = "ἡ", - ["Ἢ"] = "ἢ", - ["Ἣ"] = "ἣ", - ["Ἤ"] = "ἤ", - ["Ἥ"] = "ἥ", - ["Ἦ"] = "ἦ", - ["Ἧ"] = "ἧ", - ["Ἰ"] = "ἰ", - ["Ἱ"] = "ἱ", - ["Ἲ"] = "ἲ", - ["Ἳ"] = "ἳ", - ["Ἴ"] = "ἴ", - ["Ἵ"] = "ἵ", - ["Ἶ"] = "ἶ", - ["Ἷ"] = "ἷ", - ["Ὀ"] = "ὀ", - ["Ὁ"] = "ὁ", - ["Ὂ"] = "ὂ", - ["Ὃ"] = "ὃ", - ["Ὄ"] = "ὄ", - ["Ὅ"] = "ὅ", - ["Ὑ"] = "ὑ", - ["Ὓ"] = "ὓ", - ["Ὕ"] = "ὕ", - ["Ὗ"] = "ὗ", - ["Ὠ"] = "ὠ", - ["Ὡ"] = "ὡ", - ["Ὢ"] = "ὢ", - ["Ὣ"] = "ὣ", - ["Ὤ"] = "ὤ", - ["Ὥ"] = "ὥ", - ["Ὦ"] = "ὦ", - ["Ὧ"] = "ὧ", - ["ᾈ"] = "ᾀ", - ["ᾉ"] = "ᾁ", - ["ᾊ"] = "ᾂ", - ["ᾋ"] = "ᾃ", - ["ᾌ"] = "ᾄ", - ["ᾍ"] = "ᾅ", - ["ᾎ"] = "ᾆ", - ["ᾏ"] = "ᾇ", - ["ᾘ"] = "ᾐ", - ["ᾙ"] = "ᾑ", - ["ᾚ"] = "ᾒ", - ["ᾛ"] = "ᾓ", - ["ᾜ"] = "ᾔ", - ["ᾝ"] = "ᾕ", - ["ᾞ"] = "ᾖ", - ["ᾟ"] = "ᾗ", - ["ᾨ"] = "ᾠ", - ["ᾩ"] = "ᾡ", - ["ᾪ"] = "ᾢ", - ["ᾫ"] = "ᾣ", - ["ᾬ"] = "ᾤ", - ["ᾭ"] = "ᾥ", - ["ᾮ"] = "ᾦ", - ["ᾯ"] = "ᾧ", - ["Ᾰ"] = "ᾰ", - ["Ᾱ"] = "ᾱ", - ["Ὰ"] = "ὰ", - ["Ά"] = "ά", - ["ᾼ"] = "ᾳ", - ["Ὲ"] = "ὲ", - ["Έ"] = "έ", - ["Ὴ"] = "ὴ", - ["Ή"] = "ή", - ["ῌ"] = "ῃ", - ["Ῐ"] = "ῐ", - ["Ῑ"] = "ῑ", - ["Ὶ"] = "ὶ", - ["Ί"] = "ί", - ["Ῠ"] = "ῠ", - ["Ῡ"] = "ῡ", - ["Ὺ"] = "ὺ", - ["Ύ"] = "ύ", - ["Ῥ"] = "ῥ", - ["Ὸ"] = "ὸ", - ["Ό"] = "ό", - ["Ὼ"] = "ὼ", - ["Ώ"] = "ώ", - ["ῼ"] = "ῳ", - ["Ω"] = "ω", - ["K"] = "k", - ["Å"] = "å", - ["Ⅎ"] = "ⅎ", - ["Ⅰ"] = "ⅰ", - ["Ⅱ"] = "ⅱ", - ["Ⅲ"] = "ⅲ", - ["Ⅳ"] = "ⅳ", - ["Ⅴ"] = "ⅴ", - ["Ⅵ"] = "ⅵ", - ["Ⅶ"] = "ⅶ", - ["Ⅷ"] = "ⅷ", - ["Ⅸ"] = "ⅸ", - ["Ⅹ"] = "ⅹ", - ["Ⅺ"] = "ⅺ", - ["Ⅻ"] = "ⅻ", - ["Ⅼ"] = "ⅼ", - ["Ⅽ"] = "ⅽ", - ["Ⅾ"] = "ⅾ", - ["Ⅿ"] = "ⅿ", - ["Ↄ"] = "ↄ", - ["Ⓐ"] = "ⓐ", - ["Ⓑ"] = "ⓑ", - ["Ⓒ"] = "ⓒ", - ["Ⓓ"] = "ⓓ", - ["Ⓔ"] = "ⓔ", - ["Ⓕ"] = "ⓕ", - ["Ⓖ"] = "ⓖ", - ["Ⓗ"] = "ⓗ", - ["Ⓘ"] = "ⓘ", - ["Ⓙ"] = "ⓙ", - ["Ⓚ"] = "ⓚ", - ["Ⓛ"] = "ⓛ", - ["Ⓜ"] = "ⓜ", - ["Ⓝ"] = "ⓝ", - ["Ⓞ"] = "ⓞ", - ["Ⓟ"] = "ⓟ", - ["Ⓠ"] = "ⓠ", - ["Ⓡ"] = "ⓡ", - ["Ⓢ"] = "ⓢ", - ["Ⓣ"] = "ⓣ", - ["Ⓤ"] = "ⓤ", - ["Ⓥ"] = "ⓥ", - ["Ⓦ"] = "ⓦ", - ["Ⓧ"] = "ⓧ", - ["Ⓨ"] = "ⓨ", - ["Ⓩ"] = "ⓩ", - ["Ⰰ"] = "ⰰ", - ["Ⰱ"] = "ⰱ", - ["Ⰲ"] = "ⰲ", - ["Ⰳ"] = "ⰳ", - ["Ⰴ"] = "ⰴ", - ["Ⰵ"] = "ⰵ", - ["Ⰶ"] = "ⰶ", - ["Ⰷ"] = "ⰷ", - ["Ⰸ"] = "ⰸ", - ["Ⰹ"] = "ⰹ", - ["Ⰺ"] = "ⰺ", - ["Ⰻ"] = "ⰻ", - ["Ⰼ"] = "ⰼ", - ["Ⰽ"] = "ⰽ", - ["Ⰾ"] = "ⰾ", - ["Ⰿ"] = "ⰿ", - ["Ⱀ"] = "ⱀ", - ["Ⱁ"] = "ⱁ", - ["Ⱂ"] = "ⱂ", - ["Ⱃ"] = "ⱃ", - ["Ⱄ"] = "ⱄ", - ["Ⱅ"] = "ⱅ", - ["Ⱆ"] = "ⱆ", - ["Ⱇ"] = "ⱇ", - ["Ⱈ"] = "ⱈ", - ["Ⱉ"] = "ⱉ", - ["Ⱊ"] = "ⱊ", - ["Ⱋ"] = "ⱋ", - ["Ⱌ"] = "ⱌ", - ["Ⱍ"] = "ⱍ", - ["Ⱎ"] = "ⱎ", - ["Ⱏ"] = "ⱏ", - ["Ⱐ"] = "ⱐ", - ["Ⱑ"] = "ⱑ", - ["Ⱒ"] = "ⱒ", - ["Ⱓ"] = "ⱓ", - ["Ⱔ"] = "ⱔ", - ["Ⱕ"] = "ⱕ", - ["Ⱖ"] = "ⱖ", - ["Ⱗ"] = "ⱗ", - ["Ⱘ"] = "ⱘ", - ["Ⱙ"] = "ⱙ", - ["Ⱚ"] = "ⱚ", - ["Ⱛ"] = "ⱛ", - ["Ⱜ"] = "ⱜ", - ["Ⱝ"] = "ⱝ", - ["Ⱞ"] = "ⱞ", - ["Ⱡ"] = "ⱡ", - ["Ɫ"] = "ɫ", - ["Ᵽ"] = "ᵽ", - ["Ɽ"] = "ɽ", - ["Ⱨ"] = "ⱨ", - ["Ⱪ"] = "ⱪ", - ["Ⱬ"] = "ⱬ", - ["Ⱶ"] = "ⱶ", - ["Ⲁ"] = "ⲁ", - ["Ⲃ"] = "ⲃ", - ["Ⲅ"] = "ⲅ", - ["Ⲇ"] = "ⲇ", - ["Ⲉ"] = "ⲉ", - ["Ⲋ"] = "ⲋ", - ["Ⲍ"] = "ⲍ", - ["Ⲏ"] = "ⲏ", - ["Ⲑ"] = "ⲑ", - ["Ⲓ"] = "ⲓ", - ["Ⲕ"] = "ⲕ", - ["Ⲗ"] = "ⲗ", - ["Ⲙ"] = "ⲙ", - ["Ⲛ"] = "ⲛ", - ["Ⲝ"] = "ⲝ", - ["Ⲟ"] = "ⲟ", - ["Ⲡ"] = "ⲡ", - ["Ⲣ"] = "ⲣ", - ["Ⲥ"] = "ⲥ", - ["Ⲧ"] = "ⲧ", - ["Ⲩ"] = "ⲩ", - ["Ⲫ"] = "ⲫ", - ["Ⲭ"] = "ⲭ", - ["Ⲯ"] = "ⲯ", - ["Ⲱ"] = "ⲱ", - ["Ⲳ"] = "ⲳ", - ["Ⲵ"] = "ⲵ", - ["Ⲷ"] = "ⲷ", - ["Ⲹ"] = "ⲹ", - ["Ⲻ"] = "ⲻ", - ["Ⲽ"] = "ⲽ", - ["Ⲿ"] = "ⲿ", - ["Ⳁ"] = "ⳁ", - ["Ⳃ"] = "ⳃ", - ["Ⳅ"] = "ⳅ", - ["Ⳇ"] = "ⳇ", - ["Ⳉ"] = "ⳉ", - ["Ⳋ"] = "ⳋ", - ["Ⳍ"] = "ⳍ", - ["Ⳏ"] = "ⳏ", - ["Ⳑ"] = "ⳑ", - ["Ⳓ"] = "ⳓ", - ["Ⳕ"] = "ⳕ", - ["Ⳗ"] = "ⳗ", - ["Ⳙ"] = "ⳙ", - ["Ⳛ"] = "ⳛ", - ["Ⳝ"] = "ⳝ", - ["Ⳟ"] = "ⳟ", - ["Ⳡ"] = "ⳡ", - ["Ⳣ"] = "ⳣ", - ["A"] = "a", - ["B"] = "b", - ["C"] = "c", - ["D"] = "d", - ["E"] = "e", - ["F"] = "f", - ["G"] = "g", - ["H"] = "h", - ["I"] = "i", - ["J"] = "j", - ["K"] = "k", - ["L"] = "l", - ["M"] = "m", - ["N"] = "n", - ["O"] = "o", - ["P"] = "p", - ["Q"] = "q", - ["R"] = "r", - ["S"] = "s", - ["T"] = "t", - ["U"] = "u", - ["V"] = "v", - ["W"] = "w", - ["X"] = "x", - ["Y"] = "y", - ["Z"] = "z", - ["𐐀"] = "𐐨", - ["𐐁"] = "𐐩", - ["𐐂"] = "𐐪", - ["𐐃"] = "𐐫", - ["𐐄"] = "𐐬", - ["𐐅"] = "𐐭", - ["𐐆"] = "𐐮", - ["𐐇"] = "𐐯", - ["𐐈"] = "𐐰", - ["𐐉"] = "𐐱", - ["𐐊"] = "𐐲", - ["𐐋"] = "𐐳", - ["𐐌"] = "𐐴", - ["𐐍"] = "𐐵", - ["𐐎"] = "𐐶", - ["𐐏"] = "𐐷", - ["𐐐"] = "𐐸", - ["𐐑"] = "𐐹", - ["𐐒"] = "𐐺", - ["𐐓"] = "𐐻", - ["𐐔"] = "𐐼", - ["𐐕"] = "𐐽", - ["𐐖"] = "𐐾", - ["𐐗"] = "𐐿", - ["𐐘"] = "𐑀", - ["𐐙"] = "𐑁", - ["𐐚"] = "𐑂", - ["𐐛"] = "𐑃", - ["𐐜"] = "𐑄", - ["𐐝"] = "𐑅", - ["𐐞"] = "𐑆", - ["𐐟"] = "𐑇", - ["𐐠"] = "𐑈", - ["𐐡"] = "𐑉", - ["𐐢"] = "𐑊", - ["𐐣"] = "𐑋", - ["𐐤"] = "𐑌", - ["𐐥"] = "𐑍", - ["𐐦"] = "𐑎", - ["𐐧"] = "𐑏", + ["A"] = "a", + ["B"] = "b", + ["C"] = "c", + ["D"] = "d", + ["E"] = "e", + ["F"] = "f", + ["G"] = "g", + ["H"] = "h", + ["I"] = "i", + ["J"] = "j", + ["K"] = "k", + ["L"] = "l", + ["M"] = "m", + ["N"] = "n", + ["O"] = "o", + ["P"] = "p", + ["Q"] = "q", + ["R"] = "r", + ["S"] = "s", + ["T"] = "t", + ["U"] = "u", + ["V"] = "v", + ["W"] = "w", + ["X"] = "x", + ["Y"] = "y", + ["Z"] = "z", + ["À"] = "à", + ["Á"] = "á", + ["Â"] = "â", + ["Ã"] = "ã", + ["Ä"] = "ä", + ["Å"] = "å", + ["Æ"] = "æ", + ["Ç"] = "ç", + ["È"] = "è", + ["É"] = "é", + ["Ê"] = "ê", + ["Ë"] = "ë", + ["Ì"] = "ì", + ["Í"] = "í", + ["Î"] = "î", + ["Ï"] = "ï", + ["Ð"] = "ð", + ["Ñ"] = "ñ", + ["Ò"] = "ò", + ["Ó"] = "ó", + ["Ô"] = "ô", + ["Õ"] = "õ", + ["Ö"] = "ö", + ["Ø"] = "ø", + ["Ù"] = "ù", + ["Ú"] = "ú", + ["Û"] = "û", + ["Ü"] = "ü", + ["Ý"] = "ý", + ["Þ"] = "þ", + ["Ā"] = "ā", + ["Ă"] = "ă", + ["Ą"] = "ą", + ["Ć"] = "ć", + ["Ĉ"] = "ĉ", + ["Ċ"] = "ċ", + ["Č"] = "č", + ["Ď"] = "ď", + ["Đ"] = "đ", + ["Ē"] = "ē", + ["Ĕ"] = "ĕ", + ["Ė"] = "ė", + ["Ę"] = "ę", + ["Ě"] = "ě", + ["Ĝ"] = "ĝ", + ["Ğ"] = "ğ", + ["Ġ"] = "ġ", + ["Ģ"] = "ģ", + ["Ĥ"] = "ĥ", + ["Ħ"] = "ħ", + ["Ĩ"] = "ĩ", + ["Ī"] = "ī", + ["Ĭ"] = "ĭ", + ["Į"] = "į", + ["İ"] = "i", + ["IJ"] = "ij", + ["Ĵ"] = "ĵ", + ["Ķ"] = "ķ", + ["Ĺ"] = "ĺ", + ["Ļ"] = "ļ", + ["Ľ"] = "ľ", + ["Ŀ"] = "ŀ", + ["Ł"] = "ł", + ["Ń"] = "ń", + ["Ņ"] = "ņ", + ["Ň"] = "ň", + ["Ŋ"] = "ŋ", + ["Ō"] = "ō", + ["Ŏ"] = "ŏ", + ["Ő"] = "ő", + ["Œ"] = "œ", + ["Ŕ"] = "ŕ", + ["Ŗ"] = "ŗ", + ["Ř"] = "ř", + ["Ś"] = "ś", + ["Ŝ"] = "ŝ", + ["Ş"] = "ş", + ["Š"] = "š", + ["Ţ"] = "ţ", + ["Ť"] = "ť", + ["Ŧ"] = "ŧ", + ["Ũ"] = "ũ", + ["Ū"] = "ū", + ["Ŭ"] = "ŭ", + ["Ů"] = "ů", + ["Ű"] = "ű", + ["Ų"] = "ų", + ["Ŵ"] = "ŵ", + ["Ŷ"] = "ŷ", + ["Ÿ"] = "ÿ", + ["Ź"] = "ź", + ["Ż"] = "ż", + ["Ž"] = "ž", + ["Ɓ"] = "ɓ", + ["Ƃ"] = "ƃ", + ["Ƅ"] = "ƅ", + ["Ɔ"] = "ɔ", + ["Ƈ"] = "ƈ", + ["Ɖ"] = "ɖ", + ["Ɗ"] = "ɗ", + ["Ƌ"] = "ƌ", + ["Ǝ"] = "ǝ", + ["Ə"] = "ə", + ["Ɛ"] = "ɛ", + ["Ƒ"] = "ƒ", + ["Ɠ"] = "ɠ", + ["Ɣ"] = "ɣ", + ["Ɩ"] = "ɩ", + ["Ɨ"] = "ɨ", + ["Ƙ"] = "ƙ", + ["Ɯ"] = "ɯ", + ["Ɲ"] = "ɲ", + ["Ɵ"] = "ɵ", + ["Ơ"] = "ơ", + ["Ƣ"] = "ƣ", + ["Ƥ"] = "ƥ", + ["Ʀ"] = "ʀ", + ["Ƨ"] = "ƨ", + ["Ʃ"] = "ʃ", + ["Ƭ"] = "ƭ", + ["Ʈ"] = "ʈ", + ["Ư"] = "ư", + ["Ʊ"] = "ʊ", + ["Ʋ"] = "ʋ", + ["Ƴ"] = "ƴ", + ["Ƶ"] = "ƶ", + ["Ʒ"] = "ʒ", + ["Ƹ"] = "ƹ", + ["Ƽ"] = "ƽ", + ["DŽ"] = "dž", + ["Dž"] = "dž", + ["LJ"] = "lj", + ["Lj"] = "lj", + ["NJ"] = "nj", + ["Nj"] = "nj", + ["Ǎ"] = "ǎ", + ["Ǐ"] = "ǐ", + ["Ǒ"] = "ǒ", + ["Ǔ"] = "ǔ", + ["Ǖ"] = "ǖ", + ["Ǘ"] = "ǘ", + ["Ǚ"] = "ǚ", + ["Ǜ"] = "ǜ", + ["Ǟ"] = "ǟ", + ["Ǡ"] = "ǡ", + ["Ǣ"] = "ǣ", + ["Ǥ"] = "ǥ", + ["Ǧ"] = "ǧ", + ["Ǩ"] = "ǩ", + ["Ǫ"] = "ǫ", + ["Ǭ"] = "ǭ", + ["Ǯ"] = "ǯ", + ["DZ"] = "dz", + ["Dz"] = "dz", + ["Ǵ"] = "ǵ", + ["Ƕ"] = "ƕ", + ["Ƿ"] = "ƿ", + ["Ǹ"] = "ǹ", + ["Ǻ"] = "ǻ", + ["Ǽ"] = "ǽ", + ["Ǿ"] = "ǿ", + ["Ȁ"] = "ȁ", + ["Ȃ"] = "ȃ", + ["Ȅ"] = "ȅ", + ["Ȇ"] = "ȇ", + ["Ȉ"] = "ȉ", + ["Ȋ"] = "ȋ", + ["Ȍ"] = "ȍ", + ["Ȏ"] = "ȏ", + ["Ȑ"] = "ȑ", + ["Ȓ"] = "ȓ", + ["Ȕ"] = "ȕ", + ["Ȗ"] = "ȗ", + ["Ș"] = "ș", + ["Ț"] = "ț", + ["Ȝ"] = "ȝ", + ["Ȟ"] = "ȟ", + ["Ƞ"] = "ƞ", + ["Ȣ"] = "ȣ", + ["Ȥ"] = "ȥ", + ["Ȧ"] = "ȧ", + ["Ȩ"] = "ȩ", + ["Ȫ"] = "ȫ", + ["Ȭ"] = "ȭ", + ["Ȯ"] = "ȯ", + ["Ȱ"] = "ȱ", + ["Ȳ"] = "ȳ", + ["Ⱥ"] = "ⱥ", + ["Ȼ"] = "ȼ", + ["Ƚ"] = "ƚ", + ["Ⱦ"] = "ⱦ", + ["Ɂ"] = "ɂ", + ["Ƀ"] = "ƀ", + ["Ʉ"] = "ʉ", + ["Ʌ"] = "ʌ", + ["Ɇ"] = "ɇ", + ["Ɉ"] = "ɉ", + ["Ɋ"] = "ɋ", + ["Ɍ"] = "ɍ", + ["Ɏ"] = "ɏ", + ["Ά"] = "ά", + ["Έ"] = "έ", + ["Ή"] = "ή", + ["Ί"] = "ί", + ["Ό"] = "ό", + ["Ύ"] = "ύ", + ["Ώ"] = "ώ", + ["Α"] = "α", + ["Β"] = "β", + ["Γ"] = "γ", + ["Δ"] = "δ", + ["Ε"] = "ε", + ["Ζ"] = "ζ", + ["Η"] = "η", + ["Θ"] = "θ", + ["Ι"] = "ι", + ["Κ"] = "κ", + ["Λ"] = "λ", + ["Μ"] = "μ", + ["Ν"] = "ν", + ["Ξ"] = "ξ", + ["Ο"] = "ο", + ["Π"] = "π", + ["Ρ"] = "ρ", + ["Σ"] = "σ", + ["Τ"] = "τ", + ["Υ"] = "υ", + ["Φ"] = "φ", + ["Χ"] = "χ", + ["Ψ"] = "ψ", + ["Ω"] = "ω", + ["Ϊ"] = "ϊ", + ["Ϋ"] = "ϋ", + ["Ϙ"] = "ϙ", + ["Ϛ"] = "ϛ", + ["Ϝ"] = "ϝ", + ["Ϟ"] = "ϟ", + ["Ϡ"] = "ϡ", + ["Ϣ"] = "ϣ", + ["Ϥ"] = "ϥ", + ["Ϧ"] = "ϧ", + ["Ϩ"] = "ϩ", + ["Ϫ"] = "ϫ", + ["Ϭ"] = "ϭ", + ["Ϯ"] = "ϯ", + ["ϴ"] = "θ", + ["Ϸ"] = "ϸ", + ["Ϲ"] = "ϲ", + ["Ϻ"] = "ϻ", + ["Ͻ"] = "ͻ", + ["Ͼ"] = "ͼ", + ["Ͽ"] = "ͽ", + ["Ѐ"] = "ѐ", + ["Ё"] = "ё", + ["Ђ"] = "ђ", + ["Ѓ"] = "ѓ", + ["Є"] = "є", + ["Ѕ"] = "ѕ", + ["І"] = "і", + ["Ї"] = "ї", + ["Ј"] = "ј", + ["Љ"] = "љ", + ["Њ"] = "њ", + ["Ћ"] = "ћ", + ["Ќ"] = "ќ", + ["Ѝ"] = "ѝ", + ["Ў"] = "ў", + ["Џ"] = "џ", + ["А"] = "а", + ["Б"] = "б", + ["В"] = "в", + ["Г"] = "г", + ["Д"] = "д", + ["Е"] = "е", + ["Ж"] = "ж", + ["З"] = "з", + ["И"] = "и", + ["Й"] = "й", + ["К"] = "к", + ["Л"] = "л", + ["М"] = "м", + ["Н"] = "н", + ["О"] = "о", + ["П"] = "п", + ["Р"] = "р", + ["С"] = "с", + ["Т"] = "т", + ["У"] = "у", + ["Ф"] = "ф", + ["Х"] = "х", + ["Ц"] = "ц", + ["Ч"] = "ч", + ["Ш"] = "ш", + ["Щ"] = "щ", + ["Ъ"] = "ъ", + ["Ы"] = "ы", + ["Ь"] = "ь", + ["Э"] = "э", + ["Ю"] = "ю", + ["Я"] = "я", + ["Ѡ"] = "ѡ", + ["Ѣ"] = "ѣ", + ["Ѥ"] = "ѥ", + ["Ѧ"] = "ѧ", + ["Ѩ"] = "ѩ", + ["Ѫ"] = "ѫ", + ["Ѭ"] = "ѭ", + ["Ѯ"] = "ѯ", + ["Ѱ"] = "ѱ", + ["Ѳ"] = "ѳ", + ["Ѵ"] = "ѵ", + ["Ѷ"] = "ѷ", + ["Ѹ"] = "ѹ", + ["Ѻ"] = "ѻ", + ["Ѽ"] = "ѽ", + ["Ѿ"] = "ѿ", + ["Ҁ"] = "ҁ", + ["Ҋ"] = "ҋ", + ["Ҍ"] = "ҍ", + ["Ҏ"] = "ҏ", + ["Ґ"] = "ґ", + ["Ғ"] = "ғ", + ["Ҕ"] = "ҕ", + ["Җ"] = "җ", + ["Ҙ"] = "ҙ", + ["Қ"] = "қ", + ["Ҝ"] = "ҝ", + ["Ҟ"] = "ҟ", + ["Ҡ"] = "ҡ", + ["Ң"] = "ң", + ["Ҥ"] = "ҥ", + ["Ҧ"] = "ҧ", + ["Ҩ"] = "ҩ", + ["Ҫ"] = "ҫ", + ["Ҭ"] = "ҭ", + ["Ү"] = "ү", + ["Ұ"] = "ұ", + ["Ҳ"] = "ҳ", + ["Ҵ"] = "ҵ", + ["Ҷ"] = "ҷ", + ["Ҹ"] = "ҹ", + ["Һ"] = "һ", + ["Ҽ"] = "ҽ", + ["Ҿ"] = "ҿ", + ["Ӏ"] = "ӏ", + ["Ӂ"] = "ӂ", + ["Ӄ"] = "ӄ", + ["Ӆ"] = "ӆ", + ["Ӈ"] = "ӈ", + ["Ӊ"] = "ӊ", + ["Ӌ"] = "ӌ", + ["Ӎ"] = "ӎ", + ["Ӑ"] = "ӑ", + ["Ӓ"] = "ӓ", + ["Ӕ"] = "ӕ", + ["Ӗ"] = "ӗ", + ["Ә"] = "ә", + ["Ӛ"] = "ӛ", + ["Ӝ"] = "ӝ", + ["Ӟ"] = "ӟ", + ["Ӡ"] = "ӡ", + ["Ӣ"] = "ӣ", + ["Ӥ"] = "ӥ", + ["Ӧ"] = "ӧ", + ["Ө"] = "ө", + ["Ӫ"] = "ӫ", + ["Ӭ"] = "ӭ", + ["Ӯ"] = "ӯ", + ["Ӱ"] = "ӱ", + ["Ӳ"] = "ӳ", + ["Ӵ"] = "ӵ", + ["Ӷ"] = "ӷ", + ["Ӹ"] = "ӹ", + ["Ӻ"] = "ӻ", + ["Ӽ"] = "ӽ", + ["Ӿ"] = "ӿ", + ["Ԁ"] = "ԁ", + ["Ԃ"] = "ԃ", + ["Ԅ"] = "ԅ", + ["Ԇ"] = "ԇ", + ["Ԉ"] = "ԉ", + ["Ԋ"] = "ԋ", + ["Ԍ"] = "ԍ", + ["Ԏ"] = "ԏ", + ["Ԑ"] = "ԑ", + ["Ԓ"] = "ԓ", + ["Ա"] = "ա", + ["Բ"] = "բ", + ["Գ"] = "գ", + ["Դ"] = "դ", + ["Ե"] = "ե", + ["Զ"] = "զ", + ["Է"] = "է", + ["Ը"] = "ը", + ["Թ"] = "թ", + ["Ժ"] = "ժ", + ["Ի"] = "ի", + ["Լ"] = "լ", + ["Խ"] = "խ", + ["Ծ"] = "ծ", + ["Կ"] = "կ", + ["Հ"] = "հ", + ["Ձ"] = "ձ", + ["Ղ"] = "ղ", + ["Ճ"] = "ճ", + ["Մ"] = "մ", + ["Յ"] = "յ", + ["Ն"] = "ն", + ["Շ"] = "շ", + ["Ո"] = "ո", + ["Չ"] = "չ", + ["Պ"] = "պ", + ["Ջ"] = "ջ", + ["Ռ"] = "ռ", + ["Ս"] = "ս", + ["Վ"] = "վ", + ["Տ"] = "տ", + ["Ր"] = "ր", + ["Ց"] = "ց", + ["Ւ"] = "ւ", + ["Փ"] = "փ", + ["Ք"] = "ք", + ["Օ"] = "օ", + ["Ֆ"] = "ֆ", + ["Ⴀ"] = "ⴀ", + ["Ⴁ"] = "ⴁ", + ["Ⴂ"] = "ⴂ", + ["Ⴃ"] = "ⴃ", + ["Ⴄ"] = "ⴄ", + ["Ⴅ"] = "ⴅ", + ["Ⴆ"] = "ⴆ", + ["Ⴇ"] = "ⴇ", + ["Ⴈ"] = "ⴈ", + ["Ⴉ"] = "ⴉ", + ["Ⴊ"] = "ⴊ", + ["Ⴋ"] = "ⴋ", + ["Ⴌ"] = "ⴌ", + ["Ⴍ"] = "ⴍ", + ["Ⴎ"] = "ⴎ", + ["Ⴏ"] = "ⴏ", + ["Ⴐ"] = "ⴐ", + ["Ⴑ"] = "ⴑ", + ["Ⴒ"] = "ⴒ", + ["Ⴓ"] = "ⴓ", + ["Ⴔ"] = "ⴔ", + ["Ⴕ"] = "ⴕ", + ["Ⴖ"] = "ⴖ", + ["Ⴗ"] = "ⴗ", + ["Ⴘ"] = "ⴘ", + ["Ⴙ"] = "ⴙ", + ["Ⴚ"] = "ⴚ", + ["Ⴛ"] = "ⴛ", + ["Ⴜ"] = "ⴜ", + ["Ⴝ"] = "ⴝ", + ["Ⴞ"] = "ⴞ", + ["Ⴟ"] = "ⴟ", + ["Ⴠ"] = "ⴠ", + ["Ⴡ"] = "ⴡ", + ["Ⴢ"] = "ⴢ", + ["Ⴣ"] = "ⴣ", + ["Ⴤ"] = "ⴤ", + ["Ⴥ"] = "ⴥ", + ["Ḁ"] = "ḁ", + ["Ḃ"] = "ḃ", + ["Ḅ"] = "ḅ", + ["Ḇ"] = "ḇ", + ["Ḉ"] = "ḉ", + ["Ḋ"] = "ḋ", + ["Ḍ"] = "ḍ", + ["Ḏ"] = "ḏ", + ["Ḑ"] = "ḑ", + ["Ḓ"] = "ḓ", + ["Ḕ"] = "ḕ", + ["Ḗ"] = "ḗ", + ["Ḙ"] = "ḙ", + ["Ḛ"] = "ḛ", + ["Ḝ"] = "ḝ", + ["Ḟ"] = "ḟ", + ["Ḡ"] = "ḡ", + ["Ḣ"] = "ḣ", + ["Ḥ"] = "ḥ", + ["Ḧ"] = "ḧ", + ["Ḩ"] = "ḩ", + ["Ḫ"] = "ḫ", + ["Ḭ"] = "ḭ", + ["Ḯ"] = "ḯ", + ["Ḱ"] = "ḱ", + ["Ḳ"] = "ḳ", + ["Ḵ"] = "ḵ", + ["Ḷ"] = "ḷ", + ["Ḹ"] = "ḹ", + ["Ḻ"] = "ḻ", + ["Ḽ"] = "ḽ", + ["Ḿ"] = "ḿ", + ["Ṁ"] = "ṁ", + ["Ṃ"] = "ṃ", + ["Ṅ"] = "ṅ", + ["Ṇ"] = "ṇ", + ["Ṉ"] = "ṉ", + ["Ṋ"] = "ṋ", + ["Ṍ"] = "ṍ", + ["Ṏ"] = "ṏ", + ["Ṑ"] = "ṑ", + ["Ṓ"] = "ṓ", + ["Ṕ"] = "ṕ", + ["Ṗ"] = "ṗ", + ["Ṙ"] = "ṙ", + ["Ṛ"] = "ṛ", + ["Ṝ"] = "ṝ", + ["Ṟ"] = "ṟ", + ["Ṡ"] = "ṡ", + ["Ṣ"] = "ṣ", + ["Ṥ"] = "ṥ", + ["Ṧ"] = "ṧ", + ["Ṩ"] = "ṩ", + ["Ṫ"] = "ṫ", + ["Ṭ"] = "ṭ", + ["Ṯ"] = "ṯ", + ["Ṱ"] = "ṱ", + ["Ṳ"] = "ṳ", + ["Ṵ"] = "ṵ", + ["Ṷ"] = "ṷ", + ["Ṹ"] = "ṹ", + ["Ṻ"] = "ṻ", + ["Ṽ"] = "ṽ", + ["Ṿ"] = "ṿ", + ["Ẁ"] = "ẁ", + ["Ẃ"] = "ẃ", + ["Ẅ"] = "ẅ", + ["Ẇ"] = "ẇ", + ["Ẉ"] = "ẉ", + ["Ẋ"] = "ẋ", + ["Ẍ"] = "ẍ", + ["Ẏ"] = "ẏ", + ["Ẑ"] = "ẑ", + ["Ẓ"] = "ẓ", + ["Ẕ"] = "ẕ", + ["Ạ"] = "ạ", + ["Ả"] = "ả", + ["Ấ"] = "ấ", + ["Ầ"] = "ầ", + ["Ẩ"] = "ẩ", + ["Ẫ"] = "ẫ", + ["Ậ"] = "ậ", + ["Ắ"] = "ắ", + ["Ằ"] = "ằ", + ["Ẳ"] = "ẳ", + ["Ẵ"] = "ẵ", + ["Ặ"] = "ặ", + ["Ẹ"] = "ẹ", + ["Ẻ"] = "ẻ", + ["Ẽ"] = "ẽ", + ["Ế"] = "ế", + ["Ề"] = "ề", + ["Ể"] = "ể", + ["Ễ"] = "ễ", + ["Ệ"] = "ệ", + ["Ỉ"] = "ỉ", + ["Ị"] = "ị", + ["Ọ"] = "ọ", + ["Ỏ"] = "ỏ", + ["Ố"] = "ố", + ["Ồ"] = "ồ", + ["Ổ"] = "ổ", + ["Ỗ"] = "ỗ", + ["Ộ"] = "ộ", + ["Ớ"] = "ớ", + ["Ờ"] = "ờ", + ["Ở"] = "ở", + ["Ỡ"] = "ỡ", + ["Ợ"] = "ợ", + ["Ụ"] = "ụ", + ["Ủ"] = "ủ", + ["Ứ"] = "ứ", + ["Ừ"] = "ừ", + ["Ử"] = "ử", + ["Ữ"] = "ữ", + ["Ự"] = "ự", + ["Ỳ"] = "ỳ", + ["Ỵ"] = "ỵ", + ["Ỷ"] = "ỷ", + ["Ỹ"] = "ỹ", + ["Ἀ"] = "ἀ", + ["Ἁ"] = "ἁ", + ["Ἂ"] = "ἂ", + ["Ἃ"] = "ἃ", + ["Ἄ"] = "ἄ", + ["Ἅ"] = "ἅ", + ["Ἆ"] = "ἆ", + ["Ἇ"] = "ἇ", + ["Ἐ"] = "ἐ", + ["Ἑ"] = "ἑ", + ["Ἒ"] = "ἒ", + ["Ἓ"] = "ἓ", + ["Ἔ"] = "ἔ", + ["Ἕ"] = "ἕ", + ["Ἠ"] = "ἠ", + ["Ἡ"] = "ἡ", + ["Ἢ"] = "ἢ", + ["Ἣ"] = "ἣ", + ["Ἤ"] = "ἤ", + ["Ἥ"] = "ἥ", + ["Ἦ"] = "ἦ", + ["Ἧ"] = "ἧ", + ["Ἰ"] = "ἰ", + ["Ἱ"] = "ἱ", + ["Ἲ"] = "ἲ", + ["Ἳ"] = "ἳ", + ["Ἴ"] = "ἴ", + ["Ἵ"] = "ἵ", + ["Ἶ"] = "ἶ", + ["Ἷ"] = "ἷ", + ["Ὀ"] = "ὀ", + ["Ὁ"] = "ὁ", + ["Ὂ"] = "ὂ", + ["Ὃ"] = "ὃ", + ["Ὄ"] = "ὄ", + ["Ὅ"] = "ὅ", + ["Ὑ"] = "ὑ", + ["Ὓ"] = "ὓ", + ["Ὕ"] = "ὕ", + ["Ὗ"] = "ὗ", + ["Ὠ"] = "ὠ", + ["Ὡ"] = "ὡ", + ["Ὢ"] = "ὢ", + ["Ὣ"] = "ὣ", + ["Ὤ"] = "ὤ", + ["Ὥ"] = "ὥ", + ["Ὦ"] = "ὦ", + ["Ὧ"] = "ὧ", + ["ᾈ"] = "ᾀ", + ["ᾉ"] = "ᾁ", + ["ᾊ"] = "ᾂ", + ["ᾋ"] = "ᾃ", + ["ᾌ"] = "ᾄ", + ["ᾍ"] = "ᾅ", + ["ᾎ"] = "ᾆ", + ["ᾏ"] = "ᾇ", + ["ᾘ"] = "ᾐ", + ["ᾙ"] = "ᾑ", + ["ᾚ"] = "ᾒ", + ["ᾛ"] = "ᾓ", + ["ᾜ"] = "ᾔ", + ["ᾝ"] = "ᾕ", + ["ᾞ"] = "ᾖ", + ["ᾟ"] = "ᾗ", + ["ᾨ"] = "ᾠ", + ["ᾩ"] = "ᾡ", + ["ᾪ"] = "ᾢ", + ["ᾫ"] = "ᾣ", + ["ᾬ"] = "ᾤ", + ["ᾭ"] = "ᾥ", + ["ᾮ"] = "ᾦ", + ["ᾯ"] = "ᾧ", + ["Ᾰ"] = "ᾰ", + ["Ᾱ"] = "ᾱ", + ["Ὰ"] = "ὰ", + ["Ά"] = "ά", + ["ᾼ"] = "ᾳ", + ["Ὲ"] = "ὲ", + ["Έ"] = "έ", + ["Ὴ"] = "ὴ", + ["Ή"] = "ή", + ["ῌ"] = "ῃ", + ["Ῐ"] = "ῐ", + ["Ῑ"] = "ῑ", + ["Ὶ"] = "ὶ", + ["Ί"] = "ί", + ["Ῠ"] = "ῠ", + ["Ῡ"] = "ῡ", + ["Ὺ"] = "ὺ", + ["Ύ"] = "ύ", + ["Ῥ"] = "ῥ", + ["Ὸ"] = "ὸ", + ["Ό"] = "ό", + ["Ὼ"] = "ὼ", + ["Ώ"] = "ώ", + ["ῼ"] = "ῳ", + ["Ω"] = "ω", + ["K"] = "k", + ["Å"] = "å", + ["Ⅎ"] = "ⅎ", + ["Ⅰ"] = "ⅰ", + ["Ⅱ"] = "ⅱ", + ["Ⅲ"] = "ⅲ", + ["Ⅳ"] = "ⅳ", + ["Ⅴ"] = "ⅴ", + ["Ⅵ"] = "ⅵ", + ["Ⅶ"] = "ⅶ", + ["Ⅷ"] = "ⅷ", + ["Ⅸ"] = "ⅸ", + ["Ⅹ"] = "ⅹ", + ["Ⅺ"] = "ⅺ", + ["Ⅻ"] = "ⅻ", + ["Ⅼ"] = "ⅼ", + ["Ⅽ"] = "ⅽ", + ["Ⅾ"] = "ⅾ", + ["Ⅿ"] = "ⅿ", + ["Ↄ"] = "ↄ", + ["Ⓐ"] = "ⓐ", + ["Ⓑ"] = "ⓑ", + ["Ⓒ"] = "ⓒ", + ["Ⓓ"] = "ⓓ", + ["Ⓔ"] = "ⓔ", + ["Ⓕ"] = "ⓕ", + ["Ⓖ"] = "ⓖ", + ["Ⓗ"] = "ⓗ", + ["Ⓘ"] = "ⓘ", + ["Ⓙ"] = "ⓙ", + ["Ⓚ"] = "ⓚ", + ["Ⓛ"] = "ⓛ", + ["Ⓜ"] = "ⓜ", + ["Ⓝ"] = "ⓝ", + ["Ⓞ"] = "ⓞ", + ["Ⓟ"] = "ⓟ", + ["Ⓠ"] = "ⓠ", + ["Ⓡ"] = "ⓡ", + ["Ⓢ"] = "ⓢ", + ["Ⓣ"] = "ⓣ", + ["Ⓤ"] = "ⓤ", + ["Ⓥ"] = "ⓥ", + ["Ⓦ"] = "ⓦ", + ["Ⓧ"] = "ⓧ", + ["Ⓨ"] = "ⓨ", + ["Ⓩ"] = "ⓩ", + ["Ⰰ"] = "ⰰ", + ["Ⰱ"] = "ⰱ", + ["Ⰲ"] = "ⰲ", + ["Ⰳ"] = "ⰳ", + ["Ⰴ"] = "ⰴ", + ["Ⰵ"] = "ⰵ", + ["Ⰶ"] = "ⰶ", + ["Ⰷ"] = "ⰷ", + ["Ⰸ"] = "ⰸ", + ["Ⰹ"] = "ⰹ", + ["Ⰺ"] = "ⰺ", + ["Ⰻ"] = "ⰻ", + ["Ⰼ"] = "ⰼ", + ["Ⰽ"] = "ⰽ", + ["Ⰾ"] = "ⰾ", + ["Ⰿ"] = "ⰿ", + ["Ⱀ"] = "ⱀ", + ["Ⱁ"] = "ⱁ", + ["Ⱂ"] = "ⱂ", + ["Ⱃ"] = "ⱃ", + ["Ⱄ"] = "ⱄ", + ["Ⱅ"] = "ⱅ", + ["Ⱆ"] = "ⱆ", + ["Ⱇ"] = "ⱇ", + ["Ⱈ"] = "ⱈ", + ["Ⱉ"] = "ⱉ", + ["Ⱊ"] = "ⱊ", + ["Ⱋ"] = "ⱋ", + ["Ⱌ"] = "ⱌ", + ["Ⱍ"] = "ⱍ", + ["Ⱎ"] = "ⱎ", + ["Ⱏ"] = "ⱏ", + ["Ⱐ"] = "ⱐ", + ["Ⱑ"] = "ⱑ", + ["Ⱒ"] = "ⱒ", + ["Ⱓ"] = "ⱓ", + ["Ⱔ"] = "ⱔ", + ["Ⱕ"] = "ⱕ", + ["Ⱖ"] = "ⱖ", + ["Ⱗ"] = "ⱗ", + ["Ⱘ"] = "ⱘ", + ["Ⱙ"] = "ⱙ", + ["Ⱚ"] = "ⱚ", + ["Ⱛ"] = "ⱛ", + ["Ⱜ"] = "ⱜ", + ["Ⱝ"] = "ⱝ", + ["Ⱞ"] = "ⱞ", + ["Ⱡ"] = "ⱡ", + ["Ɫ"] = "ɫ", + ["Ᵽ"] = "ᵽ", + ["Ɽ"] = "ɽ", + ["Ⱨ"] = "ⱨ", + ["Ⱪ"] = "ⱪ", + ["Ⱬ"] = "ⱬ", + ["Ⱶ"] = "ⱶ", + ["Ⲁ"] = "ⲁ", + ["Ⲃ"] = "ⲃ", + ["Ⲅ"] = "ⲅ", + ["Ⲇ"] = "ⲇ", + ["Ⲉ"] = "ⲉ", + ["Ⲋ"] = "ⲋ", + ["Ⲍ"] = "ⲍ", + ["Ⲏ"] = "ⲏ", + ["Ⲑ"] = "ⲑ", + ["Ⲓ"] = "ⲓ", + ["Ⲕ"] = "ⲕ", + ["Ⲗ"] = "ⲗ", + ["Ⲙ"] = "ⲙ", + ["Ⲛ"] = "ⲛ", + ["Ⲝ"] = "ⲝ", + ["Ⲟ"] = "ⲟ", + ["Ⲡ"] = "ⲡ", + ["Ⲣ"] = "ⲣ", + ["Ⲥ"] = "ⲥ", + ["Ⲧ"] = "ⲧ", + ["Ⲩ"] = "ⲩ", + ["Ⲫ"] = "ⲫ", + ["Ⲭ"] = "ⲭ", + ["Ⲯ"] = "ⲯ", + ["Ⲱ"] = "ⲱ", + ["Ⲳ"] = "ⲳ", + ["Ⲵ"] = "ⲵ", + ["Ⲷ"] = "ⲷ", + ["Ⲹ"] = "ⲹ", + ["Ⲻ"] = "ⲻ", + ["Ⲽ"] = "ⲽ", + ["Ⲿ"] = "ⲿ", + ["Ⳁ"] = "ⳁ", + ["Ⳃ"] = "ⳃ", + ["Ⳅ"] = "ⳅ", + ["Ⳇ"] = "ⳇ", + ["Ⳉ"] = "ⳉ", + ["Ⳋ"] = "ⳋ", + ["Ⳍ"] = "ⳍ", + ["Ⳏ"] = "ⳏ", + ["Ⳑ"] = "ⳑ", + ["Ⳓ"] = "ⳓ", + ["Ⳕ"] = "ⳕ", + ["Ⳗ"] = "ⳗ", + ["Ⳙ"] = "ⳙ", + ["Ⳛ"] = "ⳛ", + ["Ⳝ"] = "ⳝ", + ["Ⳟ"] = "ⳟ", + ["Ⳡ"] = "ⳡ", + ["Ⳣ"] = "ⳣ", + ["A"] = "a", + ["B"] = "b", + ["C"] = "c", + ["D"] = "d", + ["E"] = "e", + ["F"] = "f", + ["G"] = "g", + ["H"] = "h", + ["I"] = "i", + ["J"] = "j", + ["K"] = "k", + ["L"] = "l", + ["M"] = "m", + ["N"] = "n", + ["O"] = "o", + ["P"] = "p", + ["Q"] = "q", + ["R"] = "r", + ["S"] = "s", + ["T"] = "t", + ["U"] = "u", + ["V"] = "v", + ["W"] = "w", + ["X"] = "x", + ["Y"] = "y", + ["Z"] = "z", + ["𐐀"] = "𐐨", + ["𐐁"] = "𐐩", + ["𐐂"] = "𐐪", + ["𐐃"] = "𐐫", + ["𐐄"] = "𐐬", + ["𐐅"] = "𐐭", + ["𐐆"] = "𐐮", + ["𐐇"] = "𐐯", + ["𐐈"] = "𐐰", + ["𐐉"] = "𐐱", + ["𐐊"] = "𐐲", + ["𐐋"] = "𐐳", + ["𐐌"] = "𐐴", + ["𐐍"] = "𐐵", + ["𐐎"] = "𐐶", + ["𐐏"] = "𐐷", + ["𐐐"] = "𐐸", + ["𐐑"] = "𐐹", + ["𐐒"] = "𐐺", + ["𐐓"] = "𐐻", + ["𐐔"] = "𐐼", + ["𐐕"] = "𐐽", + ["𐐖"] = "𐐾", + ["𐐗"] = "𐐿", + ["𐐘"] = "𐑀", + ["𐐙"] = "𐑁", + ["𐐚"] = "𐑂", + ["𐐛"] = "𐑃", + ["𐐜"] = "𐑄", + ["𐐝"] = "𐑅", + ["𐐞"] = "𐑆", + ["𐐟"] = "𐑇", + ["𐐠"] = "𐑈", + ["𐐡"] = "𐑉", + ["𐐢"] = "𐑊", + ["𐐣"] = "𐑋", + ["𐐤"] = "𐑌", + ["𐐥"] = "𐑍", + ["𐐦"] = "𐑎", + ["𐐧"] = "𐑏", } - return { - utf8_lc_uc = utf8_lc_uc, - utf8_uc_lc = utf8_uc_lc, + utf8_lc_uc = utf8_lc_uc, + utf8_uc_lc = utf8_uc_lc, } diff --git a/ar/.config/mpv/scripts/SimpleBookmark.lua b/ar/.config/mpv/scripts/SimpleBookmark.lua index 5af7f74..8f09d87 100644 --- a/ar/.config/mpv/scripts/SimpleBookmark.lua +++ b/ar/.config/mpv/scripts/SimpleBookmark.lua @@ -5,16 +5,16 @@ -- Version: 1.3.1 local o = { ----------------------------USER CUSTOMIZATION SETTINGS--------------------------- ---These settings are for users to manually change some options. ---Changes are recommended to be made in the script-opts directory. + ---------------------------USER CUSTOMIZATION SETTINGS--------------------------- + --These settings are for users to manually change some options. + --Changes are recommended to be made in the script-opts directory. -----Script Settings---- --Available filters: 'all', 'keybinds', 'groups', 'recents', 'distinct', 'protocols', 'fileonly', 'titleonly', 'timeonly', 'keywords'. --Filters description: "all" to display all the items. Or 'groups' to display the list filtered with items added to any group. Or 'keybinds' to display the list filtered with keybind slots. Or "recents" to display recently added items to log without duplicate. Or "distinct" to show recent saved entries for files in different paths. Or "fileonly" to display files saved without time. Or "timeonly" to display files that have time only. Or "keywords" to display files with matching keywords specified in the configuration. Or "playing" to show list of current playing file. --Filters can also be stacked by using %+% or omitted by using %-%. e.g.: "groups%+%keybinds" shows only groups and keybinds, "all%-%groups%-%keybinds" shows all items without groups and without keybinds. --Also defined groups can be called by using /:group%Group Name% - auto_run_list_idle = 'none', --Auto run the list when opening mpv and there is no video / file loaded. none for disabled. Or choose between available filters. + auto_run_list_idle = "none", --Auto run the list when opening mpv and there is no video / file loaded. none for disabled. Or choose between available filters. load_item_on_startup = 0, --runs a saved entry when mpv starts based on its number. -1 for oldest entry, 1 for latest entry, or select the number to load a specific entry, 0 for disabled toggle_idlescreen = true, --hides OSC idle screen message when opening and closing menu (could cause unexpected behavior if multiple scripts are triggering osc-idlescreen off) resume_offset = -0.65, --change to 0 so item resumes from the exact position, or decrease the value so that it gives you a little preview before loading the resume point @@ -23,59 +23,59 @@ local o = { bookmark_fileonly_loads_last_idle = true, --When attempting to bookmark fileonly, if there is no video / file loaded, it will instead jump to your last bookmarked item without resuming. mark_bookmark_as_chapter = false, --true is for marking the time as a chapter. false disables mark as chapter behavior. preserve_video_settings = false, --(true/false). Preserve video settings when bookmarking items and loading bookmarks by writing mpv watch-later config - bookmark_save_keybind=[[ + bookmark_save_keybind = [[ ["ctrl+b", "ctrl+B"] ]], --Keybind that will be used to save the video and its time to log file - bookmark_fileonly_keybind=[[ + bookmark_fileonly_keybind = [[ ["alt+b", "alt+B"] ]], --Keybind that will be used to save the video without time to log file - open_list_keybind=[[ + open_list_keybind = [[ [ ["b", "all"], ["B", "all"], ["k", "keybinds"], ["K", "keybinds"] ] ]], --Keybind that will be used to open the list along with the specified filter. - list_filter_jump_keybind=[[ + list_filter_jump_keybind = [[ [ ["b", "all"], ["B", "all"], ["k", "keybinds"], ["K", "keybinds"], ["!", "/:group%TV Shows%"], ["@", "/:group%Movies%"], ["SHARP", "/:group%Anime%"], ["$", "/:group%Anime Movies%"], ["%", "/:group%Cartoon%"], ["r", "recents"], ["R", "recents"], ["d", "distinct"], ["D", "distinct"], ["f", "fileonly"], ["F", "fileonly"] ] ]], --Keybind that is used while the list is open to jump to the specific filter (it also enables pressing a filter keybind twice to close list). Available fitlers: 'all', 'keybinds', 'recents', 'distinct', 'protocols', 'fileonly', 'titleonly', 'timeonly', 'keywords'. - + -----Keybind Slots Settings----- keybinds_quicksave_fileonly = true, --When quick saving to a keybind slot, it will not save position keybinds_empty_auto_create = false, --If the keybind slot is empty, this enables quick logging and adding to slot, Otherwise keybinds are assigned from the list or via quicksave. keybinds_empty_fileonly = true, --When auto creating keybind slot, it will not save position. keybinds_auto_resume = true, --When loading a keybind slot, it will auto resume to the saved time. - keybinds_add_load_keybind=[[ + keybinds_add_load_keybind = [[ ["alt+1", "alt+2", "alt+3", "alt+4", "alt+5", "alt+6", "alt+7", "alt+8", "alt+9"] ]], --Keybind that will be used to bind list item to a key, as well as to load it. e.g.: Press alt+1 on list cursor position to add it, press alt+1 while list is hidden to load item keybinded into alt+1. (A new slot is automatically created for each keybind. e.g: .."alt+9, alt+0". Where alt+0 creates a new 10th slot.) - keybinds_quicksave_keybind=[[ + keybinds_quicksave_keybind = [[ ["alt+!", "alt+@", "alt+#", "alt+$", "alt+%", "alt+^", "alt+&", "alt+*", "alt+("] ]], --To save keybind to a slot without opening the list, to load these keybinds it uses keybinds_add_load_keybind - keybinds_remove_keybind=[[ + keybinds_remove_keybind = [[ ["alt+-"] ]], --Keybind that is used when list is open to remove the keybind slot based on cursor position - keybinds_remove_highlighted_keybind=[[ + keybinds_remove_highlighted_keybind = [[ ["alt+_"] ]], --Keybind that is used when list is open to remove the keybind slot based on highlighted items -----Group Settings----- - groups_list_and_keybind =[[ + groups_list_and_keybind = [[ [ ["TV Shows", "ctrl+1", "ctrl+!"], ["Movies", "ctrl+2", "ctrl+@"], ["Anime", "ctrl+3", "ctrl+#"], ["Anime Movies", "ctrl+4", "ctrl+$"], ["Cartoon", "ctrl+5"], ["Animated Movies"] ] ]], --Define the groups that can be assigned to a bookmarked item, you can also optionally assign the keybind, and the highlight keybind that puts the bookmarked item into the relevant group when the list is open. Alternatively you can use list_group_add_cycle_keybind to assign item to a group - list_groups_remove_keybind=[[ + list_groups_remove_keybind = [[ ["ctrl+-"] ]], --Keybind that is used when list is open to remove the group based on cursor position - list_groups_remove_highlighted_keybind=[[ + list_groups_remove_highlighted_keybind = [[ ["ctrl+_"] ]], --Keybind that is used when list is open to remove the group based on highlighted items - list_group_add_cycle_keybind=[[ + list_group_add_cycle_keybind = [[ ["ctrl+g"] ]], --Keybind to add an item to the group, this cycles through all the different available groups when list is open - list_group_add_cycle_highlighted_keybind=[[ + list_group_add_cycle_highlighted_keybind = [[ ["ctrl+G"] ]], --Keybind to add highlighted items to the group, this cycles through all the different available groups when list is open -----Logging Settings----- - log_path = '/:dir%mpvconf%', --Change to '/:dir%script%' for placing it in the same directory of script, OR change to '/:dir%mpvconf%' for mpv portable_config directory. OR write any variable using '/:var' then the variable '/:var%APPDATA%' you can use path also, such as: '/:var%APPDATA%\\mpv' OR '/:var%HOME%/mpv' OR specify the absolute path , e.g.: 'C:\\Users\\Eisa01\\Desktop\\' - log_file = 'mpvBookmark.log', --name+extension of the file that will be used to store the log data - file_title_logging = 'all', --Change between 'all', 'protocols', 'local', 'none'. This option will store the media title in log file, it is useful for websites / protocols because title cannot be parsed from links alone - logging_protocols=[[ + log_path = "/:dir%mpvconf%", --Change to '/:dir%script%' for placing it in the same directory of script, OR change to '/:dir%mpvconf%' for mpv portable_config directory. OR write any variable using '/:var' then the variable '/:var%APPDATA%' you can use path also, such as: '/:var%APPDATA%\\mpv' OR '/:var%HOME%/mpv' OR specify the absolute path , e.g.: 'C:\\Users\\Eisa01\\Desktop\\' + log_file = "mpvBookmark.log", --name+extension of the file that will be used to store the log data + file_title_logging = "all", --Change between 'all', 'protocols', 'local', 'none'. This option will store the media title in log file, it is useful for websites / protocols because title cannot be parsed from links alone + logging_protocols = [[ ["https?://", "magnet:", "rtmp:"] ]], --add above (after a comma) any protocol you want its title to be stored in the log file. This is valid only for (file_title_logging = 'protocols' or file_title_logging = 'all') same_entry_limit = -1, --Limit saving entries with same path: -1 for unlimited, 0 will always update entries of same path, e.g. value of 3 will have the limit of 3 then it will start updating old values on the 4th entry. @@ -87,31 +87,31 @@ local o = { quickselect_0to9_keybind = false, --Keybind entries from 0 to 9 for quick selection when list is open (list_show_amount = 10 is maximum for this feature to work) main_list_keybind_twice_exits = true, --Will exit the list when double tapping the main list, even if the list was accessed through a different filter. search_not_typing_smartly = true, --To smartly set the search as not typing (when search box is open) without needing to press ctrl+enter. - search_behavior = 'any', --'any' to find any typed search based on combination of date, title, path / url, and time. 'any-notime' to find any typed search based on combination of date, title, and path / url, but without looking for time (this is to reduce unwanted results). + search_behavior = "any", --'any' to find any typed search based on combination of date, title, path / url, and time. 'any-notime' to find any typed search based on combination of date, title, and path / url, but without looking for time (this is to reduce unwanted results). -----Filter Settings------ - filters_and_sequence=[[ + filters_and_sequence = [[ ["all", "keybinds", "groups", "/:group%TV Shows%", "/:group%Movies%", "/:group%Anime%", "/:group%Anime Movies%", "/:group%Cartoon%", "/:group%Animated Movies%", "protocols", "fileonly", "titleonly", "timeonly", "playing", "keywords", "recents", "distinct", "keybinds%+%groups", "all%-%groups%-%keybinds"] ]], --Jump to the following filters and in the shown sequence when navigating via left and right keys. You can change the sequence and delete filters that are not needed. - next_filter_sequence_keybind=[[ + next_filter_sequence_keybind = [[ ["RIGHT", "MBTN_FORWARD"] - ]],--Keybind that will be used to go to the next available filter based on the filters_and_sequence - previous_filter_sequence_keybind=[[ + ]], --Keybind that will be used to go to the next available filter based on the filters_and_sequence + previous_filter_sequence_keybind = [[ ["LEFT", "MBTN_BACK"] - ]],--Keybind that will be used to go to the previous available filter based on the filters_and_sequence + ]], --Keybind that will be used to go to the previous available filter based on the filters_and_sequence loop_through_filters = true, --true is for bypassing the last filter to go to first filter when navigating through filters using arrow keys, and vice-versa. false disables this behavior. - keywords_filter_list=[[ + keywords_filter_list = [[ [] ]], --Create a filter out of your desired 'keywords', e.g.: youtube.com will filter out the videos from youtube. You can also insert a portion of filename or title, or extension or a full path / portion of a path. e.g.: ["youtube.com", "mp4", "naruto", "c:\\users\\eisa01\\desktop"]. To disable this filter keep it empty [] -----Sort Settings------ --Available sorts: 'added-asc', 'added-desc', 'time-asc', 'time-desc', 'alphanum-asc', 'alphanum-desc' --Sorts description: 'added-asc' is for the newest added item to show first. Or 'added-desc' for the newest added to show last. Or 'alphanum-asc' is for A to Z approach with filename and episode number lower first. Or 'alphanum-desc' is for its Z to A approach. Or 'time-asc', 'time-desc' to sort the list based on time. - list_default_sort = 'added-asc', --the default sorting method for all the different filters in the list. Choose between available sorts. - list_filters_sort=[[ + list_default_sort = "added-asc", --the default sorting method for all the different filters in the list. Choose between available sorts. + list_filters_sort = [[ [ ["keybinds", "keybind-asc"], ["fileonly", "alphanum-asc"], ["playing", "time-asc"] ] ]], --Default sort for specific filters, e.g.: [ ["all", "alphanum-asc"], ["playing", "added-desc"] ] - list_cycle_sort_keybind=[[ + list_cycle_sort_keybind = [[ ["alt+s", "alt+S"] ]], --Keybind to cycle through the different available sorts when list is open @@ -120,136 +120,136 @@ local o = { slice_name = false, --Change to true or false. Slices long names per the amount specified below slice_name_amount = 55, --Amount for slicing long names (for path, name, and title) list_content_text variables list_show_amount = 10, --Change maximum number to show items at once - list_content_text = '%number%. %name%%0_duration%%duration%%0_keybind%%keybind%%0_group%%group%%1_group%\\h\\N\\N', --Text to be shown as header for the list - --list_content_text variables: %quickselect%, %number%, %name%, %title%, %path%, %duration%, %length%, %remaining%, %dt%, %dt_"format%"% - --Variables explanation: %quickselect%: keybind for quickselect. %number%: numbered sequence of the item position. %name%: shows the file name. %title%: shows file title. %path%: shows the filepath or url. %duration%: the reached playback time of item. %length%: the total time length of the file. %remaining% the remaining playback time of file. %dt%: the logged date and time. - --You can also use %dt_"format%"%" as per lua date formatting (https://www.lua.org/pil/22.1.html). It is specified after dt_ ..example: (%dt_%a% %dt_%b% %dt_%y%) for abbreviated day month year - list_content_variables=[[ + list_content_text = "%number%. %name%%0_duration%%duration%%0_keybind%%keybind%%0_group%%group%%1_group%\\h\\N\\N", --Text to be shown as header for the list + --list_content_text variables: %quickselect%, %number%, %name%, %title%, %path%, %duration%, %length%, %remaining%, %dt%, %dt_"format%"% + --Variables explanation: %quickselect%: keybind for quickselect. %number%: numbered sequence of the item position. %name%: shows the file name. %title%: shows file title. %path%: shows the filepath or url. %duration%: the reached playback time of item. %length%: the total time length of the file. %remaining% the remaining playback time of file. %dt%: the logged date and time. + --You can also use %dt_"format%"%" as per lua date formatting (https://www.lua.org/pil/22.1.html). It is specified after dt_ ..example: (%dt_%a% %dt_%b% %dt_%y%) for abbreviated day month year + list_content_variables = [[ [ ["0_duration", " 🕒 "], ["0_keybind", " ⌨ "], ["0_group", " 🖿 "] ] ]], --User defined variables that only displays if the related variable is triggered. - --#_group, #_keybind, #_duration, #_length, #_remaining, #_dt. (# represents the possibility of creating many variables using different numbers. e.g.: "0_keybind", "1_keybind") - list_sliced_prefix = '...\\h\\N\\N', --The text that indicates there are more items above. \\N is for new line. \\h is for hard space. - list_sliced_suffix = '...', --The text that indicates there are more items below. - text_color = 'ffffff', --Text color for list in BGR hexadecimal + --#_group, #_keybind, #_duration, #_length, #_remaining, #_dt. (# represents the possibility of creating many variables using different numbers. e.g.: "0_keybind", "1_keybind") + list_sliced_prefix = "...\\h\\N\\N", --The text that indicates there are more items above. \\N is for new line. \\h is for hard space. + list_sliced_suffix = "...", --The text that indicates there are more items below. + text_color = "ffffff", --Text color for list in BGR hexadecimal text_scale = 50, --Font size for the text of list text_border = 0.7, --Black border size for the text of list - text_cursor_color = 'ffbf7f', --Text color of current cursor position in BGR hexadecimal + text_cursor_color = "ffbf7f", --Text color of current cursor position in BGR hexadecimal text_cursor_scale = 50, --Font size for text of current cursor position in list text_cursor_border = 0.7, --Black border size for text of current cursor position in list - text_highlight_pre_text = '✅ ', --Pre text for highlighted multi-select item - search_color_typing = '00bfff', --Search color when in typing mode - search_color_not_typing = 'ffffaa', --Search color when not in typing mode and it is active - header_color = 'ffffaa', --Header color in BGR hexadecimal + text_highlight_pre_text = "✅ ", --Pre text for highlighted multi-select item + search_color_typing = "00bfff", --Search color when in typing mode + search_color_not_typing = "ffffaa", --Search color when not in typing mode and it is active + header_color = "ffffaa", --Header color in BGR hexadecimal header_scale = 55, --Header text size for the list header_border = 0.8, --Black border size for the Header of list - header_text = '🔖 Bookmarks [%cursor%/%total%]%0_highlight%%highlight%%0_filter%%filter%%1_filter%%0_sort%%sort%%1_sort%%0_search%%search%%1_search%\\h\\N\\N', --The formatting of the items when you open the list - --header_text variables: %cursor%, %total%, %highlight%, %filter%, %search%, %duration%, %length%, %remaining%. - --Variables explanation: %cursor%: the number of cursor position. %total%: total amount in current list. %highlight%: total number of highlighted items. %filter%: shows the filter name, %search%: shows the typed search. %duration%: the total reached playback time of all displayed items. %length%: the total time length of the file for all displayed items. %remaining% the remaining playback time of file for all the displayed items. - header_variables=[[ + header_text = "🔖 Bookmarks [%cursor%/%total%]%0_highlight%%highlight%%0_filter%%filter%%1_filter%%0_sort%%sort%%1_sort%%0_search%%search%%1_search%\\h\\N\\N", --The formatting of the items when you open the list + --header_text variables: %cursor%, %total%, %highlight%, %filter%, %search%, %duration%, %length%, %remaining%. + --Variables explanation: %cursor%: the number of cursor position. %total%: total amount in current list. %highlight%: total number of highlighted items. %filter%: shows the filter name, %search%: shows the typed search. %duration%: the total reached playback time of all displayed items. %length%: the total time length of the file for all displayed items. %remaining% the remaining playback time of file for all the displayed items. + header_variables = [[ [ ["0_highlight", "✅"], ["0_filter", " [Filter: "], ["1_filter", "]"], ["0_sort", " \\{"], ["1_sort", "}"], ["0_search", "\\h\\N\\N[Search="], ["1_search", "..]"] ] ]], --User defined variables that only displays if the related variable is triggered. - --#_filter, #_sort, #_highlight, #_search, #_duration, #_length%, #_remaining. (# represents the possibility of creating many variables using different numbers. e.g.: "0_filter", "1_filter") - header_sort_hide_text = 'added-asc',--Sort method that is hidden from header when using %sort% variable + --#_filter, #_sort, #_highlight, #_search, #_duration, #_length%, #_remaining. (# represents the possibility of creating many variables using different numbers. e.g.: "0_filter", "1_filter") + header_sort_hide_text = "added-asc", --Sort method that is hidden from header when using %sort% variable -----Time Format Settings----- --in the first parameter, you can define from the available styles: default, hms, hms-full, timestamp, timestamp-concise "default" to show in HH:MM:SS.sss format. "hms" to show in 1h 2m 3.4s format. "hms-full" is the same as hms but keeps the hours and minutes persistent when they are 0. "timestamp" to show the total time as timestamp 123456.700 format. "timestamp-concise" shows the total time in 123456.7 format (shows and hides decimals depending on availability). --in the second parameter, you can define whether to show milliseconds, round them or truncate them. Available options: 'truncate' to remove the milliseconds and keep the seconds. 0 to remove the milliseconds and round the seconds. 1 or above is the amount of milliseconds to display. The default value is 3 milliseconds. --in the third parameter you can define the seperator between hour:minute:second. "default" style is automatically set to ":", "hms", "hms-full" are automatically set to " ". You can define your own. Some examples: ["default", 3, "-"],["hms-full", 5, "."],["hms", "truncate", ":"],["timestamp-concise"],["timestamp", 0],["timestamp", "truncate"],["timestamp", 5] - osd_time_format=[[ + osd_time_format = [[ ["default", "truncate"] ]], - list_duration_time_format=[[ + list_duration_time_format = [[ ["default", "truncate"] ]], - list_length_time_format=[[ + list_length_time_format = [[ ["default", "truncate"] ]], - list_remaining_time_format=[[ + list_remaining_time_format = [[ ["default", "truncate"] - ]], - header_duration_time_format=[[ + ]], + header_duration_time_format = [[ ["hms", "truncate", ":"] ]], - header_length_time_format=[[ + header_length_time_format = [[ ["hms", "truncate", ":"] ]], - header_remaining_time_format=[[ + header_remaining_time_format = [[ ["hms", "truncate", ":"] ]], -----List Keybind Settings----- --Add below (after a comma) any additional keybind you want to bind. Or change the letter inside the quotes to change the keybind --Example of changing and adding keybinds: --From ["b", "B"] To ["b"]. --From [""] to ["alt+b"]. --From [""] to ["a" "ctrl+a", "alt+a"] - list_move_up_keybind=[[ + list_move_up_keybind = [[ ["UP", "WHEEL_UP"] ]], --Keybind that will be used to navigate up on the list - list_move_down_keybind=[[ + list_move_down_keybind = [[ ["DOWN", "WHEEL_DOWN"] ]], --Keybind that will be used to navigate down on the list - list_page_up_keybind=[[ + list_page_up_keybind = [[ ["PGUP"] ]], --Keybind that will be used to go to the first item for the page shown on the list - list_page_down_keybind=[[ + list_page_down_keybind = [[ ["PGDWN"] ]], --Keybind that will be used to go to the last item for the page shown on the list - list_move_first_keybind=[[ + list_move_first_keybind = [[ ["HOME"] ]], --Keybind that will be used to navigate to the first item on the list - list_move_last_keybind=[[ + list_move_last_keybind = [[ ["END"] ]], --Keybind that will be used to navigate to the last item on the list - list_highlight_move_keybind=[[ + list_highlight_move_keybind = [[ ["SHIFT"] ]], --Keybind that will be used to highlight while pressing a navigational keybind, keep holding shift and then press any navigation keybind, such as: up, down, home, pgdwn, etc.. - list_highlight_all_keybind=[[ + list_highlight_all_keybind = [[ ["ctrl+a", "ctrl+A"] ]], --Keybind that will be used to highlight all displayed items on the list - list_unhighlight_all_keybind=[[ + list_unhighlight_all_keybind = [[ ["ctrl+d", "ctrl+D"] ]], --Keybind that will be used to remove all currently highlighted items from the list - list_select_keybind=[[ + list_select_keybind = [[ ["ENTER", "MBTN_MID"] ]], --Keybind that will be used to load entry based on cursor position - list_add_playlist_keybind=[[ + list_add_playlist_keybind = [[ ["CTRL+ENTER"] ]], --Keybind that will be used to add entry to playlist based on cursor position - list_add_playlist_highlighted_keybind=[[ + list_add_playlist_highlighted_keybind = [[ ["SHIFT+ENTER"] ]], --Keybind that will be used to add all highlighted entries to playlist - list_close_keybind=[[ + list_close_keybind = [[ ["ESC", "MBTN_RIGHT"] ]], --Keybind that will be used to close the list (closes search first if it is open) - list_delete_keybind=[[ + list_delete_keybind = [[ ["DEL"] ]], --Keybind that will be used to delete the entry based on cursor position - list_delete_highlighted_keybind=[[ + list_delete_highlighted_keybind = [[ ["SHIFT+DEL"] ]], --Keybind that will be used to delete all highlighted entries from the list - list_search_activate_keybind=[[ + list_search_activate_keybind = [[ ["ctrl+f", "ctrl+F"] ]], --Keybind that will be used to trigger search - list_search_not_typing_mode_keybind=[[ + list_search_not_typing_mode_keybind = [[ ["ALT+ENTER"] ]], --Keybind that will be used to exit typing mode of search while keeping search open - list_ignored_keybind=[[ + list_ignored_keybind = [[ ["h", "H", "r", "R", "c", "C"] ]], --Keybind thats are ignored when list is open - ----------------------------END OF USER CUSTOMIZATION SETTINGS--------------------------- + + ---------------------------END OF USER CUSTOMIZATION SETTINGS--------------------------- } -(require 'mp.options').read_options(o) -local utils = require 'mp.utils' -local msg = require 'mp.msg' +(require("mp.options")).read_options(o) +local utils = require("mp.utils") +local msg = require("mp.msg") o.filters_and_sequence = utils.parse_json(o.filters_and_sequence) o.keywords_filter_list = utils.parse_json(o.keywords_filter_list) o.list_filters_sort = utils.parse_json(o.list_filters_sort) o.logging_protocols = utils.parse_json(o.logging_protocols) o.osd_time_format = utils.parse_json(o.osd_time_format) -o.list_duration_time_format = utils.parse_json(o.list_duration_time_format)--1.3# changed and added time format for each in the list -o.list_length_time_format = utils.parse_json(o.list_length_time_format)--1.3# added time format for each in the list -o.list_remaining_time_format = utils.parse_json(o.list_remaining_time_format)--1.3# added time format for each in the list +o.list_duration_time_format = utils.parse_json(o.list_duration_time_format) --1.3# changed and added time format for each in the list +o.list_length_time_format = utils.parse_json(o.list_length_time_format) --1.3# added time format for each in the list +o.list_remaining_time_format = utils.parse_json(o.list_remaining_time_format) --1.3# added time format for each in the list o.header_duration_time_format = utils.parse_json(o.header_duration_time_format) o.header_length_time_format = utils.parse_json(o.header_length_time_format) o.header_remaining_time_format = utils.parse_json(o.header_remaining_time_format) @@ -274,8 +274,8 @@ o.list_highlight_move_keybind = utils.parse_json(o.list_highlight_move_keybind) o.list_highlight_all_keybind = utils.parse_json(o.list_highlight_all_keybind) o.list_unhighlight_all_keybind = utils.parse_json(o.list_unhighlight_all_keybind) o.list_cycle_sort_keybind = utils.parse_json(o.list_cycle_sort_keybind) -o.list_content_variables = utils.parse_json(o.list_content_variables)--1.3# for new config -o.header_variables = utils.parse_json(o.header_variables)--1.3# for new config +o.list_content_variables = utils.parse_json(o.list_content_variables) --1.3# for new config +o.header_variables = utils.parse_json(o.header_variables) --1.3# for new config o.list_select_keybind = utils.parse_json(o.list_select_keybind) o.list_add_playlist_keybind = utils.parse_json(o.list_add_playlist_keybind) o.list_add_playlist_highlighted_keybind = utils.parse_json(o.list_add_playlist_highlighted_keybind) @@ -291,27 +291,27 @@ o.list_filter_jump_keybind = utils.parse_json(o.list_filter_jump_keybind) o.list_ignored_keybind = utils.parse_json(o.list_ignored_keybind) if utils.shared_script_property_set then - utils.shared_script_property_set('simplebookmark-menu-open', 'no') + utils.shared_script_property_set("simplebookmark-menu-open", "no") end -mp.set_property('user-data/simplebookmark/menu-open', 'no') +mp.set_property("user-data/simplebookmark/menu-open", "no") -if string.lower(o.log_path) == '/:dir%mpvconf%' then - o.log_path = mp.find_config_file('.') -elseif string.lower(o.log_path) == '/:dir%script%' then - o.log_path = debug.getinfo(1).source:match('@?(.*/)') -elseif o.log_path:match('/:var%%(.*)%%') then - local os_variable = o.log_path:match('/:var%%(.*)%%') - o.log_path = o.log_path:gsub('/:var%%(.*)%%', os.getenv(os_variable)) +if string.lower(o.log_path) == "/:dir%mpvconf%" then + o.log_path = mp.find_config_file(".") +elseif string.lower(o.log_path) == "/:dir%script%" then + o.log_path = debug.getinfo(1).source:match("@?(.*/)") +elseif o.log_path:match("/:var%%(.*)%%") then + local os_variable = o.log_path:match("/:var%%(.*)%%") + o.log_path = o.log_path:gsub("/:var%%(.*)%%", os.getenv(os_variable)) end local log_fullpath = utils.join_path(o.log_path, o.log_file) -local log_length_text = 'length=' -local log_time_text = 'time=' -local log_keybind_text = 'slot=' -local log_group_text = 'group=' -local protocols = {'https?:', 'magnet:', 'rtmps?:', 'smb:', 'ftps?:', 'sftp:'} -local available_sorts = {'added-asc', 'added-desc', 'time-asc', 'time-desc', 'alphanum-asc', 'alphanum-desc'} -local search_string = '' +local log_length_text = "length=" +local log_time_text = "time=" +local log_keybind_text = "slot=" +local log_group_text = "group=" +local protocols = { "https?:", "magnet:", "rtmps?:", "smb:", "ftps?:", "sftp:" } +local available_sorts = { "added-asc", "added-desc", "time-asc", "time-desc", "alphanum-asc", "alphanum-desc" } +local search_string = "" local search_active = false local loadTriggered = false --1.3.0# to identify if load is triggered atleast once for idle option local resume_selected = false @@ -323,12 +323,12 @@ local list_drawn = false local list_pages = {} local filePath, fileTitle, fileLength local seekTime = 0 -local filterName = 'all' +local filterName = "all" local sortName function starts_protocol(tab, val) for index, value in ipairs(tab) do - if (val:find(value) == 1) then + if val:find(value) == 1 then return true end end @@ -336,21 +336,29 @@ function starts_protocol(tab, val) end function contain_value(tab, val) - if not tab then return msg.error('check value passed') end - if not val then return msg.error('check value passed') end - + if not tab then + return msg.error("check value passed") + end + if not val then + return msg.error("check value passed") + end + for index, value in ipairs(tab) do if value.match(string.lower(val), string.lower(value)) then return true end end - + return false end function has_value(tab, val, array2d) - if not tab then return msg.error('check value passed') end - if not val then return msg.error('check value passed') end + if not tab then + return msg.error("check value passed") + end + if not val then + return msg.error("check value passed") + end if not array2d then for index, value in ipairs(tab) do if string.lower(value) == string.lower(val) then @@ -359,67 +367,74 @@ function has_value(tab, val, array2d) end end if array2d then - for i=1, #tab do + for i = 1, #tab do if tab[i] and string.lower(tab[i][array2d]) == string.lower(val) then return true end end end - + return false end function file_exists(name) local f = io.open(name, "r") - if f ~= nil then io.close(f) return true else return false end + if f ~= nil then + io.close(f) + return true + else + return false + end end function format_time(seconds, sep, decimals, style) - local function divmod (a, b) + local function divmod(a, b) return math.floor(a / b), a % b end decimals = decimals == nil and 3 or decimals - + local s = seconds - local h, s = divmod(s, 60*60) + local h, s = divmod(s, 60 * 60) local m, s = divmod(s, 60) - if decimals == 'truncate' then + if decimals == "truncate" then s = math.floor(s) decimals = 0 - if style == 'timestamp' then + if style == "timestamp" then seconds = math.floor(seconds) end end - - if not style or style == '' or style == 'default' then - local second_format = string.format("%%0%d.%df", 2+(decimals > 0 and decimals+1 or 0), decimals) + + if not style or style == "" or style == "default" then + local second_format = string.format("%%0%d.%df", 2 + (decimals > 0 and decimals + 1 or 0), decimals) sep = sep and sep or ":" - return string.format("%02d"..sep.."%02d"..sep..second_format, h, m, s) - elseif style == 'hms' or style == 'hms-full' then + return string.format("%02d" .. sep .. "%02d" .. sep .. second_format, h, m, s) + elseif style == "hms" or style == "hms-full" then sep = sep ~= nil and sep or " " - if style == 'hms-full' or h > 0 then - return string.format("%dh"..sep.."%dm"..sep.."%." .. tostring(decimals) .. "fs", h, m, s) + if style == "hms-full" or h > 0 then + return string.format("%dh" .. sep .. "%dm" .. sep .. "%." .. tostring(decimals) .. "fs", h, m, s) elseif m > 0 then - return string.format("%dm"..sep.."%." .. tostring(decimals) .. "fs", m, s) + return string.format("%dm" .. sep .. "%." .. tostring(decimals) .. "fs", m, s) else - return string.format("%." .. tostring(decimals) .. "fs", s) + return string.format("%." .. tostring(decimals) .. "fs", s) end - elseif style == 'timestamp' then + elseif style == "timestamp" then return string.format("%." .. tostring(decimals) .. "f", seconds) - elseif style == 'timestamp-concise' then + elseif style == "timestamp-concise" then return seconds end end function get_file() --1.3# removed prefer filename overtitle - local path = mp.get_property('path') - if not path then return end - - local length = (mp.get_property_number('duration') or 0) - - local title = mp.get_property('media-title'):gsub("\"", "") - + local path = mp.get_property("path") + if not path then + return + end + + local length = (mp.get_property_number("duration") or 0) + + local title = mp.get_property("media-title"):gsub('"', "") + return path, title, length end @@ -429,45 +444,45 @@ function get_local_names(target, property) --1.3# function to get names and fall local target_filetitle = target.found_title or target.found_name or target.found_path if not property then return target_filename, target_filepath, target_filetitle - elseif property == 'osd' then --1.3# added osd property so it removes special character functions when displaying osd in mpv (uses gsub from search) + elseif property == "osd" then --1.3# added osd property so it removes special character functions when displaying osd in mpv (uses gsub from search) return esc_ass(target_filename), esc_ass(target_filepath), esc_ass(target_filetitle) end end function get_slot_keybind(keyindex) local keybind_return - + if o.keybinds_add_load_keybind[keyindex] then keybind_return = o.keybinds_add_load_keybind[keyindex] else - keybind_return = log_keybind_text .. (keyindex or '') .. ' undefined' + keybind_return = log_keybind_text .. (keyindex or "") .. " undefined" end - + return keybind_return end function get_group_properties(groupindex, action) local gname, gkeybind, ghkeybind - + if o.groups_list_and_keybind[groupindex] and o.groups_list_and_keybind[groupindex][1] then gname = o.groups_list_and_keybind[groupindex][1] else - gname = log_group_text ..(groupindex or '').. ' undefined' + gname = log_group_text .. (groupindex or "") .. " undefined" end - + if o.groups_list_and_keybind[groupindex] and o.groups_list_and_keybind[groupindex][2] then gkeybind = o.groups_list_and_keybind[groupindex][2] else - gkeybind = log_group_text ..(groupindex or '').. ' undefined' + gkeybind = log_group_text .. (groupindex or "") .. " undefined" end - + if o.groups_list_and_keybind[groupindex] and o.groups_list_and_keybind[groupindex][3] then ghkeybind = o.groups_list_and_keybind[groupindex][3] else - ghkeybind = log_group_text ..(groupindex or '').. ' undefined' + ghkeybind = log_group_text .. (groupindex or "") .. " undefined" end - - return {name = gname, keybind = gkeybind, highlight_keybind = ghkeybind} + + return { name = gname, keybind = gkeybind, highlight_keybind = ghkeybind } end function bind_keys(keys, name, func, opts) @@ -475,9 +490,9 @@ function bind_keys(keys, name, func, opts) mp.add_forced_key_binding(keys, name, func, opts) return end - + for i = 1, #keys do - if i == 1 then + if i == 1 then mp.add_forced_key_binding(keys[i], name, func, opts) else mp.add_forced_key_binding(keys[i], name .. i, func, opts) @@ -490,7 +505,7 @@ function unbind_keys(keys, name) mp.remove_key_binding(name) return end - + for i = 1, #keys do if i == 1 then mp.remove_key_binding(name) @@ -505,14 +520,16 @@ function esc_string(str) end function esc_ass(str) --1.3# used function to escape, also this function will use the byte order mark instead of immediately pasting the zero-width space - return str:gsub('\\', '\\\239\187\191'):gsub('{', '\\{') + return str:gsub("\\", "\\\239\187\191"):gsub("{", "\\{") end ---------Start of LogManager--------- --LogManager (Read and Format the List from Log)-- function read_log(func) local f = io.open(log_fullpath, "r") - if not f then return end + if not f then + return + end local contents = {} local line_count = 0 for line in f:lines() do @@ -526,54 +543,95 @@ function read_log_table() local line_pos = 0 return read_log(function(line) local tt, p, t, s, d, n, e, l, dt, ln, r, g - if line:match('^.-\"(.-)\"') then --1.3# changed if statement to only get title and path - tt, p = line:match('^.-\"(.-)\" | (.*) | ' .. esc_string(log_length_text) .. '(.*)') + if line:match('^.-"(.-)"') then --1.3# changed if statement to only get title and path + tt, p = line:match('^.-"(.-)" | (.*) | ' .. esc_string(log_length_text) .. "(.*)") else --1.3# get path only if no title is there - p = line:match('[(.*)%]]%s(.*) | ' .. esc_string(log_length_text) .. '(.*)') - end - d, n, e = p:match('^(.-)([^\\/]-)%.([^\\/%.]-)%.?$') --1.3# not inside if statement anymore since we are not changing name with title anymore - dt = line:match('%[(.-)%]') - t = line:match(' | ' .. esc_string(log_time_text) .. '(%d*%.?%d*)(.*)$') - ln = line:match(' | ' .. esc_string(log_length_text) .. '(%d*%.?%d*)(.*)$') - if tonumber(ln) and tonumber(t) then r = tonumber(ln) - tonumber(t) else r = 0 end - s = line:match(' | .* | ' .. esc_string(log_keybind_text) .. '(%d*)(.*)$') - g = line:match(' | .* | ' .. esc_string(log_group_text) .. '(%d*)(.*)$') + p = line:match("[(.*)%]]%s(.*) | " .. esc_string(log_length_text) .. "(.*)") + end + d, n, e = p:match("^(.-)([^\\/]-)%.([^\\/%.]-)%.?$") --1.3# not inside if statement anymore since we are not changing name with title anymore + dt = line:match("%[(.-)%]") + t = line:match(" | " .. esc_string(log_time_text) .. "(%d*%.?%d*)(.*)$") + ln = line:match(" | " .. esc_string(log_length_text) .. "(%d*%.?%d*)(.*)$") + if tonumber(ln) and tonumber(t) then + r = tonumber(ln) - tonumber(t) + else + r = 0 + end + s = line:match(" | .* | " .. esc_string(log_keybind_text) .. "(%d*)(.*)$") + g = line:match(" | .* | " .. esc_string(log_group_text) .. "(%d*)(.*)$") l = line line_pos = line_pos + 1 - return {found_path = p, found_time = t, found_name = n, found_title = tt, found_line = l, found_sequence = line_pos, found_directory = d, found_datetime = dt, found_length = ln, found_remaining = r, found_slot = s, found_group = g} + return { + found_path = p, + found_time = t, + found_name = n, + found_title = tt, + found_line = l, + found_sequence = line_pos, + found_directory = d, + found_datetime = dt, + found_length = ln, + found_remaining = r, + found_slot = s, + found_group = g, + } end) end function list_sort(tab, sort) - if sort == 'added-asc' then - table.sort(tab, function(a, b) return a['found_sequence'] < b['found_sequence'] end) - elseif sort == 'added-desc' then - table.sort(tab, function(a, b) return a['found_sequence'] > b['found_sequence'] end) - elseif sort == 'keybind-asc' and filterName == 'keybinds' then - table.sort(tab, function(a, b) return a['found_slot'] > b['found_slot'] end) - elseif sort == 'keybind-desc' and filterName == 'keybinds' then - table.sort(tab, function(a, b) return a['found_slot'] < b['found_slot'] end) - elseif sort == 'time-asc' then - table.sort(tab, function(a, b) return tonumber(a['found_time']) > tonumber(b['found_time']) end) - elseif sort == 'time-desc' then - table.sort(tab, function(a, b) return tonumber(a['found_time']) < tonumber(b['found_time']) end) - elseif sort == 'alphanum-asc' or sort == 'alphanum-desc' then - local function padnum(d) local dec, n = string.match(d, "(%.?)0*(.+)") - return #dec > 0 and ("%.12f"):format(d) or ("%s%03d%s"):format(dec, #n, n) end - if sort == 'alphanum-asc' then - table.sort(tab, function(a, b) return tostring(a['found_path']):gsub("%.?%d+", padnum) .. ("%3d"):format(#b) > tostring(b['found_path']):gsub("%.?%d+", padnum) .. ("%3d"):format(#a) end) - elseif sort == 'alphanum-desc' then - table.sort(tab, function(a, b) return tostring(a['found_path']):gsub("%.?%d+", padnum) .. ("%3d"):format(#b) < tostring(b['found_path']):gsub("%.?%d+", padnum) .. ("%3d"):format(#a) end) - end - end - + if sort == "added-asc" then + table.sort(tab, function(a, b) + return a["found_sequence"] < b["found_sequence"] + end) + elseif sort == "added-desc" then + table.sort(tab, function(a, b) + return a["found_sequence"] > b["found_sequence"] + end) + elseif sort == "keybind-asc" and filterName == "keybinds" then + table.sort(tab, function(a, b) + return a["found_slot"] > b["found_slot"] + end) + elseif sort == "keybind-desc" and filterName == "keybinds" then + table.sort(tab, function(a, b) + return a["found_slot"] < b["found_slot"] + end) + elseif sort == "time-asc" then + table.sort(tab, function(a, b) + return tonumber(a["found_time"]) > tonumber(b["found_time"]) + end) + elseif sort == "time-desc" then + table.sort(tab, function(a, b) + return tonumber(a["found_time"]) < tonumber(b["found_time"]) + end) + elseif sort == "alphanum-asc" or sort == "alphanum-desc" then + local function padnum(d) + local dec, n = string.match(d, "(%.?)0*(.+)") + return #dec > 0 and ("%.12f"):format(d) or ("%s%03d%s"):format(dec, #n, n) + end + if sort == "alphanum-asc" then + table.sort(tab, function(a, b) + return tostring(a["found_path"]):gsub("%.?%d+", padnum) .. ("%3d"):format(#b) + > tostring(b["found_path"]):gsub("%.?%d+", padnum) .. ("%3d"):format(#a) + end) + elseif sort == "alphanum-desc" then + table.sort(tab, function(a, b) + return tostring(a["found_path"]):gsub("%.?%d+", padnum) .. ("%3d"):format(#b) + < tostring(b["found_path"]):gsub("%.?%d+", padnum) .. ("%3d"):format(#a) + end) + end + end + return tab end function get_o_variable(str, arr_var) --1.3# function to get variable content from passed array - if not str then return end - if str:match('%%(.*)%%') then str = str:match('%%(.*)%%') end --1.3# if the entry has % around it, then remove it - + if not str then + return + end + if str:match("%%(.*)%%") then + str = str:match("%%(.*)%%") + end --1.3# if the entry has % around it, then remove it + local var_return for i = 1, #arr_var do --1.3# loop through the passed array and get the value of the matched variable if arr_var[i][1] == str then @@ -583,13 +641,14 @@ function get_o_variable(str, arr_var) --1.3# function to get variable content fr end return var_return or "" --1.3# return the founded variable content or empty string if nothing is found - end function parse_list_item(str, properties) --1.3#add ability to parse the contents of the list like the header - if not str then return msg.error('str in parse_list_item is nil') end - - local list_filename, list_filepath, list_filetitle = get_local_names(properties["item"],'osd')--1.3# added osd property so it removes special characters for displaying list + if not str then + return msg.error("str in parse_list_item is nil") + end + + local list_filename, list_filepath, list_filetitle = get_local_names(properties["item"], "osd") --1.3# added osd property so it removes special characters for displaying list if o.slice_name and list_filepath:len() > o.slice_name_amount then --1.3.1# fix #86 since p doesn't exist anymore, and checks for specific filename / filepath / filetitle, so slicing is accurate. list_filepath = list_filepath:sub(1, o.slice_name_amount) .. "..." @@ -601,17 +660,18 @@ function parse_list_item(str, properties) --1.3#add ability to parse the content list_filetitle = list_filetitle:sub(1, o.slice_name_amount) .. "..." end - str = str:gsub("%%name%%", list_filename) + str = str + :gsub("%%name%%", list_filename) :gsub("%%path%%", list_filepath) - :gsub("%%title%%", list_filetitle) - :gsub("%%number%%", properties["index"]+1) --1.3# index +1 is the osd_index + :gsub("%%title%%", list_filetitle) + :gsub("%%number%%", properties["index"] + 1) --1.3# index +1 is the osd_index :gsub("%%dt%%", properties["item"].found_datetime) - + for s in str:gmatch("%%dt_%%.%%") do --1.3# loop through all found dt_ in the script a local svar = s:match("_%%."):sub(2) --1.3# match whatever starting from _ when using %dt_var%, then sub(2) to remove the first letter which is _ (then var will remain) to use in our gsub if parse_8601(properties["item"].found_datetime) then --1.3.1# for backward compatibility if matching did not work reset to null str = str:gsub(esc_string(s), os.date(svar, parse_8601(properties["item"].found_datetime))) --1.3# replaces the found dt_var with eg. dt_%a from config - + for x in str:gmatch("%%%d_dt%%") do --1.3.1# for backward compatibility adds 0_dt to be able to force customize date and time variables str = str:gsub(esc_string(x), get_o_variable(x, o.list_content_variables)) end @@ -647,16 +707,24 @@ function parse_list_item(str, properties) --1.3#add ability to parse the content str = str:gsub(esc_string(s), "") end end - - if properties['quickselect'] and str:match("%%quickselect%%") then --1.2# replace quickselect with the actual key if its available - str = str:gsub("%%quickselect%%", properties['quickselect']) + + if properties["quickselect"] and str:match("%%quickselect%%") then --1.2# replace quickselect with the actual key if its available + str = str:gsub("%%quickselect%%", properties["quickselect"]) else str = str:gsub("%%quickselect%%", "") end --1.3# same concept for showing groups but for time if properties["item"].found_time and tonumber(properties["item"].found_time) > 0 then - str = str:gsub('%%duration%%', format_time(properties["item"].found_time, o.list_duration_time_format[3], o.list_duration_time_format[2], o.list_duration_time_format[1])) + str = str:gsub( + "%%duration%%", + format_time( + properties["item"].found_time, + o.list_duration_time_format[3], + o.list_duration_time_format[2], + o.list_duration_time_format[1] + ) + ) for s in str:gmatch("%%%d*_duration%%") do str = str:gsub(esc_string(s), get_o_variable(s, o.list_content_variables)) end @@ -667,7 +735,15 @@ function parse_list_item(str, properties) --1.3#add ability to parse the content end end if properties["item"].found_length and tonumber(properties["item"].found_length) > 0 then - str = str:gsub('%%length%%', format_time(properties["item"].found_length, o.list_length_time_format[3], o.list_length_time_format[2], o.list_length_time_format[1])) + str = str:gsub( + "%%length%%", + format_time( + properties["item"].found_length, + o.list_length_time_format[3], + o.list_length_time_format[2], + o.list_length_time_format[1] + ) + ) for s in str:gmatch("%%%d*_length%%") do str = str:gsub(esc_string(s), get_o_variable(s, o.list_content_variables)) end @@ -678,7 +754,15 @@ function parse_list_item(str, properties) --1.3#add ability to parse the content end end if properties["item"].found_remaining and tonumber(properties["item"].found_remaining) > 0 then - str = str:gsub('%%remaining%%', format_time(properties["item"].found_remaining, o.list_remaining_time_format[3], o.list_remaining_time_format[2], o.list_remaining_time_format[1])) + str = str:gsub( + "%%remaining%%", + format_time( + properties["item"].found_remaining, + o.list_remaining_time_format[3], + o.list_remaining_time_format[2], + o.list_remaining_time_format[1] + ) + ) for s in str:gmatch("%%%d*_remaining%%") do str = str:gsub(esc_string(s), get_o_variable(s, o.list_content_variables)) end @@ -696,107 +780,132 @@ end function parse_header(str) local osd_header_color = string.format("{\\1c&H%s}", o.header_color) local osd_search_color = osd_header_color - if search_active == 'typing' then + if search_active == "typing" then osd_search_color = string.format("{\\1c&H%s}", o.search_color_typing) - elseif search_active == 'not_typing' then + elseif search_active == "not_typing" then osd_search_color = string.format("{\\1c&H%s}", o.search_color_not_typing) end - - str = str:gsub("%%total%%", #osd_log_contents) - :gsub("%%cursor%%", list_cursor) - + + str = str:gsub("%%total%%", #osd_log_contents):gsub("%%cursor%%", list_cursor) + local filter_osd = filterName - if filter_osd ~= 'all' then - if filter_osd:match('/:group%%(.*)%%') then filter_osd = filter_osd:match('/:group%%(.*)%%') end + if filter_osd ~= "all" then + if filter_osd:match("/:group%%(.*)%%") then + filter_osd = filter_osd:match("/:group%%(.*)%%") + end str = str:gsub("%%filter%%", filter_osd) for s in str:gmatch("%%%d*_filter%%") do --1.3# if a filter variable is found, such as %0_filter% then get the content of the variable for it str = str:gsub(esc_string(s), get_o_variable(s, o.header_variables)) end else - str = str:gsub("%%filter%%", '') + str = str:gsub("%%filter%%", "") for s in str:gmatch("%%%d*_filter%%") do --1.3# if a custom slot is found and there is no slot assigned, remove it str = str:gsub(esc_string(s), "") end end - - if str:match('%%duration%%') then - if get_total_duration('found_time') > 0 then - str = str:gsub("%%duration%%", format_time(get_total_duration('found_time'), o.header_duration_time_format[3], o.header_duration_time_format[2], o.header_duration_time_format[1])) + + if str:match("%%duration%%") then + if get_total_duration("found_time") > 0 then + str = str:gsub( + "%%duration%%", + format_time( + get_total_duration("found_time"), + o.header_duration_time_format[3], + o.header_duration_time_format[2], + o.header_duration_time_format[1] + ) + ) for s in str:gmatch("%%%d*_duration%%") do --1.3# if a filter variable is found, such as %0_filter% then get the content of the variable for it str = str:gsub(esc_string(s), get_o_variable(s, o.header_variables)) end else - str = str:gsub("%%duration%%", '') + str = str:gsub("%%duration%%", "") for s in str:gmatch("%%%d*_duration%%") do --1.3# if a custom slot is found and there is no slot assigned, remove it str = str:gsub(esc_string(s), "") end end end - - if str:match('%%length%%') then - if get_total_duration('found_length') > 0 then - str = str:gsub("%%length%%", format_time(get_total_duration('found_length'), o.header_length_time_format[3], o.header_length_time_format[2], o.header_length_time_format[1])) + + if str:match("%%length%%") then + if get_total_duration("found_length") > 0 then + str = str:gsub( + "%%length%%", + format_time( + get_total_duration("found_length"), + o.header_length_time_format[3], + o.header_length_time_format[2], + o.header_length_time_format[1] + ) + ) for s in str:gmatch("%%%d*_length%%") do --1.3# if a filter variable is found, such as %0_filter% then get the content of the variable for it str = str:gsub(esc_string(s), get_o_variable(s, o.header_variables)) end else - str = str:gsub("%%length%%", '') + str = str:gsub("%%length%%", "") for s in str:gmatch("%%%d*_length%%") do --1.3# if a filter variable is found, such as %0_filter% then get the content of the variable for it str = str:gsub(esc_string(s), "") end end end - - if str:match('%remaining%%') then - if get_total_duration('found_remaining') > 0 then - str = str:gsub("%%remaining%%", format_time(get_total_duration('found_remaining'), o.header_remaining_time_format[3], o.header_remaining_time_format[2], o.header_remaining_time_format[1])) + + if str:match("%remaining%%") then + if get_total_duration("found_remaining") > 0 then + str = str:gsub( + "%%remaining%%", + format_time( + get_total_duration("found_remaining"), + o.header_remaining_time_format[3], + o.header_remaining_time_format[2], + o.header_remaining_time_format[1] + ) + ) for s in str:gmatch("%%%d*_remaining%%") do --1.3# if a filter variable is found, such as %0_filter% then get the content of the variable for it str = str:gsub(esc_string(s), get_o_variable(s, o.header_variables)) end else - str = str:gsub("%%remaining%%", '') + str = str:gsub("%%remaining%%", "") for s in str:gmatch("%%%d*_remaining%%") do --1.3# if a filter variable is found, such as %0_filter% then get the content of the variable for it str = str:gsub(esc_string(s), "") end end - end - + end + if #list_highlight_cursor > 0 then str = str:gsub("%%highlight%%", #list_highlight_cursor) for s in str:gmatch("%%%d*_highlight%%") do --1.3# if a filter variable is found, such as %0_filter% then get the content of the variable for it str = str:gsub(esc_string(s), get_o_variable(s, o.header_variables)) end else - str = str:gsub("%%highlight%%", '') + str = str:gsub("%%highlight%%", "") for s in str:gmatch("%%%d*_highlight%%") do --1.3# if a filter variable is found, such as %0_filter% then get the content of the variable for it str = str:gsub(esc_string(s), "") end end - + if sortName and sortName ~= o.header_sort_hide_text then str = str:gsub("%%sort%%", sortName) for s in str:gmatch("%%%d*_sort%%") do --1.3# if a filter variable is found, such as %0_filter% then get the content of the variable for it str = str:gsub(esc_string(s), get_o_variable(s, o.header_variables)) end else - str = str:gsub("%%sort%%", '') + str = str:gsub("%%sort%%", "") for s in str:gmatch("%%%d*_sort%%") do --1.3# if a filter variable is found, such as %0_filter% then get the content of the variable for it str = str:gsub(esc_string(s), "") end end - + if search_active then local search_string_osd = search_string - if search_string_osd ~= '' then - search_string_osd = esc_ass(search_string:gsub('%%', '%%%%%%%%')) --1.3# used ass_escape instead of gsub + if search_string_osd ~= "" then + search_string_osd = esc_ass(search_string:gsub("%%", "%%%%%%%%")) --1.3# used ass_escape instead of gsub end - - str = str:gsub("%%search%%", osd_search_color..search_string_osd..osd_header_color) + + str = str:gsub("%%search%%", osd_search_color .. search_string_osd .. osd_header_color) for s in str:gmatch("%%%d*_search%%") do --1.3# if a filter variable is found, such as %0_filter% then get the content of the variable for it str = str:gsub(esc_string(s), get_o_variable(s, o.header_variables)) end else - str = str:gsub("%%search%%", '') + str = str:gsub("%%search%%", "") for s in str:gmatch("%%%d*_search%%") do --1.3# if a filter variable is found, such as %0_filter% then get the content of the variable for it str = str:gsub(esc_string(s), "") end @@ -806,85 +915,157 @@ function parse_header(str) end function search_log_contents(arr_contents) - if not arr_contents or not arr_contents[1] or not search_active or not search_string == '' then return false end - - local search_query = '' + if not arr_contents or not arr_contents[1] or not search_active or not search_string == "" then + return false + end + + local search_query = "" for search in search_string:gmatch("[^%s]+") do - search_query = search_query..'.-'..esc_string(search) - end - local contents_string = '' + search_query = search_query .. ".-" .. esc_string(search) + end + local contents_string = "" local search_arr_contents = {} for i = 1, #arr_contents do --1.3# removed specific search method as it doesn't seem useful anymore, --1.3.1# utilize arr_contents instead of osd_log_contents - if o.search_behavior == 'any' then + if o.search_behavior == "any" then contents_string = arr_contents[i].found_datetime --1.3# seperated date and time for search if parse_8601(arr_contents[i].found_datetime) then --1.3# an if statement to check if date could be parsed --1.3# allows for all type of dates to be searched thanks to the loop - local os_date_tag= {'%a', '%A', '%b', '%B', '%c', '%d', '%H', '%I', '%M', '%m', '%p', '%S', '%w', '%x', '%X', '%Y', '%y'} --1.3# add all lua date time parameters - for j=1, #os_date_tag do --1.3# replace all lua parameters with date values that can be searched - contents_string = contents_string..os.date(os_date_tag[j], parse_8601(arr_contents[i].found_datetime)) + local os_date_tag = { + "%a", + "%A", + "%b", + "%B", + "%c", + "%d", + "%H", + "%I", + "%M", + "%m", + "%p", + "%S", + "%w", + "%x", + "%X", + "%Y", + "%y", + } --1.3# add all lua date time parameters + for j = 1, #os_date_tag do --1.3# replace all lua parameters with date values that can be searched + contents_string = contents_string + .. os.date(os_date_tag[j], parse_8601(arr_contents[i].found_datetime)) end end - contents_string = contents_string..(arr_contents[i].found_title or '')..(arr_contents[i].found_name or '')..arr_contents[i].found_path --1.3# added found_name since parsing is different now + contents_string = contents_string + .. (arr_contents[i].found_title or "") + .. (arr_contents[i].found_name or "") + .. arr_contents[i].found_path --1.3# added found_name since parsing is different now if tonumber(arr_contents[i].found_time) > 0 then - contents_string = contents_string..format_time(arr_contents[i].found_time, o.list_duration_time_format[3], o.list_duration_time_format[2], o.list_duration_time_format[1]) + contents_string = contents_string + .. format_time( + arr_contents[i].found_time, + o.list_duration_time_format[3], + o.list_duration_time_format[2], + o.list_duration_time_format[1] + ) end if tonumber(arr_contents[i].found_length) > 0 then - contents_string = contents_string..format_time(arr_contents[i].found_length, o.list_length_time_format[3], o.list_length_time_format[2], o.list_length_time_format[1]) + contents_string = contents_string + .. format_time( + arr_contents[i].found_length, + o.list_length_time_format[3], + o.list_length_time_format[2], + o.list_length_time_format[1] + ) end if tonumber(arr_contents[i].found_remaining) > 0 then - contents_string = contents_string..format_time(arr_contents[i].found_remaining, o.list_remaining_time_format[3], o.list_remaining_time_format[2], o.list_remaining_time_format[1]) - end + contents_string = contents_string + .. format_time( + arr_contents[i].found_remaining, + o.list_remaining_time_format[3], + o.list_remaining_time_format[2], + o.list_remaining_time_format[1] + ) + end if arr_contents[i].found_slot then - contents_string = contents_string..get_slot_keybind(tonumber(arr_contents[i].found_slot)) + contents_string = contents_string .. get_slot_keybind(tonumber(arr_contents[i].found_slot)) end if arr_contents[i].found_group then - contents_string = contents_string..get_group_properties(tonumber(arr_contents[i].found_group)).name + contents_string = contents_string .. get_group_properties(tonumber(arr_contents[i].found_group)).name end - elseif o.search_behavior == 'any-notime' then + elseif o.search_behavior == "any-notime" then contents_string = arr_contents[i].found_datetime --1.3# seperated date and time for search if parse_8601(arr_contents[i].found_datetime) then --1.3# an if statement to check if date could be parsed - local os_date_tag= {'%a', '%A', '%b', '%B', '%c', '%d', '%H', '%I', '%M', '%m', '%p', '%S', '%w', '%x', '%X', '%Y', '%y'} --1.3# add all lua date time parameters - for j=1, #os_date_tag do --1.3# replace all lua parameters with values that can be searched - contents_string = contents_string..os.date(os_date_tag[j], parse_8601(arr_contents[i].found_datetime)) + local os_date_tag = { + "%a", + "%A", + "%b", + "%B", + "%c", + "%d", + "%H", + "%I", + "%M", + "%m", + "%p", + "%S", + "%w", + "%x", + "%X", + "%Y", + "%y", + } --1.3# add all lua date time parameters + for j = 1, #os_date_tag do --1.3# replace all lua parameters with values that can be searched + contents_string = contents_string + .. os.date(os_date_tag[j], parse_8601(arr_contents[i].found_datetime)) end end - contents_string = contents_string..(arr_contents[i].found_title or '')..(arr_contents[i].found_name or '')..arr_contents[i].found_path --1.3# added found_name since parsing is different now + contents_string = contents_string + .. (arr_contents[i].found_title or "") + .. (arr_contents[i].found_name or "") + .. arr_contents[i].found_path --1.3# added found_name since parsing is different now if arr_contents[i].found_slot then - contents_string = contents_string..get_slot_keybind(tonumber(arr_contents[i].found_slot)) + contents_string = contents_string .. get_slot_keybind(tonumber(arr_contents[i].found_slot)) end if arr_contents[i].found_group then - contents_string = contents_string..get_group_properties(tonumber(arr_contents[i].found_group)).name + contents_string = contents_string .. get_group_properties(tonumber(arr_contents[i].found_group)).name end end - + if string.lower(contents_string):match(string.lower(search_query)) then table.insert(search_arr_contents, arr_contents[i]) end end return search_arr_contents - end function filter_log_contents(arr_contents, filter) - if not arr_contents or not arr_contents[1] or not filter or filter == 'all' then return false end + if not arr_contents or not arr_contents[1] or not filter or filter == "all" then + return false + end local filtered_arr_contents = {} - if filter:match('%%%+%%') then - if filter_stack(arr_contents,filter) then filtered_arr_contents = filter_stack(arr_contents, filter) end - elseif filter:match('%%%-%%') then - if filter_omit(arr_contents,filter) then filtered_arr_contents = filter_omit(arr_contents, filter) end + if filter:match("%%%+%%") then + if filter_stack(arr_contents, filter) then + filtered_arr_contents = filter_stack(arr_contents, filter) + end + elseif filter:match("%%%-%%") then + if filter_omit(arr_contents, filter) then + filtered_arr_contents = filter_omit(arr_contents, filter) + end else - if filter_apply(arr_contents, filter) then filtered_arr_contents = filter_apply(arr_contents, filter) end + if filter_apply(arr_contents, filter) then + filtered_arr_contents = filter_apply(arr_contents, filter) + end end return filtered_arr_contents end - function filter_omit(arr_contents, filter) - if not arr_contents or not arr_contents[1] or not filter or filter == 'all' or not filter:match('%%%-%%') then return false end + if not arr_contents or not arr_contents[1] or not filter or filter == "all" or not filter:match("%%%-%%") then + return false + end local omitted_arr_table = arr_contents local filter_items = {} @@ -893,13 +1074,21 @@ function filter_omit(arr_contents, filter) end local temp_filtered_contents = arr_contents - for i=1, #filter_items do - if i== 1 and filter_apply(arr_contents, filter_items[i]) then omitted_arr_table = filter_apply(arr_contents, filter_items[i]) end + for i = 1, #filter_items do + if i == 1 and filter_apply(arr_contents, filter_items[i]) then + omitted_arr_table = filter_apply(arr_contents, filter_items[i]) + end if i > 1 then - if filter_apply(arr_contents, filter_items[i]) then temp_filtered_contents = filter_apply(arr_contents, filter_items[i]) end - for j=1, #temp_filtered_contents do - for k=1, #omitted_arr_table do - if temp_filtered_contents[j] and omitted_arr_table[k] and temp_filtered_contents[j].found_sequence == omitted_arr_table[k].found_sequence then + if filter_apply(arr_contents, filter_items[i]) then + temp_filtered_contents = filter_apply(arr_contents, filter_items[i]) + end + for j = 1, #temp_filtered_contents do + for k = 1, #omitted_arr_table do + if + temp_filtered_contents[j] + and omitted_arr_table[k] + and temp_filtered_contents[j].found_sequence == omitted_arr_table[k].found_sequence + then table.remove(omitted_arr_table, k) end end @@ -907,13 +1096,17 @@ function filter_omit(arr_contents, filter) end end - table.sort(omitted_arr_table, function(a, b) return a['found_sequence'] < b['found_sequence'] end) + table.sort(omitted_arr_table, function(a, b) + return a["found_sequence"] < b["found_sequence"] + end) return omitted_arr_table end function filter_stack(arr_contents, filter) - if not arr_contents or not arr_contents[1] or not filter or filter == 'all' or not filter:match('%%%+%%') then return false end + if not arr_contents or not arr_contents[1] or not filter or filter == "all" or not filter:match("%%%+%%") then + return false + end local stacked_arr_table = {} local filter_items = {} @@ -923,127 +1116,153 @@ function filter_stack(arr_contents, filter) local unique_values = {} local temp_filtered_contents = arr_contents - for i=1, #filter_items do - if filter_apply(arr_contents, filter_items[i]) then temp_filtered_contents = filter_apply(arr_contents, filter_items[i]) end - for j=1, #temp_filtered_contents do - if not has_value(unique_values, temp_filtered_contents[j].found_sequence) then - table.insert(unique_values, temp_filtered_contents[j].found_sequence) - table.insert(stacked_arr_table, temp_filtered_contents[j]) - end + for i = 1, #filter_items do + if filter_apply(arr_contents, filter_items[i]) then + temp_filtered_contents = filter_apply(arr_contents, filter_items[i]) + end + for j = 1, #temp_filtered_contents do + if not has_value(unique_values, temp_filtered_contents[j].found_sequence) then + table.insert(unique_values, temp_filtered_contents[j].found_sequence) + table.insert(stacked_arr_table, temp_filtered_contents[j]) end + end end - table.sort(stacked_arr_table, function(a, b) return a['found_sequence'] < b['found_sequence'] end) + table.sort(stacked_arr_table, function(a, b) + return a["found_sequence"] < b["found_sequence"] + end) return stacked_arr_table - end function filter_apply(arr_contents, filter) - if not arr_contents or not arr_contents[1] or not filter or filter == 'all' then return false end + if not arr_contents or not arr_contents[1] or not filter or filter == "all" then + return false + end local filtered_arr_contents = {} - if filter == 'groups' then + if filter == "groups" then for i = 1, #arr_contents do if arr_contents[i].found_group then table.insert(filtered_arr_contents, arr_contents[i]) end end end - - if filter:match('/:group%%(.*)%%') then - filter = filter:match('/:group%%(.*)%%') + + if filter:match("/:group%%(.*)%%") then + filter = filter:match("/:group%%(.*)%%") for i = 1, #arr_contents do - if arr_contents[i].found_group and filter == get_group_properties(tonumber(arr_contents[i].found_group)).name then + if + arr_contents[i].found_group + and filter == get_group_properties(tonumber(arr_contents[i].found_group)).name + then table.insert(filtered_arr_contents, arr_contents[i]) end end end - - if filter == 'keybinds' then + + if filter == "keybinds" then for i = 1, #arr_contents do if arr_contents[i].found_slot then table.insert(filtered_arr_contents, arr_contents[i]) end end end - - if filter == 'recents' then - table.sort(arr_contents, function(a, b) return a['found_sequence'] < b['found_sequence'] end) + + if filter == "recents" then + table.sort(arr_contents, function(a, b) + return a["found_sequence"] < b["found_sequence"] + end) local unique_values = {} local list_total = #arr_contents - - if filePath == arr_contents[#arr_contents].found_path and tonumber(arr_contents[#arr_contents].found_time) == 0 then - list_total = list_total -1 + + if + filePath == arr_contents[#arr_contents].found_path + and tonumber(arr_contents[#arr_contents].found_time) == 0 + then + list_total = list_total - 1 end - + for i = list_total, 1, -1 do if not has_value(unique_values, arr_contents[i].found_path) then table.insert(unique_values, arr_contents[i].found_path) table.insert(filtered_arr_contents, arr_contents[i]) end end - table.sort(filtered_arr_contents, function(a, b) return a['found_sequence'] < b['found_sequence'] end) + table.sort(filtered_arr_contents, function(a, b) + return a["found_sequence"] < b["found_sequence"] + end) end - - if filter == 'distinct' then - table.sort(arr_contents, function(a, b) return a['found_sequence'] < b['found_sequence'] end) + + if filter == "distinct" then + table.sort(arr_contents, function(a, b) + return a["found_sequence"] < b["found_sequence"] + end) local unique_values = {} local list_total = #arr_contents - - if filePath == arr_contents[#arr_contents].found_path and tonumber(arr_contents[#arr_contents].found_time) == 0 then - list_total = list_total -1 + + if + filePath == arr_contents[#arr_contents].found_path + and tonumber(arr_contents[#arr_contents].found_time) == 0 + then + list_total = list_total - 1 end - + for i = list_total, 1, -1 do - if arr_contents[i].found_directory and not has_value(unique_values, arr_contents[i].found_directory) and not starts_protocol(protocols, arr_contents[i].found_path) then + if + arr_contents[i].found_directory + and not has_value(unique_values, arr_contents[i].found_directory) + and not starts_protocol(protocols, arr_contents[i].found_path) + then table.insert(unique_values, arr_contents[i].found_directory) table.insert(filtered_arr_contents, arr_contents[i]) end end - table.sort(filtered_arr_contents, function(a, b) return a['found_sequence'] < b['found_sequence'] end) + table.sort(filtered_arr_contents, function(a, b) + return a["found_sequence"] < b["found_sequence"] + end) end - - if filter == 'fileonly' then + + if filter == "fileonly" then for i = 1, #arr_contents do if tonumber(arr_contents[i].found_time) == 0 then table.insert(filtered_arr_contents, arr_contents[i]) end end end - - if filter == 'timeonly' then + + if filter == "timeonly" then for i = 1, #arr_contents do if tonumber(arr_contents[i].found_time) > 0 then table.insert(filtered_arr_contents, arr_contents[i]) end end end - - if filter == 'titleonly' then + + if filter == "titleonly" then for i = 1, #arr_contents do if arr_contents[i].found_title then table.insert(filtered_arr_contents, arr_contents[i]) end end end - - if filter == 'protocols' then + + if filter == "protocols" then for i = 1, #arr_contents do if starts_protocol(o.logging_protocols, arr_contents[i].found_path) then table.insert(filtered_arr_contents, arr_contents[i]) end end end - - if filter == 'keywords' then + + if filter == "keywords" then for i = 1, #arr_contents do if contain_value(o.keywords_filter_list, arr_contents[i].found_line) then table.insert(filtered_arr_contents, arr_contents[i]) end end end - - if filter == 'playing' then + + if filter == "playing" then for i = 1, #arr_contents do if arr_contents[i].found_path == filePath then table.insert(filtered_arr_contents, arr_contents[i]) @@ -1055,81 +1274,139 @@ function filter_apply(arr_contents, filter) end function get_osd_log_contents(filter, sort) - if not filter then filter = filterName end - if not sort then sort = get_list_sort(filter) end - + if not filter then + filter = filterName + end + if not sort then + sort = get_list_sort(filter) + end + local current_sort osd_log_contents = read_log_table() - if not osd_log_contents or not osd_log_contents[1] then return end + if not osd_log_contents or not osd_log_contents[1] then + return + end + + current_sort = "added-asc" - current_sort = 'added-asc' + if filter_log_contents(osd_log_contents, filter) then + osd_log_contents = filter_log_contents(osd_log_contents, filter) + end + if search_log_contents(osd_log_contents) then + osd_log_contents = search_log_contents(osd_log_contents) + end - if filter_log_contents(osd_log_contents, filter) then osd_log_contents = filter_log_contents(osd_log_contents, filter) end - if search_log_contents(osd_log_contents) then osd_log_contents = search_log_contents(osd_log_contents) end - if sort ~= current_sort then list_sort(osd_log_contents, sort) end end function get_list_sort(filter) - if not filter then filter = filterName end - - if filter == 'keybinds' then - available_sorts = {'added-asc', 'added-desc', 'keybind-asc', 'keybind-desc', 'time-asc', 'time-desc', 'alphanum-asc', 'alphanum-desc'} + if not filter then + filter = filterName + end + + if filter == "keybinds" then + available_sorts = { + "added-asc", + "added-desc", + "keybind-asc", + "keybind-desc", + "time-asc", + "time-desc", + "alphanum-asc", + "alphanum-desc", + } else - available_sorts = {'added-asc', 'added-desc', 'time-asc', 'time-desc', 'alphanum-asc', 'alphanum-desc'} + available_sorts = { "added-asc", "added-desc", "time-asc", "time-desc", "alphanum-asc", "alphanum-desc" } end - + local sort - for i=1, #o.list_filters_sort do + for i = 1, #o.list_filters_sort do if o.list_filters_sort[i][1] == filter then - if has_value(available_sorts, o.list_filters_sort[i][2]) then sort = o.list_filters_sort[i][2] end + if has_value(available_sorts, o.list_filters_sort[i][2]) then + sort = o.list_filters_sort[i][2] + end break end end - - if not sort and has_value(available_sorts, o.list_default_sort) then sort = o.list_default_sort end - - if not sort then sort = 'added-asc' end - + + if not sort and has_value(available_sorts, o.list_default_sort) then + sort = o.list_default_sort + end + + if not sort then + sort = "added-asc" + end + return sort end function parse_8601(timestamp) - if not string.match(timestamp, '^(%d%d%d%d)-(%d%d)-(%d%d)T(%d%d):(%d%d):(%d%d)(.-)$') then return false end - local inYear, inMonth, inDay, inHour, inMinute, inSecond, inZone = string.match(timestamp, '^(%d%d%d%d)-(%d%d)-(%d%d)T(%d%d):(%d%d):(%d%d)(.-)$') + if not string.match(timestamp, "^(%d%d%d%d)-(%d%d)-(%d%d)T(%d%d):(%d%d):(%d%d)(.-)$") then + return false + end + local inYear, inMonth, inDay, inHour, inMinute, inSecond, inZone = + string.match(timestamp, "^(%d%d%d%d)-(%d%d)-(%d%d)T(%d%d):(%d%d):(%d%d)(.-)$") + + local zHours, zMinutes = string.match(inZone, "^(.-):(%d%d)$") + + local returnTime = os.time({ + year = inYear, + month = inMonth, + day = inDay, + hour = inHour, + min = inMinute, + sec = inSecond, + isdst = false, + }) - local zHours, zMinutes = string.match(inZone, '^(.-):(%d%d)$') - - local returnTime = os.time({year=inYear, month=inMonth, day=inDay, hour=inHour, min=inMinute, sec=inSecond, isdst=false}) - if zHours then - returnTime = returnTime - ((tonumber(zHours)*3600) + (tonumber(zMinutes)*60)) + returnTime = returnTime - ((tonumber(zHours) * 3600) + (tonumber(zMinutes) * 60)) end - + return returnTime - end function draw_list(arr_contents) - local osd_msg = '' - local osd_color = '' + local osd_msg = "" + local osd_color = "" local key = 0 - local osd_text = string.format("{\\an%f{\\fscx%f}{\\fscy%f}{\\bord%f}{\\1c&H%s}", o.list_alignment, o.text_scale, o.text_scale, o.text_border, o.text_color) - local osd_cursor = string.format("{\\an%f}{\\fscx%f}{\\fscy%f}{\\bord%f}{\\1c&H%s}", o.list_alignment, o.text_cursor_scale, o.text_cursor_scale, o.text_cursor_border, o.text_cursor_color) - local osd_header = string.format("{\\an%f}{\\fscx%f}{\\fscy%f}{\\bord%f}{\\1c&H%s}", o.list_alignment, o.header_scale, o.header_scale, o.header_border, o.header_color) + local osd_text = string.format( + "{\\an%f{\\fscx%f}{\\fscy%f}{\\bord%f}{\\1c&H%s}", + o.list_alignment, + o.text_scale, + o.text_scale, + o.text_border, + o.text_color + ) + local osd_cursor = string.format( + "{\\an%f}{\\fscx%f}{\\fscy%f}{\\bord%f}{\\1c&H%s}", + o.list_alignment, + o.text_cursor_scale, + o.text_cursor_scale, + o.text_cursor_border, + o.text_cursor_color + ) + local osd_header = string.format( + "{\\an%f}{\\fscx%f}{\\fscy%f}{\\bord%f}{\\1c&H%s}", + o.list_alignment, + o.header_scale, + o.header_scale, + o.header_border, + o.header_color + ) local osd_msg_end = "{\\1c&HFFFFFF}" local item_properties = {} --1.3# to hold all of the stuff that we extract from within this table, such as the osd_index, etc.. - - if o.header_text ~= '' then + + if o.header_text ~= "" then osd_msg = osd_msg .. osd_header .. parse_header(o.header_text) .. osd_msg_end --1.3.0# made line break part of the config end - + if search_active and not arr_contents[1] then --1.3.1# changed osd_log_contents to arr_contents - osd_msg = osd_msg .. 'No search results found' .. osd_msg_end + osd_msg = osd_msg .. "No search results found" .. osd_msg_end end - + local list_start if o.list_middle_loader then list_start = list_cursor - math.floor(o.list_show_amount / 2) @@ -1138,7 +1415,9 @@ function draw_list(arr_contents) end local showall = false local showrest = false - if list_start < 0 then list_start = 0 end + if list_start < 0 then + list_start = 0 + end if #arr_contents <= o.list_show_amount then list_start = 0 showall = true @@ -1151,14 +1430,18 @@ function draw_list(arr_contents) osd_msg = osd_msg .. o.list_sliced_prefix .. osd_msg_end end for i = list_start, list_start + o.list_show_amount - 1, 1 do - if i == #arr_contents then break end + if i == #arr_contents then + break + end item_properties["item"] = arr_contents[#arr_contents - i] --1.3# stores the targetted item - item_properties['index'] = i --1.3# stores the index of which the item is found in the arr_contents table + item_properties["index"] = i --1.3# stores the index of which the item is found in the arr_contents table if o.quickselect_0to9_keybind and o.list_show_amount <= 10 then key = key + 1 - if key == 10 then key = 0 end - item_properties['quickselect'] = key --1.3# added osd_key to item_properties to call it in parse_list_item + if key == 10 then + key = 0 + end + item_properties["quickselect"] = key --1.3# added osd_key to item_properties to call it in parse_list_item end if i + 1 == list_cursor then @@ -1168,27 +1451,28 @@ function draw_list(arr_contents) end for j = 1, #list_highlight_cursor do - if list_highlight_cursor[j] and list_highlight_cursor[j][1] == i+1 then - osd_msg = osd_msg..osd_color..esc_string(o.text_highlight_pre_text) + if list_highlight_cursor[j] and list_highlight_cursor[j][1] == i + 1 then + osd_msg = osd_msg .. osd_color .. esc_string(o.text_highlight_pre_text) end end - if o.list_content_text ~= '' then --1.3# use parse_list_item to make the list customizable - osd_msg = osd_msg..osd_color..parse_list_item(o.list_content_text, item_properties) .. osd_msg_end --1.3.0# made line break part of the config + if o.list_content_text ~= "" then --1.3# use parse_list_item to make the list customizable + osd_msg = osd_msg .. osd_color .. parse_list_item(o.list_content_text, item_properties) .. osd_msg_end --1.3.0# made line break part of the config end if i == list_start + o.list_show_amount - 1 and not showall and not showrest then osd_msg = osd_msg .. o.list_sliced_suffix end - end mp.set_osd_ass(0, 0, osd_msg) end function list_empty_error_msg() - if osd_log_contents ~= nil and osd_log_contents[1] then return end + if osd_log_contents ~= nil and osd_log_contents[1] then + return + end local msg_text - if filterName ~= 'all' then + if filterName ~= "all" then msg_text = filterName .. " filter in Bookmark Empty" else msg_text = "Bookmark Empty" @@ -1200,21 +1484,29 @@ function list_empty_error_msg() end function display_list(filter, sort, action) - if not filter then filter = 'all' end - if not sortName then sortName = get_list_sort(filter) end - + if not filter then + filter = "all" + end + if not sortName then + sortName = get_list_sort(filter) + end + local prev_sort = sortName - if not has_value(available_sorts, prev_sort) then prev_sort = get_list_sort() end + if not has_value(available_sorts, prev_sort) then + prev_sort = get_list_sort() + end - if not sort then sort = get_list_sort(filter) end + if not sort then + sort = get_list_sort(filter) + end sortName = sort local prev_filter = filterName filterName = filter - + get_osd_log_contents(filter, sort) - if action ~= 'hide-osd' then + if action ~= "hide-osd" then if not osd_log_contents or not osd_log_contents[1] then list_empty_error_msg() filterName = prev_filter @@ -1222,24 +1514,25 @@ function display_list(filter, sort, action) return end end - if not osd_log_contents and not search_active or not osd_log_contents[1] and not search_active then return end - + if not osd_log_contents and not search_active or not osd_log_contents[1] and not search_active then + return + end + if not has_value(o.filters_and_sequence, filter) then table.insert(o.filters_and_sequence, filter) end - + local insert_new = false - + local trigger_close_list = false local trigger_initial_list = false - - + if not list_pages or not list_pages[1] then - table.insert(list_pages, {filter, 1, 1, {}, sort}) + table.insert(list_pages, { filter, 1, 1, {}, sort }) else for i = 1, #list_pages do if list_pages[i][1] == filter then - list_pages[i][3] = list_pages[i][3]+1 + list_pages[i][3] = list_pages[i][3] + 1 insert_new = false break else @@ -1247,9 +1540,11 @@ function display_list(filter, sort, action) end end end - - if insert_new then table.insert(list_pages, {filter, 1, 1, {}, sort}) end - + + if insert_new then + table.insert(list_pages, { filter, 1, 1, {}, sort }) + end + for i = 1, #list_pages do if not search_active and list_pages[i][1] == prev_filter then list_pages[i][2] = list_cursor @@ -1259,34 +1554,42 @@ function display_list(filter, sort, action) if list_pages[i][1] ~= filter then list_pages[i][3] = 0 end - if list_pages[i][3] == 2 and filter == 'all' and o.main_list_keybind_twice_exits then + if list_pages[i][3] == 2 and filter == "all" and o.main_list_keybind_twice_exits then trigger_close_list = true elseif list_pages[i][3] == 2 and list_pages[1][1] == filter then - trigger_close_list = true + trigger_close_list = true elseif list_pages[i][3] == 2 then trigger_initial_list = true end end - + if trigger_initial_list then - display_list(list_pages[1][1], nil, 'hide-osd') + display_list(list_pages[1][1], nil, "hide-osd") return end - + if trigger_close_list then list_close_and_trash_collection() return end - - if not search_active then get_page_properties(filter) else update_search_results('','') end + + if not search_active then + get_page_properties(filter) + else + update_search_results("", "") + end draw_list(osd_log_contents) if utils.shared_script_property_set then - utils.shared_script_property_set('simplebookmark-menu-open', 'yes') + utils.shared_script_property_set("simplebookmark-menu-open", "yes") + end + mp.set_property("user-data/simplebookmark/menu-open", "yes") + if o.toggle_idlescreen then + mp.commandv("script-message", "osc-idlescreen", "no", "no_osd") end - mp.set_property('user-data/simplebookmark/menu-open', 'yes') - if o.toggle_idlescreen then mp.commandv('script-message', 'osc-idlescreen', 'no', 'no_osd') end list_drawn = true - if not search_active then get_list_keybinds() end + if not search_active then + get_list_keybinds() + end end --End of LogManager (Read and Format the List from Log)-- @@ -1299,45 +1602,54 @@ function select(pos, action) return end end - + local list_cursor_temp = list_cursor + pos if list_cursor_temp > 0 and list_cursor_temp <= #osd_log_contents then list_cursor = list_cursor_temp - if action == 'highlight' then + if action == "highlight" then if not has_value(list_highlight_cursor, list_cursor, 1) then if pos > -1 then for i = pos, 1, -1 do - if not has_value(list_highlight_cursor, list_cursor-i, 1) then - table.insert(list_highlight_cursor, {list_cursor-i, osd_log_contents[#osd_log_contents+1+i - list_cursor]}) + if not has_value(list_highlight_cursor, list_cursor - i, 1) then + table.insert( + list_highlight_cursor, + { list_cursor - i, osd_log_contents[#osd_log_contents + 1 + i - list_cursor] } + ) end end else for i = pos, -1, 1 do - if not has_value(list_highlight_cursor, list_cursor-i, 1) then - table.insert(list_highlight_cursor, {list_cursor-i, osd_log_contents[#osd_log_contents+1+i - list_cursor]}) + if not has_value(list_highlight_cursor, list_cursor - i, 1) then + table.insert( + list_highlight_cursor, + { list_cursor - i, osd_log_contents[#osd_log_contents + 1 + i - list_cursor] } + ) end end end - table.insert(list_highlight_cursor, {list_cursor, osd_log_contents[#osd_log_contents+1 - list_cursor]}) + table.insert( + list_highlight_cursor, + { list_cursor, osd_log_contents[#osd_log_contents + 1 - list_cursor] } + ) else - for i=1, #list_highlight_cursor do + for i = 1, #list_highlight_cursor do if list_highlight_cursor[i] and list_highlight_cursor[i][1] == list_cursor then table.remove(list_highlight_cursor, i) end end if pos > -1 then - for i=1, #list_highlight_cursor do + for i = 1, #list_highlight_cursor do for j = pos, 1, -1 do - if list_highlight_cursor[i] and list_highlight_cursor[i][1] == list_cursor-j then + if list_highlight_cursor[i] and list_highlight_cursor[i][1] == list_cursor - j then table.remove(list_highlight_cursor, i) end end end else - for i=#list_highlight_cursor, 1, -1 do + for i = #list_highlight_cursor, 1, -1 do for j = pos, -1, 1 do - if list_highlight_cursor[i] and list_highlight_cursor[i][1] == list_cursor-j then + if list_highlight_cursor[i] and list_highlight_cursor[i][1] == list_cursor - j then table.remove(list_highlight_cursor, i) end end @@ -1346,7 +1658,7 @@ function select(pos, action) end end end - + if o.loop_through_list then if list_cursor_temp > #osd_log_contents then list_cursor = 1 @@ -1354,7 +1666,7 @@ function select(pos, action) list_cursor = #osd_log_contents end end - + draw_list(osd_log_contents) end @@ -1395,7 +1707,7 @@ function list_page_up(action) if search_active and o.search_not_typing_smartly then list_search_not_typing_mode(true) - end + end end function list_page_down(action) @@ -1417,18 +1729,20 @@ function list_page_down(action) if search_active and o.search_not_typing_smartly then list_search_not_typing_mode(true) - end + end end function list_highlight_all() get_osd_log_contents(filterName) - if not osd_log_contents or not osd_log_contents[1] then return end - + if not osd_log_contents or not osd_log_contents[1] then + return + end + if #list_highlight_cursor < #osd_log_contents then - for i=1, #osd_log_contents do + for i = 1, #osd_log_contents do if not has_value(list_highlight_cursor, i, 1) then - table.insert(list_highlight_cursor, {i, osd_log_contents[#osd_log_contents+1-i]}) - end + table.insert(list_highlight_cursor, { i, osd_log_contents[#osd_log_contents + 1 - i] }) + end end select(0) else @@ -1437,7 +1751,9 @@ function list_highlight_all() end function list_unhighlight_all() - if not list_highlight_cursor or not list_highlight_cursor[1] then return end + if not list_highlight_cursor or not list_highlight_cursor[1] then + return + end list_highlight_cursor = {} select(0) end @@ -1445,43 +1761,61 @@ end --LogManager Actions-- function load(list_cursor, add_playlist, target_time) - if not osd_log_contents or not osd_log_contents[1] then return end + if not osd_log_contents or not osd_log_contents[1] then + return + end if not target_time then - if not osd_log_contents[#osd_log_contents - list_cursor + 1] then return end --1.3.0# fixes crash when loading an entry that doesn't exist - seekTime = tonumber(osd_log_contents[#osd_log_contents - list_cursor + 1].found_time) + o.resume_offset - if (seekTime < 0) then + if not osd_log_contents[#osd_log_contents - list_cursor + 1] then + return + end --1.3.0# fixes crash when loading an entry that doesn't exist + seekTime = tonumber(osd_log_contents[#osd_log_contents - list_cursor + 1].found_time) + o.resume_offset + if seekTime < 0 then seekTime = 0 end else seekTime = target_time end - if file_exists(osd_log_contents[#osd_log_contents - list_cursor + 1].found_path) or starts_protocol(protocols, osd_log_contents[#osd_log_contents - list_cursor + 1].found_path) then - local list_filename, list_filepath, list_filetitle = get_local_names(osd_log_contents[#osd_log_contents - list_cursor + 1]) --1.3# use the name that automatically falls back instead for osd printing or msg (solves the issue that causes concatinating found_name to crash because it sometimes doesn't exist due to parsing changes) + if + file_exists(osd_log_contents[#osd_log_contents - list_cursor + 1].found_path) + or starts_protocol(protocols, osd_log_contents[#osd_log_contents - list_cursor + 1].found_path) + then + local list_filename, list_filepath, list_filetitle = + get_local_names(osd_log_contents[#osd_log_contents - list_cursor + 1]) --1.3# use the name that automatically falls back instead for osd printing or msg (solves the issue that causes concatinating found_name to crash because it sometimes doesn't exist due to parsing changes) if not add_playlist then - if o.preserve_video_settings then mp.command("write-watch-later-config") end--1.3.1# option to preserve video settings by using write-watch-later-config when loading bookmark replaces current file #84 + if o.preserve_video_settings then + mp.command("write-watch-later-config") + end --1.3.1# option to preserve video settings by using write-watch-later-config when loading bookmark replaces current file #84 if filePath ~= osd_log_contents[#osd_log_contents - list_cursor + 1].found_path then - mp.commandv('loadfile', osd_log_contents[#osd_log_contents - list_cursor + 1].found_path) + mp.commandv("loadfile", osd_log_contents[#osd_log_contents - list_cursor + 1].found_path) resume_selected = true else - mp.commandv('seek', seekTime, 'absolute', 'exact') + mp.commandv("seek", seekTime, "absolute", "exact") list_close_and_trash_collection() end if o.osd_messages == true then - mp.osd_message('Loaded:\n' .. list_filename.. ' 🕒 ' .. format_time(seekTime, o.osd_time_format[3], o.osd_time_format[2], o.osd_time_format[1])) + mp.osd_message( + "Loaded:\n" + .. list_filename + .. " 🕒 " + .. format_time(seekTime, o.osd_time_format[3], o.osd_time_format[2], o.osd_time_format[1]) + ) end - msg.info('Loaded the below file:\n' .. list_filename .. ' | '.. format_time(seekTime)) + msg.info("Loaded the below file:\n" .. list_filename .. " | " .. format_time(seekTime)) else - mp.commandv('loadfile', osd_log_contents[#osd_log_contents - list_cursor + 1].found_path, 'append-play') + mp.commandv("loadfile", osd_log_contents[#osd_log_contents - list_cursor + 1].found_path, "append-play") if o.osd_messages == true then - mp.osd_message('Added into Playlist:\n'..list_filename..' ') + mp.osd_message("Added into Playlist:\n" .. list_filename .. " ") end - msg.info('Added the below file into playlist:\n' .. list_filepath) + msg.info("Added the below file into playlist:\n" .. list_filepath) end else if o.osd_messages == true then - mp.osd_message('File Doesn\'t Exist:\n' .. osd_log_contents[#osd_log_contents - list_cursor + 1].found_path) --1.3# cant use the list_filepath because file doesn't exist so it returns nil + mp.osd_message("File Doesn't Exist:\n" .. osd_log_contents[#osd_log_contents - list_cursor + 1].found_path) --1.3# cant use the list_filepath because file doesn't exist so it returns nil end - msg.info('The file below doesn\'t seem to exist:\n' .. osd_log_contents[#osd_log_contents - list_cursor + 1].found_path) + msg.info( + "The file below doesn't seem to exist:\n" + .. osd_log_contents[#osd_log_contents - list_cursor + 1].found_path + ) return end end @@ -1493,38 +1827,56 @@ end function list_add_playlist(action) if not action then load(list_cursor, true) - elseif action == 'highlight' then - if not list_highlight_cursor or not list_highlight_cursor[1] then return end + elseif action == "highlight" then + if not list_highlight_cursor or not list_highlight_cursor[1] then + return + end local file_ignored_total = 0 - - for i=1, #list_highlight_cursor do - if file_exists(list_highlight_cursor[i][2].found_path) or starts_protocol(protocols, list_highlight_cursor[i][2].found_path) then + + for i = 1, #list_highlight_cursor do + if + file_exists(list_highlight_cursor[i][2].found_path) + or starts_protocol(protocols, list_highlight_cursor[i][2].found_path) + then mp.commandv("loadfile", list_highlight_cursor[i][2].found_path, "append-play") else - msg.warn('The below file was not added into playlist as it does not seem to exist:\n' .. list_highlight_cursor[i][2].found_path) + msg.warn( + "The below file was not added into playlist as it does not seem to exist:\n" + .. list_highlight_cursor[i][2].found_path + ) file_ignored_total = file_ignored_total + 1 end end if o.osd_messages == true then if file_ignored_total > 0 then - mp.osd_message('Added into Playlist '..#list_highlight_cursor - file_ignored_total..' Item/s\nIgnored '..file_ignored_total.. " Item/s That Do Not Exist") + mp.osd_message( + "Added into Playlist " + .. #list_highlight_cursor - file_ignored_total + .. " Item/s\nIgnored " + .. file_ignored_total + .. " Item/s That Do Not Exist" + ) else - mp.osd_message('Added into Playlist '..#list_highlight_cursor - file_ignored_total..' Item/s') + mp.osd_message("Added into Playlist " .. #list_highlight_cursor - file_ignored_total .. " Item/s") end end if file_ignored_total > 0 then - msg.warn('Ignored a total of '..file_ignored_total.. " Item/s that does not seem to exist") + msg.warn("Ignored a total of " .. file_ignored_total .. " Item/s that does not seem to exist") end - msg.info('Added into playlist a total of '..#list_highlight_cursor - file_ignored_total..' item/s') + msg.info("Added into playlist a total of " .. #list_highlight_cursor - file_ignored_total .. " item/s") end end function same_path_log_delete(target_path, entry_limit, arr_contents) --1.2.5# seperate function for entry_limit - if not target_path then return msg.error('same_path_log_delete no target_path defined') end + if not target_path then + return msg.error("same_path_log_delete no target_path defined") + end if not arr_contents then --1.2.6# ability to pass array (usually automatically defining array here is fine, but just for performance sake when calling multiple functions that use the same array) arr_contents = read_log_table() - if not arr_contents or not arr_contents[1] then return end + if not arr_contents or not arr_contents[1] then + return + end end --1.2.7# return deleted_entries for o.overwrite_preserve_properties option @@ -1538,13 +1890,15 @@ function same_path_log_delete(target_path, entry_limit, arr_contents) entries_found = entries_found + 1 elseif arr_contents[i].found_path == target_path and entries_found >= entry_limit then table.insert(deleted_entries, arr_contents[i]) --1.2.7# store entries that will be deleted in a new array - table.remove(arr_contents,i) + table.remove(arr_contents, i) trigger_delete = true end end end - - if not trigger_delete then return end + + if not trigger_delete then + return + end local f = io.open(log_fullpath, "w+") if arr_contents ~= nil and arr_contents[1] then for i = 1, #arr_contents do @@ -1555,37 +1909,49 @@ function same_path_log_delete(target_path, entry_limit, arr_contents) return deleted_entries end - function find_entry(round, target_path, target_time) --1.2.6# changed it to find_entry to have the sequence and any other additional property --1.2.5# get the entry log sequence which is basically the id using path and time - if not target_path or not target_time then return msg.error('find_entry no target_path or target_time defined') end + if not target_path or not target_time then + return msg.error("find_entry no target_path or target_time defined") + end local temp_log_contents = read_log_table() - if not temp_log_contents or not temp_log_contents[1] then return end - + if not temp_log_contents or not temp_log_contents[1] then + return + end + for i = #temp_log_contents, 1, -1 do if not round then - if temp_log_contents[i].found_path == target_path and tonumber(temp_log_contents[i].found_time) == target_time then + if + temp_log_contents[i].found_path == target_path + and tonumber(temp_log_contents[i].found_time) == target_time + then return temp_log_contents[i] end else - if temp_log_contents[i].found_path == target_path and math.floor(tonumber(temp_log_contents[i].found_time)) == target_time then + if + temp_log_contents[i].found_path == target_path + and math.floor(tonumber(temp_log_contents[i].found_time)) == target_time + then return temp_log_contents[i] end end end end - function delete_log_entry(target_sequence, arr_contents) --1.2.5# new function to delete based on sequence which is (id) - if not target_sequence then return end --1.2.5# if no sequence found then just exit the function - if not arr_contents then --1.2.5# ability to pass an array instead of looping through + if not target_sequence then + return + end --1.2.5# if no sequence found then just exit the function + if not arr_contents then --1.2.5# ability to pass an array instead of looping through arr_contents = read_log_table() - if not arr_contents or not arr_contents[1] then return end + if not arr_contents or not arr_contents[1] then + return + end end table.remove(arr_contents, target_sequence) - + local f = io.open(log_fullpath, "w+") if arr_contents ~= nil and arr_contents[1] then for i = 1, #arr_contents do @@ -1596,26 +1962,33 @@ function delete_log_entry(target_sequence, arr_contents) end function delete_log_entry_highlighted() - if not list_highlight_cursor or not list_highlight_cursor[1] then return end + if not list_highlight_cursor or not list_highlight_cursor[1] then + return + end local temp_log_contents = read_log_table() - if not temp_log_contents or not temp_log_contents[1] then return end - + if not temp_log_contents or not temp_log_contents[1] then + return + end + local log_contents_length = #temp_log_contents - + for i = 1, log_contents_length do - for j=1, #list_highlight_cursor do - if temp_log_contents[log_contents_length+1-i] then - if temp_log_contents[log_contents_length+1-i].found_sequence == list_highlight_cursor[j][2].found_sequence then - table.remove(temp_log_contents, log_contents_length+1-i) + for j = 1, #list_highlight_cursor do + if temp_log_contents[log_contents_length + 1 - i] then + if + temp_log_contents[log_contents_length + 1 - i].found_sequence + == list_highlight_cursor[j][2].found_sequence + then + table.remove(temp_log_contents, log_contents_length + 1 - i) end end end end - - msg.info("Deleted "..#list_highlight_cursor.." Item/s") - + + msg.info("Deleted " .. #list_highlight_cursor .. " Item/s") + list_unhighlight_all() - + local f = io.open(log_fullpath, "w+") if temp_log_contents ~= nil and temp_log_contents[1] then for i = 1, #temp_log_contents do @@ -1623,7 +1996,6 @@ function delete_log_entry_highlighted() end end f:close() - end function delete_selected() @@ -1637,18 +2009,18 @@ function delete_selected() return end delete_log_entry(list_sequence) - msg.info("Deleted \"" .. list_filepath .. "\" | " .. format_time(list_seektime)) + msg.info('Deleted "' .. list_filepath .. '" | ' .. format_time(list_seektime)) end function list_delete(action) if not action then delete_selected() - elseif action == 'highlight' then + elseif action == "highlight" then delete_log_entry_highlighted() end get_osd_log_contents() if #osd_log_contents == 0 then - display_list('all') + display_list("all") select(0) elseif list_cursor < #osd_log_contents + 1 then select(0) @@ -1658,9 +2030,11 @@ function list_delete(action) end function get_total_duration(action) - if not osd_log_contents or not osd_log_contents[1] then return 0 end + if not osd_log_contents or not osd_log_contents[1] then + return 0 + end local list_total_duration = 0 - if action == 'found_time' or action == 'found_length' or action == 'found_remaining' then + if action == "found_time" or action == "found_length" or action == "found_remaining" then for i = #osd_log_contents, 1, -1 do if tonumber(osd_log_contents[i][action]) > 0 then list_total_duration = list_total_duration + osd_log_contents[i][action] @@ -1671,10 +2045,19 @@ function get_total_duration(action) end function list_cycle_sort() - if filterName == 'keybinds' then - available_sorts = {'added-asc', 'added-desc', 'keybind-asc', 'keybind-desc', 'time-asc', 'time-desc', 'alphanum-asc', 'alphanum-desc'} + if filterName == "keybinds" then + available_sorts = { + "added-asc", + "added-desc", + "keybind-asc", + "keybind-desc", + "time-asc", + "time-desc", + "alphanum-asc", + "alphanum-desc", + } else - available_sorts = {'added-asc', 'added-desc', 'time-asc', 'time-desc', 'alphanum-asc', 'alphanum-desc'} + available_sorts = { "added-asc", "added-desc", "time-asc", "time-desc", "alphanum-asc", "alphanum-desc" } end local next_sort @@ -1684,12 +2067,14 @@ function list_cycle_sort() next_sort = available_sorts[1] break else - next_sort = available_sorts[i+1] + next_sort = available_sorts[i + 1] break end end end - if not next_sort then return end + if not next_sort then + return + end get_osd_log_contents(filterName, next_sort) sortName = next_sort update_list_highlist_cursor() @@ -1697,13 +2082,17 @@ function list_cycle_sort() end function update_list_highlist_cursor() - if not list_highlight_cursor or not list_highlight_cursor[1] then return end + if not list_highlight_cursor or not list_highlight_cursor[1] then + return + end local temp_list_highlight_cursor = {} for i = 1, #osd_log_contents do - for j=1, #list_highlight_cursor do - if osd_log_contents[#osd_log_contents+1-i].found_sequence == list_highlight_cursor[j][2].found_sequence then - table.insert(temp_list_highlight_cursor, {i, list_highlight_cursor[j][2]}) + for j = 1, #list_highlight_cursor do + if + osd_log_contents[#osd_log_contents + 1 - i].found_sequence == list_highlight_cursor[j][2].found_sequence + then + table.insert(temp_list_highlight_cursor, { i, list_highlight_cursor[j][2] }) end end end @@ -1715,8 +2104,10 @@ end --LogManager Filter Functions-- function get_page_properties(filter) - if not filter then return end - for i=1, #list_pages do + if not filter then + return + end + for i = 1, #list_pages do if list_pages[i][1] == filter then list_cursor = list_pages[i][2] list_highlight_cursor = list_pages[i][4] @@ -1729,7 +2120,9 @@ function get_page_properties(filter) end function select_filter_sequence(pos) - if not list_drawn then return end + if not list_drawn then + return + end local curr_pos local target_pos @@ -1738,7 +2131,7 @@ function select_filter_sequence(pos) curr_pos = i end end - + if curr_pos and pos > -1 then for i = curr_pos, #o.filters_and_sequence do if o.filters_and_sequence[i + pos] then @@ -1760,7 +2153,7 @@ function select_filter_sequence(pos) end end end - + if o.loop_through_filters then if not target_pos and pos > -1 or target_pos and target_pos > #o.filters_and_sequence then for i = 1, #o.filters_and_sequence do @@ -1783,7 +2176,7 @@ function select_filter_sequence(pos) end if o.filters_and_sequence[target_pos] then - display_list(o.filters_and_sequence[target_pos], nil, 'hide-osd') + display_list(o.filters_and_sequence[target_pos], nil, "hide-osd") end end @@ -1797,158 +2190,248 @@ end --LogManager (List Bind and Unbind)-- function get_list_keybinds() - bind_keys(o.list_ignored_keybind, 'ignore') - bind_keys(o.list_move_up_keybind, 'move-up', list_move_up, 'repeatable') - bind_keys(o.list_move_down_keybind, 'move-down', list_move_down, 'repeatable') - bind_keys(o.list_move_first_keybind, 'move-first', list_move_first, 'repeatable') - bind_keys(o.list_move_last_keybind, 'move-last', list_move_last, 'repeatable') - bind_keys(o.list_page_up_keybind, 'page-up', list_page_up, 'repeatable') - bind_keys(o.list_page_down_keybind, 'page-down', list_page_down, 'repeatable') - bind_keys(o.list_select_keybind, 'list-select', list_select) - bind_keys(o.list_add_playlist_keybind, 'list-add-playlist', list_add_playlist) - bind_keys(o.list_add_playlist_highlighted_keybind, 'list-add-playlist-highlight', function()list_add_playlist('highlight')end) - bind_keys(o.list_delete_keybind, 'list-delete', list_delete) - bind_keys(o.list_delete_highlighted_keybind, 'list-delete-highlight', function()list_delete('highlight')end) - bind_keys(o.next_filter_sequence_keybind, 'list-filter-next', list_filter_next) - bind_keys(o.previous_filter_sequence_keybind, 'list-filter-previous', list_filter_previous) - bind_keys(o.list_search_activate_keybind, 'list-search-activate', list_search_activate) - bind_keys(o.list_highlight_all_keybind, 'list-highlight-all', list_highlight_all) - bind_keys(o.list_unhighlight_all_keybind, 'list-unhighlight-all', list_unhighlight_all) - bind_keys(o.list_cycle_sort_keybind, 'list-cycle-sort', list_cycle_sort) - bind_keys(o.keybinds_remove_keybind, 'keybind-slot-remove', slot_remove) - bind_keys(o.keybinds_remove_highlighted_keybind, 'keybind-slot-remove-highlight', function()slot_remove('highlight')end) - bind_keys(o.list_group_add_cycle_keybind, 'group-add-cycle', list_group_add_cycle) - bind_keys(o.list_group_add_cycle_highlighted_keybind, 'group-add-cycle-highlight', function()list_group_add_cycle('highlight')end) - bind_keys(o.list_groups_remove_keybind, 'group-remove', group_remove) - bind_keys(o.list_groups_remove_highlighted_keybind, 'group-remove-highlight', function()group_remove('highlight')end) + bind_keys(o.list_ignored_keybind, "ignore") + bind_keys(o.list_move_up_keybind, "move-up", list_move_up, "repeatable") + bind_keys(o.list_move_down_keybind, "move-down", list_move_down, "repeatable") + bind_keys(o.list_move_first_keybind, "move-first", list_move_first, "repeatable") + bind_keys(o.list_move_last_keybind, "move-last", list_move_last, "repeatable") + bind_keys(o.list_page_up_keybind, "page-up", list_page_up, "repeatable") + bind_keys(o.list_page_down_keybind, "page-down", list_page_down, "repeatable") + bind_keys(o.list_select_keybind, "list-select", list_select) + bind_keys(o.list_add_playlist_keybind, "list-add-playlist", list_add_playlist) + bind_keys(o.list_add_playlist_highlighted_keybind, "list-add-playlist-highlight", function() + list_add_playlist("highlight") + end) + bind_keys(o.list_delete_keybind, "list-delete", list_delete) + bind_keys(o.list_delete_highlighted_keybind, "list-delete-highlight", function() + list_delete("highlight") + end) + bind_keys(o.next_filter_sequence_keybind, "list-filter-next", list_filter_next) + bind_keys(o.previous_filter_sequence_keybind, "list-filter-previous", list_filter_previous) + bind_keys(o.list_search_activate_keybind, "list-search-activate", list_search_activate) + bind_keys(o.list_highlight_all_keybind, "list-highlight-all", list_highlight_all) + bind_keys(o.list_unhighlight_all_keybind, "list-unhighlight-all", list_unhighlight_all) + bind_keys(o.list_cycle_sort_keybind, "list-cycle-sort", list_cycle_sort) + bind_keys(o.keybinds_remove_keybind, "keybind-slot-remove", slot_remove) + bind_keys(o.keybinds_remove_highlighted_keybind, "keybind-slot-remove-highlight", function() + slot_remove("highlight") + end) + bind_keys(o.list_group_add_cycle_keybind, "group-add-cycle", list_group_add_cycle) + bind_keys(o.list_group_add_cycle_highlighted_keybind, "group-add-cycle-highlight", function() + list_group_add_cycle("highlight") + end) + bind_keys(o.list_groups_remove_keybind, "group-remove", group_remove) + bind_keys(o.list_groups_remove_highlighted_keybind, "group-remove-highlight", function() + group_remove("highlight") + end) for i = 1, #o.groups_list_and_keybind do - if not o.groups_list_and_keybind[i][2] then break end - mp.add_forced_key_binding(o.groups_list_and_keybind[i][2], 'group-add-'..i, function()group_add(i)end) + if not o.groups_list_and_keybind[i][2] then + break + end + mp.add_forced_key_binding(o.groups_list_and_keybind[i][2], "group-add-" .. i, function() + group_add(i) + end) end for i = 1, #o.groups_list_and_keybind do - if not o.groups_list_and_keybind[i][3] then break end - mp.add_forced_key_binding(o.groups_list_and_keybind[i][3], 'group-add-highlight-'..i, function()group_add(i, 'highlight')end) + if not o.groups_list_and_keybind[i][3] then + break + end + mp.add_forced_key_binding(o.groups_list_and_keybind[i][3], "group-add-highlight-" .. i, function() + group_add(i, "highlight") + end) end - + for i = 1, #o.list_highlight_move_keybind do for j = 1, #o.list_move_up_keybind do - mp.add_forced_key_binding(o.list_highlight_move_keybind[i]..'+'..o.list_move_up_keybind[j], 'highlight-move-up'..j, function()list_move_up('highlight') end, 'repeatable') + mp.add_forced_key_binding( + o.list_highlight_move_keybind[i] .. "+" .. o.list_move_up_keybind[j], + "highlight-move-up" .. j, + function() + list_move_up("highlight") + end, + "repeatable" + ) end for j = 1, #o.list_move_down_keybind do - mp.add_forced_key_binding(o.list_highlight_move_keybind[i]..'+'..o.list_move_down_keybind[j], 'highlight-move-down'..j, function()list_move_down('highlight') end, 'repeatable') + mp.add_forced_key_binding( + o.list_highlight_move_keybind[i] .. "+" .. o.list_move_down_keybind[j], + "highlight-move-down" .. j, + function() + list_move_down("highlight") + end, + "repeatable" + ) end for j = 1, #o.list_move_first_keybind do - mp.add_forced_key_binding(o.list_highlight_move_keybind[i]..'+'..o.list_move_first_keybind[j], 'highlight-move-first'..j, function()list_move_first('highlight') end, 'repeatable') + mp.add_forced_key_binding( + o.list_highlight_move_keybind[i] .. "+" .. o.list_move_first_keybind[j], + "highlight-move-first" .. j, + function() + list_move_first("highlight") + end, + "repeatable" + ) end for j = 1, #o.list_move_last_keybind do - mp.add_forced_key_binding(o.list_highlight_move_keybind[i]..'+'..o.list_move_last_keybind[j], 'highlight-move-last'..j, function()list_move_last('highlight') end, 'repeatable') + mp.add_forced_key_binding( + o.list_highlight_move_keybind[i] .. "+" .. o.list_move_last_keybind[j], + "highlight-move-last" .. j, + function() + list_move_last("highlight") + end, + "repeatable" + ) end for j = 1, #o.list_page_up_keybind do - mp.add_forced_key_binding(o.list_highlight_move_keybind[i]..'+'..o.list_page_up_keybind[j], 'highlight-page-up'..j, function()list_page_up('highlight') end, 'repeatable') + mp.add_forced_key_binding( + o.list_highlight_move_keybind[i] .. "+" .. o.list_page_up_keybind[j], + "highlight-page-up" .. j, + function() + list_page_up("highlight") + end, + "repeatable" + ) end for j = 1, #o.list_page_down_keybind do - mp.add_forced_key_binding(o.list_highlight_move_keybind[i]..'+'..o.list_page_down_keybind[j], 'highlight-page-down'..j, function()list_page_down('highlight') end, 'repeatable') + mp.add_forced_key_binding( + o.list_highlight_move_keybind[i] .. "+" .. o.list_page_down_keybind[j], + "highlight-page-down" .. j, + function() + list_page_down("highlight") + end, + "repeatable" + ) end end - + if not search_active then - bind_keys(o.list_close_keybind, 'list-close', list_close_and_trash_collection) + bind_keys(o.list_close_keybind, "list-close", list_close_and_trash_collection) end - + for i = 1, #o.list_filter_jump_keybind do - mp.add_forced_key_binding(o.list_filter_jump_keybind[i][1], 'list-filter-jump'..i, function()display_list(o.list_filter_jump_keybind[i][2]) end) + mp.add_forced_key_binding(o.list_filter_jump_keybind[i][1], "list-filter-jump" .. i, function() + display_list(o.list_filter_jump_keybind[i][2]) + end) end for i = 1, #o.open_list_keybind do if i == 1 then - mp.remove_key_binding('open-list') + mp.remove_key_binding("open-list") else - mp.remove_key_binding('open-list'..i) + mp.remove_key_binding("open-list" .. i) end - end - + end + if o.quickselect_0to9_keybind and o.list_show_amount <= 10 then - mp.add_forced_key_binding("1", "recent-1", function()load(list_start + 1) end) - mp.add_forced_key_binding("2", "recent-2", function()load(list_start + 2) end) - mp.add_forced_key_binding("3", "recent-3", function()load(list_start + 3) end) - mp.add_forced_key_binding("4", "recent-4", function()load(list_start + 4) end) - mp.add_forced_key_binding("5", "recent-5", function()load(list_start + 5) end) - mp.add_forced_key_binding("6", "recent-6", function()load(list_start + 6) end) - mp.add_forced_key_binding("7", "recent-7", function()load(list_start + 7) end) - mp.add_forced_key_binding("8", "recent-8", function()load(list_start + 8) end) - mp.add_forced_key_binding("9", "recent-9", function()load(list_start + 9) end) - mp.add_forced_key_binding("0", "recent-0", function()load(list_start + 10) end) + mp.add_forced_key_binding("1", "recent-1", function() + load(list_start + 1) + end) + mp.add_forced_key_binding("2", "recent-2", function() + load(list_start + 2) + end) + mp.add_forced_key_binding("3", "recent-3", function() + load(list_start + 3) + end) + mp.add_forced_key_binding("4", "recent-4", function() + load(list_start + 4) + end) + mp.add_forced_key_binding("5", "recent-5", function() + load(list_start + 5) + end) + mp.add_forced_key_binding("6", "recent-6", function() + load(list_start + 6) + end) + mp.add_forced_key_binding("7", "recent-7", function() + load(list_start + 7) + end) + mp.add_forced_key_binding("8", "recent-8", function() + load(list_start + 8) + end) + mp.add_forced_key_binding("9", "recent-9", function() + load(list_start + 9) + end) + mp.add_forced_key_binding("0", "recent-0", function() + load(list_start + 10) + end) end end function unbind_list_keys() - unbind_keys(o.list_ignored_keybind, 'ignore') - unbind_keys(o.list_move_up_keybind, 'move-up') - unbind_keys(o.list_move_down_keybind, 'move-down') - unbind_keys(o.list_move_first_keybind, 'move-first') - unbind_keys(o.list_move_last_keybind, 'move-last') - unbind_keys(o.list_page_up_keybind, 'page-up') - unbind_keys(o.list_page_down_keybind, 'page-down') - unbind_keys(o.list_select_keybind, 'list-select') - unbind_keys(o.list_add_playlist_keybind, 'list-add-playlist') - unbind_keys(o.list_add_playlist_highlighted_keybind, 'list-add-playlist-highlight') - unbind_keys(o.list_delete_keybind, 'list-delete') - unbind_keys(o.list_delete_highlighted_keybind, 'list-delete-highlight') - unbind_keys(o.list_close_keybind, 'list-close') - unbind_keys(o.next_filter_sequence_keybind, 'list-filter-next') - unbind_keys(o.previous_filter_sequence_keybind, 'list-filter-previous') - unbind_keys(o.list_highlight_all_keybind, 'list-highlight-all') - unbind_keys(o.list_highlight_all_keybind, 'list-unhighlight-all') - unbind_keys(o.list_cycle_sort_keybind, 'list-cycle-sort') - unbind_keys(o.keybinds_remove_keybind, 'keybind-slot-remove') - unbind_keys(o.keybinds_remove_keybind, 'keybind-slot-remove-highlight') - - unbind_keys(o.list_group_add_cycle_keybind, 'group-add-cycle') - unbind_keys(o.list_group_add_cycle_highlighted_keybind, 'group-add-cycle-highlight') - unbind_keys(o.list_groups_remove_keybind, 'group-remove') - unbind_keys(o.list_groups_remove_highlighted_keybind, 'group-remove-highlight') - + unbind_keys(o.list_ignored_keybind, "ignore") + unbind_keys(o.list_move_up_keybind, "move-up") + unbind_keys(o.list_move_down_keybind, "move-down") + unbind_keys(o.list_move_first_keybind, "move-first") + unbind_keys(o.list_move_last_keybind, "move-last") + unbind_keys(o.list_page_up_keybind, "page-up") + unbind_keys(o.list_page_down_keybind, "page-down") + unbind_keys(o.list_select_keybind, "list-select") + unbind_keys(o.list_add_playlist_keybind, "list-add-playlist") + unbind_keys(o.list_add_playlist_highlighted_keybind, "list-add-playlist-highlight") + unbind_keys(o.list_delete_keybind, "list-delete") + unbind_keys(o.list_delete_highlighted_keybind, "list-delete-highlight") + unbind_keys(o.list_close_keybind, "list-close") + unbind_keys(o.next_filter_sequence_keybind, "list-filter-next") + unbind_keys(o.previous_filter_sequence_keybind, "list-filter-previous") + unbind_keys(o.list_highlight_all_keybind, "list-highlight-all") + unbind_keys(o.list_highlight_all_keybind, "list-unhighlight-all") + unbind_keys(o.list_cycle_sort_keybind, "list-cycle-sort") + unbind_keys(o.keybinds_remove_keybind, "keybind-slot-remove") + unbind_keys(o.keybinds_remove_keybind, "keybind-slot-remove-highlight") + + unbind_keys(o.list_group_add_cycle_keybind, "group-add-cycle") + unbind_keys(o.list_group_add_cycle_highlighted_keybind, "group-add-cycle-highlight") + unbind_keys(o.list_groups_remove_keybind, "group-remove") + unbind_keys(o.list_groups_remove_highlighted_keybind, "group-remove-highlight") + for i = 1, #o.groups_list_and_keybind do - if not o.groups_list_and_keybind[i][2] then break end - mp.remove_key_binding('group-add-'..i) + if not o.groups_list_and_keybind[i][2] then + break + end + mp.remove_key_binding("group-add-" .. i) end for i = 1, #o.groups_list_and_keybind do - if not o.groups_list_and_keybind[i][3] then break end - mp.remove_key_binding('group-add-highlight-'..i) + if not o.groups_list_and_keybind[i][3] then + break + end + mp.remove_key_binding("group-add-highlight-" .. i) end for i = 1, #o.list_move_up_keybind do - mp.remove_key_binding('highlight-move-up'..i) + mp.remove_key_binding("highlight-move-up" .. i) end for i = 1, #o.list_move_down_keybind do - mp.remove_key_binding('highlight-move-down'..i) + mp.remove_key_binding("highlight-move-down" .. i) end for i = 1, #o.list_move_first_keybind do - mp.remove_key_binding('highlight-move-first'..i) + mp.remove_key_binding("highlight-move-first" .. i) end for i = 1, #o.list_move_last_keybind do - mp.remove_key_binding('highlight-move-last'..i) + mp.remove_key_binding("highlight-move-last" .. i) end for i = 1, #o.list_page_up_keybind do - mp.remove_key_binding('highlight-page-up'..i) + mp.remove_key_binding("highlight-page-up" .. i) end for i = 1, #o.list_page_down_keybind do - mp.remove_key_binding('highlight-page-down'..i) + mp.remove_key_binding("highlight-page-down" .. i) end - + for i = 1, #o.list_filter_jump_keybind do - mp.remove_key_binding('list-filter-jump'..i) + mp.remove_key_binding("list-filter-jump" .. i) end for i = 1, #o.open_list_keybind do if i == 1 then - mp.add_forced_key_binding(o.open_list_keybind[i][1], 'open-list', function()display_list(o.open_list_keybind[i][2]) end) + mp.add_forced_key_binding(o.open_list_keybind[i][1], "open-list", function() + display_list(o.open_list_keybind[i][2]) + end) else - mp.add_forced_key_binding(o.open_list_keybind[i][1], 'open-list'..i, function()display_list(o.open_list_keybind[i][2]) end) + mp.add_forced_key_binding(o.open_list_keybind[i][1], "open-list" .. i, function() + display_list(o.open_list_keybind[i][2]) + end) end end - + if o.quickselect_0to9_keybind and o.list_show_amount <= 10 then mp.remove_key_binding("recent-1") mp.remove_key_binding("recent-2") @@ -1965,19 +2448,21 @@ end function list_close_and_trash_collection() if utils.shared_script_property_set then - utils.shared_script_property_set('simplebookmark-menu-open', 'no') + utils.shared_script_property_set("simplebookmark-menu-open", "no") + end + mp.set_property("user-data/simplebookmark/menu-open", "no") + if o.toggle_idlescreen then + mp.commandv("script-message", "osc-idlescreen", "yes", "no_osd") end - mp.set_property('user-data/simplebookmark/menu-open', 'no') - if o.toggle_idlescreen then mp.commandv('script-message', 'osc-idlescreen', 'yes', 'no_osd') end unbind_list_keys() unbind_search_keys() mp.set_osd_ass(0, 0, "") list_drawn = false list_cursor = 1 list_start = 0 - filterName = 'all' + filterName = "all" list_pages = {} - search_string = '' + search_string = "" search_active = false list_highlight_cursor = {} sortName = nil @@ -1996,17 +2481,17 @@ end function list_search_not_typing_mode(auto_triggered) if auto_triggered then - if search_string ~= '' and osd_log_contents[1] then - search_active = 'not_typing' + if search_string ~= "" and osd_log_contents[1] then + search_active = "not_typing" elseif not osd_log_contents[1] then return else search_active = false end else - if search_string ~= '' then - search_active = 'not_typing' - else + if search_string ~= "" then + search_active = "not_typing" + else search_active = false end end @@ -2016,10 +2501,15 @@ function list_search_not_typing_mode(auto_triggered) end function list_search_activate() - if not list_drawn then return end - if search_active == 'typing' then list_search_exit() return end - search_active = 'typing' - + if not list_drawn then + return + end + if search_active == "typing" then + list_search_exit() + return + end + search_active = "typing" + for i = 1, #list_pages do if list_pages[i][1] == filterName then list_pages[i][2] = list_cursor @@ -2027,26 +2517,28 @@ function list_search_activate() list_pages[i][5] = sortName end end - - update_search_results('','') + + update_search_results("", "") bind_search_keys() end function update_search_results(character, action) - if not character then character = '' end - if action == 'string_del' then - search_string = search_string:sub(1, -2) + if not character then + character = "" + end + if action == "string_del" then + search_string = search_string:sub(1, -2) end - search_string = search_string..character + search_string = search_string .. character local prev_contents_length = #osd_log_contents get_osd_log_contents(filterName) - + if prev_contents_length ~= #osd_log_contents then list_highlight_cursor = {} end - - if character ~= '' and #osd_log_contents > 0 or action ~= nil and #osd_log_contents > 0 then - select(1-list_cursor) + + if character ~= "" and #osd_log_contents > 0 or action ~= nil and #osd_log_contents > 0 then + select(1 - list_cursor) elseif #osd_log_contents == 0 then list_cursor = 0 select(list_cursor) @@ -2056,222 +2548,436 @@ function update_search_results(character, action) end function bind_search_keys() - mp.add_forced_key_binding('a', 'search_string_a', function() update_search_results('a') end, 'repeatable') - mp.add_forced_key_binding('b', 'search_string_b', function() update_search_results('b') end, 'repeatable') - mp.add_forced_key_binding('c', 'search_string_c', function() update_search_results('c') end, 'repeatable') - mp.add_forced_key_binding('d', 'search_string_d', function() update_search_results('d') end, 'repeatable') - mp.add_forced_key_binding('e', 'search_string_e', function() update_search_results('e') end, 'repeatable') - mp.add_forced_key_binding('f', 'search_string_f', function() update_search_results('f') end, 'repeatable') - mp.add_forced_key_binding('g', 'search_string_g', function() update_search_results('g') end, 'repeatable') - mp.add_forced_key_binding('h', 'search_string_h', function() update_search_results('h') end, 'repeatable') - mp.add_forced_key_binding('i', 'search_string_i', function() update_search_results('i') end, 'repeatable') - mp.add_forced_key_binding('j', 'search_string_j', function() update_search_results('j') end, 'repeatable') - mp.add_forced_key_binding('k', 'search_string_k', function() update_search_results('k') end, 'repeatable') - mp.add_forced_key_binding('l', 'search_string_l', function() update_search_results('l') end, 'repeatable') - mp.add_forced_key_binding('m', 'search_string_m', function() update_search_results('m') end, 'repeatable') - mp.add_forced_key_binding('n', 'search_string_n', function() update_search_results('n') end, 'repeatable') - mp.add_forced_key_binding('o', 'search_string_o', function() update_search_results('o') end, 'repeatable') - mp.add_forced_key_binding('p', 'search_string_p', function() update_search_results('p') end, 'repeatable') - mp.add_forced_key_binding('q', 'search_string_q', function() update_search_results('q') end, 'repeatable') - mp.add_forced_key_binding('r', 'search_string_r', function() update_search_results('r') end, 'repeatable') - mp.add_forced_key_binding('s', 'search_string_s', function() update_search_results('s') end, 'repeatable') - mp.add_forced_key_binding('t', 'search_string_t', function() update_search_results('t') end, 'repeatable') - mp.add_forced_key_binding('u', 'search_string_u', function() update_search_results('u') end, 'repeatable') - mp.add_forced_key_binding('v', 'search_string_v', function() update_search_results('v') end, 'repeatable') - mp.add_forced_key_binding('w', 'search_string_w', function() update_search_results('w') end, 'repeatable') - mp.add_forced_key_binding('x', 'search_string_x', function() update_search_results('x') end, 'repeatable') - mp.add_forced_key_binding('y', 'search_string_y', function() update_search_results('y') end, 'repeatable') - mp.add_forced_key_binding('z', 'search_string_z', function() update_search_results('z') end, 'repeatable') - - mp.add_forced_key_binding('A', 'search_string_A', function() update_search_results('A') end, 'repeatable') - mp.add_forced_key_binding('B', 'search_string_B', function() update_search_results('B') end, 'repeatable') - mp.add_forced_key_binding('C', 'search_string_C', function() update_search_results('C') end, 'repeatable') - mp.add_forced_key_binding('D', 'search_string_D', function() update_search_results('D') end, 'repeatable') - mp.add_forced_key_binding('E', 'search_string_E', function() update_search_results('E') end, 'repeatable') - mp.add_forced_key_binding('F', 'search_string_F', function() update_search_results('F') end, 'repeatable') - mp.add_forced_key_binding('G', 'search_string_G', function() update_search_results('G') end, 'repeatable') - mp.add_forced_key_binding('H', 'search_string_H', function() update_search_results('H') end, 'repeatable') - mp.add_forced_key_binding('I', 'search_string_I', function() update_search_results('I') end, 'repeatable') - mp.add_forced_key_binding('J', 'search_string_J', function() update_search_results('J') end, 'repeatable') - mp.add_forced_key_binding('K', 'search_string_K', function() update_search_results('K') end, 'repeatable') - mp.add_forced_key_binding('L', 'search_string_L', function() update_search_results('L') end, 'repeatable') - mp.add_forced_key_binding('M', 'search_string_M', function() update_search_results('M') end, 'repeatable') - mp.add_forced_key_binding('N', 'search_string_N', function() update_search_results('N') end, 'repeatable') - mp.add_forced_key_binding('O', 'search_string_O', function() update_search_results('O') end, 'repeatable') - mp.add_forced_key_binding('P', 'search_string_P', function() update_search_results('P') end, 'repeatable') - mp.add_forced_key_binding('Q', 'search_string_Q', function() update_search_results('Q') end, 'repeatable') - mp.add_forced_key_binding('R', 'search_string_R', function() update_search_results('R') end, 'repeatable') - mp.add_forced_key_binding('S', 'search_string_S', function() update_search_results('S') end, 'repeatable') - mp.add_forced_key_binding('T', 'search_string_T', function() update_search_results('T') end, 'repeatable') - mp.add_forced_key_binding('U', 'search_string_U', function() update_search_results('U') end, 'repeatable') - mp.add_forced_key_binding('V', 'search_string_V', function() update_search_results('V') end, 'repeatable') - mp.add_forced_key_binding('W', 'search_string_W', function() update_search_results('W') end, 'repeatable') - mp.add_forced_key_binding('X', 'search_string_X', function() update_search_results('X') end, 'repeatable') - mp.add_forced_key_binding('Y', 'search_string_Y', function() update_search_results('Y') end, 'repeatable') - mp.add_forced_key_binding('Z', 'search_string_Z', function() update_search_results('Z') end, 'repeatable') - - mp.add_forced_key_binding('1', 'search_string_1', function() update_search_results('1') end, 'repeatable') - mp.add_forced_key_binding('2', 'search_string_2', function() update_search_results('2') end, 'repeatable') - mp.add_forced_key_binding('3', 'search_string_3', function() update_search_results('3') end, 'repeatable') - mp.add_forced_key_binding('4', 'search_string_4', function() update_search_results('4') end, 'repeatable') - mp.add_forced_key_binding('5', 'search_string_5', function() update_search_results('5') end, 'repeatable') - mp.add_forced_key_binding('6', 'search_string_6', function() update_search_results('6') end, 'repeatable') - mp.add_forced_key_binding('7', 'search_string_7', function() update_search_results('7') end, 'repeatable') - mp.add_forced_key_binding('8', 'search_string_8', function() update_search_results('8') end, 'repeatable') - mp.add_forced_key_binding('9', 'search_string_9', function() update_search_results('9') end, 'repeatable') - mp.add_forced_key_binding('0', 'search_string_0', function() update_search_results('0') end, 'repeatable') - - mp.add_forced_key_binding('SPACE', 'search_string_space', function() update_search_results(' ') end, 'repeatable') - mp.add_forced_key_binding('`', 'search_string_`', function() update_search_results('`') end, 'repeatable') - mp.add_forced_key_binding('~', 'search_string_~', function() update_search_results('~') end, 'repeatable') - mp.add_forced_key_binding('!', 'search_string_!', function() update_search_results('!') end, 'repeatable') - mp.add_forced_key_binding('@', 'search_string_@', function() update_search_results('@') end, 'repeatable') - mp.add_forced_key_binding('SHARP', 'search_string_sharp', function() update_search_results('#') end, 'repeatable') - mp.add_forced_key_binding('$', 'search_string_$', function() update_search_results('$') end, 'repeatable') - mp.add_forced_key_binding('%', 'search_string_percentage', function() update_search_results('%') end, 'repeatable') - mp.add_forced_key_binding('^', 'search_string_^', function() update_search_results('^') end, 'repeatable') - mp.add_forced_key_binding('&', 'search_string_&', function() update_search_results('&') end, 'repeatable') - mp.add_forced_key_binding('*', 'search_string_*', function() update_search_results('*') end, 'repeatable') - mp.add_forced_key_binding('(', 'search_string_(', function() update_search_results('(') end, 'repeatable') - mp.add_forced_key_binding(')', 'search_string_)', function() update_search_results(')') end, 'repeatable') - mp.add_forced_key_binding('-', 'search_string_-', function() update_search_results('-') end, 'repeatable') - mp.add_forced_key_binding('_', 'search_string__', function() update_search_results('_') end, 'repeatable') - mp.add_forced_key_binding('=', 'search_string_=', function() update_search_results('=') end, 'repeatable') - mp.add_forced_key_binding('+', 'search_string_+', function() update_search_results('+') end, 'repeatable') - mp.add_forced_key_binding('\\', 'search_string_\\', function() update_search_results('\\') end, 'repeatable') - mp.add_forced_key_binding('|', 'search_string_|', function() update_search_results('|') end, 'repeatable') - mp.add_forced_key_binding(']', 'search_string_]', function() update_search_results(']') end, 'repeatable') - mp.add_forced_key_binding('}', 'search_string_rightcurly', function() update_search_results('}') end, 'repeatable') - mp.add_forced_key_binding('[', 'search_string_[', function() update_search_results('[') end, 'repeatable') - mp.add_forced_key_binding('{', 'search_string_leftcurly', function() update_search_results('{') end, 'repeatable') - mp.add_forced_key_binding('\'', 'search_string_\'', function() update_search_results('\'') end, 'repeatable') - mp.add_forced_key_binding('\"', 'search_string_\"', function() update_search_results('\"') end, 'repeatable') - mp.add_forced_key_binding(';', 'search_string_semicolon', function() update_search_results(';') end, 'repeatable') - mp.add_forced_key_binding(':', 'search_string_:', function() update_search_results(':') end, 'repeatable') - mp.add_forced_key_binding('/', 'search_string_/', function() update_search_results('/') end, 'repeatable') - mp.add_forced_key_binding('?', 'search_string_?', function() update_search_results('?') end, 'repeatable') - mp.add_forced_key_binding('.', 'search_string_.', function() update_search_results('.') end, 'repeatable') - mp.add_forced_key_binding('>', 'search_string_>', function() update_search_results('>') end, 'repeatable') - mp.add_forced_key_binding(',', 'search_string_,', function() update_search_results(',') end, 'repeatable') - mp.add_forced_key_binding('<', 'search_string_<', function() update_search_results('<') end, 'repeatable') - - mp.add_forced_key_binding('bs', 'search_string_del', function() update_search_results('', 'string_del') end, 'repeatable') - bind_keys(o.list_close_keybind, 'search_exit', function() list_search_exit() end) - bind_keys(o.list_search_not_typing_mode_keybind, 'search_string_not_typing', function()list_search_not_typing_mode(false) end) + mp.add_forced_key_binding("a", "search_string_a", function() + update_search_results("a") + end, "repeatable") + mp.add_forced_key_binding("b", "search_string_b", function() + update_search_results("b") + end, "repeatable") + mp.add_forced_key_binding("c", "search_string_c", function() + update_search_results("c") + end, "repeatable") + mp.add_forced_key_binding("d", "search_string_d", function() + update_search_results("d") + end, "repeatable") + mp.add_forced_key_binding("e", "search_string_e", function() + update_search_results("e") + end, "repeatable") + mp.add_forced_key_binding("f", "search_string_f", function() + update_search_results("f") + end, "repeatable") + mp.add_forced_key_binding("g", "search_string_g", function() + update_search_results("g") + end, "repeatable") + mp.add_forced_key_binding("h", "search_string_h", function() + update_search_results("h") + end, "repeatable") + mp.add_forced_key_binding("i", "search_string_i", function() + update_search_results("i") + end, "repeatable") + mp.add_forced_key_binding("j", "search_string_j", function() + update_search_results("j") + end, "repeatable") + mp.add_forced_key_binding("k", "search_string_k", function() + update_search_results("k") + end, "repeatable") + mp.add_forced_key_binding("l", "search_string_l", function() + update_search_results("l") + end, "repeatable") + mp.add_forced_key_binding("m", "search_string_m", function() + update_search_results("m") + end, "repeatable") + mp.add_forced_key_binding("n", "search_string_n", function() + update_search_results("n") + end, "repeatable") + mp.add_forced_key_binding("o", "search_string_o", function() + update_search_results("o") + end, "repeatable") + mp.add_forced_key_binding("p", "search_string_p", function() + update_search_results("p") + end, "repeatable") + mp.add_forced_key_binding("q", "search_string_q", function() + update_search_results("q") + end, "repeatable") + mp.add_forced_key_binding("r", "search_string_r", function() + update_search_results("r") + end, "repeatable") + mp.add_forced_key_binding("s", "search_string_s", function() + update_search_results("s") + end, "repeatable") + mp.add_forced_key_binding("t", "search_string_t", function() + update_search_results("t") + end, "repeatable") + mp.add_forced_key_binding("u", "search_string_u", function() + update_search_results("u") + end, "repeatable") + mp.add_forced_key_binding("v", "search_string_v", function() + update_search_results("v") + end, "repeatable") + mp.add_forced_key_binding("w", "search_string_w", function() + update_search_results("w") + end, "repeatable") + mp.add_forced_key_binding("x", "search_string_x", function() + update_search_results("x") + end, "repeatable") + mp.add_forced_key_binding("y", "search_string_y", function() + update_search_results("y") + end, "repeatable") + mp.add_forced_key_binding("z", "search_string_z", function() + update_search_results("z") + end, "repeatable") + + mp.add_forced_key_binding("A", "search_string_A", function() + update_search_results("A") + end, "repeatable") + mp.add_forced_key_binding("B", "search_string_B", function() + update_search_results("B") + end, "repeatable") + mp.add_forced_key_binding("C", "search_string_C", function() + update_search_results("C") + end, "repeatable") + mp.add_forced_key_binding("D", "search_string_D", function() + update_search_results("D") + end, "repeatable") + mp.add_forced_key_binding("E", "search_string_E", function() + update_search_results("E") + end, "repeatable") + mp.add_forced_key_binding("F", "search_string_F", function() + update_search_results("F") + end, "repeatable") + mp.add_forced_key_binding("G", "search_string_G", function() + update_search_results("G") + end, "repeatable") + mp.add_forced_key_binding("H", "search_string_H", function() + update_search_results("H") + end, "repeatable") + mp.add_forced_key_binding("I", "search_string_I", function() + update_search_results("I") + end, "repeatable") + mp.add_forced_key_binding("J", "search_string_J", function() + update_search_results("J") + end, "repeatable") + mp.add_forced_key_binding("K", "search_string_K", function() + update_search_results("K") + end, "repeatable") + mp.add_forced_key_binding("L", "search_string_L", function() + update_search_results("L") + end, "repeatable") + mp.add_forced_key_binding("M", "search_string_M", function() + update_search_results("M") + end, "repeatable") + mp.add_forced_key_binding("N", "search_string_N", function() + update_search_results("N") + end, "repeatable") + mp.add_forced_key_binding("O", "search_string_O", function() + update_search_results("O") + end, "repeatable") + mp.add_forced_key_binding("P", "search_string_P", function() + update_search_results("P") + end, "repeatable") + mp.add_forced_key_binding("Q", "search_string_Q", function() + update_search_results("Q") + end, "repeatable") + mp.add_forced_key_binding("R", "search_string_R", function() + update_search_results("R") + end, "repeatable") + mp.add_forced_key_binding("S", "search_string_S", function() + update_search_results("S") + end, "repeatable") + mp.add_forced_key_binding("T", "search_string_T", function() + update_search_results("T") + end, "repeatable") + mp.add_forced_key_binding("U", "search_string_U", function() + update_search_results("U") + end, "repeatable") + mp.add_forced_key_binding("V", "search_string_V", function() + update_search_results("V") + end, "repeatable") + mp.add_forced_key_binding("W", "search_string_W", function() + update_search_results("W") + end, "repeatable") + mp.add_forced_key_binding("X", "search_string_X", function() + update_search_results("X") + end, "repeatable") + mp.add_forced_key_binding("Y", "search_string_Y", function() + update_search_results("Y") + end, "repeatable") + mp.add_forced_key_binding("Z", "search_string_Z", function() + update_search_results("Z") + end, "repeatable") + + mp.add_forced_key_binding("1", "search_string_1", function() + update_search_results("1") + end, "repeatable") + mp.add_forced_key_binding("2", "search_string_2", function() + update_search_results("2") + end, "repeatable") + mp.add_forced_key_binding("3", "search_string_3", function() + update_search_results("3") + end, "repeatable") + mp.add_forced_key_binding("4", "search_string_4", function() + update_search_results("4") + end, "repeatable") + mp.add_forced_key_binding("5", "search_string_5", function() + update_search_results("5") + end, "repeatable") + mp.add_forced_key_binding("6", "search_string_6", function() + update_search_results("6") + end, "repeatable") + mp.add_forced_key_binding("7", "search_string_7", function() + update_search_results("7") + end, "repeatable") + mp.add_forced_key_binding("8", "search_string_8", function() + update_search_results("8") + end, "repeatable") + mp.add_forced_key_binding("9", "search_string_9", function() + update_search_results("9") + end, "repeatable") + mp.add_forced_key_binding("0", "search_string_0", function() + update_search_results("0") + end, "repeatable") + + mp.add_forced_key_binding("SPACE", "search_string_space", function() + update_search_results(" ") + end, "repeatable") + mp.add_forced_key_binding("`", "search_string_`", function() + update_search_results("`") + end, "repeatable") + mp.add_forced_key_binding("~", "search_string_~", function() + update_search_results("~") + end, "repeatable") + mp.add_forced_key_binding("!", "search_string_!", function() + update_search_results("!") + end, "repeatable") + mp.add_forced_key_binding("@", "search_string_@", function() + update_search_results("@") + end, "repeatable") + mp.add_forced_key_binding("SHARP", "search_string_sharp", function() + update_search_results("#") + end, "repeatable") + mp.add_forced_key_binding("$", "search_string_$", function() + update_search_results("$") + end, "repeatable") + mp.add_forced_key_binding("%", "search_string_percentage", function() + update_search_results("%") + end, "repeatable") + mp.add_forced_key_binding("^", "search_string_^", function() + update_search_results("^") + end, "repeatable") + mp.add_forced_key_binding("&", "search_string_&", function() + update_search_results("&") + end, "repeatable") + mp.add_forced_key_binding("*", "search_string_*", function() + update_search_results("*") + end, "repeatable") + mp.add_forced_key_binding("(", "search_string_(", function() + update_search_results("(") + end, "repeatable") + mp.add_forced_key_binding(")", "search_string_)", function() + update_search_results(")") + end, "repeatable") + mp.add_forced_key_binding("-", "search_string_-", function() + update_search_results("-") + end, "repeatable") + mp.add_forced_key_binding("_", "search_string__", function() + update_search_results("_") + end, "repeatable") + mp.add_forced_key_binding("=", "search_string_=", function() + update_search_results("=") + end, "repeatable") + mp.add_forced_key_binding("+", "search_string_+", function() + update_search_results("+") + end, "repeatable") + mp.add_forced_key_binding("\\", "search_string_\\", function() + update_search_results("\\") + end, "repeatable") + mp.add_forced_key_binding("|", "search_string_|", function() + update_search_results("|") + end, "repeatable") + mp.add_forced_key_binding("]", "search_string_]", function() + update_search_results("]") + end, "repeatable") + mp.add_forced_key_binding("}", "search_string_rightcurly", function() + update_search_results("}") + end, "repeatable") + mp.add_forced_key_binding("[", "search_string_[", function() + update_search_results("[") + end, "repeatable") + mp.add_forced_key_binding("{", "search_string_leftcurly", function() + update_search_results("{") + end, "repeatable") + mp.add_forced_key_binding("'", "search_string_'", function() + update_search_results("'") + end, "repeatable") + mp.add_forced_key_binding('"', 'search_string_"', function() + update_search_results('"') + end, "repeatable") + mp.add_forced_key_binding(";", "search_string_semicolon", function() + update_search_results(";") + end, "repeatable") + mp.add_forced_key_binding(":", "search_string_:", function() + update_search_results(":") + end, "repeatable") + mp.add_forced_key_binding("/", "search_string_/", function() + update_search_results("/") + end, "repeatable") + mp.add_forced_key_binding("?", "search_string_?", function() + update_search_results("?") + end, "repeatable") + mp.add_forced_key_binding(".", "search_string_.", function() + update_search_results(".") + end, "repeatable") + mp.add_forced_key_binding(">", "search_string_>", function() + update_search_results(">") + end, "repeatable") + mp.add_forced_key_binding(",", "search_string_,", function() + update_search_results(",") + end, "repeatable") + mp.add_forced_key_binding("<", "search_string_<", function() + update_search_results("<") + end, "repeatable") + + mp.add_forced_key_binding("bs", "search_string_del", function() + update_search_results("", "string_del") + end, "repeatable") + bind_keys(o.list_close_keybind, "search_exit", function() + list_search_exit() + end) + bind_keys(o.list_search_not_typing_mode_keybind, "search_string_not_typing", function() + list_search_not_typing_mode(false) + end) if o.search_not_typing_smartly then - bind_keys(o.next_filter_sequence_keybind, 'list-filter-next', function() list_filter_next() list_search_not_typing_mode(true) end) - bind_keys(o.previous_filter_sequence_keybind, 'list-filter-previous', function() list_filter_previous() list_search_not_typing_mode(true) end) - bind_keys(o.list_delete_keybind, 'list-delete', function() list_delete() list_search_not_typing_mode(true) end) - bind_keys(o.list_delete_highlighted_keybind, 'list-delete-highlight', function() list_delete('highlight') list_search_not_typing_mode(true) end) - bind_keys(o.keybinds_remove_keybind, 'keybind-slot-remove', function() slot_remove() list_search_not_typing_mode(true) end) - bind_keys(o.keybinds_remove_keybind, 'keybind-slot-remove-highlight', function() slot_remove('highlight') list_search_not_typing_mode(true) end) + bind_keys(o.next_filter_sequence_keybind, "list-filter-next", function() + list_filter_next() + list_search_not_typing_mode(true) + end) + bind_keys(o.previous_filter_sequence_keybind, "list-filter-previous", function() + list_filter_previous() + list_search_not_typing_mode(true) + end) + bind_keys(o.list_delete_keybind, "list-delete", function() + list_delete() + list_search_not_typing_mode(true) + end) + bind_keys(o.list_delete_highlighted_keybind, "list-delete-highlight", function() + list_delete("highlight") + list_search_not_typing_mode(true) + end) + bind_keys(o.keybinds_remove_keybind, "keybind-slot-remove", function() + slot_remove() + list_search_not_typing_mode(true) + end) + bind_keys(o.keybinds_remove_keybind, "keybind-slot-remove-highlight", function() + slot_remove("highlight") + list_search_not_typing_mode(true) + end) end end function unbind_search_keys() - mp.remove_key_binding('search_string_a') - mp.remove_key_binding('search_string_b') - mp.remove_key_binding('search_string_c') - mp.remove_key_binding('search_string_d') - mp.remove_key_binding('search_string_e') - mp.remove_key_binding('search_string_f') - mp.remove_key_binding('search_string_g') - mp.remove_key_binding('search_string_h') - mp.remove_key_binding('search_string_i') - mp.remove_key_binding('search_string_j') - mp.remove_key_binding('search_string_k') - mp.remove_key_binding('search_string_l') - mp.remove_key_binding('search_string_m') - mp.remove_key_binding('search_string_n') - mp.remove_key_binding('search_string_o') - mp.remove_key_binding('search_string_p') - mp.remove_key_binding('search_string_q') - mp.remove_key_binding('search_string_r') - mp.remove_key_binding('search_string_s') - mp.remove_key_binding('search_string_t') - mp.remove_key_binding('search_string_u') - mp.remove_key_binding('search_string_v') - mp.remove_key_binding('search_string_w') - mp.remove_key_binding('search_string_x') - mp.remove_key_binding('search_string_y') - mp.remove_key_binding('search_string_z') - - mp.remove_key_binding('search_string_A') - mp.remove_key_binding('search_string_B') - mp.remove_key_binding('search_string_C') - mp.remove_key_binding('search_string_D') - mp.remove_key_binding('search_string_E') - mp.remove_key_binding('search_string_F') - mp.remove_key_binding('search_string_G') - mp.remove_key_binding('search_string_H') - mp.remove_key_binding('search_string_I') - mp.remove_key_binding('search_string_J') - mp.remove_key_binding('search_string_K') - mp.remove_key_binding('search_string_L') - mp.remove_key_binding('search_string_M') - mp.remove_key_binding('search_string_N') - mp.remove_key_binding('search_string_O') - mp.remove_key_binding('search_string_P') - mp.remove_key_binding('search_string_Q') - mp.remove_key_binding('search_string_R') - mp.remove_key_binding('search_string_S') - mp.remove_key_binding('search_string_T') - mp.remove_key_binding('search_string_U') - mp.remove_key_binding('search_string_V') - mp.remove_key_binding('search_string_W') - mp.remove_key_binding('search_string_X') - mp.remove_key_binding('search_string_Y') - mp.remove_key_binding('search_string_Z') - - mp.remove_key_binding('search_string_1') - mp.remove_key_binding('search_string_2') - mp.remove_key_binding('search_string_3') - mp.remove_key_binding('search_string_4') - mp.remove_key_binding('search_string_5') - mp.remove_key_binding('search_string_6') - mp.remove_key_binding('search_string_7') - mp.remove_key_binding('search_string_8') - mp.remove_key_binding('search_string_9') - mp.remove_key_binding('search_string_0') - - mp.remove_key_binding('search_string_space') - mp.remove_key_binding('search_string_`') - mp.remove_key_binding('search_string_~') - mp.remove_key_binding('search_string_!') - mp.remove_key_binding('search_string_@') - mp.remove_key_binding('search_string_sharp') - mp.remove_key_binding('search_string_$') - mp.remove_key_binding('search_string_percentage') - mp.remove_key_binding('search_string_^') - mp.remove_key_binding('search_string_&') - mp.remove_key_binding('search_string_*') - mp.remove_key_binding('search_string_(') - mp.remove_key_binding('search_string_)') - mp.remove_key_binding('search_string_-') - mp.remove_key_binding('search_string__') - mp.remove_key_binding('search_string_=') - mp.remove_key_binding('search_string_+') - mp.remove_key_binding('search_string_\\') - mp.remove_key_binding('search_string_|') - mp.remove_key_binding('search_string_]') - mp.remove_key_binding('search_string_rightcurly') - mp.remove_key_binding('search_string_[') - mp.remove_key_binding('search_string_leftcurly') - mp.remove_key_binding('search_string_\'') - mp.remove_key_binding('search_string_\"') - mp.remove_key_binding('search_string_semicolon') - mp.remove_key_binding('search_string_:') - mp.remove_key_binding('search_string_/') - mp.remove_key_binding('search_string_?') - mp.remove_key_binding('search_string_.') - mp.remove_key_binding('search_string_>') - mp.remove_key_binding('search_string_,') - mp.remove_key_binding('search_string_<') - - mp.remove_key_binding('search_string_del') + mp.remove_key_binding("search_string_a") + mp.remove_key_binding("search_string_b") + mp.remove_key_binding("search_string_c") + mp.remove_key_binding("search_string_d") + mp.remove_key_binding("search_string_e") + mp.remove_key_binding("search_string_f") + mp.remove_key_binding("search_string_g") + mp.remove_key_binding("search_string_h") + mp.remove_key_binding("search_string_i") + mp.remove_key_binding("search_string_j") + mp.remove_key_binding("search_string_k") + mp.remove_key_binding("search_string_l") + mp.remove_key_binding("search_string_m") + mp.remove_key_binding("search_string_n") + mp.remove_key_binding("search_string_o") + mp.remove_key_binding("search_string_p") + mp.remove_key_binding("search_string_q") + mp.remove_key_binding("search_string_r") + mp.remove_key_binding("search_string_s") + mp.remove_key_binding("search_string_t") + mp.remove_key_binding("search_string_u") + mp.remove_key_binding("search_string_v") + mp.remove_key_binding("search_string_w") + mp.remove_key_binding("search_string_x") + mp.remove_key_binding("search_string_y") + mp.remove_key_binding("search_string_z") + + mp.remove_key_binding("search_string_A") + mp.remove_key_binding("search_string_B") + mp.remove_key_binding("search_string_C") + mp.remove_key_binding("search_string_D") + mp.remove_key_binding("search_string_E") + mp.remove_key_binding("search_string_F") + mp.remove_key_binding("search_string_G") + mp.remove_key_binding("search_string_H") + mp.remove_key_binding("search_string_I") + mp.remove_key_binding("search_string_J") + mp.remove_key_binding("search_string_K") + mp.remove_key_binding("search_string_L") + mp.remove_key_binding("search_string_M") + mp.remove_key_binding("search_string_N") + mp.remove_key_binding("search_string_O") + mp.remove_key_binding("search_string_P") + mp.remove_key_binding("search_string_Q") + mp.remove_key_binding("search_string_R") + mp.remove_key_binding("search_string_S") + mp.remove_key_binding("search_string_T") + mp.remove_key_binding("search_string_U") + mp.remove_key_binding("search_string_V") + mp.remove_key_binding("search_string_W") + mp.remove_key_binding("search_string_X") + mp.remove_key_binding("search_string_Y") + mp.remove_key_binding("search_string_Z") + + mp.remove_key_binding("search_string_1") + mp.remove_key_binding("search_string_2") + mp.remove_key_binding("search_string_3") + mp.remove_key_binding("search_string_4") + mp.remove_key_binding("search_string_5") + mp.remove_key_binding("search_string_6") + mp.remove_key_binding("search_string_7") + mp.remove_key_binding("search_string_8") + mp.remove_key_binding("search_string_9") + mp.remove_key_binding("search_string_0") + + mp.remove_key_binding("search_string_space") + mp.remove_key_binding("search_string_`") + mp.remove_key_binding("search_string_~") + mp.remove_key_binding("search_string_!") + mp.remove_key_binding("search_string_@") + mp.remove_key_binding("search_string_sharp") + mp.remove_key_binding("search_string_$") + mp.remove_key_binding("search_string_percentage") + mp.remove_key_binding("search_string_^") + mp.remove_key_binding("search_string_&") + mp.remove_key_binding("search_string_*") + mp.remove_key_binding("search_string_(") + mp.remove_key_binding("search_string_)") + mp.remove_key_binding("search_string_-") + mp.remove_key_binding("search_string__") + mp.remove_key_binding("search_string_=") + mp.remove_key_binding("search_string_+") + mp.remove_key_binding("search_string_\\") + mp.remove_key_binding("search_string_|") + mp.remove_key_binding("search_string_]") + mp.remove_key_binding("search_string_rightcurly") + mp.remove_key_binding("search_string_[") + mp.remove_key_binding("search_string_leftcurly") + mp.remove_key_binding("search_string_'") + mp.remove_key_binding('search_string_"') + mp.remove_key_binding("search_string_semicolon") + mp.remove_key_binding("search_string_:") + mp.remove_key_binding("search_string_/") + mp.remove_key_binding("search_string_?") + mp.remove_key_binding("search_string_.") + mp.remove_key_binding("search_string_>") + mp.remove_key_binding("search_string_,") + mp.remove_key_binding("search_string_<") + + mp.remove_key_binding("search_string_del") if not search_active then - unbind_keys(o.list_close_keybind, 'search_exit') + unbind_keys(o.list_close_keybind, "search_exit") end end --End of LogManager Search Feature-- @@ -2279,13 +2985,18 @@ end --Modify Additional Log Parameters-- function remove_all_additional_param_log_entry(index, log_text) - if not index or not log_text then return end + if not index or not log_text then + return + end local temp_log_contents = read_log_table() - if not temp_log_contents or not temp_log_contents[1] then return end + if not temp_log_contents or not temp_log_contents[1] then + return + end for i = #temp_log_contents, 1, -1 do - if temp_log_contents[i].found_line:find(log_text..index) then - temp_log_contents[i].found_line = string.gsub(temp_log_contents[i].found_line, ' | '..log_text..index, "") + if temp_log_contents[i].found_line:find(log_text .. index) then + temp_log_contents[i].found_line = + string.gsub(temp_log_contents[i].found_line, " | " .. log_text .. index, "") end end @@ -2299,17 +3010,24 @@ function remove_all_additional_param_log_entry(index, log_text) end function remove_additional_param_log_entry(index, target, log_text) - if not index or not target or not log_text then return msg.error('remove_additional_param_log_entry parameters not defined') end - if not osd_log_contents or not osd_log_contents[1] then return end + if not index or not target or not log_text then + return msg.error("remove_additional_param_log_entry parameters not defined") + end + if not osd_log_contents or not osd_log_contents[1] then + return + end local temp_log_contents = read_log_table() - if not temp_log_contents or not temp_log_contents[1] then return end - + if not temp_log_contents or not temp_log_contents[1] then + return + end + local log_index = osd_log_contents[target].found_sequence - if temp_log_contents[log_index].found_line:find(log_text..index) then - temp_log_contents[log_index].found_line = string.gsub(temp_log_contents[log_index].found_line, ' | '..log_text..index, "") + if temp_log_contents[log_index].found_line:find(log_text .. index) then + temp_log_contents[log_index].found_line = + string.gsub(temp_log_contents[log_index].found_line, " | " .. log_text .. index, "") else - return msg.error('temp_log_contents[log_index].found_line is not found') + return msg.error("temp_log_contents[log_index].found_line is not found") end f = io.open(log_fullpath, "w+") @@ -2322,20 +3040,33 @@ function remove_additional_param_log_entry(index, target, log_text) end function add_additional_param_log_entry(index, target, log_text) - if not index or not target or not log_text then return msg.error('add_additional_param_log_entry parameters not defined') end - if not osd_log_contents or not osd_log_contents[1] then return end + if not index or not target or not log_text then + return msg.error("add_additional_param_log_entry parameters not defined") + end + if not osd_log_contents or not osd_log_contents[1] then + return + end local temp_log_contents = read_log_table() - if not temp_log_contents or not temp_log_contents[1] then return end + if not temp_log_contents or not temp_log_contents[1] then + return + end local log_index = osd_log_contents[target].found_sequence if temp_log_contents[log_index].found_line then - if temp_log_contents[log_index].found_line:sub(-1) ~= ' ' then - temp_log_contents[log_index].found_line = temp_log_contents[log_index].found_line..' | '..log_text .. index..' | ' + if temp_log_contents[log_index].found_line:sub(-1) ~= " " then + temp_log_contents[log_index].found_line = temp_log_contents[log_index].found_line + .. " | " + .. log_text + .. index + .. " | " else - temp_log_contents[log_index].found_line = temp_log_contents[log_index].found_line..log_text .. index..' | ' + temp_log_contents[log_index].found_line = temp_log_contents[log_index].found_line + .. log_text + .. index + .. " | " end else - return msg.error('temp_log_contents[log_index].found_line is not found') + return msg.error("temp_log_contents[log_index].found_line is not found") end f = io.open(log_fullpath, "w+") @@ -2350,32 +3081,51 @@ end --Keybind Slot Feature-- function list_slot_remove(index, action) - if not list_drawn then return end - if not osd_log_contents or not osd_log_contents[1] then return end - if not index then index = tonumber(osd_log_contents[#osd_log_contents - list_cursor + 1].found_slot) end - + if not list_drawn then + return + end + if not osd_log_contents or not osd_log_contents[1] then + return + end + if not index then + index = tonumber(osd_log_contents[#osd_log_contents - list_cursor + 1].found_slot) + end + if not index then - if action ~= 'silent' then msg.info("Failed to remove") end + if action ~= "silent" then + msg.info("Failed to remove") + end return end remove_all_additional_param_log_entry(index, log_keybind_text) - if action ~= 'silent' then msg.info('Removed Keybind: ' .. get_slot_keybind(index)) end + if action ~= "silent" then + msg.info("Removed Keybind: " .. get_slot_keybind(index)) + end end function list_slot_remove_highlighted() - if not list_drawn then return end - if not list_highlight_cursor or not list_highlight_cursor[1] then return end - if not osd_log_contents or not osd_log_contents[1] then return end + if not list_drawn then + return + end + if not list_highlight_cursor or not list_highlight_cursor[1] then + return + end + if not osd_log_contents or not osd_log_contents[1] then + return + end local slotIndex for i = 1, #osd_log_contents do - for j=1, #list_highlight_cursor do - if osd_log_contents[#osd_log_contents+1-i] then - if osd_log_contents[#osd_log_contents+1-i].found_sequence == list_highlight_cursor[j][2].found_sequence then - slotIndex = tonumber(osd_log_contents[#osd_log_contents+1-i].found_slot) + for j = 1, #list_highlight_cursor do + if osd_log_contents[#osd_log_contents + 1 - i] then + if + osd_log_contents[#osd_log_contents + 1 - i].found_sequence + == list_highlight_cursor[j][2].found_sequence + then + slotIndex = tonumber(osd_log_contents[#osd_log_contents + 1 - i].found_slot) if slotIndex then remove_all_additional_param_log_entry(slotIndex, log_keybind_text) - msg.info('Removed Keybind: ' .. get_slot_keybind(slotIndex)) + msg.info("Removed Keybind: " .. get_slot_keybind(slotIndex)) end end end @@ -2384,47 +3134,62 @@ function list_slot_remove_highlighted() end function list_slot_add(index) - if not list_drawn then return end - if not osd_log_contents or not osd_log_contents[1] then return end - if not index then return end - - local cursor_filename, cursor_filepath, cursor_filetitle = get_local_names(osd_log_contents[#osd_log_contents - list_cursor + 1]) --1.2.4# added the new name calling method to fix issue of unable to add urls into groups or slots + if not list_drawn then + return + end + if not osd_log_contents or not osd_log_contents[1] then + return + end + if not index then + return + end + + local cursor_filename, cursor_filepath, cursor_filetitle = + get_local_names(osd_log_contents[#osd_log_contents - list_cursor + 1]) --1.2.4# added the new name calling method to fix issue of unable to add urls into groups or slots local cursor_seektime = tonumber(osd_log_contents[#osd_log_contents - list_cursor + 1].found_time) if not cursor_filename or not cursor_seektime then msg.info("Failed to add slot") return end - - + local slotIndex = osd_log_contents[#osd_log_contents - list_cursor + 1].found_slot if slotIndex then - remove_additional_param_log_entry(slotIndex,#osd_log_contents-list_cursor+1, log_keybind_text) + remove_additional_param_log_entry(slotIndex, #osd_log_contents - list_cursor + 1, log_keybind_text) end - - list_slot_remove(index, 'silent') - add_additional_param_log_entry(index, #osd_log_contents-list_cursor+1, log_keybind_text) - msg.info('Added Keybind:\n' .. cursor_filetitle .. ' 🕒 ' .. format_time(cursor_seektime) .. ' ⌨ ' .. get_slot_keybind(index)) + + list_slot_remove(index, "silent") + add_additional_param_log_entry(index, #osd_log_contents - list_cursor + 1, log_keybind_text) + msg.info( + "Added Keybind:\n" + .. cursor_filetitle + .. " 🕒 " + .. format_time(cursor_seektime) + .. " ⌨ " + .. get_slot_keybind(index) + ) end function slot_remove(action) if not action then list_slot_remove() - elseif action == 'highlight' then + elseif action == "highlight" then list_slot_remove_highlighted() end get_osd_log_contents() if #osd_log_contents == 0 then - display_list('all') + display_list("all") return elseif list_cursor ~= #osd_log_contents + 1 then - select(0) - else - select(-1) + select(0) + else + select(-1) end end function slot_add(index) - if not index then return end + if not index then + return + end list_slot_add(index) get_osd_log_contents() @@ -2432,41 +3197,58 @@ function slot_add(index) list_cursor = 0 select(list_cursor) elseif list_cursor ~= #osd_log_contents + 1 then - select(0) - else - select(-1) + select(0) + else + select(-1) end end --End of Keybind Slot Feature-- --Group Feature-- function list_group_remove(action) - if not list_drawn then return end - if not osd_log_contents or not osd_log_contents[1] then return end - + if not list_drawn then + return + end + if not osd_log_contents or not osd_log_contents[1] then + return + end + local groupCursorIndex = tonumber(osd_log_contents[#osd_log_contents - list_cursor + 1].found_group) if not groupCursorIndex then - if action ~= 'silent' then msg.info("Failed to remove") end + if action ~= "silent" then + msg.info("Failed to remove") + end return end - remove_additional_param_log_entry(groupCursorIndex, #osd_log_contents-list_cursor+1, log_group_text) - if action ~= 'silent' then msg.info('Removed Group: ' .. get_group_properties(groupCursorIndex).name) end + remove_additional_param_log_entry(groupCursorIndex, #osd_log_contents - list_cursor + 1, log_group_text) + if action ~= "silent" then + msg.info("Removed Group: " .. get_group_properties(groupCursorIndex).name) + end end function list_group_remove_highlighted() - if not list_drawn then return end - if not list_highlight_cursor or not list_highlight_cursor[1] then return end - if not osd_log_contents or not osd_log_contents[1] then return end - + if not list_drawn then + return + end + if not list_highlight_cursor or not list_highlight_cursor[1] then + return + end + if not osd_log_contents or not osd_log_contents[1] then + return + end + local groupIndex for i = 1, #osd_log_contents do - for j=1, #list_highlight_cursor do - if osd_log_contents[#osd_log_contents+1-i] then - if osd_log_contents[#osd_log_contents+1-i].found_sequence == list_highlight_cursor[j][2].found_sequence then - groupIndex = tonumber(osd_log_contents[#osd_log_contents+1-i].found_group) + for j = 1, #list_highlight_cursor do + if osd_log_contents[#osd_log_contents + 1 - i] then + if + osd_log_contents[#osd_log_contents + 1 - i].found_sequence + == list_highlight_cursor[j][2].found_sequence + then + groupIndex = tonumber(osd_log_contents[#osd_log_contents + 1 - i].found_group) if groupIndex then - remove_additional_param_log_entry(groupIndex, #osd_log_contents+1-i, log_group_text) - msg.info('Removed Group: ' .. get_group_properties(groupIndex).name) + remove_additional_param_log_entry(groupIndex, #osd_log_contents + 1 - i, log_group_text) + msg.info("Removed Group: " .. get_group_properties(groupIndex).name) end end end @@ -2475,35 +3257,60 @@ function list_group_remove_highlighted() end function list_group_add(index) - if not list_drawn then return end - if not osd_log_contents or not osd_log_contents[1] then return end - if not index then return end - - local cursor_filename, cursor_filepath, cursor_filetitle = get_local_names(osd_log_contents[#osd_log_contents - list_cursor + 1]) --1.2.4# added the new name calling method to fix issue of unable to add urls into groups or slots + if not list_drawn then + return + end + if not osd_log_contents or not osd_log_contents[1] then + return + end + if not index then + return + end + + local cursor_filename, cursor_filepath, cursor_filetitle = + get_local_names(osd_log_contents[#osd_log_contents - list_cursor + 1]) --1.2.4# added the new name calling method to fix issue of unable to add urls into groups or slots local cursor_seektime = tonumber(osd_log_contents[#osd_log_contents - list_cursor + 1].found_time) if not cursor_filename or not cursor_seektime then msg.info("Failed to add group") return end - - list_group_remove('silent') - add_additional_param_log_entry(index, #osd_log_contents-list_cursor+1, log_group_text) - msg.info('Added Group:\n' .. cursor_filename .. ' 🕒 ' .. format_time(cursor_seektime) .. ' 🖿 ' .. get_group_properties(index).name) + + list_group_remove("silent") + add_additional_param_log_entry(index, #osd_log_contents - list_cursor + 1, log_group_text) + msg.info( + "Added Group:\n" + .. cursor_filename + .. " 🕒 " + .. format_time(cursor_seektime) + .. " 🖿 " + .. get_group_properties(index).name + ) end function list_group_add_highlighted(index) - if not list_drawn then return end - if not list_highlight_cursor or not list_highlight_cursor[1] then return end - if not osd_log_contents or not osd_log_contents[1] then return end - if not index then return end + if not list_drawn then + return + end + if not list_highlight_cursor or not list_highlight_cursor[1] then + return + end + if not osd_log_contents or not osd_log_contents[1] then + return + end + if not index then + return + end list_group_remove_highlighted() - + for i = 1, #osd_log_contents do - for j=1, #list_highlight_cursor do - if osd_log_contents[#osd_log_contents+1-i] then - if osd_log_contents[#osd_log_contents+1-i].found_sequence == list_highlight_cursor[j][2].found_sequence then - add_additional_param_log_entry(index, #osd_log_contents+1-i, log_group_text) - msg.info('Added Group: ' .. get_group_properties(index).name) + for j = 1, #list_highlight_cursor do + if osd_log_contents[#osd_log_contents + 1 - i] then + if + osd_log_contents[#osd_log_contents + 1 - i].found_sequence + == list_highlight_cursor[j][2].found_sequence + then + add_additional_param_log_entry(index, #osd_log_contents + 1 - i, log_group_text) + msg.info("Added Group: " .. get_group_properties(index).name) end end end @@ -2511,18 +3318,26 @@ function list_group_add_highlighted(index) end function list_group_add_cycle(action) - if not list_drawn then return end - if not osd_log_contents or not osd_log_contents[1] then return end + if not list_drawn then + return + end + if not osd_log_contents or not osd_log_contents[1] then + return + end local next_index = tonumber(osd_log_contents[#osd_log_contents - list_cursor + 1].found_group) - if next_index then next_index = next_index + 1 else next_index = 0 end + if next_index then + next_index = next_index + 1 + else + next_index = 0 + end if next_index > #o.groups_list_and_keybind or next_index == 0 then next_index = 1 end - + if not action then group_add(next_index) - elseif action == 'highlight' then + elseif action == "highlight" then group_add(next_index, action) end end @@ -2530,26 +3345,28 @@ end function group_remove(action) if not action then list_group_remove() - elseif action == 'highlight' then + elseif action == "highlight" then list_group_remove_highlighted() end get_osd_log_contents() if #osd_log_contents == 0 then - display_list('all') + display_list("all") return elseif list_cursor ~= #osd_log_contents + 1 then - select(0) - else - select(-1) + select(0) + else + select(-1) end end function group_add(index, action) - if not index then return end - + if not index then + return + end + if not action then list_group_add(index) - elseif action == 'highlight' then + elseif action == "highlight" then list_group_add_highlighted(index) end get_osd_log_contents() @@ -2557,98 +3374,150 @@ function group_add(index, action) list_cursor = 0 select(list_cursor) elseif list_cursor ~= #osd_log_contents + 1 then - select(0) - else - select(-1) - end + select(0) + else + select(-1) + end end --End of Group Feature-- function mark_chapter() - if not o.mark_bookmark_as_chapter then return end - + if not o.mark_bookmark_as_chapter then + return + end + local all_chapters = mp.get_property_native("chapter-list") local chapter_index = 0 local chapters_time = {} - + get_osd_log_contents() - if not osd_log_contents or not osd_log_contents[1] then return end + if not osd_log_contents or not osd_log_contents[1] then + return + end for i = 1, #osd_log_contents do if osd_log_contents[i].found_path == filePath and tonumber(osd_log_contents[i].found_time) > 0 then table.insert(chapters_time, tonumber(osd_log_contents[i].found_time)) end end - if not chapters_time[1] then return end - - table.sort(chapters_time, function(a, b) return a < b end) - + if not chapters_time[1] then + return + end + + table.sort(chapters_time, function(a, b) + return a < b + end) + for i = 1, #chapters_time do chapter_index = chapter_index + 1 - + all_chapters[chapter_index] = { - title = 'SimpleBookmark ' .. chapter_index, - time = chapters_time[i] + title = "SimpleBookmark " .. chapter_index, + time = chapters_time[i], } end - - table.sort(all_chapters, function(a, b) return a['time'] < b['time'] end) - + + table.sort(all_chapters, function(a, b) + return a["time"] < b["time"] + end) + mp.set_property_native("chapter-list", all_chapters) end function write_log(target_time, update_seekTime, entry_limit) - if not filePath then return end - if o.preserve_video_settings then mp.command("write-watch-later-config") end--1.3.1# option to preserve video settings by using write-watch-later-config when saving bookmark #84 - + if not filePath then + return + end + if o.preserve_video_settings then + mp.command("write-watch-later-config") + end --1.3.1# option to preserve video settings by using write-watch-later-config when saving bookmark #84 + local prev_seekTime = seekTime local deleted_entries = {} --1.2.7# add it above since we need to call it later for preserving properties - seekTime = (mp.get_property_number('time-pos') or 0) + seekTime = (mp.get_property_number("time-pos") or 0) if target_time then seekTime = target_time end - if seekTime < 0 then seekTime = 0 end - + if seekTime < 0 then + seekTime = 0 + end + local found_entry = find_entry(true, filePath, math.floor(seekTime)) --1.2.5# finds entry_sequence using new function --1.2.6# updated to find_entry --1.2.8# first delete_log_entry to correctly overwrite the data (having same_path_log_delete() function runs first will result in overwriting wrong entry) - if found_entry and found_entry['found_sequence'] ~= nil then --1.2.6# if the entry exists then proceed to delete it - delete_log_entry(found_entry['found_sequence']) --1.2.5# deletes log entry using new function that uses sequence to delete --1.2.8# removed calling the array earlier and automatically call inside function + if found_entry and found_entry["found_sequence"] ~= nil then --1.2.6# if the entry exists then proceed to delete it + delete_log_entry(found_entry["found_sequence"]) --1.2.5# deletes log entry using new function that uses sequence to delete --1.2.8# removed calling the array earlier and automatically call inside function end deleted_entries = same_path_log_delete(filePath, entry_limit) --1.2.5# seperate function to delete any additional entries based on the same_entry_limit set by user --1.2.7# assign it to varible since function now returns status and an array of deleted_entries --1.2.8# removed calling the array earlier and automatically call inside function - local f = io.open(log_fullpath, "a+")--1.3# dont allow customization to date_format so it can be saved in a standard in which I can parse for search results, etc.. - if o.file_title_logging == 'all' then - f:write(("[%s] \"%s\" | %s | %s | %s | "):format(os.date("%Y-%m-%dT%H:%M:%S"), fileTitle, filePath, log_length_text .. tostring(fileLength), log_time_text .. tostring(seekTime))) - elseif o.file_title_logging == 'protocols' and (starts_protocol(o.logging_protocols, filePath)) or o.file_title_logging == 'local' and not (starts_protocol(o.logging_protocols, filePath)) then --1.3# added file_title_logging for local - f:write(("[%s] \"%s\" | %s | %s | %s | "):format(os.date("%Y-%m-%dT%H:%M:%S"), fileTitle, filePath, log_length_text .. tostring(fileLength), log_time_text .. tostring(seekTime))) - elseif o.file_title_logging == 'protocols' and not (starts_protocol(o.logging_protocols, filePath)) then - f:write(("[%s] %s | %s | %s | "):format(os.date("%Y-%m-%dT%H:%M:%S"), filePath, log_length_text .. tostring(fileLength), log_time_text .. tostring(seekTime))) + local f = io.open(log_fullpath, "a+") --1.3# dont allow customization to date_format so it can be saved in a standard in which I can parse for search results, etc.. + if o.file_title_logging == "all" then + f:write( + ('[%s] "%s" | %s | %s | %s | '):format( + os.date("%Y-%m-%dT%H:%M:%S"), + fileTitle, + filePath, + log_length_text .. tostring(fileLength), + log_time_text .. tostring(seekTime) + ) + ) + elseif + o.file_title_logging == "protocols" and (starts_protocol(o.logging_protocols, filePath)) + or o.file_title_logging == "local" and not (starts_protocol(o.logging_protocols, filePath)) + then --1.3# added file_title_logging for local + f:write( + ('[%s] "%s" | %s | %s | %s | '):format( + os.date("%Y-%m-%dT%H:%M:%S"), + fileTitle, + filePath, + log_length_text .. tostring(fileLength), + log_time_text .. tostring(seekTime) + ) + ) + elseif o.file_title_logging == "protocols" and not (starts_protocol(o.logging_protocols, filePath)) then + f:write( + ("[%s] %s | %s | %s | "):format( + os.date("%Y-%m-%dT%H:%M:%S"), + filePath, + log_length_text .. tostring(fileLength), + log_time_text .. tostring(seekTime) + ) + ) else - f:write(("[%s] %s | %s | %s | "):format(os.date("%Y-%m-%dT%H:%M:%S"), filePath, log_length_text .. tostring(fileLength), log_time_text .. tostring(seekTime))) + f:write( + ("[%s] %s | %s | %s | "):format( + os.date("%Y-%m-%dT%H:%M:%S"), + filePath, + log_length_text .. tostring(fileLength), + log_time_text .. tostring(seekTime) + ) + ) end - f:write('\n') + f:write("\n") f:close() - --1.2.6# restore properties if o.overwrite_preserve_properties is enabled if found_entry and o.overwrite_preserve_properties then local temp_log_contents = read_log_table() --1.2.6# loop through table with the new additions - if not temp_log_contents or not temp_log_contents[1] then return end + if not temp_log_contents or not temp_log_contents[1] then + return + end --1.2.6# when a slot or group was found previously, then add it - if found_entry['found_slot'] then - remove_all_additional_param_log_entry(found_entry['found_slot'], log_keybind_text) --1.2.9# replaced list_slot_remove with remove_all_.. function to avoid possible errors since list_slot_remove has a check for list_drawn - add_additional_param_log_entry(found_entry['found_slot'], #temp_log_contents, log_keybind_text) + if found_entry["found_slot"] then + remove_all_additional_param_log_entry(found_entry["found_slot"], log_keybind_text) --1.2.9# replaced list_slot_remove with remove_all_.. function to avoid possible errors since list_slot_remove has a check for list_drawn + add_additional_param_log_entry(found_entry["found_slot"], #temp_log_contents, log_keybind_text) end - if found_entry['found_group'] then - add_additional_param_log_entry(found_entry['found_group'], #temp_log_contents, log_group_text) + if found_entry["found_group"] then + add_additional_param_log_entry(found_entry["found_group"], #temp_log_contents, log_group_text) end end --1.2.7# if an exact match is not found, and there are multiple deleted entries because of same_path_log_delete then add the latest deleted property to the newly added entry if not found_entry and deleted_entries ~= nil and deleted_entries[1] and o.overwrite_preserve_properties then local temp_log_contents = read_log_table() --1.2.6# loop through table with the new additions - if not temp_log_contents or not temp_log_contents[1] then return end + if not temp_log_contents or not temp_log_contents[1] then + return + end --1.2.7# loop through all deleted entries and get the first found slot and group then append it to the latest entry and then break loop local break_table = false for i = 1, #deleted_entries do @@ -2668,16 +3537,18 @@ function write_log(target_time, update_seekTime, entry_limit) end end end - + if not update_seekTime then seekTime = prev_seekTime end end function add_load_slot(key_index) - if not key_index then return end + if not key_index then + return + end - local current_filePath = mp.get_property('path') + local current_filePath = mp.get_property("path") local list_filepath, list_filetitle, list_seektime if list_drawn then slot_add(key_index) @@ -2697,34 +3568,59 @@ function add_load_slot(key_index) if slot_taken then if file_exists(list_filepath) or starts_protocol(protocols, list_filepath) then if list_filepath ~= current_filePath then - if o.preserve_video_settings then mp.command("write-watch-later-config") end--1.3.1# option to preserve video settings by using write-watch-later-config when loading bookmark replaces current file #84 - mp.commandv('loadfile', list_filepath) + if o.preserve_video_settings then + mp.command("write-watch-later-config") + end --1.3.1# option to preserve video settings by using write-watch-later-config when loading bookmark replaces current file #84 + mp.commandv("loadfile", list_filepath) if o.keybinds_auto_resume then resume_selected = true end elseif list_filepath == current_filePath and o.keybinds_auto_resume then - mp.commandv('seek', list_seektime, 'absolute', 'exact') + mp.commandv("seek", list_seektime, "absolute", "exact") list_close_and_trash_collection() elseif list_filepath == current_filePath and not o.keybinds_auto_resume then - mp.commandv('seek', 0, 'absolute', 'exact') + mp.commandv("seek", 0, "absolute", "exact") list_close_and_trash_collection() end if o.keybinds_auto_resume then if o.osd_messages == true then - mp.osd_message('Loaded slot:' .. ' ⌨ ' .. get_slot_keybind(key_index) .. '\n' .. list_filetitle .. ' 🕒 ' .. format_time(list_seektime, o.osd_time_format[3], o.osd_time_format[2], o.osd_time_format[1])) + mp.osd_message( + "Loaded slot:" + .. " ⌨ " + .. get_slot_keybind(key_index) + .. "\n" + .. list_filetitle + .. " 🕒 " + .. format_time( + list_seektime, + o.osd_time_format[3], + o.osd_time_format[2], + o.osd_time_format[1] + ) + ) end - msg.info('Loaded slot:' .. ' ⌨ ' .. get_slot_keybind(key_index) .. '\n' .. list_filetitle .. ' 🕒 ' .. format_time(list_seektime)) + msg.info( + "Loaded slot:" + .. " ⌨ " + .. get_slot_keybind(key_index) + .. "\n" + .. list_filetitle + .. " 🕒 " + .. format_time(list_seektime) + ) else if o.osd_messages == true then - mp.osd_message('Loaded slot:' .. ' ⌨ ' .. get_slot_keybind(key_index) .. '\n' .. list_filetitle) + mp.osd_message( + "Loaded slot:" .. " ⌨ " .. get_slot_keybind(key_index) .. "\n" .. list_filetitle + ) end - msg.info('Loaded slot:' .. ' ⌨ ' .. get_slot_keybind(key_index) .. '\n' .. list_filetitle) + msg.info("Loaded slot:" .. " ⌨ " .. get_slot_keybind(key_index) .. "\n" .. list_filetitle) end else if o.osd_messages == true then - mp.osd_message('File Doesn\'t Exist:\n' .. list_filepath) + mp.osd_message("File Doesn't Exist:\n" .. list_filepath) end - msg.info('The file below doesn\'t seem to exist:\n' .. list_filepath) + msg.info("The file below doesn't seem to exist:\n" .. list_filepath) return end else @@ -2746,34 +3642,62 @@ function add_load_slot(key_index) add_additional_param_log_entry(key_index, #osd_log_contents, log_keybind_text) --1.2.9# adds the slot of the passed index end if o.osd_messages == true then - mp.osd_message('Bookmarked & Added Keybind:\n' .. fileTitle .. ' 🕒 ' .. format_time(mp.get_property_number('time-pos'), o.osd_time_format[3], o.osd_time_format[2], o.osd_time_format[1]) .. ' ⌨ ' .. get_slot_keybind(key_index)) + mp.osd_message( + "Bookmarked & Added Keybind:\n" + .. fileTitle + .. " 🕒 " + .. format_time( + mp.get_property_number("time-pos"), + o.osd_time_format[3], + o.osd_time_format[2], + o.osd_time_format[1] + ) + .. " ⌨ " + .. get_slot_keybind(key_index) + ) end - msg.info('Bookmarked the below & added keybind:\n' .. fileTitle .. ' 🕒 ' .. format_time(mp.get_property_number('time-pos')) .. ' ⌨ ' .. get_slot_keybind(key_index)) + msg.info( + "Bookmarked the below & added keybind:\n" + .. fileTitle + .. " 🕒 " + .. format_time(mp.get_property_number("time-pos")) + .. " ⌨ " + .. get_slot_keybind(key_index) + ) else if o.osd_messages == true then - mp.osd_message('Failed to Bookmark & Auto Create Keybind\nNo Video Found') + mp.osd_message("Failed to Bookmark & Auto Create Keybind\nNo Video Found") end msg.info("Failed to bookmark & auto create keybind, no video found") end else if o.osd_messages == true then - mp.osd_message('No Bookmark Slot For' .. ' ⌨ ' .. get_slot_keybind(key_index) .. ' Yet') + mp.osd_message("No Bookmark Slot For" .. " ⌨ " .. get_slot_keybind(key_index) .. " Yet") end - msg.info('No bookmark slot has been assigned for' .. ' ⌨ ' .. get_slot_keybind(key_index) .. ' keybind yet') + msg.info( + "No bookmark slot has been assigned for" + .. " ⌨ " + .. get_slot_keybind(key_index) + .. " keybind yet" + ) end end else if o.osd_messages == true then - mp.osd_message('No Bookmark Slot For' .. ' ⌨ ' .. get_slot_keybind(key_index) .. ' Yet') + mp.osd_message("No Bookmark Slot For" .. " ⌨ " .. get_slot_keybind(key_index) .. " Yet") end - msg.info('No bookmark slot has been assigned for' .. ' ⌨ ' .. get_slot_keybind(key_index) .. ' keybind yet') + msg.info( + "No bookmark slot has been assigned for" .. " ⌨ " .. get_slot_keybind(key_index) .. " keybind yet" + ) end end end function quicksave_slot(key_index) - if not key_index then return end - + if not key_index then + return + end + if list_drawn then slot_add(key_index) else @@ -2787,9 +3711,13 @@ function quicksave_slot(key_index) add_additional_param_log_entry(key_index, #osd_log_contents, log_keybind_text) --1.2.9# adds the slot of the passed index if o.osd_messages == true then - mp.osd_message('Bookmarked Fileonly & Added Keybind:\n' .. fileTitle .. ' ⌨ ' .. get_slot_keybind(key_index)) + mp.osd_message( + "Bookmarked Fileonly & Added Keybind:\n" .. fileTitle .. " ⌨ " .. get_slot_keybind(key_index) + ) end - msg.info('Bookmarked the below & added keybind:\n' .. fileTitle .. ' ⌨ ' .. get_slot_keybind(key_index)) + msg.info( + "Bookmarked the below & added keybind:\n" .. fileTitle .. " ⌨ " .. get_slot_keybind(key_index) + ) else write_log(false, true) --1.2.9# reflect removal of key_index in write_log function, fixes bug and cleaner code get_osd_log_contents() --1.2.9# reflect removal of key_index in write_log function, fixes bug and cleaner code @@ -2797,15 +3725,29 @@ function quicksave_slot(key_index) remove_all_additional_param_log_entry(current_slot, log_keybind_text) --1.2.9# removes all the slots of the current item remove_all_additional_param_log_entry(key_index, log_keybind_text) --1.2.9# removes all the slots that are going to be added based on passed index add_additional_param_log_entry(key_index, #osd_log_contents, log_keybind_text) --1.2.9# adds the slot of the passed index - + if o.osd_messages == true then - mp.osd_message('Bookmarked & Added Keybind:\n' .. fileTitle .. ' 🕒 ' .. format_time(seekTime, o.osd_time_format[3], o.osd_time_format[2], o.osd_time_format[1]) .. ' ⌨ ' .. get_slot_keybind(key_index)) + mp.osd_message( + "Bookmarked & Added Keybind:\n" + .. fileTitle + .. " 🕒 " + .. format_time(seekTime, o.osd_time_format[3], o.osd_time_format[2], o.osd_time_format[1]) + .. " ⌨ " + .. get_slot_keybind(key_index) + ) end - msg.info('Bookmarked the below & added keybind:\n' .. fileTitle .. ' 🕒 ' .. format_time(seekTime) .. ' ⌨ ' .. get_slot_keybind(key_index)) + msg.info( + "Bookmarked the below & added keybind:\n" + .. fileTitle + .. " 🕒 " + .. format_time(seekTime) + .. " ⌨ " + .. get_slot_keybind(key_index) + ) end else if o.osd_messages == true then - mp.osd_message('Failed to Bookmark & Auto Create Keybind\nNo Video Found') + mp.osd_message("Failed to Bookmark & Auto Create Keybind\nNo Video Found") end msg.info("Failed to bookmark & auto create keybind, no video found") end @@ -2820,15 +3762,20 @@ function bookmark_save() select(0) end if o.osd_messages == true then - mp.osd_message('Bookmarked:\n' .. fileTitle .. ' 🕒 ' .. format_time(seekTime, o.osd_time_format[3], o.osd_time_format[2], o.osd_time_format[1])) - end - msg.info('Added the below to bookmarks\n' .. fileTitle .. ' 🕒 ' .. format_time(seekTime)) + mp.osd_message( + "Bookmarked:\n" + .. fileTitle + .. " 🕒 " + .. format_time(seekTime, o.osd_time_format[3], o.osd_time_format[2], o.osd_time_format[1]) + ) + end + msg.info("Added the below to bookmarks\n" .. fileTitle .. " 🕒 " .. format_time(seekTime)) elseif filePath == nil and o.bookmark_loads_last_idle then osd_log_contents = read_log_table() load(1) else if o.osd_messages == true then - mp.osd_message('Failed to Bookmark\nNo Video Found') + mp.osd_message("Failed to Bookmark\nNo Video Found") end msg.info("Failed to bookmark, no video found") end @@ -2842,26 +3789,26 @@ function bookmark_fileonly_save() select(0) end if o.osd_messages == true then - mp.osd_message('Bookmarked File Only:\n' .. fileTitle) + mp.osd_message("Bookmarked File Only:\n" .. fileTitle) end - msg.info('Added the below to bookmarks\n' .. fileTitle) + msg.info("Added the below to bookmarks\n" .. fileTitle) elseif filePath == nil and o.bookmark_fileonly_loads_last_idle then osd_log_contents = read_log_table() load(1, false, 0) else if o.osd_messages == true then - mp.osd_message('Failed to Bookmark\nNo Video Found') + mp.osd_message("Failed to Bookmark\nNo Video Found") end msg.info("Failed to bookmark, no video found") end end -mp.register_event('file-loaded', function() +mp.register_event("file-loaded", function() list_close_and_trash_collection() filePath, fileTitle, fileLength = get_file() loadTriggered = true --1.1.5# for resume and resume-notime startup behavior (so that it only triggers if started as idle and only once) - if (resume_selected == true and seekTime ~= nil) then - mp.commandv('seek', seekTime, 'absolute', 'exact') + if resume_selected == true and seekTime ~= nil then + mp.commandv("seek", seekTime, "absolute", "exact") resume_selected = false end mark_chapter() @@ -2872,36 +3819,50 @@ mp.observe_property("idle-active", "bool", function(_, v) filePath, fileTitle, fileLength = nil --1.3.0# set it back to nil if idle is triggered for better trash collection. issue #69 end - if v and o.auto_run_list_idle ~= 'none' then - display_list(o.auto_run_list_idle, nil, 'hide-osd') + if v and o.auto_run_list_idle ~= "none" then + display_list(o.auto_run_list_idle, nil, "hide-osd") end - + if v and type(o.load_item_on_startup) == "number" and not loadTriggered then --1.3.0# option to immediately load an entry based on number - if o.load_item_on_startup == 0 then return end --1.3.0# if the entry loaded is 0 then exit this, this is automatically handled in load also but it is better to exit here since there will be a loop below this - + if o.load_item_on_startup == 0 then + return + end --1.3.0# if the entry loaded is 0 then exit this, this is automatically handled in load also but it is better to exit here since there will be a loop below this + osd_log_contents = read_log_table() --1.3.0# get the item list to use load function - if not osd_log_contents or not osd_log_contents[1] then return end + if not osd_log_contents or not osd_log_contents[1] then + return + end - if o.load_item_on_startup == -1 then o.load_item_on_startup = #osd_log_contents end --1.3.0# specify -1 as last entry + if o.load_item_on_startup == -1 then + o.load_item_on_startup = #osd_log_contents + end --1.3.0# specify -1 as last entry load(o.load_item_on_startup) end end) -bind_keys(o.bookmark_save_keybind, 'bookmark-save', bookmark_save) -bind_keys(o.bookmark_fileonly_keybind, 'bookmark-fileonly', bookmark_fileonly_save) +bind_keys(o.bookmark_save_keybind, "bookmark-save", bookmark_save) +bind_keys(o.bookmark_fileonly_keybind, "bookmark-fileonly", bookmark_fileonly_save) for i = 1, #o.open_list_keybind do if i == 1 then - mp.add_forced_key_binding(o.open_list_keybind[i][1], 'open-list', function()display_list(o.open_list_keybind[i][2]) end) + mp.add_forced_key_binding(o.open_list_keybind[i][1], "open-list", function() + display_list(o.open_list_keybind[i][2]) + end) else - mp.add_forced_key_binding(o.open_list_keybind[i][1], 'open-list'..i, function()display_list(o.open_list_keybind[i][2]) end) + mp.add_forced_key_binding(o.open_list_keybind[i][1], "open-list" .. i, function() + display_list(o.open_list_keybind[i][2]) + end) end end for i = 1, #o.keybinds_add_load_keybind do - mp.add_forced_key_binding(o.keybinds_add_load_keybind[i], 'keybind-slot-' .. i, function()add_load_slot(i) end) + mp.add_forced_key_binding(o.keybinds_add_load_keybind[i], "keybind-slot-" .. i, function() + add_load_slot(i) + end) end for i = 1, #o.keybinds_quicksave_keybind do - mp.add_forced_key_binding(o.keybinds_quicksave_keybind[i], 'keybind-slot-save-' .. i, function()quicksave_slot(i) end) + mp.add_forced_key_binding(o.keybinds_quicksave_keybind[i], "keybind-slot-save-" .. i, function() + quicksave_slot(i) + end) end diff --git a/ar/.config/mpv/scripts/SmartCopyPaste_II.lua b/ar/.config/mpv/scripts/SmartCopyPaste_II.lua index ca5bf58..f0456eb 100644 --- a/ar/.config/mpv/scripts/SmartCopyPaste_II.lua +++ b/ar/.config/mpv/scripts/SmartCopyPaste_II.lua @@ -239,7 +239,7 @@ local o = { ]], --Keybind thats are ignored when list is open ---------------------------END OF USER CUSTOMIZATION SETTINGS--------------------------- -}; +} (require("mp.options")).read_options(o) local utils = require("mp.utils") diff --git a/ar/.config/mpv/scripts/gallery-thumbgen.lua b/ar/.config/mpv/scripts/gallery-thumbgen.lua index dc0db1a..2dc0669 100644 --- a/ar/.config/mpv/scripts/gallery-thumbgen.lua +++ b/ar/.config/mpv/scripts/gallery-thumbgen.lua @@ -18,7 +18,7 @@ local script_id = mp.get_script_name() .. utils.getpid() local opts = { ytdl_exclude = "", -}; +} (require("mp.options")).read_options(opts, "gallery_worker") local ytdl = { diff --git a/ar/.config/mpv/scripts/mdmenu.lua b/ar/.config/mpv/scripts/mdmenu.lua index 1a0513c..9635394 100644 --- a/ar/.config/mpv/scripts/mdmenu.lua +++ b/ar/.config/mpv/scripts/mdmenu.lua @@ -1,16 +1,16 @@ --[[ This file is part of mdmenu. - + mdmenu is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. - + mdmenu is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. - + You should have received a copy of the GNU Affero General Public License along with mdmenu. If not, see <https://www.gnu.org/licenses/>. ]] diff --git a/ar/.config/mpv/scripts/navigator.lua b/ar/.config/mpv/scripts/navigator.lua index 4533f60..9bc6cb8 100644 --- a/ar/.config/mpv/scripts/navigator.lua +++ b/ar/.config/mpv/scripts/navigator.lua @@ -36,7 +36,6 @@ local settings = { --on windows use double backslash c:\\my\\directory\\ favorites = { "/media/" .. user, - "/mnt/second/videos", home .. "/Downloads", home .. "/Torrents/complete", home .. "/Videos", diff --git a/ar/.config/mpv/scripts/osc.lua b/ar/.config/mpv/scripts/osc.lua index 37791b0..8bf04c6 100644 --- a/ar/.config/mpv/scripts/osc.lua +++ b/ar/.config/mpv/scripts/osc.lua @@ -1,18 +1,18 @@ mp.set_property("osc", "no") if mp.get_script_name() ~= "osc" then - -- reclaim osc script name after the builtin osc unloads - local script_path = debug.getinfo(1, "S").source:match("^@?(.*[\\/]osc%.lua)$") - if script_path then - mp.add_timeout(0.05, function() - mp.commandv("load-script", script_path) - end) - return - end -end -local assdraw = require 'mp.assdraw' -local msg = require 'mp.msg' -local opt = require 'mp.options' -local utils = require 'mp.utils' + -- reclaim osc script name after the builtin osc unloads + local script_path = debug.getinfo(1, "S").source:match("^@?(.*[\\/]osc%.lua)$") + if script_path then + mp.add_timeout(0.05, function() + mp.commandv("load-script", script_path) + end) + return + end +end +local assdraw = require("mp.assdraw") +local msg = require("mp.msg") +local opt = require("mp.options") +local utils = require("mp.utils") -- -- Parameters @@ -20,140 +20,149 @@ local utils = require 'mp.utils' -- default user option values -- do not touch, change them in osc.conf local user_opts = { - showwindowed = true, -- show OSC when windowed? - showfullscreen = true, -- show OSC when fullscreen? - idlescreen = true, -- show mpv logo on idle - scalewindowed = 1, -- scaling of the controller when windowed - scalefullscreen = 1, -- scaling of the controller when fullscreen - scaleforcedwindow = 2, -- scaling when rendered on a forced window - vidscale = true, -- scale the controller with the video? - valign = 0.8, -- vertical alignment, -1 (top) to 1 (bottom) - halign = 0, -- horizontal alignment, -1 (left) to 1 (right) - barmargin = 0, -- vertical margin of top/bottombar - boxalpha = 80, -- alpha of the background box, - -- 0 (opaque) to 255 (fully transparent) - hidetimeout = 500, -- duration in ms until the OSC hides if no - -- mouse movement. enforced non-negative for the - -- user, but internally negative is "always-on". - fadeduration = 200, -- duration of fade out in ms, 0 = no fade - deadzonesize = 0.5, -- size of deadzone - minmousemove = 0, -- minimum amount of pixels the mouse has to - -- move between ticks to make the OSC show up - iamaprogrammer = false, -- use native mpv values and disable OSC - -- internal track list management (and some - -- functions that depend on it) - layout = "bottombar", - seekbarstyle = "bar", -- bar, diamond or knob - seekbarhandlesize = 0.6, -- size ratio of the diamond and knob handle - seekrangestyle = "inverted",-- bar, line, slider, inverted or none - seekrangeseparate = true, -- whether the seekranges overlay on the bar-style seekbar - seekrangealpha = 200, -- transparency of seekranges - seekbarkeyframes = true, -- use keyframes when dragging the seekbar - scrollcontrols = true, -- allow scrolling when hovering certain OSC elements - title = "${media-title}", -- string compatible with property-expansion - -- to be shown as OSC title - tooltipborder = 1, -- border of tooltip in bottom/topbar - timetotal = false, -- display total time instead of remaining time? - remaining_playtime = true, -- display the remaining time in playtime or video-time mode - -- playtime takes speed into account, whereas video-time doesn't - timems = false, -- display timecodes with milliseconds? - tcspace = 100, -- timecode spacing (compensate font size estimation) - visibility = "auto", -- only used at init to set visibility_mode(...) - boxmaxchars = 80, -- title crop threshold for box layout - boxvideo = false, -- apply osc_param.video_margins to video - windowcontrols = "auto", -- whether to show window controls - windowcontrols_alignment = "right", -- which side to show window controls on - greenandgrumpy = false, -- disable santa hat - livemarkers = true, -- update seekbar chapter markers on duration change - chapters_osd = true, -- whether to show chapters OSD on next/prev - playlist_osd = true, -- whether to show playlist OSD on next/prev - chapter_fmt = "Chapter: %s", -- chapter print format for seekbar-hover. "no" to disable - unicodeminus = false, -- whether to use the Unicode minus sign character + showwindowed = true, -- show OSC when windowed? + showfullscreen = true, -- show OSC when fullscreen? + idlescreen = true, -- show mpv logo on idle + scalewindowed = 1, -- scaling of the controller when windowed + scalefullscreen = 1, -- scaling of the controller when fullscreen + scaleforcedwindow = 2, -- scaling when rendered on a forced window + vidscale = true, -- scale the controller with the video? + valign = 0.8, -- vertical alignment, -1 (top) to 1 (bottom) + halign = 0, -- horizontal alignment, -1 (left) to 1 (right) + barmargin = 0, -- vertical margin of top/bottombar + boxalpha = 80, -- alpha of the background box, + -- 0 (opaque) to 255 (fully transparent) + hidetimeout = 500, -- duration in ms until the OSC hides if no + -- mouse movement. enforced non-negative for the + -- user, but internally negative is "always-on". + fadeduration = 200, -- duration of fade out in ms, 0 = no fade + deadzonesize = 0.5, -- size of deadzone + minmousemove = 0, -- minimum amount of pixels the mouse has to + -- move between ticks to make the OSC show up + iamaprogrammer = false, -- use native mpv values and disable OSC + -- internal track list management (and some + -- functions that depend on it) + layout = "bottombar", + seekbarstyle = "bar", -- bar, diamond or knob + seekbarhandlesize = 0.6, -- size ratio of the diamond and knob handle + seekrangestyle = "inverted", -- bar, line, slider, inverted or none + seekrangeseparate = true, -- whether the seekranges overlay on the bar-style seekbar + seekrangealpha = 200, -- transparency of seekranges + seekbarkeyframes = true, -- use keyframes when dragging the seekbar + scrollcontrols = true, -- allow scrolling when hovering certain OSC elements + title = "${media-title}", -- string compatible with property-expansion + -- to be shown as OSC title + tooltipborder = 1, -- border of tooltip in bottom/topbar + timetotal = false, -- display total time instead of remaining time? + remaining_playtime = true, -- display the remaining time in playtime or video-time mode + -- playtime takes speed into account, whereas video-time doesn't + timems = false, -- display timecodes with milliseconds? + tcspace = 100, -- timecode spacing (compensate font size estimation) + visibility = "auto", -- only used at init to set visibility_mode(...) + boxmaxchars = 80, -- title crop threshold for box layout + boxvideo = false, -- apply osc_param.video_margins to video + windowcontrols = "auto", -- whether to show window controls + windowcontrols_alignment = "right", -- which side to show window controls on + greenandgrumpy = false, -- disable santa hat + livemarkers = true, -- update seekbar chapter markers on duration change + chapters_osd = true, -- whether to show chapters OSD on next/prev + playlist_osd = true, -- whether to show playlist OSD on next/prev + chapter_fmt = "Chapter: %s", -- chapter print format for seekbar-hover. "no" to disable + unicodeminus = false, -- whether to use the Unicode minus sign character } -- read options from config and command-line -opt.read_options(user_opts, "osc", function(list) update_options(list) end) +opt.read_options(user_opts, "osc", function(list) + update_options(list) +end) local osc_param = { -- calculated by osc_init() - playresy = 0, -- canvas size Y - playresx = 0, -- canvas size X - display_aspect = 1, - unscaled_y = 0, - areas = {}, - video_margins = { - l = 0, r = 0, t = 0, b = 0, -- left/right/top/bottom - }, + playresy = 0, -- canvas size Y + playresx = 0, -- canvas size X + display_aspect = 1, + unscaled_y = 0, + areas = {}, + video_margins = { + l = 0, + r = 0, + t = 0, + b = 0, -- left/right/top/bottom + }, } local osc_styles = { - bigButtons = "{\\blur0\\bord0\\1c&HFFFFFF\\3c&HFFFFFF\\fs50\\fnmpv-osd-symbols}", - smallButtonsL = "{\\blur0\\bord0\\1c&HFFFFFF\\3c&HFFFFFF\\fs19\\fnmpv-osd-symbols}", - smallButtonsLlabel = "{\\fscx105\\fscy105\\fn" .. mp.get_property("options/osd-font") .. "}", - smallButtonsR = "{\\blur0\\bord0\\1c&HFFFFFF\\3c&HFFFFFF\\fs30\\fnmpv-osd-symbols}", - topButtons = "{\\blur0\\bord0\\1c&HFFFFFF\\3c&HFFFFFF\\fs12\\fnmpv-osd-symbols}", - - elementDown = "{\\1c&H999999}", - timecodes = "{\\blur0\\bord0\\1c&HFFFFFF\\3c&HFFFFFF\\fs20}", - vidtitle = "{\\blur0\\bord0\\1c&HFFFFFF\\3c&HFFFFFF\\fs12\\q2}", - box = "{\\rDefault\\blur0\\bord1\\1c&H000000\\3c&HFFFFFF}", - - topButtonsBar = "{\\blur0\\bord0\\1c&HFFFFFF\\3c&HFFFFFF\\fs18\\fnmpv-osd-symbols}", - smallButtonsBar = "{\\blur0\\bord0\\1c&HFFFFFF\\3c&HFFFFFF\\fs28\\fnmpv-osd-symbols}", - timecodesBar = "{\\blur0\\bord0\\1c&HFFFFFF\\3c&HFFFFFF\\fs27}", - timePosBar = "{\\blur0\\bord".. user_opts.tooltipborder .."\\1c&HFFFFFF\\3c&H000000\\fs30}", - vidtitleBar = "{\\blur0\\bord0\\1c&HFFFFFF\\3c&HFFFFFF\\fs18\\q2}", - - wcButtons = "{\\1c&HFFFFFF\\fs24\\fnmpv-osd-symbols}", - wcTitle = "{\\1c&HFFFFFF\\fs24\\q2}", - wcBar = "{\\1c&H000000}", + bigButtons = "{\\blur0\\bord0\\1c&HFFFFFF\\3c&HFFFFFF\\fs50\\fnmpv-osd-symbols}", + smallButtonsL = "{\\blur0\\bord0\\1c&HFFFFFF\\3c&HFFFFFF\\fs19\\fnmpv-osd-symbols}", + smallButtonsLlabel = "{\\fscx105\\fscy105\\fn" .. mp.get_property("options/osd-font") .. "}", + smallButtonsR = "{\\blur0\\bord0\\1c&HFFFFFF\\3c&HFFFFFF\\fs30\\fnmpv-osd-symbols}", + topButtons = "{\\blur0\\bord0\\1c&HFFFFFF\\3c&HFFFFFF\\fs12\\fnmpv-osd-symbols}", + + elementDown = "{\\1c&H999999}", + timecodes = "{\\blur0\\bord0\\1c&HFFFFFF\\3c&HFFFFFF\\fs20}", + vidtitle = "{\\blur0\\bord0\\1c&HFFFFFF\\3c&HFFFFFF\\fs12\\q2}", + box = "{\\rDefault\\blur0\\bord1\\1c&H000000\\3c&HFFFFFF}", + + topButtonsBar = "{\\blur0\\bord0\\1c&HFFFFFF\\3c&HFFFFFF\\fs18\\fnmpv-osd-symbols}", + smallButtonsBar = "{\\blur0\\bord0\\1c&HFFFFFF\\3c&HFFFFFF\\fs28\\fnmpv-osd-symbols}", + timecodesBar = "{\\blur0\\bord0\\1c&HFFFFFF\\3c&HFFFFFF\\fs27}", + timePosBar = "{\\blur0\\bord" .. user_opts.tooltipborder .. "\\1c&HFFFFFF\\3c&H000000\\fs30}", + vidtitleBar = "{\\blur0\\bord0\\1c&HFFFFFF\\3c&HFFFFFF\\fs18\\q2}", + + wcButtons = "{\\1c&HFFFFFF\\fs24\\fnmpv-osd-symbols}", + wcTitle = "{\\1c&HFFFFFF\\fs24\\q2}", + wcBar = "{\\1c&H000000}", } local function create_osd_overlay(...) - if not mp.create_osd_overlay then return end - return mp.create_osd_overlay(...) + if not mp.create_osd_overlay then + return + end + return mp.create_osd_overlay(...) end -- internal states, do not touch local state = { - showtime, -- time of last invocation (last mouse move) - osc_visible = false, - anistart, -- time when the animation started - anitype, -- current type of animation - animation, -- current animation alpha - mouse_down_counter = 0, -- used for softrepeat - active_element = nil, -- nil = none, 0 = background, 1+ = see elements[] - active_event_source = nil, -- the "button" that issued the current event - rightTC_trem = not user_opts.timetotal, -- if the right timecode should display total or remaining time - tc_ms = user_opts.timems, -- Should the timecodes display their time with milliseconds - mp_screen_sizeX, mp_screen_sizeY, -- last screen-resolution, to detect resolution changes to issue reINITs - initREQ = false, -- is a re-init request pending? - marginsREQ = false, -- is a margins update pending? - last_mouseX, last_mouseY, -- last mouse position, to detect significant mouse movement - mouse_in_window = false, - message_text, - message_hide_timer, - fullscreen = false, - tick_timer = nil, - tick_last_time = 0, -- when the last tick() was run - hide_timer = nil, - cache_state = nil, - idle = false, - enabled = true, - input_enabled = true, - showhide_enabled = false, - windowcontrols_buttons = false, - dmx_cache = 0, - using_video_margins = false, - border = true, - maximized = false, - osd = create_osd_overlay("ass-events"), - chapter_list = {}, -- sorted by time + showtime, -- time of last invocation (last mouse move) + osc_visible = false, + anistart, -- time when the animation started + anitype, -- current type of animation + animation, -- current animation alpha + mouse_down_counter = 0, -- used for softrepeat + active_element = nil, -- nil = none, 0 = background, 1+ = see elements[] + active_event_source = nil, -- the "button" that issued the current event + rightTC_trem = not user_opts.timetotal, -- if the right timecode should display total or remaining time + tc_ms = user_opts.timems, -- Should the timecodes display their time with milliseconds + mp_screen_sizeX, + mp_screen_sizeY, -- last screen-resolution, to detect resolution changes to issue reINITs + initREQ = false, -- is a re-init request pending? + marginsREQ = false, -- is a margins update pending? + last_mouseX, + last_mouseY, -- last mouse position, to detect significant mouse movement + mouse_in_window = false, + message_text, + message_hide_timer, + fullscreen = false, + tick_timer = nil, + tick_last_time = 0, -- when the last tick() was run + hide_timer = nil, + cache_state = nil, + idle = false, + enabled = true, + input_enabled = true, + showhide_enabled = false, + windowcontrols_buttons = false, + dmx_cache = 0, + using_video_margins = false, + border = true, + maximized = false, + osd = create_osd_overlay("ass-events"), + chapter_list = {}, -- sorted by time } local thumbfast = { - width = 0, - height = 0, - disabled = false + width = 0, + height = 0, + disabled = false, } local window_control_box_width = 80 @@ -166,149 +175,162 @@ local is_december = os.date("*t").month == 12 -- function kill_animation() - state.anistart = nil - state.animation = nil - state.anitype = nil + state.anistart = nil + state.animation = nil + state.anitype = nil end function set_osd(res_x, res_y, text, z) - if state.osd.res_x == res_x and - state.osd.res_y == res_y and - state.osd.data == text then - return - end - state.osd.res_x = res_x - state.osd.res_y = res_y - state.osd.data = text - state.osd.z = z - state.osd:update() + if state.osd.res_x == res_x and state.osd.res_y == res_y and state.osd.data == text then + return + end + state.osd.res_x = res_x + state.osd.res_y = res_y + state.osd.data = text + state.osd.z = z + state.osd:update() end set_osd = state.osd and set_osd or mp.set_osd_ass local margins_opts = { - {"l", "video-margin-ratio-left"}, - {"r", "video-margin-ratio-right"}, - {"t", "video-margin-ratio-top"}, - {"b", "video-margin-ratio-bottom"}, + { "l", "video-margin-ratio-left" }, + { "r", "video-margin-ratio-right" }, + { "t", "video-margin-ratio-top" }, + { "b", "video-margin-ratio-bottom" }, } -- scale factor for translating between real and virtual ASS coordinates function get_virt_scale_factor() - local w, h = mp.get_osd_size() - if w <= 0 or h <= 0 then - return 0, 0 - end - return osc_param.playresx / w, osc_param.playresy / h + local w, h = mp.get_osd_size() + if w <= 0 or h <= 0 then + return 0, 0 + end + return osc_param.playresx / w, osc_param.playresy / h end -- return mouse position in virtual ASS coordinates (playresx/y) function get_virt_mouse_pos() - if state.mouse_in_window then - local sx, sy = get_virt_scale_factor() - local x, y = mp.get_mouse_pos() - return x * sx, y * sy - else - return -1, -1 - end + if state.mouse_in_window then + local sx, sy = get_virt_scale_factor() + local x, y = mp.get_mouse_pos() + return x * sx, y * sy + else + return -1, -1 + end end function set_virt_mouse_area(x0, y0, x1, y1, name) - local sx, sy = get_virt_scale_factor() - mp.set_mouse_area(x0 / sx, y0 / sy, x1 / sx, y1 / sy, name) + local sx, sy = get_virt_scale_factor() + mp.set_mouse_area(x0 / sx, y0 / sy, x1 / sx, y1 / sy, name) end function scale_value(x0, x1, y0, y1, val) - local m = (y1 - y0) / (x1 - x0) - local b = y0 - (m * x0) - return (m * val) + b + local m = (y1 - y0) / (x1 - x0) + local b = y0 - (m * x0) + return (m * val) + b end -- returns hitbox spanning coordinates (top left, bottom right corner) -- according to alignment function get_hitbox_coords(x, y, an, w, h) - - local alignments = { - [1] = function () return x, y-h, x+w, y end, - [2] = function () return x-(w/2), y-h, x+(w/2), y end, - [3] = function () return x-w, y-h, x, y end, - - [4] = function () return x, y-(h/2), x+w, y+(h/2) end, - [5] = function () return x-(w/2), y-(h/2), x+(w/2), y+(h/2) end, - [6] = function () return x-w, y-(h/2), x, y+(h/2) end, - - [7] = function () return x, y, x+w, y+h end, - [8] = function () return x-(w/2), y, x+(w/2), y+h end, - [9] = function () return x-w, y, x, y+h end, - } - - return alignments[an]() + local alignments = { + [1] = function() + return x, y - h, x + w, y + end, + [2] = function() + return x - (w / 2), y - h, x + (w / 2), y + end, + [3] = function() + return x - w, y - h, x, y + end, + + [4] = function() + return x, y - (h / 2), x + w, y + (h / 2) + end, + [5] = function() + return x - (w / 2), y - (h / 2), x + (w / 2), y + (h / 2) + end, + [6] = function() + return x - w, y - (h / 2), x, y + (h / 2) + end, + + [7] = function() + return x, y, x + w, y + h + end, + [8] = function() + return x - (w / 2), y, x + (w / 2), y + h + end, + [9] = function() + return x - w, y, x, y + h + end, + } + + return alignments[an]() end function get_hitbox_coords_geo(geometry) - return get_hitbox_coords(geometry.x, geometry.y, geometry.an, - geometry.w, geometry.h) + return get_hitbox_coords(geometry.x, geometry.y, geometry.an, geometry.w, geometry.h) end function get_element_hitbox(element) - return element.hitbox.x1, element.hitbox.y1, - element.hitbox.x2, element.hitbox.y2 + return element.hitbox.x1, element.hitbox.y1, element.hitbox.x2, element.hitbox.y2 end function mouse_hit(element) - return mouse_hit_coords(get_element_hitbox(element)) + return mouse_hit_coords(get_element_hitbox(element)) end function mouse_hit_coords(bX1, bY1, bX2, bY2) - local mX, mY = get_virt_mouse_pos() - return (mX >= bX1 and mX <= bX2 and mY >= bY1 and mY <= bY2) + local mX, mY = get_virt_mouse_pos() + return (mX >= bX1 and mX <= bX2 and mY >= bY1 and mY <= bY2) end function limit_range(min, max, val) - if val > max then - val = max - elseif val < min then - val = min - end - return val + if val > max then + val = max + elseif val < min then + val = min + end + return val end -- translate value into element coordinates function get_slider_ele_pos_for(element, val) + local ele_pos = scale_value( + element.slider.min.value, + element.slider.max.value, + element.slider.min.ele_pos, + element.slider.max.ele_pos, + val + ) - local ele_pos = scale_value( - element.slider.min.value, element.slider.max.value, - element.slider.min.ele_pos, element.slider.max.ele_pos, - val) - - return limit_range( - element.slider.min.ele_pos, element.slider.max.ele_pos, - ele_pos) + return limit_range(element.slider.min.ele_pos, element.slider.max.ele_pos, ele_pos) end -- translates global (mouse) coordinates to value function get_slider_value_at(element, glob_pos) + local val = scale_value( + element.slider.min.glob_pos, + element.slider.max.glob_pos, + element.slider.min.value, + element.slider.max.value, + glob_pos + ) - local val = scale_value( - element.slider.min.glob_pos, element.slider.max.glob_pos, - element.slider.min.value, element.slider.max.value, - glob_pos) - - return limit_range( - element.slider.min.value, element.slider.max.value, - val) + return limit_range(element.slider.min.value, element.slider.max.value, val) end -- get value at current mouse position function get_slider_value(element) - return get_slider_value_at(element, get_virt_mouse_pos()) + return get_slider_value_at(element, get_virt_mouse_pos()) end function countone(val) - if not (user_opts.iamaprogrammer) then - val = val + 1 - end - return val + if not user_opts.iamaprogrammer then + val = val + 1 + end + return val end -- align: -1 .. +1 @@ -316,242 +338,251 @@ end -- obj: size of the object that should be positioned inside the area -- margin: min. distance from object to frame (as long as -1 <= align <= +1) function get_align(align, frame, obj, margin) - return (frame / 2) + (((frame / 2) - margin - (obj / 2)) * align) + return (frame / 2) + (((frame / 2) - margin - (obj / 2)) * align) end -- multiplies two alpha values, formular can probably be improved function mult_alpha(alphaA, alphaB) - return 255 - (((1-(alphaA/255)) * (1-(alphaB/255))) * 255) + return 255 - (((1 - (alphaA / 255)) * (1 - (alphaB / 255))) * 255) end function add_area(name, x1, y1, x2, y2) - -- create area if needed - if (osc_param.areas[name] == nil) then - osc_param.areas[name] = {} - end - table.insert(osc_param.areas[name], {x1=x1, y1=y1, x2=x2, y2=y2}) + -- create area if needed + if osc_param.areas[name] == nil then + osc_param.areas[name] = {} + end + table.insert(osc_param.areas[name], { x1 = x1, y1 = y1, x2 = x2, y2 = y2 }) end function ass_append_alpha(ass, alpha, modifier) - local ar = {} + local ar = {} - for ai, av in pairs(alpha) do - av = mult_alpha(av, modifier) - if state.animation then - av = mult_alpha(av, state.animation) - end - ar[ai] = av - end + for ai, av in pairs(alpha) do + av = mult_alpha(av, modifier) + if state.animation then + av = mult_alpha(av, state.animation) + end + ar[ai] = av + end - ass:append(string.format("{\\1a&H%X&\\2a&H%X&\\3a&H%X&\\4a&H%X&}", - ar[1], ar[2], ar[3], ar[4])) + ass:append(string.format("{\\1a&H%X&\\2a&H%X&\\3a&H%X&\\4a&H%X&}", ar[1], ar[2], ar[3], ar[4])) end local c = 0.551915024494 -- circle approximation function hexagon_cw(ass, x0, y0, x1, y1, r1, r2) - if r2 == nil then - r2 = r1 - end - ass:move_to(x0 + r1, y0) - if x0 ~= x1 then - ass:line_to(x1 - r2, y0) - end - ass:line_to(x1, y0 + r2) - if x0 ~= x1 then - ass:line_to(x1 - r2, y1) - end - ass:line_to(x0 + r1, y1) - ass:line_to(x0, y0 + r1) + if r2 == nil then + r2 = r1 + end + ass:move_to(x0 + r1, y0) + if x0 ~= x1 then + ass:line_to(x1 - r2, y0) + end + ass:line_to(x1, y0 + r2) + if x0 ~= x1 then + ass:line_to(x1 - r2, y1) + end + ass:line_to(x0 + r1, y1) + ass:line_to(x0, y0 + r1) end function hexagon_ccw(ass, x0, y0, x1, y1, r1, r2) - if r2 == nil then - r2 = r1 - end - ass:move_to(x0 + r1, y0) - ass:line_to(x0, y0 + r1) - ass:line_to(x0 + r1, y1) - if x0 ~= x1 then - ass:line_to(x1 - r2, y1) - end - ass:line_to(x1, y0 + r2) - if x0 ~= x1 then - ass:line_to(x1 - r2, y0) - end + if r2 == nil then + r2 = r1 + end + ass:move_to(x0 + r1, y0) + ass:line_to(x0, y0 + r1) + ass:line_to(x0 + r1, y1) + if x0 ~= x1 then + ass:line_to(x1 - r2, y1) + end + ass:line_to(x1, y0 + r2) + if x0 ~= x1 then + ass:line_to(x1 - r2, y0) + end end function round_rect_cw(ass, x0, y0, x1, y1, r1, r2) - if r2 == nil then - r2 = r1 - end - local c1 = c * r1 -- circle approximation - local c2 = c * r2 -- circle approximation - ass:move_to(x0 + r1, y0) - ass:line_to(x1 - r2, y0) -- top line - if r2 > 0 then - ass:bezier_curve(x1 - r2 + c2, y0, x1, y0 + r2 - c2, x1, y0 + r2) -- top right corner - end - ass:line_to(x1, y1 - r2) -- right line - if r2 > 0 then - ass:bezier_curve(x1, y1 - r2 + c2, x1 - r2 + c2, y1, x1 - r2, y1) -- bottom right corner - end - ass:line_to(x0 + r1, y1) -- bottom line - if r1 > 0 then - ass:bezier_curve(x0 + r1 - c1, y1, x0, y1 - r1 + c1, x0, y1 - r1) -- bottom left corner - end - ass:line_to(x0, y0 + r1) -- left line - if r1 > 0 then - ass:bezier_curve(x0, y0 + r1 - c1, x0 + r1 - c1, y0, x0 + r1, y0) -- top left corner - end + if r2 == nil then + r2 = r1 + end + local c1 = c * r1 -- circle approximation + local c2 = c * r2 -- circle approximation + ass:move_to(x0 + r1, y0) + ass:line_to(x1 - r2, y0) -- top line + if r2 > 0 then + ass:bezier_curve(x1 - r2 + c2, y0, x1, y0 + r2 - c2, x1, y0 + r2) -- top right corner + end + ass:line_to(x1, y1 - r2) -- right line + if r2 > 0 then + ass:bezier_curve(x1, y1 - r2 + c2, x1 - r2 + c2, y1, x1 - r2, y1) -- bottom right corner + end + ass:line_to(x0 + r1, y1) -- bottom line + if r1 > 0 then + ass:bezier_curve(x0 + r1 - c1, y1, x0, y1 - r1 + c1, x0, y1 - r1) -- bottom left corner + end + ass:line_to(x0, y0 + r1) -- left line + if r1 > 0 then + ass:bezier_curve(x0, y0 + r1 - c1, x0 + r1 - c1, y0, x0 + r1, y0) -- top left corner + end end function round_rect_ccw(ass, x0, y0, x1, y1, r1, r2) - if r2 == nil then - r2 = r1 - end - local c1 = c * r1 -- circle approximation - local c2 = c * r2 -- circle approximation - ass:move_to(x0 + r1, y0) - if r1 > 0 then - ass:bezier_curve(x0 + r1 - c1, y0, x0, y0 + r1 - c1, x0, y0 + r1) -- top left corner - end - ass:line_to(x0, y1 - r1) -- left line - if r1 > 0 then - ass:bezier_curve(x0, y1 - r1 + c1, x0 + r1 - c1, y1, x0 + r1, y1) -- bottom left corner - end - ass:line_to(x1 - r2, y1) -- bottom line - if r2 > 0 then - ass:bezier_curve(x1 - r2 + c2, y1, x1, y1 - r2 + c2, x1, y1 - r2) -- bottom right corner - end - ass:line_to(x1, y0 + r2) -- right line - if r2 > 0 then - ass:bezier_curve(x1, y0 + r2 - c2, x1 - r2 + c2, y0, x1 - r2, y0) -- top right corner - end + if r2 == nil then + r2 = r1 + end + local c1 = c * r1 -- circle approximation + local c2 = c * r2 -- circle approximation + ass:move_to(x0 + r1, y0) + if r1 > 0 then + ass:bezier_curve(x0 + r1 - c1, y0, x0, y0 + r1 - c1, x0, y0 + r1) -- top left corner + end + ass:line_to(x0, y1 - r1) -- left line + if r1 > 0 then + ass:bezier_curve(x0, y1 - r1 + c1, x0 + r1 - c1, y1, x0 + r1, y1) -- bottom left corner + end + ass:line_to(x1 - r2, y1) -- bottom line + if r2 > 0 then + ass:bezier_curve(x1 - r2 + c2, y1, x1, y1 - r2 + c2, x1, y1 - r2) -- bottom right corner + end + ass:line_to(x1, y0 + r2) -- right line + if r2 > 0 then + ass:bezier_curve(x1, y0 + r2 - c2, x1 - r2 + c2, y0, x1 - r2, y0) -- top right corner + end end function ass_draw_rr_h_cw(ass, x0, y0, x1, y1, r1, hexagon, r2) - if hexagon then - hexagon_cw(ass, x0, y0, x1, y1, r1, r2) - else - round_rect_cw(ass, x0, y0, x1, y1, r1, r2) - end + if hexagon then + hexagon_cw(ass, x0, y0, x1, y1, r1, r2) + else + round_rect_cw(ass, x0, y0, x1, y1, r1, r2) + end end function ass_draw_rr_h_ccw(ass, x0, y0, x1, y1, r1, hexagon, r2) - if hexagon then - hexagon_ccw(ass, x0, y0, x1, y1, r1, r2) - else - round_rect_ccw(ass, x0, y0, x1, y1, r1, r2) - end + if hexagon then + hexagon_ccw(ass, x0, y0, x1, y1, r1, r2) + else + round_rect_ccw(ass, x0, y0, x1, y1, r1, r2) + end end - -- -- Tracklist Management -- -local nicetypes = {video = "Video", audio = "Audio", sub = "Subtitle"} +local nicetypes = { video = "Video", audio = "Audio", sub = "Subtitle" } -- updates the OSC internal playlists, should be run each time the track-layout changes function update_tracklist() - local tracktable = mp.get_property_native("track-list", {}) + local tracktable = mp.get_property_native("track-list", {}) - -- by osc_id - tracks_osc = {} - tracks_osc.video, tracks_osc.audio, tracks_osc.sub = {}, {}, {} - -- by mpv_id - tracks_mpv = {} - tracks_mpv.video, tracks_mpv.audio, tracks_mpv.sub = {}, {}, {} - for n = 1, #tracktable do - if not (tracktable[n].type == "unknown") then - local type = tracktable[n].type - local mpv_id = tonumber(tracktable[n].id) + -- by osc_id + tracks_osc = {} + tracks_osc.video, tracks_osc.audio, tracks_osc.sub = {}, {}, {} + -- by mpv_id + tracks_mpv = {} + tracks_mpv.video, tracks_mpv.audio, tracks_mpv.sub = {}, {}, {} + for n = 1, #tracktable do + if not (tracktable[n].type == "unknown") then + local type = tracktable[n].type + local mpv_id = tonumber(tracktable[n].id) - -- by osc_id - table.insert(tracks_osc[type], tracktable[n]) + -- by osc_id + table.insert(tracks_osc[type], tracktable[n]) - -- by mpv_id - tracks_mpv[type][mpv_id] = tracktable[n] - tracks_mpv[type][mpv_id].osc_id = #tracks_osc[type] - end - end + -- by mpv_id + tracks_mpv[type][mpv_id] = tracktable[n] + tracks_mpv[type][mpv_id].osc_id = #tracks_osc[type] + end + end end -- return a nice list of tracks of the given type (video, audio, sub) function get_tracklist(type) - local msg = "Available " .. nicetypes[type] .. " Tracks: " - if not tracks_osc or #tracks_osc[type] == 0 then - msg = msg .. "none" - else - for n = 1, #tracks_osc[type] do - local track = tracks_osc[type][n] - local lang, title, selected = "unknown", "", "○" - if not(track.lang == nil) then lang = track.lang end - if not(track.title == nil) then title = track.title end - if (track.id == tonumber(mp.get_property(type))) then - selected = "●" - end - msg = msg.."\n"..selected.." "..n..": ["..lang.."] "..title - end - end - return msg + local msg = "Available " .. nicetypes[type] .. " Tracks: " + if not tracks_osc or #tracks_osc[type] == 0 then + msg = msg .. "none" + else + for n = 1, #tracks_osc[type] do + local track = tracks_osc[type][n] + local lang, title, selected = "unknown", "", "○" + if not (track.lang == nil) then + lang = track.lang + end + if not (track.title == nil) then + title = track.title + end + if track.id == tonumber(mp.get_property(type)) then + selected = "●" + end + msg = msg .. "\n" .. selected .. " " .. n .. ": [" .. lang .. "] " .. title + end + end + return msg end -- relatively change the track of given <type> by <next> tracks - --(+1 -> next, -1 -> previous) +--(+1 -> next, -1 -> previous) function set_track(type, next) - local current_track_mpv, current_track_osc - if (mp.get_property(type) == "no") then - current_track_osc = 0 - else - current_track_mpv = tonumber(mp.get_property(type)) - current_track_osc = tracks_mpv[type][current_track_mpv].osc_id - end - local new_track_osc = (current_track_osc + next) % (#tracks_osc[type] + 1) - local new_track_mpv - if new_track_osc == 0 then - new_track_mpv = "no" - else - new_track_mpv = tracks_osc[type][new_track_osc].id - end - - mp.commandv("set", type, new_track_mpv) - - if (new_track_osc == 0) then - show_message(nicetypes[type] .. " Track: none") - else - show_message(nicetypes[type] .. " Track: " - .. new_track_osc .. "/" .. #tracks_osc[type] - .. " [".. (tracks_osc[type][new_track_osc].lang or "unknown") .."] " - .. (tracks_osc[type][new_track_osc].title or "")) - end + local current_track_mpv, current_track_osc + if mp.get_property(type) == "no" then + current_track_osc = 0 + else + current_track_mpv = tonumber(mp.get_property(type)) + current_track_osc = tracks_mpv[type][current_track_mpv].osc_id + end + local new_track_osc = (current_track_osc + next) % (#tracks_osc[type] + 1) + local new_track_mpv + if new_track_osc == 0 then + new_track_mpv = "no" + else + new_track_mpv = tracks_osc[type][new_track_osc].id + end + + mp.commandv("set", type, new_track_mpv) + + if new_track_osc == 0 then + show_message(nicetypes[type] .. " Track: none") + else + show_message( + nicetypes[type] + .. " Track: " + .. new_track_osc + .. "/" + .. #tracks_osc[type] + .. " [" + .. (tracks_osc[type][new_track_osc].lang or "unknown") + .. "] " + .. (tracks_osc[type][new_track_osc].title or "") + ) + end end -- get the currently selected track of <type>, OSC-style counted function get_track(type) - local track = mp.get_property(type) - if track ~= "no" and track ~= nil then - local tr = tracks_mpv[type][tonumber(track)] - if tr then - return tr.osc_id - end - end - return 0 + local track = mp.get_property(type) + if track ~= "no" and track ~= nil then + local tr = tracks_mpv[type][tonumber(track)] + if tr then + return tr.osc_id + end + end + return 0 end -- WindowControl helpers function window_controls_enabled() - val = user_opts.windowcontrols - if val == "auto" then - return not state.border - else - return val ~= "no" - end + val = user_opts.windowcontrols + if val == "auto" then + return not state.border + else + return val ~= "no" + end end function window_controls_alignment() - return user_opts.windowcontrols_alignment + return user_opts.windowcontrols_alignment end -- @@ -561,460 +592,508 @@ end local elements = {} function prepare_elements() - - -- remove elements without layout or invisible - local elements2 = {} - for n, element in pairs(elements) do - if not (element.layout == nil) and (element.visible) then - table.insert(elements2, element) - end - end - elements = elements2 - - function elem_compare (a, b) - return a.layout.layer < b.layout.layer - end - - table.sort(elements, elem_compare) - - - for _,element in pairs(elements) do - - local elem_geo = element.layout.geometry - - -- Calculate the hitbox - local bX1, bY1, bX2, bY2 = get_hitbox_coords_geo(elem_geo) - element.hitbox = {x1 = bX1, y1 = bY1, x2 = bX2, y2 = bY2} - - local style_ass = assdraw.ass_new() - - -- prepare static elements - style_ass:append("{}") -- hack to troll new_event into inserting a \n - style_ass:new_event() - style_ass:pos(elem_geo.x, elem_geo.y) - style_ass:an(elem_geo.an) - style_ass:append(element.layout.style) - - element.style_ass = style_ass - - local static_ass = assdraw.ass_new() - - - if (element.type == "box") then - --draw box - static_ass:draw_start() - ass_draw_rr_h_cw(static_ass, 0, 0, elem_geo.w, elem_geo.h, - element.layout.box.radius, element.layout.box.hexagon) - static_ass:draw_stop() - - elseif (element.type == "slider") then - --draw static slider parts - - local r1 = 0 - local r2 = 0 - local slider_lo = element.layout.slider - -- offset between element outline and drag-area - local foV = slider_lo.border + slider_lo.gap - - -- calculate positions of min and max points - if (slider_lo.stype ~= "bar") then - r1 = elem_geo.h / 2 - element.slider.min.ele_pos = elem_geo.h / 2 - element.slider.max.ele_pos = elem_geo.w - (elem_geo.h / 2) - if (slider_lo.stype == "diamond") then - r2 = (elem_geo.h - 2 * slider_lo.border) / 2 - elseif (slider_lo.stype == "knob") then - r2 = r1 - end - else - element.slider.min.ele_pos = - slider_lo.border + slider_lo.gap - element.slider.max.ele_pos = - elem_geo.w - (slider_lo.border + slider_lo.gap) - end - - element.slider.min.glob_pos = - element.hitbox.x1 + element.slider.min.ele_pos - element.slider.max.glob_pos = - element.hitbox.x1 + element.slider.max.ele_pos - - -- -- -- - - static_ass:draw_start() - - -- the box - ass_draw_rr_h_cw(static_ass, 0, 0, elem_geo.w, elem_geo.h, r1, slider_lo.stype == "diamond") - - -- the "hole" - ass_draw_rr_h_ccw(static_ass, slider_lo.border, slider_lo.border, - elem_geo.w - slider_lo.border, elem_geo.h - slider_lo.border, - r2, slider_lo.stype == "diamond") - - -- marker nibbles - if not (element.slider.markerF == nil) and (slider_lo.gap > 0) then - local markers = element.slider.markerF() - for _,marker in pairs(markers) do - if (marker > element.slider.min.value) and - (marker < element.slider.max.value) then - - local s = get_slider_ele_pos_for(element, marker) - - if (slider_lo.gap > 1) then -- draw triangles - - local a = slider_lo.gap / 0.5 --0.866 - - --top - if (slider_lo.nibbles_top) then - static_ass:move_to(s - (a/2), slider_lo.border) - static_ass:line_to(s + (a/2), slider_lo.border) - static_ass:line_to(s, foV) - end - - --bottom - if (slider_lo.nibbles_bottom) then - static_ass:move_to(s - (a/2), - elem_geo.h - slider_lo.border) - static_ass:line_to(s, - elem_geo.h - foV) - static_ass:line_to(s + (a/2), - elem_geo.h - slider_lo.border) - end - - else -- draw 2x1px nibbles - - --top - if (slider_lo.nibbles_top) then - static_ass:rect_cw(s - 1, slider_lo.border, - s + 1, slider_lo.border + slider_lo.gap); - end - - --bottom - if (slider_lo.nibbles_bottom) then - static_ass:rect_cw(s - 1, - elem_geo.h -slider_lo.border -slider_lo.gap, - s + 1, elem_geo.h - slider_lo.border); - end - end - end - end - end - end - - element.static_ass = static_ass - - - -- if the element is supposed to be disabled, - -- style it accordingly and kill the eventresponders - if not (element.enabled) then - element.layout.alpha[1] = 136 - element.eventresponder = nil - end - end + -- remove elements without layout or invisible + local elements2 = {} + for n, element in pairs(elements) do + if not (element.layout == nil) and element.visible then + table.insert(elements2, element) + end + end + elements = elements2 + + function elem_compare(a, b) + return a.layout.layer < b.layout.layer + end + + table.sort(elements, elem_compare) + + for _, element in pairs(elements) do + local elem_geo = element.layout.geometry + + -- Calculate the hitbox + local bX1, bY1, bX2, bY2 = get_hitbox_coords_geo(elem_geo) + element.hitbox = { x1 = bX1, y1 = bY1, x2 = bX2, y2 = bY2 } + + local style_ass = assdraw.ass_new() + + -- prepare static elements + style_ass:append("{}") -- hack to troll new_event into inserting a \n + style_ass:new_event() + style_ass:pos(elem_geo.x, elem_geo.y) + style_ass:an(elem_geo.an) + style_ass:append(element.layout.style) + + element.style_ass = style_ass + + local static_ass = assdraw.ass_new() + + if element.type == "box" then + --draw box + static_ass:draw_start() + ass_draw_rr_h_cw( + static_ass, + 0, + 0, + elem_geo.w, + elem_geo.h, + element.layout.box.radius, + element.layout.box.hexagon + ) + static_ass:draw_stop() + elseif element.type == "slider" then + --draw static slider parts + + local r1 = 0 + local r2 = 0 + local slider_lo = element.layout.slider + -- offset between element outline and drag-area + local foV = slider_lo.border + slider_lo.gap + + -- calculate positions of min and max points + if slider_lo.stype ~= "bar" then + r1 = elem_geo.h / 2 + element.slider.min.ele_pos = elem_geo.h / 2 + element.slider.max.ele_pos = elem_geo.w - (elem_geo.h / 2) + if slider_lo.stype == "diamond" then + r2 = (elem_geo.h - 2 * slider_lo.border) / 2 + elseif slider_lo.stype == "knob" then + r2 = r1 + end + else + element.slider.min.ele_pos = slider_lo.border + slider_lo.gap + element.slider.max.ele_pos = elem_geo.w - (slider_lo.border + slider_lo.gap) + end + + element.slider.min.glob_pos = element.hitbox.x1 + element.slider.min.ele_pos + element.slider.max.glob_pos = element.hitbox.x1 + element.slider.max.ele_pos + + -- -- -- + + static_ass:draw_start() + + -- the box + ass_draw_rr_h_cw(static_ass, 0, 0, elem_geo.w, elem_geo.h, r1, slider_lo.stype == "diamond") + + -- the "hole" + ass_draw_rr_h_ccw( + static_ass, + slider_lo.border, + slider_lo.border, + elem_geo.w - slider_lo.border, + elem_geo.h - slider_lo.border, + r2, + slider_lo.stype == "diamond" + ) + + -- marker nibbles + if not (element.slider.markerF == nil) and (slider_lo.gap > 0) then + local markers = element.slider.markerF() + for _, marker in pairs(markers) do + if (marker > element.slider.min.value) and (marker < element.slider.max.value) then + local s = get_slider_ele_pos_for(element, marker) + + if slider_lo.gap > 1 then -- draw triangles + local a = slider_lo.gap / 0.5 --0.866 + + --top + if slider_lo.nibbles_top then + static_ass:move_to(s - (a / 2), slider_lo.border) + static_ass:line_to(s + (a / 2), slider_lo.border) + static_ass:line_to(s, foV) + end + + --bottom + if slider_lo.nibbles_bottom then + static_ass:move_to(s - (a / 2), elem_geo.h - slider_lo.border) + static_ass:line_to(s, elem_geo.h - foV) + static_ass:line_to(s + (a / 2), elem_geo.h - slider_lo.border) + end + else -- draw 2x1px nibbles + --top + if slider_lo.nibbles_top then + static_ass:rect_cw(s - 1, slider_lo.border, s + 1, slider_lo.border + slider_lo.gap) + end + + --bottom + if slider_lo.nibbles_bottom then + static_ass:rect_cw( + s - 1, + elem_geo.h - slider_lo.border - slider_lo.gap, + s + 1, + elem_geo.h - slider_lo.border + ) + end + end + end + end + end + end + + element.static_ass = static_ass + + -- if the element is supposed to be disabled, + -- style it accordingly and kill the eventresponders + if not element.enabled then + element.layout.alpha[1] = 136 + element.eventresponder = nil + end + end end - -- -- Element Rendering -- -- returns nil or a chapter element from the native property chapter-list function get_chapter(possec) - local cl = state.chapter_list -- sorted, get latest before possec, if any + local cl = state.chapter_list -- sorted, get latest before possec, if any - for n=#cl,1,-1 do - if possec >= cl[n].time then - return cl[n] - end - end + for n = #cl, 1, -1 do + if possec >= cl[n].time then + return cl[n] + end + end end function render_elements(master_ass) - - -- when the slider is dragged or hovered and we have a target chapter name - -- then we use it instead of the normal title. we calculate it before the - -- render iterations because the title may be rendered before the slider. - state.forced_title = nil - local se, ae = state.slider_element, elements[state.active_element] - if user_opts.chapter_fmt ~= "no" and se and (ae == se or (not ae and mouse_hit(se))) then - local dur = mp.get_property_number("duration", 0) - if dur > 0 then - local possec = get_slider_value(se) * dur / 100 -- of mouse pos - local ch = get_chapter(possec) - if ch and ch.title and ch.title ~= "" then - state.forced_title = string.format(user_opts.chapter_fmt, ch.title) - end - end - end - - for n=1, #elements do - local element = elements[n] - - local style_ass = assdraw.ass_new() - style_ass:merge(element.style_ass) - ass_append_alpha(style_ass, element.layout.alpha, 0) - - if element.eventresponder and (state.active_element == n) then - - -- run render event functions - if not (element.eventresponder.render == nil) then - element.eventresponder.render(element) - end - - if mouse_hit(element) then - -- mouse down styling - if (element.styledown) then - style_ass:append(osc_styles.elementDown) - end - - if (element.softrepeat) and (state.mouse_down_counter >= 15 - and state.mouse_down_counter % 5 == 0) then - - element.eventresponder[state.active_event_source.."_down"](element) - end - state.mouse_down_counter = state.mouse_down_counter + 1 - end - - end - - local elem_ass = assdraw.ass_new() - - elem_ass:merge(style_ass) - - if not (element.type == "button") then - elem_ass:merge(element.static_ass) - end - - if (element.type == "slider") then - - local slider_lo = element.layout.slider - local elem_geo = element.layout.geometry - local s_min = element.slider.min.value - local s_max = element.slider.max.value - - -- draw pos marker - local foH, xp - local pos = element.slider.posF() - local foV = slider_lo.border + slider_lo.gap - local innerH = elem_geo.h - (2 * foV) - local seekRanges = element.slider.seekRangesF() - local seekRangeLineHeight = innerH / 5 - - if slider_lo.stype ~= "bar" then - foH = elem_geo.h / 2 - else - foH = slider_lo.border + slider_lo.gap - end - - if pos then - xp = get_slider_ele_pos_for(element, pos) - - if slider_lo.stype ~= "bar" then - local r = (user_opts.seekbarhandlesize * innerH) / 2 - ass_draw_rr_h_cw(elem_ass, xp - r, foH - r, - xp + r, foH + r, - r, slider_lo.stype == "diamond") - else - local h = 0 - if seekRanges and user_opts.seekrangeseparate and slider_lo.rtype ~= "inverted" then - h = seekRangeLineHeight - end - elem_ass:rect_cw(foH, foV, xp, elem_geo.h - foV - h) - - if seekRanges and not user_opts.seekrangeseparate and slider_lo.rtype ~= "inverted" then - -- Punch holes for the seekRanges to be drawn later - for _,range in pairs(seekRanges) do - if range["start"] < pos then - local pstart = get_slider_ele_pos_for(element, range["start"]) - local pend = xp - - if pos > range["end"] then - pend = get_slider_ele_pos_for(element, range["end"]) - end - elem_ass:rect_ccw(pstart, elem_geo.h - foV - seekRangeLineHeight, pend, elem_geo.h - foV) - end - end - end - end - - if slider_lo.rtype == "slider" then - ass_draw_rr_h_cw(elem_ass, foH - innerH / 6, foH - innerH / 6, - xp, foH + innerH / 6, - innerH / 6, slider_lo.stype == "diamond", 0) - ass_draw_rr_h_cw(elem_ass, xp, foH - innerH / 15, - elem_geo.w - foH + innerH / 15, foH + innerH / 15, - 0, slider_lo.stype == "diamond", innerH / 15) - for _,range in pairs(seekRanges or {}) do - local pstart = get_slider_ele_pos_for(element, range["start"]) - local pend = get_slider_ele_pos_for(element, range["end"]) - ass_draw_rr_h_ccw(elem_ass, pstart, foH - innerH / 21, - pend, foH + innerH / 21, - innerH / 21, slider_lo.stype == "diamond") - end - end - end - - if seekRanges then - if slider_lo.rtype ~= "inverted" then - elem_ass:draw_stop() - elem_ass:merge(element.style_ass) - ass_append_alpha(elem_ass, element.layout.alpha, user_opts.seekrangealpha) - elem_ass:merge(element.static_ass) - end - - for _,range in pairs(seekRanges) do - local pstart = get_slider_ele_pos_for(element, range["start"]) - local pend = get_slider_ele_pos_for(element, range["end"]) - - if slider_lo.rtype == "slider" then - ass_draw_rr_h_cw(elem_ass, pstart, foH - innerH / 21, - pend, foH + innerH / 21, - innerH / 21, slider_lo.stype == "diamond") - elseif slider_lo.rtype == "line" then - if slider_lo.stype == "bar" then - elem_ass:rect_cw(pstart, elem_geo.h - foV - seekRangeLineHeight, pend, elem_geo.h - foV) - else - ass_draw_rr_h_cw(elem_ass, pstart - innerH / 8, foH - innerH / 8, - pend + innerH / 8, foH + innerH / 8, - innerH / 8, slider_lo.stype == "diamond") - end - elseif slider_lo.rtype == "bar" then - if slider_lo.stype ~= "bar" then - ass_draw_rr_h_cw(elem_ass, pstart - innerH / 2, foV, - pend + innerH / 2, foV + innerH, - innerH / 2, slider_lo.stype == "diamond") - elseif range["end"] >= (pos or 0) then - elem_ass:rect_cw(pstart, foV, pend, elem_geo.h - foV) - else - elem_ass:rect_cw(pstart, elem_geo.h - foV - seekRangeLineHeight, pend, elem_geo.h - foV) - end - elseif slider_lo.rtype == "inverted" then - if slider_lo.stype ~= "bar" then - ass_draw_rr_h_ccw(elem_ass, pstart, (elem_geo.h / 2) - 1, pend, - (elem_geo.h / 2) + 1, - 1, slider_lo.stype == "diamond") - else - elem_ass:rect_ccw(pstart, (elem_geo.h / 2) - 1, pend, (elem_geo.h / 2) + 1) - end - end - end - end - - elem_ass:draw_stop() - - -- add tooltip - if not (element.slider.tooltipF == nil) then - - if mouse_hit(element) then - local sliderpos = get_slider_value(element) - local tooltiplabel = element.slider.tooltipF(sliderpos) - - local an = slider_lo.tooltip_an - - local ty - - if (an == 2) then - ty = element.hitbox.y1 - slider_lo.border - else - ty = element.hitbox.y1 + elem_geo.h/2 - end - - local tx = get_virt_mouse_pos() - local thumb_tx = tx - if (slider_lo.adjust_tooltip) then - if (an == 2) then - if (sliderpos < (s_min + 3)) then - an = an - 1 - elseif (sliderpos > (s_max - 3)) then - an = an + 1 - end - elseif (sliderpos > (s_max+s_min)/2) then - an = an + 1 - tx = tx - 5 - else - an = an - 1 - tx = tx + 10 - end - end - - -- tooltip label - elem_ass:new_event() - elem_ass:pos(tx, ty) - elem_ass:an(an) - elem_ass:append(slider_lo.tooltip_style) - ass_append_alpha(elem_ass, slider_lo.alpha, 0) - elem_ass:append(tooltiplabel) - - -- thumbnail - if not thumbfast.disabled and thumbfast.width ~= 0 and thumbfast.height ~= 0 then - local osd_w = mp.get_property_number("osd-width") - if osd_w then - local r_w, r_h = get_virt_scale_factor() - - local tooltip_font_size = (user_opts.layout == "box" or user_opts.layout == "slimbox") and 2 or 12 - local thumb_ty = user_opts.layout ~= "topbar" and element.hitbox.y1 - 8 or element.hitbox.y2 + tooltip_font_size + 8 - - local thumb_pad = 2 - local thumb_margin_x = 20 / r_w - local thumb_margin_y = (4 + user_opts.tooltipborder) / r_h + thumb_pad - local thumb_x = math.min(osd_w - thumbfast.width - thumb_margin_x, math.max(thumb_margin_x, thumb_tx / r_w - thumbfast.width / 2)) - local thumb_y = user_opts.layout ~= "topbar" and thumb_ty / r_h - thumbfast.height - tooltip_font_size / r_h - thumb_margin_y or thumb_ty / r_h + thumb_margin_y - - thumb_x = math.floor(thumb_x + 0.5) - thumb_y = math.floor(thumb_y + 0.5) - - elem_ass:new_event() - elem_ass:pos(thumb_x * r_w, thumb_y * r_h) - elem_ass:an(7) - elem_ass:append(osc_styles.timePosBar) - elem_ass:append("{\\1a&H20&}") - elem_ass:draw_start() - elem_ass:rect_cw(-thumb_pad * r_w, -thumb_pad * r_h, (thumbfast.width + thumb_pad) * r_w, (thumbfast.height + thumb_pad) * r_h) - elem_ass:draw_stop() - - mp.commandv("script-message-to", "thumbfast", "thumb", - mp.get_property_number("duration", 0) * (sliderpos / 100), - thumb_x, - thumb_y - ) - end - end - else - if thumbfast.width ~= 0 and thumbfast.height ~= 0 then - mp.commandv("script-message-to", "thumbfast", "clear") - end - end - end - - elseif (element.type == "button") then - - local buttontext - if type(element.content) == "function" then - buttontext = element.content() -- function objects - elseif not (element.content == nil) then - buttontext = element.content -- text objects - end - - local maxchars = element.layout.button.maxchars - if not (maxchars == nil) and (#buttontext > maxchars) then - local max_ratio = 1.25 -- up to 25% more chars while shrinking - local limit = math.max(0, math.floor(maxchars * max_ratio) - 3) - if (#buttontext > limit) then - while (#buttontext > limit) do - buttontext = buttontext:gsub(".[\128-\191]*$", "") - end - buttontext = buttontext .. "..." - end - local _, nchars2 = buttontext:gsub(".[\128-\191]*", "") - local stretch = (maxchars/#buttontext)*100 - buttontext = string.format("{\\fscx%f}", - (maxchars/#buttontext)*100) .. buttontext - end - - elem_ass:append(buttontext) - end - - master_ass:merge(elem_ass) - end + -- when the slider is dragged or hovered and we have a target chapter name + -- then we use it instead of the normal title. we calculate it before the + -- render iterations because the title may be rendered before the slider. + state.forced_title = nil + local se, ae = state.slider_element, elements[state.active_element] + if user_opts.chapter_fmt ~= "no" and se and (ae == se or (not ae and mouse_hit(se))) then + local dur = mp.get_property_number("duration", 0) + if dur > 0 then + local possec = get_slider_value(se) * dur / 100 -- of mouse pos + local ch = get_chapter(possec) + if ch and ch.title and ch.title ~= "" then + state.forced_title = string.format(user_opts.chapter_fmt, ch.title) + end + end + end + + for n = 1, #elements do + local element = elements[n] + + local style_ass = assdraw.ass_new() + style_ass:merge(element.style_ass) + ass_append_alpha(style_ass, element.layout.alpha, 0) + + if element.eventresponder and (state.active_element == n) then + -- run render event functions + if not (element.eventresponder.render == nil) then + element.eventresponder.render(element) + end + + if mouse_hit(element) then + -- mouse down styling + if element.styledown then + style_ass:append(osc_styles.elementDown) + end + + if element.softrepeat and (state.mouse_down_counter >= 15 and state.mouse_down_counter % 5 == 0) then + element.eventresponder[state.active_event_source .. "_down"](element) + end + state.mouse_down_counter = state.mouse_down_counter + 1 + end + end + + local elem_ass = assdraw.ass_new() + + elem_ass:merge(style_ass) + + if not (element.type == "button") then + elem_ass:merge(element.static_ass) + end + + if element.type == "slider" then + local slider_lo = element.layout.slider + local elem_geo = element.layout.geometry + local s_min = element.slider.min.value + local s_max = element.slider.max.value + + -- draw pos marker + local foH, xp + local pos = element.slider.posF() + local foV = slider_lo.border + slider_lo.gap + local innerH = elem_geo.h - (2 * foV) + local seekRanges = element.slider.seekRangesF() + local seekRangeLineHeight = innerH / 5 + + if slider_lo.stype ~= "bar" then + foH = elem_geo.h / 2 + else + foH = slider_lo.border + slider_lo.gap + end + + if pos then + xp = get_slider_ele_pos_for(element, pos) + + if slider_lo.stype ~= "bar" then + local r = (user_opts.seekbarhandlesize * innerH) / 2 + ass_draw_rr_h_cw(elem_ass, xp - r, foH - r, xp + r, foH + r, r, slider_lo.stype == "diamond") + else + local h = 0 + if seekRanges and user_opts.seekrangeseparate and slider_lo.rtype ~= "inverted" then + h = seekRangeLineHeight + end + elem_ass:rect_cw(foH, foV, xp, elem_geo.h - foV - h) + + if seekRanges and not user_opts.seekrangeseparate and slider_lo.rtype ~= "inverted" then + -- Punch holes for the seekRanges to be drawn later + for _, range in pairs(seekRanges) do + if range["start"] < pos then + local pstart = get_slider_ele_pos_for(element, range["start"]) + local pend = xp + + if pos > range["end"] then + pend = get_slider_ele_pos_for(element, range["end"]) + end + elem_ass:rect_ccw( + pstart, + elem_geo.h - foV - seekRangeLineHeight, + pend, + elem_geo.h - foV + ) + end + end + end + end + + if slider_lo.rtype == "slider" then + ass_draw_rr_h_cw( + elem_ass, + foH - innerH / 6, + foH - innerH / 6, + xp, + foH + innerH / 6, + innerH / 6, + slider_lo.stype == "diamond", + 0 + ) + ass_draw_rr_h_cw( + elem_ass, + xp, + foH - innerH / 15, + elem_geo.w - foH + innerH / 15, + foH + innerH / 15, + 0, + slider_lo.stype == "diamond", + innerH / 15 + ) + for _, range in pairs(seekRanges or {}) do + local pstart = get_slider_ele_pos_for(element, range["start"]) + local pend = get_slider_ele_pos_for(element, range["end"]) + ass_draw_rr_h_ccw( + elem_ass, + pstart, + foH - innerH / 21, + pend, + foH + innerH / 21, + innerH / 21, + slider_lo.stype == "diamond" + ) + end + end + end + + if seekRanges then + if slider_lo.rtype ~= "inverted" then + elem_ass:draw_stop() + elem_ass:merge(element.style_ass) + ass_append_alpha(elem_ass, element.layout.alpha, user_opts.seekrangealpha) + elem_ass:merge(element.static_ass) + end + + for _, range in pairs(seekRanges) do + local pstart = get_slider_ele_pos_for(element, range["start"]) + local pend = get_slider_ele_pos_for(element, range["end"]) + + if slider_lo.rtype == "slider" then + ass_draw_rr_h_cw( + elem_ass, + pstart, + foH - innerH / 21, + pend, + foH + innerH / 21, + innerH / 21, + slider_lo.stype == "diamond" + ) + elseif slider_lo.rtype == "line" then + if slider_lo.stype == "bar" then + elem_ass:rect_cw(pstart, elem_geo.h - foV - seekRangeLineHeight, pend, elem_geo.h - foV) + else + ass_draw_rr_h_cw( + elem_ass, + pstart - innerH / 8, + foH - innerH / 8, + pend + innerH / 8, + foH + innerH / 8, + innerH / 8, + slider_lo.stype == "diamond" + ) + end + elseif slider_lo.rtype == "bar" then + if slider_lo.stype ~= "bar" then + ass_draw_rr_h_cw( + elem_ass, + pstart - innerH / 2, + foV, + pend + innerH / 2, + foV + innerH, + innerH / 2, + slider_lo.stype == "diamond" + ) + elseif range["end"] >= (pos or 0) then + elem_ass:rect_cw(pstart, foV, pend, elem_geo.h - foV) + else + elem_ass:rect_cw(pstart, elem_geo.h - foV - seekRangeLineHeight, pend, elem_geo.h - foV) + end + elseif slider_lo.rtype == "inverted" then + if slider_lo.stype ~= "bar" then + ass_draw_rr_h_ccw( + elem_ass, + pstart, + (elem_geo.h / 2) - 1, + pend, + (elem_geo.h / 2) + 1, + 1, + slider_lo.stype == "diamond" + ) + else + elem_ass:rect_ccw(pstart, (elem_geo.h / 2) - 1, pend, (elem_geo.h / 2) + 1) + end + end + end + end + + elem_ass:draw_stop() + + -- add tooltip + if not (element.slider.tooltipF == nil) then + if mouse_hit(element) then + local sliderpos = get_slider_value(element) + local tooltiplabel = element.slider.tooltipF(sliderpos) + + local an = slider_lo.tooltip_an + + local ty + + if an == 2 then + ty = element.hitbox.y1 - slider_lo.border + else + ty = element.hitbox.y1 + elem_geo.h / 2 + end + + local tx = get_virt_mouse_pos() + local thumb_tx = tx + if slider_lo.adjust_tooltip then + if an == 2 then + if sliderpos < (s_min + 3) then + an = an - 1 + elseif sliderpos > (s_max - 3) then + an = an + 1 + end + elseif sliderpos > (s_max + s_min) / 2 then + an = an + 1 + tx = tx - 5 + else + an = an - 1 + tx = tx + 10 + end + end + + -- tooltip label + elem_ass:new_event() + elem_ass:pos(tx, ty) + elem_ass:an(an) + elem_ass:append(slider_lo.tooltip_style) + ass_append_alpha(elem_ass, slider_lo.alpha, 0) + elem_ass:append(tooltiplabel) + + -- thumbnail + if not thumbfast.disabled and thumbfast.width ~= 0 and thumbfast.height ~= 0 then + local osd_w = mp.get_property_number("osd-width") + if osd_w then + local r_w, r_h = get_virt_scale_factor() + + local tooltip_font_size = (user_opts.layout == "box" or user_opts.layout == "slimbox") and 2 + or 12 + local thumb_ty = user_opts.layout ~= "topbar" and element.hitbox.y1 - 8 + or element.hitbox.y2 + tooltip_font_size + 8 + + local thumb_pad = 2 + local thumb_margin_x = 20 / r_w + local thumb_margin_y = (4 + user_opts.tooltipborder) / r_h + thumb_pad + local thumb_x = math.min( + osd_w - thumbfast.width - thumb_margin_x, + math.max(thumb_margin_x, thumb_tx / r_w - thumbfast.width / 2) + ) + local thumb_y = user_opts.layout ~= "topbar" + and thumb_ty / r_h - thumbfast.height - tooltip_font_size / r_h - thumb_margin_y + or thumb_ty / r_h + thumb_margin_y + + thumb_x = math.floor(thumb_x + 0.5) + thumb_y = math.floor(thumb_y + 0.5) + + elem_ass:new_event() + elem_ass:pos(thumb_x * r_w, thumb_y * r_h) + elem_ass:an(7) + elem_ass:append(osc_styles.timePosBar) + elem_ass:append("{\\1a&H20&}") + elem_ass:draw_start() + elem_ass:rect_cw( + -thumb_pad * r_w, + -thumb_pad * r_h, + (thumbfast.width + thumb_pad) * r_w, + (thumbfast.height + thumb_pad) * r_h + ) + elem_ass:draw_stop() + + mp.commandv( + "script-message-to", + "thumbfast", + "thumb", + mp.get_property_number("duration", 0) * (sliderpos / 100), + thumb_x, + thumb_y + ) + end + end + else + if thumbfast.width ~= 0 and thumbfast.height ~= 0 then + mp.commandv("script-message-to", "thumbfast", "clear") + end + end + end + elseif element.type == "button" then + local buttontext + if type(element.content) == "function" then + buttontext = element.content() -- function objects + elseif not (element.content == nil) then + buttontext = element.content -- text objects + end + + local maxchars = element.layout.button.maxchars + if not (maxchars == nil) and (#buttontext > maxchars) then + local max_ratio = 1.25 -- up to 25% more chars while shrinking + local limit = math.max(0, math.floor(maxchars * max_ratio) - 3) + if #buttontext > limit then + while #buttontext > limit do + buttontext = buttontext:gsub(".[\128-\191]*$", "") + end + buttontext = buttontext .. "..." + end + local _, nchars2 = buttontext:gsub(".[\128-\191]*", "") + local stretch = (maxchars / #buttontext) * 100 + buttontext = string.format("{\\fscx%f}", (maxchars / #buttontext) * 100) .. buttontext + end + + elem_ass:append(buttontext) + end + + master_ass:merge(elem_ass) + end end -- @@ -1023,119 +1102,113 @@ end -- pos is 1 based function limited_list(prop, pos) - local proplist = mp.get_property_native(prop, {}) - local count = #proplist - if count == 0 then - return count, proplist - end - - local fs = tonumber(mp.get_property('options/osd-font-size')) - local max = math.ceil(osc_param.unscaled_y*0.75 / fs) - if max % 2 == 0 then - max = max - 1 - end - local delta = math.ceil(max / 2) - 1 - local begi = math.max(math.min(pos - delta, count - max + 1), 1) - local endi = math.min(begi + max - 1, count) - - local reslist = {} - for i=begi, endi do - local item = proplist[i] - item.current = (i == pos) and true or nil - table.insert(reslist, item) - end - return count, reslist + local proplist = mp.get_property_native(prop, {}) + local count = #proplist + if count == 0 then + return count, proplist + end + + local fs = tonumber(mp.get_property("options/osd-font-size")) + local max = math.ceil(osc_param.unscaled_y * 0.75 / fs) + if max % 2 == 0 then + max = max - 1 + end + local delta = math.ceil(max / 2) - 1 + local begi = math.max(math.min(pos - delta, count - max + 1), 1) + local endi = math.min(begi + max - 1, count) + + local reslist = {} + for i = begi, endi do + local item = proplist[i] + item.current = (i == pos) and true or nil + table.insert(reslist, item) + end + return count, reslist end function get_playlist() - local pos = mp.get_property_number('playlist-pos', 0) + 1 - local count, limlist = limited_list('playlist', pos) - if count == 0 then - return 'Empty playlist.' - end - - local message = string.format('Playlist [%d/%d]:\n', pos, count) - for i, v in ipairs(limlist) do - local title = v.title - local _, filename = utils.split_path(v.filename) - if title == nil then - title = filename - end - message = string.format('%s %s %s\n', message, - (v.current and '●' or '○'), title) - end - return message + local pos = mp.get_property_number("playlist-pos", 0) + 1 + local count, limlist = limited_list("playlist", pos) + if count == 0 then + return "Empty playlist." + end + + local message = string.format("Playlist [%d/%d]:\n", pos, count) + for i, v in ipairs(limlist) do + local title = v.title + local _, filename = utils.split_path(v.filename) + if title == nil then + title = filename + end + message = string.format("%s %s %s\n", message, (v.current and "●" or "○"), title) + end + return message end function get_chapterlist() - local pos = mp.get_property_number('chapter', 0) + 1 - local count, limlist = limited_list('chapter-list', pos) - if count == 0 then - return 'No chapters.' - end - - local message = string.format('Chapters [%d/%d]:\n', pos, count) - for i, v in ipairs(limlist) do - local time = mp.format_time(v.time) - local title = v.title - if title == nil then - title = string.format('Chapter %02d', i) - end - message = string.format('%s[%s] %s %s\n', message, time, - (v.current and '●' or '○'), title) - end - return message + local pos = mp.get_property_number("chapter", 0) + 1 + local count, limlist = limited_list("chapter-list", pos) + if count == 0 then + return "No chapters." + end + + local message = string.format("Chapters [%d/%d]:\n", pos, count) + for i, v in ipairs(limlist) do + local time = mp.format_time(v.time) + local title = v.title + if title == nil then + title = string.format("Chapter %02d", i) + end + message = string.format("%s[%s] %s %s\n", message, time, (v.current and "●" or "○"), title) + end + return message end function show_message(text, duration) + --print("text: "..text.." duration: " .. duration) + if duration == nil then + duration = tonumber(mp.get_property("options/osd-duration")) / 1000 + elseif not type(duration) == "number" then + print("duration: " .. duration) + end - --print("text: "..text.." duration: " .. duration) - if duration == nil then - duration = tonumber(mp.get_property("options/osd-duration")) / 1000 - elseif not type(duration) == "number" then - print("duration: " .. duration) - end - - -- cut the text short, otherwise the following functions - -- may slow down massively on huge input - text = string.sub(text, 0, 4000) + -- cut the text short, otherwise the following functions + -- may slow down massively on huge input + text = string.sub(text, 0, 4000) - -- replace actual linebreaks with ASS linebreaks - text = string.gsub(text, "\n", "\\N") + -- replace actual linebreaks with ASS linebreaks + text = string.gsub(text, "\n", "\\N") - state.message_text = text + state.message_text = text - if not state.message_hide_timer then - state.message_hide_timer = mp.add_timeout(0, request_tick) - end - state.message_hide_timer:kill() - state.message_hide_timer.timeout = duration - state.message_hide_timer:resume() - request_tick() + if not state.message_hide_timer then + state.message_hide_timer = mp.add_timeout(0, request_tick) + end + state.message_hide_timer:kill() + state.message_hide_timer.timeout = duration + state.message_hide_timer:resume() + request_tick() end function render_message(ass) - if state.message_hide_timer and state.message_hide_timer:is_enabled() and - state.message_text - then - local _, lines = string.gsub(state.message_text, "\\N", "") + if state.message_hide_timer and state.message_hide_timer:is_enabled() and state.message_text then + local _, lines = string.gsub(state.message_text, "\\N", "") - local fontsize = tonumber(mp.get_property("options/osd-font-size")) - local outline = tonumber(mp.get_property("options/osd-border-size")) - local maxlines = math.ceil(osc_param.unscaled_y*0.75 / fontsize) - local counterscale = osc_param.playresy / osc_param.unscaled_y + local fontsize = tonumber(mp.get_property("options/osd-font-size")) + local outline = tonumber(mp.get_property("options/osd-border-size")) + local maxlines = math.ceil(osc_param.unscaled_y * 0.75 / fontsize) + local counterscale = osc_param.playresy / osc_param.unscaled_y - fontsize = fontsize * counterscale / math.max(0.65 + math.min(lines/maxlines, 1), 1) - outline = outline * counterscale / math.max(0.75 + math.min(lines/maxlines, 1)/2, 1) + fontsize = fontsize * counterscale / math.max(0.65 + math.min(lines / maxlines, 1), 1) + outline = outline * counterscale / math.max(0.75 + math.min(lines / maxlines, 1) / 2, 1) - local style = "{\\bord" .. outline .. "\\fs" .. fontsize .. "}" + local style = "{\\bord" .. outline .. "\\fs" .. fontsize .. "}" - - ass:new_event() - ass:append(style .. state.message_text) - else - state.message_text = nil - end + ass:new_event() + ass:append(style .. state.message_text) + else + state.message_text = nil + end end -- @@ -1143,187 +1216,182 @@ end -- function new_element(name, type) - elements[name] = {} - elements[name].type = type - - -- add default stuff - elements[name].eventresponder = {} - elements[name].visible = true - elements[name].enabled = true - elements[name].softrepeat = false - elements[name].styledown = (type == "button") - elements[name].state = {} + elements[name] = {} + elements[name].type = type - if (type == "slider") then - elements[name].slider = {min = {value = 0}, max = {value = 100}} - end + -- add default stuff + elements[name].eventresponder = {} + elements[name].visible = true + elements[name].enabled = true + elements[name].softrepeat = false + elements[name].styledown = (type == "button") + elements[name].state = {} + if type == "slider" then + elements[name].slider = { min = { value = 0 }, max = { value = 100 } } + end - return elements[name] + return elements[name] end function add_layout(name) - if not (elements[name] == nil) then - -- new layout - elements[name].layout = {} - - -- set layout defaults - elements[name].layout.layer = 50 - elements[name].layout.alpha = {[1] = 0, [2] = 255, [3] = 255, [4] = 255} - - if (elements[name].type == "button") then - elements[name].layout.button = { - maxchars = nil, - } - elseif (elements[name].type == "slider") then - -- slider defaults - elements[name].layout.slider = { - border = 1, - gap = 1, - nibbles_top = true, - nibbles_bottom = true, - stype = "slider", - adjust_tooltip = true, - tooltip_style = "", - tooltip_an = 2, - alpha = {[1] = 0, [2] = 255, [3] = 88, [4] = 255}, - } - elseif (elements[name].type == "box") then - elements[name].layout.box = {radius = 0, hexagon = false} - end - - return elements[name].layout - else - msg.error("Can't add_layout to element \""..name.."\", doesn't exist.") - end + if not (elements[name] == nil) then + -- new layout + elements[name].layout = {} + + -- set layout defaults + elements[name].layout.layer = 50 + elements[name].layout.alpha = { [1] = 0, [2] = 255, [3] = 255, [4] = 255 } + + if elements[name].type == "button" then + elements[name].layout.button = { + maxchars = nil, + } + elseif elements[name].type == "slider" then + -- slider defaults + elements[name].layout.slider = { + border = 1, + gap = 1, + nibbles_top = true, + nibbles_bottom = true, + stype = "slider", + adjust_tooltip = true, + tooltip_style = "", + tooltip_an = 2, + alpha = { [1] = 0, [2] = 255, [3] = 88, [4] = 255 }, + } + elseif elements[name].type == "box" then + elements[name].layout.box = { radius = 0, hexagon = false } + end + + return elements[name].layout + else + msg.error("Can't add_layout to element \"" .. name .. "\", doesn't exist.") + end end -- Window Controls function window_controls(topbar) - local wc_geo = { - x = 0, - y = 30 + user_opts.barmargin, - an = 1, - w = osc_param.playresx, - h = 30, - } - - local alignment = window_controls_alignment() - local controlbox_w = window_control_box_width - local titlebox_w = wc_geo.w - controlbox_w - - -- Default alignment is "right" - local controlbox_left = wc_geo.w - controlbox_w - local titlebox_left = wc_geo.x - local titlebox_right = wc_geo.w - controlbox_w - - if alignment == "left" then - controlbox_left = wc_geo.x - titlebox_left = wc_geo.x + controlbox_w - titlebox_right = wc_geo.w - end - - add_area("window-controls", - get_hitbox_coords(controlbox_left, wc_geo.y, wc_geo.an, - controlbox_w, wc_geo.h)) - - local lo - - -- Background Bar - new_element("wcbar", "box") - lo = add_layout("wcbar") - lo.geometry = wc_geo - lo.layer = 10 - lo.style = osc_styles.wcBar - lo.alpha[1] = user_opts.boxalpha - - local button_y = wc_geo.y - (wc_geo.h / 2) - local first_geo = - {x = controlbox_left + 5, y = button_y, an = 4, w = 25, h = 25} - local second_geo = - {x = controlbox_left + 30, y = button_y, an = 4, w = 25, h = 25} - local third_geo = - {x = controlbox_left + 55, y = button_y, an = 4, w = 25, h = 25} - - -- Window control buttons use symbols in the custom mpv osd font - -- because the official unicode codepoints are sufficiently - -- exotic that a system might lack an installed font with them, - -- and libass will complain that they are not present in the - -- default font, even if another font with them is available. - - -- Close: 🗙 - ne = new_element("close", "button") - ne.content = "\238\132\149" - ne.eventresponder["mbtn_left_up"] = - function () mp.commandv("quit") end - lo = add_layout("close") - lo.geometry = alignment == "left" and first_geo or third_geo - lo.style = osc_styles.wcButtons - - -- Minimize: 🗕 - ne = new_element("minimize", "button") - ne.content = "\238\132\146" - ne.eventresponder["mbtn_left_up"] = - function () mp.commandv("cycle", "window-minimized") end - lo = add_layout("minimize") - lo.geometry = alignment == "left" and second_geo or first_geo - lo.style = osc_styles.wcButtons - - -- Maximize: 🗖 /🗗 - ne = new_element("maximize", "button") - if state.maximized or state.fullscreen then - ne.content = "\238\132\148" - else - ne.content = "\238\132\147" - end - ne.eventresponder["mbtn_left_up"] = - function () - if state.fullscreen then - mp.commandv("cycle", "fullscreen") - else - mp.commandv("cycle", "window-maximized") - end - end - lo = add_layout("maximize") - lo.geometry = alignment == "left" and third_geo or second_geo - lo.style = osc_styles.wcButtons - - -- deadzone below window controls - local sh_area_y0, sh_area_y1 - sh_area_y0 = user_opts.barmargin - sh_area_y1 = wc_geo.y + get_align(1 - (2 * user_opts.deadzonesize), - osc_param.playresy - wc_geo.y, 0, 0) - add_area("showhide_wc", wc_geo.x, sh_area_y0, wc_geo.w, sh_area_y1) - - if topbar then - -- The title is already there as part of the top bar - return - else - -- Apply boxvideo margins to the control bar - osc_param.video_margins.t = wc_geo.h / osc_param.playresy - end - - -- Window Title - ne = new_element("wctitle", "button") - ne.content = function () - local title = mp.command_native({"expand-text", user_opts.title}) - -- escape ASS, and strip newlines and trailing slashes - title = title:gsub("\\n", " "):gsub("\\$", ""):gsub("{","\\{") - return not (title == "") and title or "mpv" - end - local left_pad = 5 - local right_pad = 10 - lo = add_layout("wctitle") - lo.geometry = - { x = titlebox_left + left_pad, y = wc_geo.y - 3, an = 1, - w = titlebox_w, h = wc_geo.h } - lo.style = string.format("%s{\\clip(%f,%f,%f,%f)}", - osc_styles.wcTitle, - titlebox_left + left_pad, wc_geo.y - wc_geo.h, - titlebox_right - right_pad , wc_geo.y + wc_geo.h) - - add_area("window-controls-title", - titlebox_left, 0, titlebox_right, wc_geo.h) + local wc_geo = { + x = 0, + y = 30 + user_opts.barmargin, + an = 1, + w = osc_param.playresx, + h = 30, + } + + local alignment = window_controls_alignment() + local controlbox_w = window_control_box_width + local titlebox_w = wc_geo.w - controlbox_w + + -- Default alignment is "right" + local controlbox_left = wc_geo.w - controlbox_w + local titlebox_left = wc_geo.x + local titlebox_right = wc_geo.w - controlbox_w + + if alignment == "left" then + controlbox_left = wc_geo.x + titlebox_left = wc_geo.x + controlbox_w + titlebox_right = wc_geo.w + end + + add_area("window-controls", get_hitbox_coords(controlbox_left, wc_geo.y, wc_geo.an, controlbox_w, wc_geo.h)) + + local lo + + -- Background Bar + new_element("wcbar", "box") + lo = add_layout("wcbar") + lo.geometry = wc_geo + lo.layer = 10 + lo.style = osc_styles.wcBar + lo.alpha[1] = user_opts.boxalpha + + local button_y = wc_geo.y - (wc_geo.h / 2) + local first_geo = { x = controlbox_left + 5, y = button_y, an = 4, w = 25, h = 25 } + local second_geo = { x = controlbox_left + 30, y = button_y, an = 4, w = 25, h = 25 } + local third_geo = { x = controlbox_left + 55, y = button_y, an = 4, w = 25, h = 25 } + + -- Window control buttons use symbols in the custom mpv osd font + -- because the official unicode codepoints are sufficiently + -- exotic that a system might lack an installed font with them, + -- and libass will complain that they are not present in the + -- default font, even if another font with them is available. + + -- Close: 🗙 + ne = new_element("close", "button") + ne.content = "\238\132\149" + ne.eventresponder["mbtn_left_up"] = function() + mp.commandv("quit") + end + lo = add_layout("close") + lo.geometry = alignment == "left" and first_geo or third_geo + lo.style = osc_styles.wcButtons + + -- Minimize: 🗕 + ne = new_element("minimize", "button") + ne.content = "\238\132\146" + ne.eventresponder["mbtn_left_up"] = function() + mp.commandv("cycle", "window-minimized") + end + lo = add_layout("minimize") + lo.geometry = alignment == "left" and second_geo or first_geo + lo.style = osc_styles.wcButtons + + -- Maximize: 🗖 /🗗 + ne = new_element("maximize", "button") + if state.maximized or state.fullscreen then + ne.content = "\238\132\148" + else + ne.content = "\238\132\147" + end + ne.eventresponder["mbtn_left_up"] = function() + if state.fullscreen then + mp.commandv("cycle", "fullscreen") + else + mp.commandv("cycle", "window-maximized") + end + end + lo = add_layout("maximize") + lo.geometry = alignment == "left" and third_geo or second_geo + lo.style = osc_styles.wcButtons + + -- deadzone below window controls + local sh_area_y0, sh_area_y1 + sh_area_y0 = user_opts.barmargin + sh_area_y1 = wc_geo.y + get_align(1 - (2 * user_opts.deadzonesize), osc_param.playresy - wc_geo.y, 0, 0) + add_area("showhide_wc", wc_geo.x, sh_area_y0, wc_geo.w, sh_area_y1) + + if topbar then + -- The title is already there as part of the top bar + return + else + -- Apply boxvideo margins to the control bar + osc_param.video_margins.t = wc_geo.h / osc_param.playresy + end + + -- Window Title + ne = new_element("wctitle", "button") + ne.content = function() + local title = mp.command_native({ "expand-text", user_opts.title }) + -- escape ASS, and strip newlines and trailing slashes + title = title:gsub("\\n", " "):gsub("\\$", ""):gsub("{", "\\{") + return not (title == "") and title or "mpv" + end + local left_pad = 5 + local right_pad = 10 + lo = add_layout("wctitle") + lo.geometry = { x = titlebox_left + left_pad, y = wc_geo.y - 3, an = 1, w = titlebox_w, h = wc_geo.h } + lo.style = string.format( + "%s{\\clip(%f,%f,%f,%f)}", + osc_styles.wcTitle, + titlebox_left + left_pad, + wc_geo.y - wc_geo.h, + titlebox_right - right_pad, + wc_geo.y + wc_geo.h + ) + + add_area("window-controls-title", titlebox_left, 0, titlebox_right, wc_geo.h) end -- @@ -1333,1328 +1401,1280 @@ end local layouts = {} -- Classic box layout -layouts["box"] = function () - - local osc_geo = { - w = 550, -- width - h = 138, -- height - r = 10, -- corner-radius - p = 15, -- padding - } - - -- make sure the OSC actually fits into the video - if (osc_param.playresx < (osc_geo.w + (2 * osc_geo.p))) then - osc_param.playresy = (osc_geo.w+(2*osc_geo.p))/osc_param.display_aspect - osc_param.playresx = osc_param.playresy * osc_param.display_aspect - end - - -- position of the controller according to video aspect and valignment - local posX = math.floor(get_align(user_opts.halign, osc_param.playresx, - osc_geo.w, 0)) - local posY = math.floor(get_align(user_opts.valign, osc_param.playresy, - osc_geo.h, 0)) - - -- position offset for contents aligned at the borders of the box - local pos_offsetX = (osc_geo.w - (2*osc_geo.p)) / 2 - local pos_offsetY = (osc_geo.h - (2*osc_geo.p)) / 2 - - osc_param.areas = {} -- delete areas - - -- area for active mouse input - add_area("input", get_hitbox_coords(posX, posY, 5, osc_geo.w, osc_geo.h)) - - -- area for show/hide - local sh_area_y0, sh_area_y1 - if user_opts.valign > 0 then - -- deadzone above OSC - sh_area_y0 = get_align(-1 + (2*user_opts.deadzonesize), - posY - (osc_geo.h / 2), 0, 0) - sh_area_y1 = osc_param.playresy - else - -- deadzone below OSC - sh_area_y0 = 0 - sh_area_y1 = (posY + (osc_geo.h / 2)) + - get_align(1 - (2*user_opts.deadzonesize), - osc_param.playresy - (posY + (osc_geo.h / 2)), 0, 0) - end - add_area("showhide", 0, sh_area_y0, osc_param.playresx, sh_area_y1) - - -- fetch values - local osc_w, osc_h, osc_r, osc_p = - osc_geo.w, osc_geo.h, osc_geo.r, osc_geo.p - - local lo - - -- - -- Background box - -- - - new_element("bgbox", "box") - lo = add_layout("bgbox") - - lo.geometry = {x = posX, y = posY, an = 5, w = osc_w, h = osc_h} - lo.layer = 10 - lo.style = osc_styles.box - lo.alpha[1] = user_opts.boxalpha - lo.alpha[3] = user_opts.boxalpha - lo.box.radius = osc_r - - -- - -- Title row - -- - - local titlerowY = posY - pos_offsetY - 10 - - lo = add_layout("title") - lo.geometry = {x = posX, y = titlerowY, an = 8, w = 496, h = 12} - lo.style = osc_styles.vidtitle - lo.button.maxchars = user_opts.boxmaxchars - - lo = add_layout("pl_prev") - lo.geometry = - {x = (posX - pos_offsetX), y = titlerowY, an = 7, w = 12, h = 12} - lo.style = osc_styles.topButtons - - lo = add_layout("pl_next") - lo.geometry = - {x = (posX + pos_offsetX), y = titlerowY, an = 9, w = 12, h = 12} - lo.style = osc_styles.topButtons - - -- - -- Big buttons - -- - - local bigbtnrowY = posY - pos_offsetY + 35 - local bigbtndist = 60 - - lo = add_layout("playpause") - lo.geometry = - {x = posX, y = bigbtnrowY, an = 5, w = 40, h = 40} - lo.style = osc_styles.bigButtons - - lo = add_layout("skipback") - lo.geometry = - {x = posX - bigbtndist, y = bigbtnrowY, an = 5, w = 40, h = 40} - lo.style = osc_styles.bigButtons - - lo = add_layout("skipfrwd") - lo.geometry = - {x = posX + bigbtndist, y = bigbtnrowY, an = 5, w = 40, h = 40} - lo.style = osc_styles.bigButtons - - lo = add_layout("ch_prev") - lo.geometry = - {x = posX - (bigbtndist * 2), y = bigbtnrowY, an = 5, w = 40, h = 40} - lo.style = osc_styles.bigButtons - - lo = add_layout("ch_next") - lo.geometry = - {x = posX + (bigbtndist * 2), y = bigbtnrowY, an = 5, w = 40, h = 40} - lo.style = osc_styles.bigButtons - - lo = add_layout("cy_audio") - lo.geometry = - {x = posX - pos_offsetX, y = bigbtnrowY, an = 1, w = 70, h = 18} - lo.style = osc_styles.smallButtonsL - - lo = add_layout("cy_sub") - lo.geometry = - {x = posX - pos_offsetX, y = bigbtnrowY, an = 7, w = 70, h = 18} - lo.style = osc_styles.smallButtonsL - - lo = add_layout("tog_fs") - lo.geometry = - {x = posX+pos_offsetX - 25, y = bigbtnrowY, an = 4, w = 25, h = 25} - lo.style = osc_styles.smallButtonsR - - lo = add_layout("volume") - lo.geometry = - {x = posX+pos_offsetX - (25 * 2) - osc_geo.p, - y = bigbtnrowY, an = 4, w = 25, h = 25} - lo.style = osc_styles.smallButtonsR - - -- - -- Seekbar - -- - - lo = add_layout("seekbar") - lo.geometry = - {x = posX, y = posY+pos_offsetY-22, an = 2, w = pos_offsetX*2, h = 15} - lo.style = osc_styles.timecodes - lo.slider.tooltip_style = osc_styles.vidtitle - lo.slider.stype = user_opts["seekbarstyle"] - lo.slider.rtype = user_opts["seekrangestyle"] - - -- - -- Timecodes + Cache - -- - - local bottomrowY = posY + pos_offsetY - 5 - - lo = add_layout("tc_left") - lo.geometry = - {x = posX - pos_offsetX, y = bottomrowY, an = 4, w = 110, h = 18} - lo.style = osc_styles.timecodes - - lo = add_layout("tc_right") - lo.geometry = - {x = posX + pos_offsetX, y = bottomrowY, an = 6, w = 110, h = 18} - lo.style = osc_styles.timecodes - - lo = add_layout("cache") - lo.geometry = - {x = posX, y = bottomrowY, an = 5, w = 110, h = 18} - lo.style = osc_styles.timecodes +layouts["box"] = function() + local osc_geo = { + w = 550, -- width + h = 138, -- height + r = 10, -- corner-radius + p = 15, -- padding + } + + -- make sure the OSC actually fits into the video + if osc_param.playresx < (osc_geo.w + (2 * osc_geo.p)) then + osc_param.playresy = (osc_geo.w + (2 * osc_geo.p)) / osc_param.display_aspect + osc_param.playresx = osc_param.playresy * osc_param.display_aspect + end + + -- position of the controller according to video aspect and valignment + local posX = math.floor(get_align(user_opts.halign, osc_param.playresx, osc_geo.w, 0)) + local posY = math.floor(get_align(user_opts.valign, osc_param.playresy, osc_geo.h, 0)) + + -- position offset for contents aligned at the borders of the box + local pos_offsetX = (osc_geo.w - (2 * osc_geo.p)) / 2 + local pos_offsetY = (osc_geo.h - (2 * osc_geo.p)) / 2 + + osc_param.areas = {} -- delete areas + + -- area for active mouse input + add_area("input", get_hitbox_coords(posX, posY, 5, osc_geo.w, osc_geo.h)) + + -- area for show/hide + local sh_area_y0, sh_area_y1 + if user_opts.valign > 0 then + -- deadzone above OSC + sh_area_y0 = get_align(-1 + (2 * user_opts.deadzonesize), posY - (osc_geo.h / 2), 0, 0) + sh_area_y1 = osc_param.playresy + else + -- deadzone below OSC + sh_area_y0 = 0 + sh_area_y1 = (posY + (osc_geo.h / 2)) + + get_align(1 - (2 * user_opts.deadzonesize), osc_param.playresy - (posY + (osc_geo.h / 2)), 0, 0) + end + add_area("showhide", 0, sh_area_y0, osc_param.playresx, sh_area_y1) + + -- fetch values + local osc_w, osc_h, osc_r, osc_p = osc_geo.w, osc_geo.h, osc_geo.r, osc_geo.p + + local lo + + -- + -- Background box + -- + + new_element("bgbox", "box") + lo = add_layout("bgbox") + + lo.geometry = { x = posX, y = posY, an = 5, w = osc_w, h = osc_h } + lo.layer = 10 + lo.style = osc_styles.box + lo.alpha[1] = user_opts.boxalpha + lo.alpha[3] = user_opts.boxalpha + lo.box.radius = osc_r + + -- + -- Title row + -- + + local titlerowY = posY - pos_offsetY - 10 + + lo = add_layout("title") + lo.geometry = { x = posX, y = titlerowY, an = 8, w = 496, h = 12 } + lo.style = osc_styles.vidtitle + lo.button.maxchars = user_opts.boxmaxchars + + lo = add_layout("pl_prev") + lo.geometry = { x = (posX - pos_offsetX), y = titlerowY, an = 7, w = 12, h = 12 } + lo.style = osc_styles.topButtons + + lo = add_layout("pl_next") + lo.geometry = { x = (posX + pos_offsetX), y = titlerowY, an = 9, w = 12, h = 12 } + lo.style = osc_styles.topButtons + + -- + -- Big buttons + -- + + local bigbtnrowY = posY - pos_offsetY + 35 + local bigbtndist = 60 + + lo = add_layout("playpause") + lo.geometry = { x = posX, y = bigbtnrowY, an = 5, w = 40, h = 40 } + lo.style = osc_styles.bigButtons + + lo = add_layout("skipback") + lo.geometry = { x = posX - bigbtndist, y = bigbtnrowY, an = 5, w = 40, h = 40 } + lo.style = osc_styles.bigButtons + + lo = add_layout("skipfrwd") + lo.geometry = { x = posX + bigbtndist, y = bigbtnrowY, an = 5, w = 40, h = 40 } + lo.style = osc_styles.bigButtons + + lo = add_layout("ch_prev") + lo.geometry = { x = posX - (bigbtndist * 2), y = bigbtnrowY, an = 5, w = 40, h = 40 } + lo.style = osc_styles.bigButtons + + lo = add_layout("ch_next") + lo.geometry = { x = posX + (bigbtndist * 2), y = bigbtnrowY, an = 5, w = 40, h = 40 } + lo.style = osc_styles.bigButtons -end + lo = add_layout("cy_audio") + lo.geometry = { x = posX - pos_offsetX, y = bigbtnrowY, an = 1, w = 70, h = 18 } + lo.style = osc_styles.smallButtonsL --- slim box layout -layouts["slimbox"] = function () - - local osc_geo = { - w = 660, -- width - h = 70, -- height - r = 10, -- corner-radius - } - - -- make sure the OSC actually fits into the video - if (osc_param.playresx < (osc_geo.w)) then - osc_param.playresy = (osc_geo.w)/osc_param.display_aspect - osc_param.playresx = osc_param.playresy * osc_param.display_aspect - end - - -- position of the controller according to video aspect and valignment - local posX = math.floor(get_align(user_opts.halign, osc_param.playresx, - osc_geo.w, 0)) - local posY = math.floor(get_align(user_opts.valign, osc_param.playresy, - osc_geo.h, 0)) - - osc_param.areas = {} -- delete areas - - -- area for active mouse input - add_area("input", get_hitbox_coords(posX, posY, 5, osc_geo.w, osc_geo.h)) - - -- area for show/hide - local sh_area_y0, sh_area_y1 - if user_opts.valign > 0 then - -- deadzone above OSC - sh_area_y0 = get_align(-1 + (2*user_opts.deadzonesize), - posY - (osc_geo.h / 2), 0, 0) - sh_area_y1 = osc_param.playresy - else - -- deadzone below OSC - sh_area_y0 = 0 - sh_area_y1 = (posY + (osc_geo.h / 2)) + - get_align(1 - (2*user_opts.deadzonesize), - osc_param.playresy - (posY + (osc_geo.h / 2)), 0, 0) - end - add_area("showhide", 0, sh_area_y0, osc_param.playresx, sh_area_y1) - - local lo - - local tc_w, ele_h, inner_w = 100, 20, osc_geo.w - 100 - - -- styles - local styles = { - box = "{\\rDefault\\blur0\\bord1\\1c&H000000\\3c&HFFFFFF}", - timecodes = "{\\1c&HFFFFFF\\3c&H000000\\fs20\\bord2\\blur1}", - tooltip = "{\\1c&HFFFFFF\\3c&H000000\\fs12\\bord1\\blur0.5}", - } - - - new_element("bgbox", "box") - lo = add_layout("bgbox") - - lo.geometry = {x = posX, y = posY - 1, an = 2, w = inner_w, h = ele_h} - lo.layer = 10 - lo.style = osc_styles.box - lo.alpha[1] = user_opts.boxalpha - lo.alpha[3] = 0 - if not (user_opts["seekbarstyle"] == "bar") then - lo.box.radius = osc_geo.r - lo.box.hexagon = user_opts["seekbarstyle"] == "diamond" - end - - - lo = add_layout("seekbar") - lo.geometry = - {x = posX, y = posY - 1, an = 2, w = inner_w, h = ele_h} - lo.style = osc_styles.timecodes - lo.slider.border = 0 - lo.slider.gap = 1.5 - lo.slider.tooltip_style = styles.tooltip - lo.slider.stype = user_opts["seekbarstyle"] - lo.slider.rtype = user_opts["seekrangestyle"] - lo.slider.adjust_tooltip = false - - -- - -- Timecodes - -- - - lo = add_layout("tc_left") - lo.geometry = - {x = posX - (inner_w/2) + osc_geo.r, y = posY + 1, - an = 7, w = tc_w, h = ele_h} - lo.style = styles.timecodes - lo.alpha[3] = user_opts.boxalpha - - lo = add_layout("tc_right") - lo.geometry = - {x = posX + (inner_w/2) - osc_geo.r, y = posY + 1, - an = 9, w = tc_w, h = ele_h} - lo.style = styles.timecodes - lo.alpha[3] = user_opts.boxalpha - - -- Cache - - lo = add_layout("cache") - lo.geometry = - {x = posX, y = posY + 1, - an = 8, w = tc_w, h = ele_h} - lo.style = styles.timecodes - lo.alpha[3] = user_opts.boxalpha + lo = add_layout("cy_sub") + lo.geometry = { x = posX - pos_offsetX, y = bigbtnrowY, an = 7, w = 70, h = 18 } + lo.style = osc_styles.smallButtonsL + + lo = add_layout("tog_fs") + lo.geometry = { x = posX + pos_offsetX - 25, y = bigbtnrowY, an = 4, w = 25, h = 25 } + lo.style = osc_styles.smallButtonsR + + lo = add_layout("volume") + lo.geometry = { x = posX + pos_offsetX - (25 * 2) - osc_geo.p, y = bigbtnrowY, an = 4, w = 25, h = 25 } + lo.style = osc_styles.smallButtonsR + + -- + -- Seekbar + -- + lo = add_layout("seekbar") + lo.geometry = { x = posX, y = posY + pos_offsetY - 22, an = 2, w = pos_offsetX * 2, h = 15 } + lo.style = osc_styles.timecodes + lo.slider.tooltip_style = osc_styles.vidtitle + lo.slider.stype = user_opts["seekbarstyle"] + lo.slider.rtype = user_opts["seekrangestyle"] + -- + -- Timecodes + Cache + -- + + local bottomrowY = posY + pos_offsetY - 5 + + lo = add_layout("tc_left") + lo.geometry = { x = posX - pos_offsetX, y = bottomrowY, an = 4, w = 110, h = 18 } + lo.style = osc_styles.timecodes + + lo = add_layout("tc_right") + lo.geometry = { x = posX + pos_offsetX, y = bottomrowY, an = 6, w = 110, h = 18 } + lo.style = osc_styles.timecodes + + lo = add_layout("cache") + lo.geometry = { x = posX, y = bottomrowY, an = 5, w = 110, h = 18 } + lo.style = osc_styles.timecodes +end + +-- slim box layout +layouts["slimbox"] = function() + local osc_geo = { + w = 660, -- width + h = 70, -- height + r = 10, -- corner-radius + } + + -- make sure the OSC actually fits into the video + if osc_param.playresx < osc_geo.w then + osc_param.playresy = osc_geo.w / osc_param.display_aspect + osc_param.playresx = osc_param.playresy * osc_param.display_aspect + end + + -- position of the controller according to video aspect and valignment + local posX = math.floor(get_align(user_opts.halign, osc_param.playresx, osc_geo.w, 0)) + local posY = math.floor(get_align(user_opts.valign, osc_param.playresy, osc_geo.h, 0)) + + osc_param.areas = {} -- delete areas + + -- area for active mouse input + add_area("input", get_hitbox_coords(posX, posY, 5, osc_geo.w, osc_geo.h)) + + -- area for show/hide + local sh_area_y0, sh_area_y1 + if user_opts.valign > 0 then + -- deadzone above OSC + sh_area_y0 = get_align(-1 + (2 * user_opts.deadzonesize), posY - (osc_geo.h / 2), 0, 0) + sh_area_y1 = osc_param.playresy + else + -- deadzone below OSC + sh_area_y0 = 0 + sh_area_y1 = (posY + (osc_geo.h / 2)) + + get_align(1 - (2 * user_opts.deadzonesize), osc_param.playresy - (posY + (osc_geo.h / 2)), 0, 0) + end + add_area("showhide", 0, sh_area_y0, osc_param.playresx, sh_area_y1) + + local lo + + local tc_w, ele_h, inner_w = 100, 20, osc_geo.w - 100 + + -- styles + local styles = { + box = "{\\rDefault\\blur0\\bord1\\1c&H000000\\3c&HFFFFFF}", + timecodes = "{\\1c&HFFFFFF\\3c&H000000\\fs20\\bord2\\blur1}", + tooltip = "{\\1c&HFFFFFF\\3c&H000000\\fs12\\bord1\\blur0.5}", + } + + new_element("bgbox", "box") + lo = add_layout("bgbox") + + lo.geometry = { x = posX, y = posY - 1, an = 2, w = inner_w, h = ele_h } + lo.layer = 10 + lo.style = osc_styles.box + lo.alpha[1] = user_opts.boxalpha + lo.alpha[3] = 0 + if not (user_opts["seekbarstyle"] == "bar") then + lo.box.radius = osc_geo.r + lo.box.hexagon = user_opts["seekbarstyle"] == "diamond" + end + + lo = add_layout("seekbar") + lo.geometry = { x = posX, y = posY - 1, an = 2, w = inner_w, h = ele_h } + lo.style = osc_styles.timecodes + lo.slider.border = 0 + lo.slider.gap = 1.5 + lo.slider.tooltip_style = styles.tooltip + lo.slider.stype = user_opts["seekbarstyle"] + lo.slider.rtype = user_opts["seekrangestyle"] + lo.slider.adjust_tooltip = false + + -- + -- Timecodes + -- + + lo = add_layout("tc_left") + lo.geometry = { x = posX - (inner_w / 2) + osc_geo.r, y = posY + 1, an = 7, w = tc_w, h = ele_h } + lo.style = styles.timecodes + lo.alpha[3] = user_opts.boxalpha + + lo = add_layout("tc_right") + lo.geometry = { x = posX + (inner_w / 2) - osc_geo.r, y = posY + 1, an = 9, w = tc_w, h = ele_h } + lo.style = styles.timecodes + lo.alpha[3] = user_opts.boxalpha + + -- Cache + + lo = add_layout("cache") + lo.geometry = { x = posX, y = posY + 1, an = 8, w = tc_w, h = ele_h } + lo.style = styles.timecodes + lo.alpha[3] = user_opts.boxalpha end function bar_layout(direction) - local osc_geo = { - x = -2, - y, - an = (direction < 0) and 7 or 1, - w, - h = 56, - } - - local padX = 9 - local padY = 3 - local buttonW = 27 - local tcW = (state.tc_ms) and 170 or 110 - if user_opts.tcspace >= 50 and user_opts.tcspace <= 200 then - -- adjust our hardcoded font size estimation - tcW = tcW * user_opts.tcspace / 100 - end - - local tsW = 90 - local minW = (buttonW + padX)*5 + (tcW + padX)*4 + (tsW + padX)*2 - - -- Special topbar handling when window controls are present - local padwc_l - local padwc_r - if direction < 0 or not window_controls_enabled() then - padwc_l = 0 - padwc_r = 0 - elseif window_controls_alignment() == "left" then - padwc_l = window_control_box_width - padwc_r = 0 - else - padwc_l = 0 - padwc_r = window_control_box_width - end - - if ((osc_param.display_aspect > 0) and (osc_param.playresx < minW)) then - osc_param.playresy = minW / osc_param.display_aspect - osc_param.playresx = osc_param.playresy * osc_param.display_aspect - end - - osc_geo.y = direction * (54 + user_opts.barmargin) - osc_geo.w = osc_param.playresx + 4 - if direction < 0 then - osc_geo.y = osc_geo.y + osc_param.playresy - end - - local line1 = osc_geo.y - direction * (9 + padY) - local line2 = osc_geo.y - direction * (36 + padY) - - osc_param.areas = {} - - add_area("input", get_hitbox_coords(osc_geo.x, osc_geo.y, osc_geo.an, - osc_geo.w, osc_geo.h)) - - local sh_area_y0, sh_area_y1 - if direction > 0 then - -- deadzone below OSC - sh_area_y0 = user_opts.barmargin - sh_area_y1 = osc_geo.y + get_align(1 - (2 * user_opts.deadzonesize), - osc_param.playresy - osc_geo.y, 0, 0) - else - -- deadzone above OSC - sh_area_y0 = get_align(-1 + (2 * user_opts.deadzonesize), osc_geo.y, 0, 0) - sh_area_y1 = osc_param.playresy - user_opts.barmargin - end - add_area("showhide", 0, sh_area_y0, osc_param.playresx, sh_area_y1) - - local lo, geo - - -- Background bar - new_element("bgbox", "box") - lo = add_layout("bgbox") - - lo.geometry = osc_geo - lo.layer = 10 - lo.style = osc_styles.box - lo.alpha[1] = user_opts.boxalpha - - - -- Playlist prev/next - geo = { x = osc_geo.x + padX, y = line1, - an = 4, w = 18, h = 18 - padY } - lo = add_layout("pl_prev") - lo.geometry = geo - lo.style = osc_styles.topButtonsBar - - geo = { x = geo.x + geo.w + padX, y = geo.y, an = geo.an, w = geo.w, h = geo.h } - lo = add_layout("pl_next") - lo.geometry = geo - lo.style = osc_styles.topButtonsBar - - local t_l = geo.x + geo.w + padX - - -- Cache - geo = { x = osc_geo.x + osc_geo.w - padX, y = geo.y, - an = 6, w = 150, h = geo.h } - lo = add_layout("cache") - lo.geometry = geo - lo.style = osc_styles.vidtitleBar - - local t_r = geo.x - geo.w - padX*2 - - -- Title - geo = { x = t_l, y = geo.y, an = 4, - w = t_r - t_l, h = geo.h } - lo = add_layout("title") - lo.geometry = geo - lo.style = string.format("%s{\\clip(%f,%f,%f,%f)}", - osc_styles.vidtitleBar, - geo.x, geo.y-geo.h, geo.w, geo.y+geo.h) - - - -- Playback control buttons - geo = { x = osc_geo.x + padX + padwc_l, y = line2, an = 4, - w = buttonW, h = 36 - padY*2} - lo = add_layout("playpause") - lo.geometry = geo - lo.style = osc_styles.smallButtonsBar - - geo = { x = geo.x + geo.w + padX, y = geo.y, an = geo.an, w = geo.w, h = geo.h } - lo = add_layout("ch_prev") - lo.geometry = geo - lo.style = osc_styles.smallButtonsBar - - geo = { x = geo.x + geo.w + padX, y = geo.y, an = geo.an, w = geo.w, h = geo.h } - lo = add_layout("ch_next") - lo.geometry = geo - lo.style = osc_styles.smallButtonsBar - - -- Left timecode - geo = { x = geo.x + geo.w + padX + tcW, y = geo.y, an = 6, - w = tcW, h = geo.h } - lo = add_layout("tc_left") - lo.geometry = geo - lo.style = osc_styles.timecodesBar - - local sb_l = geo.x + padX - - -- Fullscreen button - geo = { x = osc_geo.x + osc_geo.w - buttonW - padX - padwc_r, y = geo.y, an = 4, - w = buttonW, h = geo.h } - lo = add_layout("tog_fs") - lo.geometry = geo - lo.style = osc_styles.smallButtonsBar - - -- Volume - geo = { x = geo.x - geo.w - padX, y = geo.y, an = geo.an, w = geo.w, h = geo.h } - lo = add_layout("volume") - lo.geometry = geo - lo.style = osc_styles.smallButtonsBar - - -- Track selection buttons - geo = { x = geo.x - tsW - padX, y = geo.y, an = geo.an, w = tsW, h = geo.h } - lo = add_layout("cy_sub") - lo.geometry = geo - lo.style = osc_styles.smallButtonsBar - - geo = { x = geo.x - geo.w - padX, y = geo.y, an = geo.an, w = geo.w, h = geo.h } - lo = add_layout("cy_audio") - lo.geometry = geo - lo.style = osc_styles.smallButtonsBar - - - -- Right timecode - geo = { x = geo.x - padX - tcW - 10, y = geo.y, an = geo.an, - w = tcW, h = geo.h } - lo = add_layout("tc_right") - lo.geometry = geo - lo.style = osc_styles.timecodesBar - - local sb_r = geo.x - padX - - - -- Seekbar - geo = { x = sb_l, y = geo.y, an = geo.an, - w = math.max(0, sb_r - sb_l), h = geo.h } - new_element("bgbar1", "box") - lo = add_layout("bgbar1") - - lo.geometry = geo - lo.layer = 15 - lo.style = osc_styles.timecodesBar - lo.alpha[1] = - math.min(255, user_opts.boxalpha + (255 - user_opts.boxalpha)*0.8) - if not (user_opts["seekbarstyle"] == "bar") then - lo.box.radius = geo.h / 2 - lo.box.hexagon = user_opts["seekbarstyle"] == "diamond" - end - - lo = add_layout("seekbar") - lo.geometry = geo - lo.style = osc_styles.timecodesBar - lo.slider.border = 0 - lo.slider.gap = 2 - lo.slider.tooltip_style = osc_styles.timePosBar - lo.slider.tooltip_an = 5 - lo.slider.stype = user_opts["seekbarstyle"] - lo.slider.rtype = user_opts["seekrangestyle"] - - if direction < 0 then - osc_param.video_margins.b = osc_geo.h / osc_param.playresy - else - osc_param.video_margins.t = osc_geo.h / osc_param.playresy - end + local osc_geo = { + x = -2, + y, + an = (direction < 0) and 7 or 1, + w, + h = 56, + } + + local padX = 9 + local padY = 3 + local buttonW = 27 + local tcW = state.tc_ms and 170 or 110 + if user_opts.tcspace >= 50 and user_opts.tcspace <= 200 then + -- adjust our hardcoded font size estimation + tcW = tcW * user_opts.tcspace / 100 + end + + local tsW = 90 + local minW = (buttonW + padX) * 5 + (tcW + padX) * 4 + (tsW + padX) * 2 + + -- Special topbar handling when window controls are present + local padwc_l + local padwc_r + if direction < 0 or not window_controls_enabled() then + padwc_l = 0 + padwc_r = 0 + elseif window_controls_alignment() == "left" then + padwc_l = window_control_box_width + padwc_r = 0 + else + padwc_l = 0 + padwc_r = window_control_box_width + end + + if (osc_param.display_aspect > 0) and (osc_param.playresx < minW) then + osc_param.playresy = minW / osc_param.display_aspect + osc_param.playresx = osc_param.playresy * osc_param.display_aspect + end + + osc_geo.y = direction * (54 + user_opts.barmargin) + osc_geo.w = osc_param.playresx + 4 + if direction < 0 then + osc_geo.y = osc_geo.y + osc_param.playresy + end + + local line1 = osc_geo.y - direction * (9 + padY) + local line2 = osc_geo.y - direction * (36 + padY) + + osc_param.areas = {} + + add_area("input", get_hitbox_coords(osc_geo.x, osc_geo.y, osc_geo.an, osc_geo.w, osc_geo.h)) + + local sh_area_y0, sh_area_y1 + if direction > 0 then + -- deadzone below OSC + sh_area_y0 = user_opts.barmargin + sh_area_y1 = osc_geo.y + get_align(1 - (2 * user_opts.deadzonesize), osc_param.playresy - osc_geo.y, 0, 0) + else + -- deadzone above OSC + sh_area_y0 = get_align(-1 + (2 * user_opts.deadzonesize), osc_geo.y, 0, 0) + sh_area_y1 = osc_param.playresy - user_opts.barmargin + end + add_area("showhide", 0, sh_area_y0, osc_param.playresx, sh_area_y1) + + local lo, geo + + -- Background bar + new_element("bgbox", "box") + lo = add_layout("bgbox") + + lo.geometry = osc_geo + lo.layer = 10 + lo.style = osc_styles.box + lo.alpha[1] = user_opts.boxalpha + + -- Playlist prev/next + geo = { x = osc_geo.x + padX, y = line1, an = 4, w = 18, h = 18 - padY } + lo = add_layout("pl_prev") + lo.geometry = geo + lo.style = osc_styles.topButtonsBar + + geo = { x = geo.x + geo.w + padX, y = geo.y, an = geo.an, w = geo.w, h = geo.h } + lo = add_layout("pl_next") + lo.geometry = geo + lo.style = osc_styles.topButtonsBar + + local t_l = geo.x + geo.w + padX + + -- Cache + geo = { x = osc_geo.x + osc_geo.w - padX, y = geo.y, an = 6, w = 150, h = geo.h } + lo = add_layout("cache") + lo.geometry = geo + lo.style = osc_styles.vidtitleBar + + local t_r = geo.x - geo.w - padX * 2 + + -- Title + geo = { x = t_l, y = geo.y, an = 4, w = t_r - t_l, h = geo.h } + lo = add_layout("title") + lo.geometry = geo + lo.style = + string.format("%s{\\clip(%f,%f,%f,%f)}", osc_styles.vidtitleBar, geo.x, geo.y - geo.h, geo.w, geo.y + geo.h) + + -- Playback control buttons + geo = { x = osc_geo.x + padX + padwc_l, y = line2, an = 4, w = buttonW, h = 36 - padY * 2 } + lo = add_layout("playpause") + lo.geometry = geo + lo.style = osc_styles.smallButtonsBar + + geo = { x = geo.x + geo.w + padX, y = geo.y, an = geo.an, w = geo.w, h = geo.h } + lo = add_layout("ch_prev") + lo.geometry = geo + lo.style = osc_styles.smallButtonsBar + + geo = { x = geo.x + geo.w + padX, y = geo.y, an = geo.an, w = geo.w, h = geo.h } + lo = add_layout("ch_next") + lo.geometry = geo + lo.style = osc_styles.smallButtonsBar + + -- Left timecode + geo = { x = geo.x + geo.w + padX + tcW, y = geo.y, an = 6, w = tcW, h = geo.h } + lo = add_layout("tc_left") + lo.geometry = geo + lo.style = osc_styles.timecodesBar + + local sb_l = geo.x + padX + + -- Fullscreen button + geo = { x = osc_geo.x + osc_geo.w - buttonW - padX - padwc_r, y = geo.y, an = 4, w = buttonW, h = geo.h } + lo = add_layout("tog_fs") + lo.geometry = geo + lo.style = osc_styles.smallButtonsBar + + -- Volume + geo = { x = geo.x - geo.w - padX, y = geo.y, an = geo.an, w = geo.w, h = geo.h } + lo = add_layout("volume") + lo.geometry = geo + lo.style = osc_styles.smallButtonsBar + + -- Track selection buttons + geo = { x = geo.x - tsW - padX, y = geo.y, an = geo.an, w = tsW, h = geo.h } + lo = add_layout("cy_sub") + lo.geometry = geo + lo.style = osc_styles.smallButtonsBar + + geo = { x = geo.x - geo.w - padX, y = geo.y, an = geo.an, w = geo.w, h = geo.h } + lo = add_layout("cy_audio") + lo.geometry = geo + lo.style = osc_styles.smallButtonsBar + + -- Right timecode + geo = { x = geo.x - padX - tcW - 10, y = geo.y, an = geo.an, w = tcW, h = geo.h } + lo = add_layout("tc_right") + lo.geometry = geo + lo.style = osc_styles.timecodesBar + + local sb_r = geo.x - padX + + -- Seekbar + geo = { x = sb_l, y = geo.y, an = geo.an, w = math.max(0, sb_r - sb_l), h = geo.h } + new_element("bgbar1", "box") + lo = add_layout("bgbar1") + + lo.geometry = geo + lo.layer = 15 + lo.style = osc_styles.timecodesBar + lo.alpha[1] = math.min(255, user_opts.boxalpha + (255 - user_opts.boxalpha) * 0.8) + if not (user_opts["seekbarstyle"] == "bar") then + lo.box.radius = geo.h / 2 + lo.box.hexagon = user_opts["seekbarstyle"] == "diamond" + end + + lo = add_layout("seekbar") + lo.geometry = geo + lo.style = osc_styles.timecodesBar + lo.slider.border = 0 + lo.slider.gap = 2 + lo.slider.tooltip_style = osc_styles.timePosBar + lo.slider.tooltip_an = 5 + lo.slider.stype = user_opts["seekbarstyle"] + lo.slider.rtype = user_opts["seekrangestyle"] + + if direction < 0 then + osc_param.video_margins.b = osc_geo.h / osc_param.playresy + else + osc_param.video_margins.t = osc_geo.h / osc_param.playresy + end end layouts["bottombar"] = function() - bar_layout(-1) + bar_layout(-1) end layouts["topbar"] = function() - bar_layout(1) + bar_layout(1) end -- Validate string type user options function validate_user_opts() - if layouts[user_opts.layout] == nil then - msg.warn("Invalid setting \""..user_opts.layout.."\" for layout") - user_opts.layout = "bottombar" - end - - if user_opts.seekbarstyle ~= "bar" and - user_opts.seekbarstyle ~= "diamond" and - user_opts.seekbarstyle ~= "knob" then - msg.warn("Invalid setting \"" .. user_opts.seekbarstyle - .. "\" for seekbarstyle") - user_opts.seekbarstyle = "bar" - end - - if user_opts.seekrangestyle ~= "bar" and - user_opts.seekrangestyle ~= "line" and - user_opts.seekrangestyle ~= "slider" and - user_opts.seekrangestyle ~= "inverted" and - user_opts.seekrangestyle ~= "none" then - msg.warn("Invalid setting \"" .. user_opts.seekrangestyle - .. "\" for seekrangestyle") - user_opts.seekrangestyle = "inverted" - end - - if user_opts.seekrangestyle == "slider" and - user_opts.seekbarstyle == "bar" then - msg.warn("Using \"slider\" seekrangestyle together with \"bar\" seekbarstyle is not supported") - user_opts.seekrangestyle = "inverted" - end - - if user_opts.windowcontrols ~= "auto" and - user_opts.windowcontrols ~= "yes" and - user_opts.windowcontrols ~= "no" then - msg.warn("windowcontrols cannot be \"" .. - user_opts.windowcontrols .. "\". Ignoring.") - user_opts.windowcontrols = "auto" - end - if user_opts.windowcontrols_alignment ~= "right" and - user_opts.windowcontrols_alignment ~= "left" then - msg.warn("windowcontrols_alignment cannot be \"" .. - user_opts.windowcontrols_alignment .. "\". Ignoring.") - user_opts.windowcontrols_alignment = "right" - end + if layouts[user_opts.layout] == nil then + msg.warn('Invalid setting "' .. user_opts.layout .. '" for layout') + user_opts.layout = "bottombar" + end + + if user_opts.seekbarstyle ~= "bar" and user_opts.seekbarstyle ~= "diamond" and user_opts.seekbarstyle ~= "knob" then + msg.warn('Invalid setting "' .. user_opts.seekbarstyle .. '" for seekbarstyle') + user_opts.seekbarstyle = "bar" + end + + if + user_opts.seekrangestyle ~= "bar" + and user_opts.seekrangestyle ~= "line" + and user_opts.seekrangestyle ~= "slider" + and user_opts.seekrangestyle ~= "inverted" + and user_opts.seekrangestyle ~= "none" + then + msg.warn('Invalid setting "' .. user_opts.seekrangestyle .. '" for seekrangestyle') + user_opts.seekrangestyle = "inverted" + end + + if user_opts.seekrangestyle == "slider" and user_opts.seekbarstyle == "bar" then + msg.warn('Using "slider" seekrangestyle together with "bar" seekbarstyle is not supported') + user_opts.seekrangestyle = "inverted" + end + + if + user_opts.windowcontrols ~= "auto" + and user_opts.windowcontrols ~= "yes" + and user_opts.windowcontrols ~= "no" + then + msg.warn('windowcontrols cannot be "' .. user_opts.windowcontrols .. '". Ignoring.') + user_opts.windowcontrols = "auto" + end + if user_opts.windowcontrols_alignment ~= "right" and user_opts.windowcontrols_alignment ~= "left" then + msg.warn('windowcontrols_alignment cannot be "' .. user_opts.windowcontrols_alignment .. '". Ignoring.') + user_opts.windowcontrols_alignment = "right" + end end function update_options(list) - validate_user_opts() - request_tick() - visibility_mode(user_opts.visibility, true) - update_duration_watch() - request_init() + validate_user_opts() + request_tick() + visibility_mode(user_opts.visibility, true) + update_duration_watch() + request_init() end -local UNICODE_MINUS = string.char(0xe2, 0x88, 0x92) -- UTF-8 for U+2212 MINUS SIGN +local UNICODE_MINUS = string.char(0xe2, 0x88, 0x92) -- UTF-8 for U+2212 MINUS SIGN -- OSC INIT function osc_init() - msg.debug("osc_init") - - -- set canvas resolution according to display aspect and scaling setting - local baseResY = 720 - local display_w, display_h, display_aspect = mp.get_osd_size() - local scale = 1 - - if (mp.get_property("video") == "no") then -- dummy/forced window - scale = user_opts.scaleforcedwindow - elseif state.fullscreen then - scale = user_opts.scalefullscreen - else - scale = user_opts.scalewindowed - end - - if user_opts.vidscale then - osc_param.unscaled_y = baseResY - else - osc_param.unscaled_y = display_h - end - osc_param.playresy = osc_param.unscaled_y / scale - if (display_aspect > 0) then - osc_param.display_aspect = display_aspect - end - osc_param.playresx = osc_param.playresy * osc_param.display_aspect - - -- stop seeking with the slider to prevent skipping files - state.active_element = nil - - osc_param.video_margins = {l = 0, r = 0, t = 0, b = 0} - - elements = {} - - -- some often needed stuff - local pl_count = mp.get_property_number("playlist-count", 0) - local have_pl = (pl_count > 1) - local pl_pos = mp.get_property_number("playlist-pos", 0) + 1 - local have_ch = (mp.get_property_number("chapters", 0) > 0) - local loop = mp.get_property("loop-playlist", "no") - - local ne - - -- title - ne = new_element("title", "button") - - ne.content = function () - local title = state.forced_title or - mp.command_native({"expand-text", user_opts.title}) - -- escape ASS, and strip newlines and trailing slashes - title = title:gsub("\\n", " "):gsub("\\$", ""):gsub("{","\\{") - return not (title == "") and title or "mpv" - end - - ne.eventresponder["mbtn_left_up"] = function () - local title = mp.get_property_osd("media-title") - if (have_pl) then - title = string.format("[%d/%d] %s", countone(pl_pos - 1), - pl_count, title) - end - show_message(title) - end - - ne.eventresponder["mbtn_right_up"] = - function () show_message(mp.get_property_osd("filename")) end - - -- playlist buttons - - -- prev - ne = new_element("pl_prev", "button") - - ne.content = "\238\132\144" - ne.enabled = (pl_pos > 1) or (loop ~= "no") - ne.eventresponder["mbtn_left_up"] = - function () - mp.commandv("playlist-prev", "weak") - if user_opts.playlist_osd then - show_message(get_playlist(), 3) - end - end - ne.eventresponder["shift+mbtn_left_up"] = - function () show_message(get_playlist(), 3) end - ne.eventresponder["mbtn_right_up"] = - function () show_message(get_playlist(), 3) end - - --next - ne = new_element("pl_next", "button") - - ne.content = "\238\132\129" - ne.enabled = (have_pl and (pl_pos < pl_count)) or (loop ~= "no") - ne.eventresponder["mbtn_left_up"] = - function () - mp.commandv("playlist-next", "weak") - if user_opts.playlist_osd then - show_message(get_playlist(), 3) - end - end - ne.eventresponder["shift+mbtn_left_up"] = - function () show_message(get_playlist(), 3) end - ne.eventresponder["mbtn_right_up"] = - function () show_message(get_playlist(), 3) end - - - -- big buttons - - --playpause - ne = new_element("playpause", "button") - - ne.content = function () - if mp.get_property("pause") == "yes" then - return ("\238\132\129") - else - return ("\238\128\130") - end - end - ne.eventresponder["mbtn_left_up"] = - function () mp.commandv("cycle", "pause") end - - --skipback - ne = new_element("skipback", "button") - - ne.softrepeat = true - ne.content = "\238\128\132" - ne.eventresponder["mbtn_left_down"] = - function () mp.commandv("seek", -5, "relative", "keyframes") end - ne.eventresponder["shift+mbtn_left_down"] = - function () mp.commandv("frame-back-step") end - ne.eventresponder["mbtn_right_down"] = - function () mp.commandv("seek", -30, "relative", "keyframes") end - - --skipfrwd - ne = new_element("skipfrwd", "button") - - ne.softrepeat = true - ne.content = "\238\128\133" - ne.eventresponder["mbtn_left_down"] = - function () mp.commandv("seek", 10, "relative", "keyframes") end - ne.eventresponder["shift+mbtn_left_down"] = - function () mp.commandv("frame-step") end - ne.eventresponder["mbtn_right_down"] = - function () mp.commandv("seek", 60, "relative", "keyframes") end - - --ch_prev - ne = new_element("ch_prev", "button") - - ne.enabled = have_ch - ne.content = "\238\132\132" - ne.eventresponder["mbtn_left_up"] = - function () - mp.commandv("add", "chapter", -1) - if user_opts.chapters_osd then - show_message(get_chapterlist(), 3) - end - end - ne.eventresponder["shift+mbtn_left_up"] = - function () show_message(get_chapterlist(), 3) end - ne.eventresponder["mbtn_right_up"] = - function () show_message(get_chapterlist(), 3) end - - --ch_next - ne = new_element("ch_next", "button") - - ne.enabled = have_ch - ne.content = "\238\132\133" - ne.eventresponder["mbtn_left_up"] = - function () - mp.commandv("add", "chapter", 1) - if user_opts.chapters_osd then - show_message(get_chapterlist(), 3) - end - end - ne.eventresponder["shift+mbtn_left_up"] = - function () show_message(get_chapterlist(), 3) end - ne.eventresponder["mbtn_right_up"] = - function () show_message(get_chapterlist(), 3) end - - -- - update_tracklist() - - --cy_audio - ne = new_element("cy_audio", "button") - - ne.enabled = (#tracks_osc.audio > 0) - ne.content = function () - local aid = "–" - if not (get_track("audio") == 0) then - aid = get_track("audio") - end - return ("\238\132\134" .. osc_styles.smallButtonsLlabel - .. " " .. aid .. "/" .. #tracks_osc.audio) - end - ne.eventresponder["mbtn_left_up"] = - function () set_track("audio", 1) end - ne.eventresponder["mbtn_right_up"] = - function () set_track("audio", -1) end - ne.eventresponder["shift+mbtn_left_down"] = - function () show_message(get_tracklist("audio"), 2) end - - if user_opts.scrollcontrols then - ne.eventresponder["wheel_down_press"] = - function () set_track("audio", 1) end - ne.eventresponder["wheel_up_press"] = - function () set_track("audio", -1) end - end - - --cy_sub - ne = new_element("cy_sub", "button") - - ne.enabled = (#tracks_osc.sub > 0) - ne.content = function () - local sid = "–" - if not (get_track("sub") == 0) then - sid = get_track("sub") - end - return ("\238\132\135" .. osc_styles.smallButtonsLlabel - .. " " .. sid .. "/" .. #tracks_osc.sub) - end - ne.eventresponder["mbtn_left_up"] = - function () set_track("sub", 1) end - ne.eventresponder["mbtn_right_up"] = - function () set_track("sub", -1) end - ne.eventresponder["shift+mbtn_left_down"] = - function () show_message(get_tracklist("sub"), 2) end - - if user_opts.scrollcontrols then - ne.eventresponder["wheel_down_press"] = - function () set_track("sub", 1) end - ne.eventresponder["wheel_up_press"] = - function () set_track("sub", -1) end - end - - --tog_fs - ne = new_element("tog_fs", "button") - ne.content = function () - if (state.fullscreen) then - return ("\238\132\137") - else - return ("\238\132\136") - end - end - ne.eventresponder["mbtn_left_up"] = - function () mp.commandv("cycle", "fullscreen") end - - --seekbar - ne = new_element("seekbar", "slider") - - ne.enabled = not (mp.get_property("percent-pos") == nil) - state.slider_element = ne.enabled and ne or nil -- used for forced_title - ne.slider.markerF = function () - local duration = mp.get_property_number("duration", nil) - if not (duration == nil) then - local chapters = mp.get_property_native("chapter-list", {}) - local markers = {} - for n = 1, #chapters do - markers[n] = (chapters[n].time / duration * 100) - end - return markers - else - return {} - end - end - ne.slider.posF = - function () return mp.get_property_number("percent-pos", nil) end - ne.slider.tooltipF = function (pos) - local duration = mp.get_property_number("duration", nil) - if not ((duration == nil) or (pos == nil)) then - possec = duration * (pos / 100) - return mp.format_time(possec) - else - return "" - end - end - ne.slider.seekRangesF = function() - if user_opts.seekrangestyle == "none" then - return nil - end - local cache_state = state.cache_state - if not cache_state then - return nil - end - local duration = mp.get_property_number("duration", nil) - if (duration == nil) or duration <= 0 then - return nil - end - local ranges = cache_state["seekable-ranges"] - if #ranges == 0 then - return nil - end - local nranges = {} - for _, range in pairs(ranges) do - nranges[#nranges + 1] = { - ["start"] = 100 * range["start"] / duration, - ["end"] = 100 * range["end"] / duration, - } - end - return nranges - end - ne.eventresponder["mouse_move"] = --keyframe seeking when mouse is dragged - function (element) - -- mouse move events may pile up during seeking and may still get - -- sent when the user is done seeking, so we need to throw away - -- identical seeks - local seekto = get_slider_value(element) - if (element.state.lastseek == nil) or - (not (element.state.lastseek == seekto)) then - local flags = "absolute-percent" - if not user_opts.seekbarkeyframes then - flags = flags .. "+exact" - end - mp.commandv("seek", seekto, flags) - element.state.lastseek = seekto - end - - end - ne.eventresponder["mbtn_left_down"] = --exact seeks on single clicks - function (element) mp.commandv("seek", get_slider_value(element), - "absolute-percent", "exact") end - ne.eventresponder["reset"] = - function (element) element.state.lastseek = nil end - - if user_opts.scrollcontrols then - ne.eventresponder["wheel_up_press"] = - function () mp.commandv("osd-auto", "seek", 10) end - ne.eventresponder["wheel_down_press"] = - function () mp.commandv("osd-auto", "seek", -10) end - end - - - -- tc_left (current pos) - ne = new_element("tc_left", "button") - - ne.content = function () - if (state.tc_ms) then - return (mp.get_property_osd("playback-time/full")) - else - return (mp.get_property_osd("playback-time")) - end - end - ne.eventresponder["mbtn_left_up"] = function () - state.tc_ms = not state.tc_ms - request_init() - end - - -- tc_right (total/remaining time) - ne = new_element("tc_right", "button") - - ne.visible = (mp.get_property_number("duration", 0) > 0) - ne.content = function () - if (state.rightTC_trem) then - local minus = user_opts.unicodeminus and UNICODE_MINUS or "-" - local property = user_opts.remaining_playtime and "playtime-remaining" - or "time-remaining" - if state.tc_ms then - return (minus..mp.get_property_osd(property .. "/full")) - else - return (minus..mp.get_property_osd(property)) - end - else - if state.tc_ms then - return (mp.get_property_osd("duration/full")) - else - return (mp.get_property_osd("duration")) - end - end - end - ne.eventresponder["mbtn_left_up"] = - function () state.rightTC_trem = not state.rightTC_trem end - - -- cache - ne = new_element("cache", "button") - - ne.content = function () - local cache_state = state.cache_state - if not (cache_state and cache_state["seekable-ranges"] and - #cache_state["seekable-ranges"] > 0) then - -- probably not a network stream - return "" - end - local dmx_cache = cache_state and cache_state["cache-duration"] - local thresh = math.min(state.dmx_cache * 0.05, 5) -- 5% or 5s - if dmx_cache and math.abs(dmx_cache - state.dmx_cache) >= thresh then - state.dmx_cache = dmx_cache - else - dmx_cache = state.dmx_cache - end - local min = math.floor(dmx_cache / 60) - local sec = math.floor(dmx_cache % 60) -- don't round e.g. 59.9 to 60 - return "Cache: " .. (min > 0 and - string.format("%sm%02.0fs", min, sec) or - string.format("%3.0fs", sec)) - end - - -- volume - ne = new_element("volume", "button") - - ne.content = function() - local volume = mp.get_property_number("volume", 0) - local mute = mp.get_property_native("mute") - local volicon = {"\238\132\139", "\238\132\140", - "\238\132\141", "\238\132\142"} - if volume == 0 or mute then - return "\238\132\138" - else - return volicon[math.min(4,math.ceil(volume / (100/3)))] - end - end - ne.eventresponder["mbtn_left_up"] = - function () mp.commandv("cycle", "mute") end - - if user_opts.scrollcontrols then - ne.eventresponder["wheel_up_press"] = - function () mp.commandv("osd-auto", "add", "volume", 5) end - ne.eventresponder["wheel_down_press"] = - function () mp.commandv("osd-auto", "add", "volume", -5) end - end - - - -- load layout - layouts[user_opts.layout]() - - -- load window controls - if window_controls_enabled() then - window_controls(user_opts.layout == "topbar") - end - - --do something with the elements - prepare_elements() - - update_margins() + msg.debug("osc_init") + + -- set canvas resolution according to display aspect and scaling setting + local baseResY = 720 + local display_w, display_h, display_aspect = mp.get_osd_size() + local scale = 1 + + if mp.get_property("video") == "no" then -- dummy/forced window + scale = user_opts.scaleforcedwindow + elseif state.fullscreen then + scale = user_opts.scalefullscreen + else + scale = user_opts.scalewindowed + end + + if user_opts.vidscale then + osc_param.unscaled_y = baseResY + else + osc_param.unscaled_y = display_h + end + osc_param.playresy = osc_param.unscaled_y / scale + if display_aspect > 0 then + osc_param.display_aspect = display_aspect + end + osc_param.playresx = osc_param.playresy * osc_param.display_aspect + + -- stop seeking with the slider to prevent skipping files + state.active_element = nil + + osc_param.video_margins = { l = 0, r = 0, t = 0, b = 0 } + + elements = {} + + -- some often needed stuff + local pl_count = mp.get_property_number("playlist-count", 0) + local have_pl = (pl_count > 1) + local pl_pos = mp.get_property_number("playlist-pos", 0) + 1 + local have_ch = (mp.get_property_number("chapters", 0) > 0) + local loop = mp.get_property("loop-playlist", "no") + + local ne + + -- title + ne = new_element("title", "button") + + ne.content = function() + local title = state.forced_title or mp.command_native({ "expand-text", user_opts.title }) + -- escape ASS, and strip newlines and trailing slashes + title = title:gsub("\\n", " "):gsub("\\$", ""):gsub("{", "\\{") + return not (title == "") and title or "mpv" + end + + ne.eventresponder["mbtn_left_up"] = function() + local title = mp.get_property_osd("media-title") + if have_pl then + title = string.format("[%d/%d] %s", countone(pl_pos - 1), pl_count, title) + end + show_message(title) + end + + ne.eventresponder["mbtn_right_up"] = function() + show_message(mp.get_property_osd("filename")) + end + + -- playlist buttons + + -- prev + ne = new_element("pl_prev", "button") + + ne.content = "\238\132\144" + ne.enabled = (pl_pos > 1) or (loop ~= "no") + ne.eventresponder["mbtn_left_up"] = function() + mp.commandv("playlist-prev", "weak") + if user_opts.playlist_osd then + show_message(get_playlist(), 3) + end + end + ne.eventresponder["shift+mbtn_left_up"] = function() + show_message(get_playlist(), 3) + end + ne.eventresponder["mbtn_right_up"] = function() + show_message(get_playlist(), 3) + end + + --next + ne = new_element("pl_next", "button") + + ne.content = "\238\132\129" + ne.enabled = (have_pl and (pl_pos < pl_count)) or (loop ~= "no") + ne.eventresponder["mbtn_left_up"] = function() + mp.commandv("playlist-next", "weak") + if user_opts.playlist_osd then + show_message(get_playlist(), 3) + end + end + ne.eventresponder["shift+mbtn_left_up"] = function() + show_message(get_playlist(), 3) + end + ne.eventresponder["mbtn_right_up"] = function() + show_message(get_playlist(), 3) + end + + -- big buttons + + --playpause + ne = new_element("playpause", "button") + + ne.content = function() + if mp.get_property("pause") == "yes" then + return "\238\132\129" + else + return "\238\128\130" + end + end + ne.eventresponder["mbtn_left_up"] = function() + mp.commandv("cycle", "pause") + end + + --skipback + ne = new_element("skipback", "button") + + ne.softrepeat = true + ne.content = "\238\128\132" + ne.eventresponder["mbtn_left_down"] = function() + mp.commandv("seek", -5, "relative", "keyframes") + end + ne.eventresponder["shift+mbtn_left_down"] = function() + mp.commandv("frame-back-step") + end + ne.eventresponder["mbtn_right_down"] = function() + mp.commandv("seek", -30, "relative", "keyframes") + end + + --skipfrwd + ne = new_element("skipfrwd", "button") + + ne.softrepeat = true + ne.content = "\238\128\133" + ne.eventresponder["mbtn_left_down"] = function() + mp.commandv("seek", 10, "relative", "keyframes") + end + ne.eventresponder["shift+mbtn_left_down"] = function() + mp.commandv("frame-step") + end + ne.eventresponder["mbtn_right_down"] = function() + mp.commandv("seek", 60, "relative", "keyframes") + end + + --ch_prev + ne = new_element("ch_prev", "button") + + ne.enabled = have_ch + ne.content = "\238\132\132" + ne.eventresponder["mbtn_left_up"] = function() + mp.commandv("add", "chapter", -1) + if user_opts.chapters_osd then + show_message(get_chapterlist(), 3) + end + end + ne.eventresponder["shift+mbtn_left_up"] = function() + show_message(get_chapterlist(), 3) + end + ne.eventresponder["mbtn_right_up"] = function() + show_message(get_chapterlist(), 3) + end + + --ch_next + ne = new_element("ch_next", "button") + + ne.enabled = have_ch + ne.content = "\238\132\133" + ne.eventresponder["mbtn_left_up"] = function() + mp.commandv("add", "chapter", 1) + if user_opts.chapters_osd then + show_message(get_chapterlist(), 3) + end + end + ne.eventresponder["shift+mbtn_left_up"] = function() + show_message(get_chapterlist(), 3) + end + ne.eventresponder["mbtn_right_up"] = function() + show_message(get_chapterlist(), 3) + end + + -- + update_tracklist() + + --cy_audio + ne = new_element("cy_audio", "button") + + ne.enabled = (#tracks_osc.audio > 0) + ne.content = function() + local aid = "–" + if not (get_track("audio") == 0) then + aid = get_track("audio") + end + return ("\238\132\134" .. osc_styles.smallButtonsLlabel .. " " .. aid .. "/" .. #tracks_osc.audio) + end + ne.eventresponder["mbtn_left_up"] = function() + set_track("audio", 1) + end + ne.eventresponder["mbtn_right_up"] = function() + set_track("audio", -1) + end + ne.eventresponder["shift+mbtn_left_down"] = function() + show_message(get_tracklist("audio"), 2) + end + + if user_opts.scrollcontrols then + ne.eventresponder["wheel_down_press"] = function() + set_track("audio", 1) + end + ne.eventresponder["wheel_up_press"] = function() + set_track("audio", -1) + end + end + + --cy_sub + ne = new_element("cy_sub", "button") + + ne.enabled = (#tracks_osc.sub > 0) + ne.content = function() + local sid = "–" + if not (get_track("sub") == 0) then + sid = get_track("sub") + end + return ("\238\132\135" .. osc_styles.smallButtonsLlabel .. " " .. sid .. "/" .. #tracks_osc.sub) + end + ne.eventresponder["mbtn_left_up"] = function() + set_track("sub", 1) + end + ne.eventresponder["mbtn_right_up"] = function() + set_track("sub", -1) + end + ne.eventresponder["shift+mbtn_left_down"] = function() + show_message(get_tracklist("sub"), 2) + end + + if user_opts.scrollcontrols then + ne.eventresponder["wheel_down_press"] = function() + set_track("sub", 1) + end + ne.eventresponder["wheel_up_press"] = function() + set_track("sub", -1) + end + end + + --tog_fs + ne = new_element("tog_fs", "button") + ne.content = function() + if state.fullscreen then + return "\238\132\137" + else + return "\238\132\136" + end + end + ne.eventresponder["mbtn_left_up"] = function() + mp.commandv("cycle", "fullscreen") + end + + --seekbar + ne = new_element("seekbar", "slider") + + ne.enabled = not (mp.get_property("percent-pos") == nil) + state.slider_element = ne.enabled and ne or nil -- used for forced_title + ne.slider.markerF = function() + local duration = mp.get_property_number("duration", nil) + if not (duration == nil) then + local chapters = mp.get_property_native("chapter-list", {}) + local markers = {} + for n = 1, #chapters do + markers[n] = (chapters[n].time / duration * 100) + end + return markers + else + return {} + end + end + ne.slider.posF = function() + return mp.get_property_number("percent-pos", nil) + end + ne.slider.tooltipF = function(pos) + local duration = mp.get_property_number("duration", nil) + if not ((duration == nil) or (pos == nil)) then + possec = duration * (pos / 100) + return mp.format_time(possec) + else + return "" + end + end + ne.slider.seekRangesF = function() + if user_opts.seekrangestyle == "none" then + return nil + end + local cache_state = state.cache_state + if not cache_state then + return nil + end + local duration = mp.get_property_number("duration", nil) + if (duration == nil) or duration <= 0 then + return nil + end + local ranges = cache_state["seekable-ranges"] + if #ranges == 0 then + return nil + end + local nranges = {} + for _, range in pairs(ranges) do + nranges[#nranges + 1] = { + ["start"] = 100 * range["start"] / duration, + ["end"] = 100 * range["end"] / duration, + } + end + return nranges + end + ne.eventresponder["mouse_move"] = --keyframe seeking when mouse is dragged + function(element) + -- mouse move events may pile up during seeking and may still get + -- sent when the user is done seeking, so we need to throw away + -- identical seeks + local seekto = get_slider_value(element) + if (element.state.lastseek == nil) or not (element.state.lastseek == seekto) then + local flags = "absolute-percent" + if not user_opts.seekbarkeyframes then + flags = flags .. "+exact" + end + mp.commandv("seek", seekto, flags) + element.state.lastseek = seekto + end + end + ne.eventresponder["mbtn_left_down"] = --exact seeks on single clicks + function(element) + mp.commandv("seek", get_slider_value(element), "absolute-percent", "exact") + end + ne.eventresponder["reset"] = function(element) + element.state.lastseek = nil + end + + if user_opts.scrollcontrols then + ne.eventresponder["wheel_up_press"] = function() + mp.commandv("osd-auto", "seek", 10) + end + ne.eventresponder["wheel_down_press"] = function() + mp.commandv("osd-auto", "seek", -10) + end + end + + -- tc_left (current pos) + ne = new_element("tc_left", "button") + + ne.content = function() + if state.tc_ms then + return (mp.get_property_osd("playback-time/full")) + else + return (mp.get_property_osd("playback-time")) + end + end + ne.eventresponder["mbtn_left_up"] = function() + state.tc_ms = not state.tc_ms + request_init() + end + + -- tc_right (total/remaining time) + ne = new_element("tc_right", "button") + + ne.visible = (mp.get_property_number("duration", 0) > 0) + ne.content = function() + if state.rightTC_trem then + local minus = user_opts.unicodeminus and UNICODE_MINUS or "-" + local property = user_opts.remaining_playtime and "playtime-remaining" or "time-remaining" + if state.tc_ms then + return (minus .. mp.get_property_osd(property .. "/full")) + else + return (minus .. mp.get_property_osd(property)) + end + else + if state.tc_ms then + return (mp.get_property_osd("duration/full")) + else + return (mp.get_property_osd("duration")) + end + end + end + ne.eventresponder["mbtn_left_up"] = function() + state.rightTC_trem = not state.rightTC_trem + end + + -- cache + ne = new_element("cache", "button") + + ne.content = function() + local cache_state = state.cache_state + if not (cache_state and cache_state["seekable-ranges"] and #cache_state["seekable-ranges"] > 0) then + -- probably not a network stream + return "" + end + local dmx_cache = cache_state and cache_state["cache-duration"] + local thresh = math.min(state.dmx_cache * 0.05, 5) -- 5% or 5s + if dmx_cache and math.abs(dmx_cache - state.dmx_cache) >= thresh then + state.dmx_cache = dmx_cache + else + dmx_cache = state.dmx_cache + end + local min = math.floor(dmx_cache / 60) + local sec = math.floor(dmx_cache % 60) -- don't round e.g. 59.9 to 60 + return "Cache: " .. (min > 0 and string.format("%sm%02.0fs", min, sec) or string.format("%3.0fs", sec)) + end + + -- volume + ne = new_element("volume", "button") + + ne.content = function() + local volume = mp.get_property_number("volume", 0) + local mute = mp.get_property_native("mute") + local volicon = { "\238\132\139", "\238\132\140", "\238\132\141", "\238\132\142" } + if volume == 0 or mute then + return "\238\132\138" + else + return volicon[math.min(4, math.ceil(volume / (100 / 3)))] + end + end + ne.eventresponder["mbtn_left_up"] = function() + mp.commandv("cycle", "mute") + end + + if user_opts.scrollcontrols then + ne.eventresponder["wheel_up_press"] = function() + mp.commandv("osd-auto", "add", "volume", 5) + end + ne.eventresponder["wheel_down_press"] = function() + mp.commandv("osd-auto", "add", "volume", -5) + end + end + + -- load layout + layouts[user_opts.layout]() + + -- load window controls + if window_controls_enabled() then + window_controls(user_opts.layout == "topbar") + end + + --do something with the elements + prepare_elements() + + update_margins() end function reset_margins() - if state.using_video_margins then - for _, opt in ipairs(margins_opts) do - mp.set_property_number(opt[2], 0.0) - end - state.using_video_margins = false - end + if state.using_video_margins then + for _, opt in ipairs(margins_opts) do + mp.set_property_number(opt[2], 0.0) + end + state.using_video_margins = false + end end function update_margins() - local margins = osc_param.video_margins - - -- Don't use margins if it's visible only temporarily. - if (not state.osc_visible) or (get_hidetimeout() >= 0) or - (state.fullscreen and not user_opts.showfullscreen) or - (not state.fullscreen and not user_opts.showwindowed) - then - margins = {l = 0, r = 0, t = 0, b = 0} - end - - if user_opts.boxvideo then - -- check whether any margin option has a non-default value - local margins_used = false - - if not state.using_video_margins then - for _, opt in ipairs(margins_opts) do - if mp.get_property_number(opt[2], 0.0) ~= 0.0 then - margins_used = true - end - end - end - - if not margins_used then - for _, opt in ipairs(margins_opts) do - local v = margins[opt[1]] - if (v ~= 0) or state.using_video_margins then - mp.set_property_number(opt[2], v) - state.using_video_margins = true - end - end - end - else - reset_margins() - end - - if mp.del_property then - mp.set_property_native("user-data/osc/margins", margins) - else - utils.shared_script_property_set("osc-margins", - string.format("%f,%f,%f,%f", margins.l, margins.r, margins.t, margins.b)) - end + local margins = osc_param.video_margins + + -- Don't use margins if it's visible only temporarily. + if + not state.osc_visible + or (get_hidetimeout() >= 0) + or (state.fullscreen and not user_opts.showfullscreen) + or (not state.fullscreen and not user_opts.showwindowed) + then + margins = { l = 0, r = 0, t = 0, b = 0 } + end + + if user_opts.boxvideo then + -- check whether any margin option has a non-default value + local margins_used = false + + if not state.using_video_margins then + for _, opt in ipairs(margins_opts) do + if mp.get_property_number(opt[2], 0.0) ~= 0.0 then + margins_used = true + end + end + end + + if not margins_used then + for _, opt in ipairs(margins_opts) do + local v = margins[opt[1]] + if (v ~= 0) or state.using_video_margins then + mp.set_property_number(opt[2], v) + state.using_video_margins = true + end + end + end + else + reset_margins() + end + + if mp.del_property then + mp.set_property_native("user-data/osc/margins", margins) + else + utils.shared_script_property_set( + "osc-margins", + string.format("%f,%f,%f,%f", margins.l, margins.r, margins.t, margins.b) + ) + end end function shutdown() - reset_margins() - if mp.del_property then - mp.del_property("user-data/osc") - else - utils.shared_script_property_set("osc-margins", nil) - end + reset_margins() + if mp.del_property then + mp.del_property("user-data/osc") + else + utils.shared_script_property_set("osc-margins", nil) + end end -- -- Other important stuff -- - function show_osc() - -- show when disabled can happen (e.g. mouse_move) due to async/delayed unbinding - if not state.enabled then return end + -- show when disabled can happen (e.g. mouse_move) due to async/delayed unbinding + if not state.enabled then + return + end - msg.trace("show_osc") - --remember last time of invocation (mouse move) - state.showtime = mp.get_time() + msg.trace("show_osc") + --remember last time of invocation (mouse move) + state.showtime = mp.get_time() - osc_visible(true) + osc_visible(true) - if (user_opts.fadeduration > 0) then - state.anitype = nil - end + if user_opts.fadeduration > 0 then + state.anitype = nil + end end function hide_osc() - msg.trace("hide_osc") - if thumbfast.width ~= 0 and thumbfast.height ~= 0 then - mp.commandv("script-message-to", "thumbfast", "clear") - end - if not state.enabled then - -- typically hide happens at render() from tick(), but now tick() is - -- no-op and won't render again to remove the osc, so do that manually. - state.osc_visible = false - render_wipe() - elseif (user_opts.fadeduration > 0) then - if not(state.osc_visible == false) then - state.anitype = "out" - request_tick() - end - else - osc_visible(false) - end + msg.trace("hide_osc") + if thumbfast.width ~= 0 and thumbfast.height ~= 0 then + mp.commandv("script-message-to", "thumbfast", "clear") + end + if not state.enabled then + -- typically hide happens at render() from tick(), but now tick() is + -- no-op and won't render again to remove the osc, so do that manually. + state.osc_visible = false + render_wipe() + elseif user_opts.fadeduration > 0 then + if not (state.osc_visible == false) then + state.anitype = "out" + request_tick() + end + else + osc_visible(false) + end end function osc_visible(visible) - if state.osc_visible ~= visible then - state.osc_visible = visible - update_margins() - end - request_tick() + if state.osc_visible ~= visible then + state.osc_visible = visible + update_margins() + end + request_tick() end function pause_state(name, enabled) - state.paused = enabled - request_tick() + state.paused = enabled + request_tick() end function cache_state(name, st) - state.cache_state = st - request_tick() + state.cache_state = st + request_tick() end -- Request that tick() is called (which typically re-renders the OSC). -- The tick is then either executed immediately, or rate-limited if it was -- called a small time ago. function request_tick() - if state.tick_timer == nil then - state.tick_timer = mp.add_timeout(0, tick) - end + if state.tick_timer == nil then + state.tick_timer = mp.add_timeout(0, tick) + end - if not state.tick_timer:is_enabled() then - local now = mp.get_time() - local timeout = tick_delay - (now - state.tick_last_time) - if timeout < 0 then - timeout = 0 - end - state.tick_timer.timeout = timeout - state.tick_timer:resume() - end + if not state.tick_timer:is_enabled() then + local now = mp.get_time() + local timeout = tick_delay - (now - state.tick_last_time) + if timeout < 0 then + timeout = 0 + end + state.tick_timer.timeout = timeout + state.tick_timer:resume() + end end function mouse_leave() - if get_hidetimeout() >= 0 then - hide_osc() - end - -- reset mouse position - state.last_mouseX, state.last_mouseY = nil, nil - state.mouse_in_window = false + if get_hidetimeout() >= 0 then + hide_osc() + end + -- reset mouse position + state.last_mouseX, state.last_mouseY = nil, nil + state.mouse_in_window = false end function request_init() - state.initREQ = true - request_tick() + state.initREQ = true + request_tick() end -- Like request_init(), but also request an immediate update function request_init_resize() - request_init() - -- ensure immediate update - state.tick_timer:kill() - state.tick_timer.timeout = 0 - state.tick_timer:resume() + request_init() + -- ensure immediate update + state.tick_timer:kill() + state.tick_timer.timeout = 0 + state.tick_timer:resume() end function render_wipe() - msg.trace("render_wipe()") - if state.osd then - state.osd.data = "" -- allows set_osd to immediately update on enable - state.osd:remove() - else - set_osd(0, 0, "{}") - end + msg.trace("render_wipe()") + if state.osd then + state.osd.data = "" -- allows set_osd to immediately update on enable + state.osd:remove() + else + set_osd(0, 0, "{}") + end end function render() - msg.trace("rendering") - local current_screen_sizeX, current_screen_sizeY, aspect = mp.get_osd_size() - local mouseX, mouseY = get_virt_mouse_pos() - local now = mp.get_time() - - -- check if display changed, if so request reinit - if not (state.mp_screen_sizeX == current_screen_sizeX - and state.mp_screen_sizeY == current_screen_sizeY) then - - request_init_resize() - - state.mp_screen_sizeX = current_screen_sizeX - state.mp_screen_sizeY = current_screen_sizeY - end - - -- init management - if state.active_element then - -- mouse is held down on some element - keep ticking and ignore initReq - -- till it's released, or else the mouse-up (click) will misbehave or - -- get ignored. that's because osc_init() recreates the osc elements, - -- but mouse handling depends on the elements staying unmodified - -- between mouse-down and mouse-up (using the index active_element). - request_tick() - elseif state.initREQ then - osc_init() - state.initREQ = false - - -- store initial mouse position - if (state.last_mouseX == nil or state.last_mouseY == nil) - and not (mouseX == nil or mouseY == nil) then - - state.last_mouseX, state.last_mouseY = mouseX, mouseY - end - end - - - -- fade animation - if not(state.anitype == nil) then - - if (state.anistart == nil) then - state.anistart = now - end - - if (now < state.anistart + (user_opts.fadeduration/1000)) then - - if (state.anitype == "in") then --fade in - osc_visible(true) - state.animation = scale_value(state.anistart, - (state.anistart + (user_opts.fadeduration/1000)), - 255, 0, now) - elseif (state.anitype == "out") then --fade out - state.animation = scale_value(state.anistart, - (state.anistart + (user_opts.fadeduration/1000)), - 0, 255, now) - end - - else - if (state.anitype == "out") then - osc_visible(false) - end - kill_animation() - end - else - kill_animation() - end - - --mouse show/hide area - for k,cords in pairs(osc_param.areas["showhide"]) do - set_virt_mouse_area(cords.x1, cords.y1, cords.x2, cords.y2, "thumbfast-osc-showhide") - end - if osc_param.areas["showhide_wc"] then - for k,cords in pairs(osc_param.areas["showhide_wc"]) do - set_virt_mouse_area(cords.x1, cords.y1, cords.x2, cords.y2, "thumbfast-osc-showhide_wc") - end - else - set_virt_mouse_area(0, 0, 0, 0, "thumbfast-osc-showhide_wc") - end - do_enable_keybindings() - - --mouse input area - local mouse_over_osc = false - - for _,cords in ipairs(osc_param.areas["input"]) do - if state.osc_visible then -- activate only when OSC is actually visible - set_virt_mouse_area(cords.x1, cords.y1, cords.x2, cords.y2, "thumbfast-osc-input") - end - if state.osc_visible ~= state.input_enabled then - if state.osc_visible then - mp.enable_key_bindings("thumbfast-osc-input") - else - mp.disable_key_bindings("thumbfast-osc-input") - end - state.input_enabled = state.osc_visible - end - - if (mouse_hit_coords(cords.x1, cords.y1, cords.x2, cords.y2)) then - mouse_over_osc = true - end - end - - if osc_param.areas["window-controls"] then - for _,cords in ipairs(osc_param.areas["window-controls"]) do - if state.osc_visible then -- activate only when OSC is actually visible - set_virt_mouse_area(cords.x1, cords.y1, cords.x2, cords.y2, "thumbfast-osc-window-controls") - end - if state.osc_visible ~= state.windowcontrols_buttons then - if state.osc_visible then - mp.enable_key_bindings("thumbfast-osc-window-controls") - else - mp.disable_key_bindings("thumbfast-osc-window-controls") - end - state.windowcontrols_buttons = state.osc_visible - end - - if (mouse_hit_coords(cords.x1, cords.y1, cords.x2, cords.y2)) then - mouse_over_osc = true - end - end - end - - if osc_param.areas["window-controls-title"] then - for _,cords in ipairs(osc_param.areas["window-controls-title"]) do - if (mouse_hit_coords(cords.x1, cords.y1, cords.x2, cords.y2)) then - mouse_over_osc = true - end - end - end - - -- autohide - if not (state.showtime == nil) and (get_hidetimeout() >= 0) then - local timeout = state.showtime + (get_hidetimeout()/1000) - now - if timeout <= 0 then - if (state.active_element == nil) and not (mouse_over_osc) then - hide_osc() - end - else - -- the timer is only used to recheck the state and to possibly run - -- the code above again - if not state.hide_timer then - state.hide_timer = mp.add_timeout(0, tick) - end - state.hide_timer.timeout = timeout - -- re-arm - state.hide_timer:kill() - state.hide_timer:resume() - end - end - - - -- actual rendering - local ass = assdraw.ass_new() - - -- Messages - render_message(ass) - - -- actual OSC - if state.osc_visible then - render_elements(ass) - end - - -- submit - set_osd(osc_param.playresy * osc_param.display_aspect, - osc_param.playresy, ass.text, 1000) + msg.trace("rendering") + local current_screen_sizeX, current_screen_sizeY, aspect = mp.get_osd_size() + local mouseX, mouseY = get_virt_mouse_pos() + local now = mp.get_time() + + -- check if display changed, if so request reinit + if not (state.mp_screen_sizeX == current_screen_sizeX and state.mp_screen_sizeY == current_screen_sizeY) then + request_init_resize() + + state.mp_screen_sizeX = current_screen_sizeX + state.mp_screen_sizeY = current_screen_sizeY + end + + -- init management + if state.active_element then + -- mouse is held down on some element - keep ticking and ignore initReq + -- till it's released, or else the mouse-up (click) will misbehave or + -- get ignored. that's because osc_init() recreates the osc elements, + -- but mouse handling depends on the elements staying unmodified + -- between mouse-down and mouse-up (using the index active_element). + request_tick() + elseif state.initREQ then + osc_init() + state.initREQ = false + + -- store initial mouse position + if (state.last_mouseX == nil or state.last_mouseY == nil) and not (mouseX == nil or mouseY == nil) then + state.last_mouseX, state.last_mouseY = mouseX, mouseY + end + end + + -- fade animation + if not (state.anitype == nil) then + if state.anistart == nil then + state.anistart = now + end + + if now < state.anistart + (user_opts.fadeduration / 1000) then + if state.anitype == "in" then --fade in + osc_visible(true) + state.animation = + scale_value(state.anistart, (state.anistart + (user_opts.fadeduration / 1000)), 255, 0, now) + elseif state.anitype == "out" then --fade out + state.animation = + scale_value(state.anistart, (state.anistart + (user_opts.fadeduration / 1000)), 0, 255, now) + end + else + if state.anitype == "out" then + osc_visible(false) + end + kill_animation() + end + else + kill_animation() + end + + --mouse show/hide area + for k, cords in pairs(osc_param.areas["showhide"]) do + set_virt_mouse_area(cords.x1, cords.y1, cords.x2, cords.y2, "thumbfast-osc-showhide") + end + if osc_param.areas["showhide_wc"] then + for k, cords in pairs(osc_param.areas["showhide_wc"]) do + set_virt_mouse_area(cords.x1, cords.y1, cords.x2, cords.y2, "thumbfast-osc-showhide_wc") + end + else + set_virt_mouse_area(0, 0, 0, 0, "thumbfast-osc-showhide_wc") + end + do_enable_keybindings() + + --mouse input area + local mouse_over_osc = false + + for _, cords in ipairs(osc_param.areas["input"]) do + if state.osc_visible then -- activate only when OSC is actually visible + set_virt_mouse_area(cords.x1, cords.y1, cords.x2, cords.y2, "thumbfast-osc-input") + end + if state.osc_visible ~= state.input_enabled then + if state.osc_visible then + mp.enable_key_bindings("thumbfast-osc-input") + else + mp.disable_key_bindings("thumbfast-osc-input") + end + state.input_enabled = state.osc_visible + end + + if mouse_hit_coords(cords.x1, cords.y1, cords.x2, cords.y2) then + mouse_over_osc = true + end + end + + if osc_param.areas["window-controls"] then + for _, cords in ipairs(osc_param.areas["window-controls"]) do + if state.osc_visible then -- activate only when OSC is actually visible + set_virt_mouse_area(cords.x1, cords.y1, cords.x2, cords.y2, "thumbfast-osc-window-controls") + end + if state.osc_visible ~= state.windowcontrols_buttons then + if state.osc_visible then + mp.enable_key_bindings("thumbfast-osc-window-controls") + else + mp.disable_key_bindings("thumbfast-osc-window-controls") + end + state.windowcontrols_buttons = state.osc_visible + end + + if mouse_hit_coords(cords.x1, cords.y1, cords.x2, cords.y2) then + mouse_over_osc = true + end + end + end + + if osc_param.areas["window-controls-title"] then + for _, cords in ipairs(osc_param.areas["window-controls-title"]) do + if mouse_hit_coords(cords.x1, cords.y1, cords.x2, cords.y2) then + mouse_over_osc = true + end + end + end + + -- autohide + if not (state.showtime == nil) and (get_hidetimeout() >= 0) then + local timeout = state.showtime + (get_hidetimeout() / 1000) - now + if timeout <= 0 then + if (state.active_element == nil) and not mouse_over_osc then + hide_osc() + end + else + -- the timer is only used to recheck the state and to possibly run + -- the code above again + if not state.hide_timer then + state.hide_timer = mp.add_timeout(0, tick) + end + state.hide_timer.timeout = timeout + -- re-arm + state.hide_timer:kill() + state.hide_timer:resume() + end + end + + -- actual rendering + local ass = assdraw.ass_new() + + -- Messages + render_message(ass) + + -- actual OSC + if state.osc_visible then + render_elements(ass) + end + + -- submit + set_osd(osc_param.playresy * osc_param.display_aspect, osc_param.playresy, ass.text, 1000) end -- @@ -2662,234 +2682,222 @@ end -- local function element_has_action(element, action) - return element and element.eventresponder and - element.eventresponder[action] + return element and element.eventresponder and element.eventresponder[action] end function process_event(source, what) - local action = string.format("%s%s", source, - what and ("_" .. what) or "") - - if what == "down" or what == "press" then - - for n = 1, #elements do - - if mouse_hit(elements[n]) and - elements[n].eventresponder and - (elements[n].eventresponder[source .. "_up"] or - elements[n].eventresponder[action]) then - - if what == "down" then - state.active_element = n - state.active_event_source = source - end - -- fire the down or press event if the element has one - if element_has_action(elements[n], action) then - elements[n].eventresponder[action](elements[n]) - end - - end - end - - elseif what == "up" then - - if elements[state.active_element] then - local n = state.active_element - - if n == 0 then - --click on background (does not work) - elseif element_has_action(elements[n], action) and - mouse_hit(elements[n]) then - - elements[n].eventresponder[action](elements[n]) - end - - --reset active element - if element_has_action(elements[n], "reset") then - elements[n].eventresponder["reset"](elements[n]) - end - - end - state.active_element = nil - state.mouse_down_counter = 0 - - elseif source == "mouse_move" then - - state.mouse_in_window = true - - local mouseX, mouseY = get_virt_mouse_pos() - if (user_opts.minmousemove == 0) or - (not ((state.last_mouseX == nil) or (state.last_mouseY == nil)) and - ((math.abs(mouseX - state.last_mouseX) >= user_opts.minmousemove) - or (math.abs(mouseY - state.last_mouseY) >= user_opts.minmousemove) - ) - ) then - show_osc() - end - state.last_mouseX, state.last_mouseY = mouseX, mouseY - - local n = state.active_element - if element_has_action(elements[n], action) then - elements[n].eventresponder[action](elements[n]) - end - end - - -- ensure rendering after any (mouse) event - icons could change etc - request_tick() + local action = string.format("%s%s", source, what and ("_" .. what) or "") + + if what == "down" or what == "press" then + for n = 1, #elements do + if + mouse_hit(elements[n]) + and elements[n].eventresponder + and (elements[n].eventresponder[source .. "_up"] or elements[n].eventresponder[action]) + then + if what == "down" then + state.active_element = n + state.active_event_source = source + end + -- fire the down or press event if the element has one + if element_has_action(elements[n], action) then + elements[n].eventresponder[action](elements[n]) + end + end + end + elseif what == "up" then + if elements[state.active_element] then + local n = state.active_element + + if n == 0 then + --click on background (does not work) + elseif element_has_action(elements[n], action) and mouse_hit(elements[n]) then + elements[n].eventresponder[action](elements[n]) + end + + --reset active element + if element_has_action(elements[n], "reset") then + elements[n].eventresponder["reset"](elements[n]) + end + end + state.active_element = nil + state.mouse_down_counter = 0 + elseif source == "mouse_move" then + state.mouse_in_window = true + + local mouseX, mouseY = get_virt_mouse_pos() + if + (user_opts.minmousemove == 0) + or ( + not ((state.last_mouseX == nil) or (state.last_mouseY == nil)) + and ( + (math.abs(mouseX - state.last_mouseX) >= user_opts.minmousemove) + or (math.abs(mouseY - state.last_mouseY) >= user_opts.minmousemove) + ) + ) + then + show_osc() + end + state.last_mouseX, state.last_mouseY = mouseX, mouseY + + local n = state.active_element + if element_has_action(elements[n], action) then + elements[n].eventresponder[action](elements[n]) + end + end + + -- ensure rendering after any (mouse) event - icons could change etc + request_tick() end - local logo_lines = { - -- White border - "{\\c&HE5E5E5&\\p6}m 895 10 b 401 10 0 410 0 905 0 1399 401 1800 895 1800 1390 1800 1790 1399 1790 905 1790 410 1390 10 895 10 {\\p0}", - -- Purple fill - "{\\c&H682167&\\p6}m 925 42 b 463 42 87 418 87 880 87 1343 463 1718 925 1718 1388 1718 1763 1343 1763 880 1763 418 1388 42 925 42{\\p0}", - -- Darker fill - "{\\c&H430142&\\p6}m 1605 828 b 1605 1175 1324 1456 977 1456 631 1456 349 1175 349 828 349 482 631 200 977 200 1324 200 1605 482 1605 828{\\p0}", - -- White fill - "{\\c&HDDDBDD&\\p6}m 1296 910 b 1296 1131 1117 1310 897 1310 676 1310 497 1131 497 910 497 689 676 511 897 511 1117 511 1296 689 1296 910{\\p0}", - -- Triangle - "{\\c&H691F69&\\p6}m 762 1113 l 762 708 b 881 776 1000 843 1119 911 1000 978 881 1046 762 1113{\\p0}", + -- White border + "{\\c&HE5E5E5&\\p6}m 895 10 b 401 10 0 410 0 905 0 1399 401 1800 895 1800 1390 1800 1790 1399 1790 905 1790 410 1390 10 895 10 {\\p0}", + -- Purple fill + "{\\c&H682167&\\p6}m 925 42 b 463 42 87 418 87 880 87 1343 463 1718 925 1718 1388 1718 1763 1343 1763 880 1763 418 1388 42 925 42{\\p0}", + -- Darker fill + "{\\c&H430142&\\p6}m 1605 828 b 1605 1175 1324 1456 977 1456 631 1456 349 1175 349 828 349 482 631 200 977 200 1324 200 1605 482 1605 828{\\p0}", + -- White fill + "{\\c&HDDDBDD&\\p6}m 1296 910 b 1296 1131 1117 1310 897 1310 676 1310 497 1131 497 910 497 689 676 511 897 511 1117 511 1296 689 1296 910{\\p0}", + -- Triangle + "{\\c&H691F69&\\p6}m 762 1113 l 762 708 b 881 776 1000 843 1119 911 1000 978 881 1046 762 1113{\\p0}", } local santa_hat_lines = { - -- Pompoms - "{\\c&HC0C0C0&\\p6}m 500 -323 b 491 -322 481 -318 475 -311 465 -312 456 -319 446 -318 434 -314 427 -304 417 -297 410 -290 404 -282 395 -278 390 -274 387 -267 381 -265 377 -261 379 -254 384 -253 397 -244 409 -232 425 -228 437 -228 446 -218 457 -217 462 -216 466 -213 468 -209 471 -205 477 -203 482 -206 491 -211 499 -217 508 -222 532 -235 556 -249 576 -267 584 -272 584 -284 578 -290 569 -305 550 -312 533 -309 523 -310 515 -316 507 -321 505 -323 503 -323 500 -323{\\p0}", - "{\\c&HE0E0E0&\\p6}m 315 -260 b 286 -258 259 -240 246 -215 235 -210 222 -215 211 -211 204 -188 177 -176 172 -151 170 -139 163 -128 154 -121 143 -103 141 -81 143 -60 139 -46 125 -34 129 -17 132 -1 134 16 142 30 145 56 161 80 181 96 196 114 210 133 231 144 266 153 303 138 328 115 373 79 401 28 423 -24 446 -73 465 -123 483 -174 487 -199 467 -225 442 -227 421 -232 402 -242 384 -254 364 -259 342 -250 322 -260 320 -260 317 -261 315 -260{\\p0}", - -- Main cap - "{\\c&H0000F0&\\p6}m 1151 -523 b 1016 -516 891 -458 769 -406 693 -369 624 -319 561 -262 526 -252 465 -235 479 -187 502 -147 551 -135 588 -111 1115 165 1379 232 1909 761 1926 800 1952 834 1987 858 2020 883 2053 912 2065 952 2088 1000 2146 962 2139 919 2162 836 2156 747 2143 662 2131 615 2116 567 2122 517 2120 410 2090 306 2089 199 2092 147 2071 99 2034 64 1987 5 1928 -41 1869 -86 1777 -157 1712 -256 1629 -337 1578 -389 1521 -436 1461 -476 1407 -509 1343 -507 1284 -515 1240 -519 1195 -521 1151 -523{\\p0}", - -- Cap shadow - "{\\c&H0000AA&\\p6}m 1657 248 b 1658 254 1659 261 1660 267 1669 276 1680 284 1689 293 1695 302 1700 311 1707 320 1716 325 1726 330 1735 335 1744 347 1752 360 1761 371 1753 352 1754 331 1753 311 1751 237 1751 163 1751 90 1752 64 1752 37 1767 14 1778 -3 1785 -24 1786 -45 1786 -60 1786 -77 1774 -87 1760 -96 1750 -78 1751 -65 1748 -37 1750 -8 1750 20 1734 78 1715 134 1699 192 1694 211 1689 231 1676 246 1671 251 1661 255 1657 248 m 1909 541 b 1914 542 1922 549 1917 539 1919 520 1921 502 1919 483 1918 458 1917 433 1915 407 1930 373 1942 338 1947 301 1952 270 1954 238 1951 207 1946 214 1947 229 1945 239 1939 278 1936 318 1924 356 1923 362 1913 382 1912 364 1906 301 1904 237 1891 175 1887 150 1892 126 1892 101 1892 68 1893 35 1888 2 1884 -9 1871 -20 1859 -14 1851 -6 1854 9 1854 20 1855 58 1864 95 1873 132 1883 179 1894 225 1899 273 1908 362 1910 451 1909 541{\\p0}", - -- Brim and tip pompom - "{\\c&HF8F8F8&\\p6}m 626 -191 b 565 -155 486 -196 428 -151 387 -115 327 -101 304 -47 273 2 267 59 249 113 219 157 217 213 215 265 217 309 260 302 285 283 373 264 465 264 555 257 608 252 655 292 709 287 759 294 816 276 863 298 903 340 972 324 1012 367 1061 394 1125 382 1167 424 1213 462 1268 482 1322 506 1385 546 1427 610 1479 662 1510 690 1534 725 1566 752 1611 796 1664 830 1703 880 1740 918 1747 986 1805 1005 1863 991 1897 932 1916 880 1914 823 1945 777 1961 725 1979 673 1957 622 1938 575 1912 534 1862 515 1836 473 1790 417 1755 351 1697 305 1658 266 1633 216 1593 176 1574 138 1539 116 1497 110 1448 101 1402 77 1371 37 1346 -16 1295 15 1254 6 1211 -27 1170 -62 1121 -86 1072 -104 1027 -128 976 -133 914 -130 851 -137 794 -162 740 -181 679 -168 626 -191 m 2051 917 b 1971 932 1929 1017 1919 1091 1912 1149 1923 1214 1970 1254 2000 1279 2027 1314 2066 1325 2139 1338 2212 1295 2254 1238 2281 1203 2287 1158 2282 1116 2292 1061 2273 1006 2229 970 2206 941 2167 938 2138 918{\\p0}", + -- Pompoms + "{\\c&HC0C0C0&\\p6}m 500 -323 b 491 -322 481 -318 475 -311 465 -312 456 -319 446 -318 434 -314 427 -304 417 -297 410 -290 404 -282 395 -278 390 -274 387 -267 381 -265 377 -261 379 -254 384 -253 397 -244 409 -232 425 -228 437 -228 446 -218 457 -217 462 -216 466 -213 468 -209 471 -205 477 -203 482 -206 491 -211 499 -217 508 -222 532 -235 556 -249 576 -267 584 -272 584 -284 578 -290 569 -305 550 -312 533 -309 523 -310 515 -316 507 -321 505 -323 503 -323 500 -323{\\p0}", + "{\\c&HE0E0E0&\\p6}m 315 -260 b 286 -258 259 -240 246 -215 235 -210 222 -215 211 -211 204 -188 177 -176 172 -151 170 -139 163 -128 154 -121 143 -103 141 -81 143 -60 139 -46 125 -34 129 -17 132 -1 134 16 142 30 145 56 161 80 181 96 196 114 210 133 231 144 266 153 303 138 328 115 373 79 401 28 423 -24 446 -73 465 -123 483 -174 487 -199 467 -225 442 -227 421 -232 402 -242 384 -254 364 -259 342 -250 322 -260 320 -260 317 -261 315 -260{\\p0}", + -- Main cap + "{\\c&H0000F0&\\p6}m 1151 -523 b 1016 -516 891 -458 769 -406 693 -369 624 -319 561 -262 526 -252 465 -235 479 -187 502 -147 551 -135 588 -111 1115 165 1379 232 1909 761 1926 800 1952 834 1987 858 2020 883 2053 912 2065 952 2088 1000 2146 962 2139 919 2162 836 2156 747 2143 662 2131 615 2116 567 2122 517 2120 410 2090 306 2089 199 2092 147 2071 99 2034 64 1987 5 1928 -41 1869 -86 1777 -157 1712 -256 1629 -337 1578 -389 1521 -436 1461 -476 1407 -509 1343 -507 1284 -515 1240 -519 1195 -521 1151 -523{\\p0}", + -- Cap shadow + "{\\c&H0000AA&\\p6}m 1657 248 b 1658 254 1659 261 1660 267 1669 276 1680 284 1689 293 1695 302 1700 311 1707 320 1716 325 1726 330 1735 335 1744 347 1752 360 1761 371 1753 352 1754 331 1753 311 1751 237 1751 163 1751 90 1752 64 1752 37 1767 14 1778 -3 1785 -24 1786 -45 1786 -60 1786 -77 1774 -87 1760 -96 1750 -78 1751 -65 1748 -37 1750 -8 1750 20 1734 78 1715 134 1699 192 1694 211 1689 231 1676 246 1671 251 1661 255 1657 248 m 1909 541 b 1914 542 1922 549 1917 539 1919 520 1921 502 1919 483 1918 458 1917 433 1915 407 1930 373 1942 338 1947 301 1952 270 1954 238 1951 207 1946 214 1947 229 1945 239 1939 278 1936 318 1924 356 1923 362 1913 382 1912 364 1906 301 1904 237 1891 175 1887 150 1892 126 1892 101 1892 68 1893 35 1888 2 1884 -9 1871 -20 1859 -14 1851 -6 1854 9 1854 20 1855 58 1864 95 1873 132 1883 179 1894 225 1899 273 1908 362 1910 451 1909 541{\\p0}", + -- Brim and tip pompom + "{\\c&HF8F8F8&\\p6}m 626 -191 b 565 -155 486 -196 428 -151 387 -115 327 -101 304 -47 273 2 267 59 249 113 219 157 217 213 215 265 217 309 260 302 285 283 373 264 465 264 555 257 608 252 655 292 709 287 759 294 816 276 863 298 903 340 972 324 1012 367 1061 394 1125 382 1167 424 1213 462 1268 482 1322 506 1385 546 1427 610 1479 662 1510 690 1534 725 1566 752 1611 796 1664 830 1703 880 1740 918 1747 986 1805 1005 1863 991 1897 932 1916 880 1914 823 1945 777 1961 725 1979 673 1957 622 1938 575 1912 534 1862 515 1836 473 1790 417 1755 351 1697 305 1658 266 1633 216 1593 176 1574 138 1539 116 1497 110 1448 101 1402 77 1371 37 1346 -16 1295 15 1254 6 1211 -27 1170 -62 1121 -86 1072 -104 1027 -128 976 -133 914 -130 851 -137 794 -162 740 -181 679 -168 626 -191 m 2051 917 b 1971 932 1929 1017 1919 1091 1912 1149 1923 1214 1970 1254 2000 1279 2027 1314 2066 1325 2139 1338 2212 1295 2254 1238 2281 1203 2287 1158 2282 1116 2292 1061 2273 1006 2229 970 2206 941 2167 938 2138 918{\\p0}", } -- called by mpv on every frame function tick() - if state.marginsREQ == true then - update_margins() - state.marginsREQ = false - end - - if (not state.enabled) then return end - - if (state.idle) then - - -- render idle message - msg.trace("idle message") - local _, _, display_aspect = mp.get_osd_size() - if display_aspect == 0 then - return - end - local display_h = 360 - local display_w = display_h * display_aspect - -- logo is rendered at 2^(6-1) = 32 times resolution with size 1800x1800 - local icon_x, icon_y = (display_w - 1800 / 32) / 2, 140 - local line_prefix = ("{\\rDefault\\an7\\1a&H00&\\bord0\\shad0\\pos(%f,%f)}"):format(icon_x, icon_y) - - local ass = assdraw.ass_new() - -- mpv logo - if user_opts.idlescreen then - for i, line in ipairs(logo_lines) do - ass:new_event() - ass:append(line_prefix .. line) - end - end - - -- Santa hat - if is_december and user_opts.idlescreen and not user_opts.greenandgrumpy then - for i, line in ipairs(santa_hat_lines) do - ass:new_event() - ass:append(line_prefix .. line) - end - end - - if user_opts.idlescreen then - ass:new_event() - ass:pos(display_w / 2, icon_y + 65) - ass:an(8) - ass:append("Drop files or URLs to play here.") - end - set_osd(display_w, display_h, ass.text, -1000) - - if state.showhide_enabled then - mp.disable_key_bindings("thumbfast-osc-showhide") - mp.disable_key_bindings("thumbfast-osc-showhide_wc") - state.showhide_enabled = false - end - - - elseif (state.fullscreen and user_opts.showfullscreen) - or (not state.fullscreen and user_opts.showwindowed) then - - -- render the OSC - render() - else - -- Flush OSD - render_wipe() - end - - state.tick_last_time = mp.get_time() - - if state.anitype ~= nil then - -- state.anistart can be nil - animation should now start, or it can - -- be a timestamp when it started. state.idle has no animation. - if not state.idle and - (not state.anistart or - mp.get_time() < 1 + state.anistart + user_opts.fadeduration/1000) - then - -- animating or starting, or still within 1s past the deadline - request_tick() - else - kill_animation() - end - end + if state.marginsREQ == true then + update_margins() + state.marginsREQ = false + end + + if not state.enabled then + return + end + + if state.idle then + -- render idle message + msg.trace("idle message") + local _, _, display_aspect = mp.get_osd_size() + if display_aspect == 0 then + return + end + local display_h = 360 + local display_w = display_h * display_aspect + -- logo is rendered at 2^(6-1) = 32 times resolution with size 1800x1800 + local icon_x, icon_y = (display_w - 1800 / 32) / 2, 140 + local line_prefix = ("{\\rDefault\\an7\\1a&H00&\\bord0\\shad0\\pos(%f,%f)}"):format(icon_x, icon_y) + + local ass = assdraw.ass_new() + -- mpv logo + if user_opts.idlescreen then + for i, line in ipairs(logo_lines) do + ass:new_event() + ass:append(line_prefix .. line) + end + end + + -- Santa hat + if is_december and user_opts.idlescreen and not user_opts.greenandgrumpy then + for i, line in ipairs(santa_hat_lines) do + ass:new_event() + ass:append(line_prefix .. line) + end + end + + if user_opts.idlescreen then + ass:new_event() + ass:pos(display_w / 2, icon_y + 65) + ass:an(8) + ass:append("Drop files or URLs to play here.") + end + set_osd(display_w, display_h, ass.text, -1000) + + if state.showhide_enabled then + mp.disable_key_bindings("thumbfast-osc-showhide") + mp.disable_key_bindings("thumbfast-osc-showhide_wc") + state.showhide_enabled = false + end + elseif (state.fullscreen and user_opts.showfullscreen) or (not state.fullscreen and user_opts.showwindowed) then + -- render the OSC + render() + else + -- Flush OSD + render_wipe() + end + + state.tick_last_time = mp.get_time() + + if state.anitype ~= nil then + -- state.anistart can be nil - animation should now start, or it can + -- be a timestamp when it started. state.idle has no animation. + if + not state.idle + and (not state.anistart or mp.get_time() < 1 + state.anistart + user_opts.fadeduration / 1000) + then + -- animating or starting, or still within 1s past the deadline + request_tick() + else + kill_animation() + end + end end function do_enable_keybindings() - if state.enabled then - if not state.showhide_enabled then - mp.enable_key_bindings("thumbfast-osc-showhide", "allow-vo-dragging+allow-hide-cursor") - mp.enable_key_bindings("thumbfast-osc-showhide_wc", "allow-vo-dragging+allow-hide-cursor") - end - state.showhide_enabled = true - end + if state.enabled then + if not state.showhide_enabled then + mp.enable_key_bindings("thumbfast-osc-showhide", "allow-vo-dragging+allow-hide-cursor") + mp.enable_key_bindings("thumbfast-osc-showhide_wc", "allow-vo-dragging+allow-hide-cursor") + end + state.showhide_enabled = true + end end function enable_osc(enable) - state.enabled = enable - if enable then - do_enable_keybindings() - else - hide_osc() -- acts immediately when state.enabled == false - if state.showhide_enabled then - mp.disable_key_bindings("thumbfast-osc-showhide") - mp.disable_key_bindings("thumbfast-osc-showhide_wc") - end - state.showhide_enabled = false - end + state.enabled = enable + if enable then + do_enable_keybindings() + else + hide_osc() -- acts immediately when state.enabled == false + if state.showhide_enabled then + mp.disable_key_bindings("thumbfast-osc-showhide") + mp.disable_key_bindings("thumbfast-osc-showhide_wc") + end + state.showhide_enabled = false + end end -- duration is observed for the sole purpose of updating chapter markers -- positions. live streams with chapters are very rare, and the update is also -- expensive (with request_init), so it's only observed when we have chapters -- and the user didn't disable the livemarkers option (update_duration_watch). -function on_duration() request_init() end +function on_duration() + request_init() +end local duration_watched = false function update_duration_watch() - local want_watch = user_opts.livemarkers and - (mp.get_property_number("chapters", 0) or 0) > 0 and - true or false -- ensure it's a boolean + local want_watch = user_opts.livemarkers and (mp.get_property_number("chapters", 0) or 0) > 0 and true or false -- ensure it's a boolean - if (want_watch ~= duration_watched) then - if want_watch then - mp.observe_property("duration", nil, on_duration) - else - mp.unobserve_property(on_duration) - end - duration_watched = want_watch - end + if want_watch ~= duration_watched then + if want_watch then + mp.observe_property("duration", nil, on_duration) + else + mp.unobserve_property(on_duration) + end + duration_watched = want_watch + end end validate_user_opts() @@ -2900,209 +2908,260 @@ mp.register_event("start-file", request_init) mp.observe_property("track-list", nil, request_init) mp.observe_property("playlist", nil, request_init) mp.observe_property("chapter-list", "native", function(_, list) - list = list or {} -- safety, shouldn't return nil - table.sort(list, function(a, b) return a.time < b.time end) - state.chapter_list = list - update_duration_watch() - request_init() + list = list or {} -- safety, shouldn't return nil + table.sort(list, function(a, b) + return a.time < b.time + end) + state.chapter_list = list + update_duration_watch() + request_init() end) mp.register_script_message("osc-message", show_message) mp.register_script_message("osc-chapterlist", function(dur) - show_message(get_chapterlist(), dur) + show_message(get_chapterlist(), dur) end) mp.register_script_message("osc-playlist", function(dur) - show_message(get_playlist(), dur) + show_message(get_playlist(), dur) end) mp.register_script_message("osc-tracklist", function(dur) - local msg = {} - for k,v in pairs(nicetypes) do - table.insert(msg, get_tracklist(k)) - end - show_message(table.concat(msg, '\n\n'), dur) + local msg = {} + for k, v in pairs(nicetypes) do + table.insert(msg, get_tracklist(k)) + end + show_message(table.concat(msg, "\n\n"), dur) end) -mp.observe_property("fullscreen", "bool", - function(name, val) - state.fullscreen = val - state.marginsREQ = true - request_init_resize() - end -) -mp.observe_property("border", "bool", - function(name, val) - state.border = val - request_init_resize() - end -) -mp.observe_property("window-maximized", "bool", - function(name, val) - state.maximized = val - request_init_resize() - end -) -mp.observe_property("idle-active", "bool", - function(name, val) - state.idle = val - request_tick() - end -) +mp.observe_property("fullscreen", "bool", function(name, val) + state.fullscreen = val + state.marginsREQ = true + request_init_resize() +end) +mp.observe_property("border", "bool", function(name, val) + state.border = val + request_init_resize() +end) +mp.observe_property("window-maximized", "bool", function(name, val) + state.maximized = val + request_init_resize() +end) +mp.observe_property("idle-active", "bool", function(name, val) + state.idle = val + request_tick() +end) mp.observe_property("pause", "bool", pause_state) mp.observe_property("demuxer-cache-state", "native", cache_state) mp.observe_property("vo-configured", "bool", function(name, val) - request_tick() + request_tick() end) mp.observe_property("playback-time", "number", function(name, val) - request_tick() + request_tick() end) mp.observe_property("osd-dimensions", "native", function(name, val) - -- (we could use the value instead of re-querying it all the time, but then - -- we might have to worry about property update ordering) - request_init_resize() + -- (we could use the value instead of re-querying it all the time, but then + -- we might have to worry about property update ordering) + request_init_resize() end) -- mouse show/hide bindings mp.set_key_bindings({ - {"mouse_move", function(e) process_event("mouse_move", nil) end}, - {"mouse_leave", mouse_leave}, + { + "mouse_move", + function(e) + process_event("mouse_move", nil) + end, + }, + { "mouse_leave", mouse_leave }, }, "thumbfast-osc-showhide", "force") mp.set_key_bindings({ - {"mouse_move", function(e) process_event("mouse_move", nil) end}, - {"mouse_leave", mouse_leave}, + { + "mouse_move", + function(e) + process_event("mouse_move", nil) + end, + }, + { "mouse_leave", mouse_leave }, }, "thumbfast-osc-showhide_wc", "force") do_enable_keybindings() --mouse input bindings mp.set_key_bindings({ - {"mbtn_left", function(e) process_event("mbtn_left", "up") end, - function(e) process_event("mbtn_left", "down") end}, - {"shift+mbtn_left", function(e) process_event("shift+mbtn_left", "up") end, - function(e) process_event("shift+mbtn_left", "down") end}, - {"mbtn_right", function(e) process_event("mbtn_right", "up") end, - function(e) process_event("mbtn_right", "down") end}, - -- alias to shift_mbtn_left for single-handed mouse use - {"mbtn_mid", function(e) process_event("shift+mbtn_left", "up") end, - function(e) process_event("shift+mbtn_left", "down") end}, - {"wheel_up", function(e) process_event("wheel_up", "press") end}, - {"wheel_down", function(e) process_event("wheel_down", "press") end}, - {"mbtn_left_dbl", "ignore"}, - {"shift+mbtn_left_dbl", "ignore"}, - {"mbtn_right_dbl", "ignore"}, + { + "mbtn_left", + function(e) + process_event("mbtn_left", "up") + end, + function(e) + process_event("mbtn_left", "down") + end, + }, + { + "shift+mbtn_left", + function(e) + process_event("shift+mbtn_left", "up") + end, + function(e) + process_event("shift+mbtn_left", "down") + end, + }, + { + "mbtn_right", + function(e) + process_event("mbtn_right", "up") + end, + function(e) + process_event("mbtn_right", "down") + end, + }, + -- alias to shift_mbtn_left for single-handed mouse use + { + "mbtn_mid", + function(e) + process_event("shift+mbtn_left", "up") + end, + function(e) + process_event("shift+mbtn_left", "down") + end, + }, + { + "wheel_up", + function(e) + process_event("wheel_up", "press") + end, + }, + { + "wheel_down", + function(e) + process_event("wheel_down", "press") + end, + }, + { "mbtn_left_dbl", "ignore" }, + { "shift+mbtn_left_dbl", "ignore" }, + { "mbtn_right_dbl", "ignore" }, }, "thumbfast-osc-input", "force") mp.enable_key_bindings("thumbfast-osc-input") mp.set_key_bindings({ - {"mbtn_left", function(e) process_event("mbtn_left", "up") end, - function(e) process_event("mbtn_left", "down") end}, + { + "mbtn_left", + function(e) + process_event("mbtn_left", "up") + end, + function(e) + process_event("mbtn_left", "down") + end, + }, }, "thumbfast-osc-window-controls", "force") mp.enable_key_bindings("thumbfast-osc-window-controls") function get_hidetimeout() - if user_opts.visibility == "always" then - return -1 -- disable autohide - end - return user_opts.hidetimeout + if user_opts.visibility == "always" then + return -1 -- disable autohide + end + return user_opts.hidetimeout end function always_on(val) - if state.enabled then - if val then - show_osc() - else - hide_osc() - end - end + if state.enabled then + if val then + show_osc() + else + hide_osc() + end + end end -- mode can be auto/always/never/cycle -- the modes only affect internal variables and not stored on its own. function visibility_mode(mode, no_osd) - if mode == "cycle" then - if not state.enabled then - mode = "auto" - elseif user_opts.visibility ~= "always" then - mode = "always" - else - mode = "never" - end - end - - if mode == "auto" then - always_on(false) - enable_osc(true) - elseif mode == "always" then - enable_osc(true) - always_on(true) - elseif mode == "never" then - enable_osc(false) - else - msg.warn("Ignoring unknown visibility mode '" .. mode .. "'") - return - end - - user_opts.visibility = mode - if mp.del_property then - mp.set_property_native("user-data/osc/visibility", mode) - else - utils.shared_script_property_set("osc-visibility", mode) - end - - if not no_osd and tonumber(mp.get_property("osd-level")) >= 1 then - mp.osd_message("OSC visibility: " .. mode) - end - - -- Reset the input state on a mode change. The input state will be - -- recalculated on the next render cycle, except in 'never' mode where it - -- will just stay disabled. - mp.disable_key_bindings("thumbfast-osc-input") - mp.disable_key_bindings("thumbfast-osc-window-controls") - state.input_enabled = false - - update_margins() - request_tick() + if mode == "cycle" then + if not state.enabled then + mode = "auto" + elseif user_opts.visibility ~= "always" then + mode = "always" + else + mode = "never" + end + end + + if mode == "auto" then + always_on(false) + enable_osc(true) + elseif mode == "always" then + enable_osc(true) + always_on(true) + elseif mode == "never" then + enable_osc(false) + else + msg.warn("Ignoring unknown visibility mode '" .. mode .. "'") + return + end + + user_opts.visibility = mode + if mp.del_property then + mp.set_property_native("user-data/osc/visibility", mode) + else + utils.shared_script_property_set("osc-visibility", mode) + end + + if not no_osd and tonumber(mp.get_property("osd-level")) >= 1 then + mp.osd_message("OSC visibility: " .. mode) + end + + -- Reset the input state on a mode change. The input state will be + -- recalculated on the next render cycle, except in 'never' mode where it + -- will just stay disabled. + mp.disable_key_bindings("thumbfast-osc-input") + mp.disable_key_bindings("thumbfast-osc-window-controls") + state.input_enabled = false + + update_margins() + request_tick() end function idlescreen_visibility(mode, no_osd) - if mode == "cycle" then - if user_opts.idlescreen then - mode = "no" - else - mode = "yes" - end - end + if mode == "cycle" then + if user_opts.idlescreen then + mode = "no" + else + mode = "yes" + end + end - if mode == "yes" then - user_opts.idlescreen = true - else - user_opts.idlescreen = false - end + if mode == "yes" then + user_opts.idlescreen = true + else + user_opts.idlescreen = false + end - if mp.del_property then - mp.set_property_native("user-data/osc/idlescreen", user_opts.idlescreen) - else - utils.shared_script_property_set("osc-idlescreen", mode) - end + if mp.del_property then + mp.set_property_native("user-data/osc/idlescreen", user_opts.idlescreen) + else + utils.shared_script_property_set("osc-idlescreen", mode) + end - if not no_osd and tonumber(mp.get_property("osd-level")) >= 1 then - mp.osd_message("OSC logo visibility: " .. tostring(mode)) - end + if not no_osd and tonumber(mp.get_property("osd-level")) >= 1 then + mp.osd_message("OSC logo visibility: " .. tostring(mode)) + end - request_tick() + request_tick() end visibility_mode(user_opts.visibility, true) mp.register_script_message("osc-visibility", visibility_mode) -mp.add_key_binding(nil, "visibility", function() visibility_mode("cycle") end) +mp.add_key_binding(nil, "visibility", function() + visibility_mode("cycle") +end) mp.register_script_message("osc-idlescreen", idlescreen_visibility) mp.register_script_message("thumbfast-info", function(json) - local data = utils.parse_json(json) - if type(data) ~= "table" or not data.width or not data.height then - msg.error("thumbfast-info: received json didn't produce a table with thumbnail information") - else - thumbfast = data - end + local data = utils.parse_json(json) + if type(data) ~= "table" or not data.width or not data.height then + msg.error("thumbfast-info: received json didn't produce a table with thumbnail information") + else + thumbfast = data + end end) set_virt_mouse_area(0, 0, 0, 0, "thumbfast-osc-input") diff --git a/ar/.config/mpv/scripts/subtitle-search.lua b/ar/.config/mpv/scripts/subtitle-search.lua index 53a2ddc..0627a2f 100644 --- a/ar/.config/mpv/scripts/subtitle-search.lua +++ b/ar/.config/mpv/scripts/subtitle-search.lua @@ -26,7 +26,6 @@ Example: and voilá, the scene pops up.' --]] - package.path = package.path .. ";" .. mp.command_native({ "expand-path", "~~/script-modules/?.lua" }) local mp = require("mp") @@ -39,644 +38,684 @@ local utf8_data = require("utf8_data") local sha1 = require("sha1") utf8.config = { - conversion = { - uc_lc = utf8_data.utf8_uc_lc, - lc_uc = utf8_data.utf8_lc_uc - }, + conversion = { + uc_lc = utf8_data.utf8_uc_lc, + lc_uc = utf8_data.utf8_lc_uc, + }, } utf8:init() table.insert(result_list.keybinds, { - "ENTER", "jump_to_result", function() - local selected_index = result_list.selected - if selected_index == nil then - return - end - - local selected = result_list.list[selected_index] - mp.commandv("seek", selected.time, "absolute+exact") - end, {} + "ENTER", + "jump_to_result", + function() + local selected_index = result_list.selected + if selected_index == nil then + return + end + + local selected = result_list.list[selected_index] + mp.commandv("seek", selected.time, "absolute+exact") + end, + {}, }) table.insert(result_list.keybinds, { - "Ctrl+Shift+ENTER", "sync_to_result", function() - local selected_index = result_list.selected - if selected_index == nil then - return - end - - local selected = result_list.list[selected_index] - local old_delay = mp.get_property_native("sub-delay") - local delay = -(selected.original_time - mp.get_property_native("time-pos")) - mp.set_property_native("sub-delay", delay) - end, {} + "Ctrl+Shift+ENTER", + "sync_to_result", + function() + local selected_index = result_list.selected + if selected_index == nil then + return + end + + local selected = result_list.list[selected_index] + local old_delay = mp.get_property_native("sub-delay") + local delay = -(selected.original_time - mp.get_property_native("time-pos")) + mp.set_property_native("sub-delay", delay) + end, + {}, }) function sub_time_to_seconds(time, sep) - if time:match("%d%d:%d%d" .. sep .. "%d%d%d") then - time = "00:" .. time - end + if time:match("%d%d:%d%d" .. sep .. "%d%d%d") then + time = "00:" .. time + end - local major, minor = time:match("(%d%d:%d%d:%d%d)" .. sep .. "(%d%d%d)") - local hours, mins, secs = major:match("(%d%d):(%d%d):(%d%d)") - return hours * 3600 + mins * 60 + secs + minor / 1000 + local major, minor = time:match("(%d%d:%d%d:%d%d)" .. sep .. "(%d%d%d)") + local hours, mins, secs = major:match("(%d%d):(%d%d):(%d%d)") + return hours * 3600 + mins * 60 + secs + minor / 1000 end local subs_cache = {} function open_file(path) - local f, err = io.open(path, "r") - if f and err == nil then - return f - end + local f, err = io.open(path, "r") + if f and err == nil then + return f + end - return nil + return nil end function is_supported_network_protocol(url) - local protocols = { "http", "https" } + local protocols = { "http", "https" } - for _, protocol in pairs(protocols) do - if url:sub(1, #protocol + 3) == protocol .. "://" then - return true - end - end + for _, protocol in pairs(protocols) do + if url:sub(1, #protocol + 3) == protocol .. "://" then + return true + end + end - return false + return false end function get_sub_filename_async(track_name, on_done) - local active_track = mp.get_property_native("current-tracks/" .. track_name) - if active_track == nil then - on_done(nil) - return - end - - local is_external = active_track.external - local external_filename = active_track["external-filename"] - - -- youtube subtitles specified with edl format - if is_external and external_filename and external_filename:sub(1, 6) == "edl://" then - download_subtitle_async(external_filename:match("https://.*"), on_done) - return - end - - if is_external and external_filename and is_supported_network_protocol(external_filename) then - download_subtitle_async(external_filename, on_done) - return - end - - if is_external and external_filename then - on_done(external_filename) - return - end - - if is_external == false then - extract_subtitle_track_async(active_track, on_done) - return - end - - on_done(nil) + local active_track = mp.get_property_native("current-tracks/" .. track_name) + if active_track == nil then + on_done(nil) + return + end + + local is_external = active_track.external + local external_filename = active_track["external-filename"] + + -- youtube subtitles specified with edl format + if is_external and external_filename and external_filename:sub(1, 6) == "edl://" then + download_subtitle_async(external_filename:match("https://.*"), on_done) + return + end + + if is_external and external_filename and is_supported_network_protocol(external_filename) then + download_subtitle_async(external_filename, on_done) + return + end + + if is_external and external_filename then + on_done(external_filename) + return + end + + if is_external == false then + extract_subtitle_track_async(active_track, on_done) + return + end + + on_done(nil) end function get_path_to_extract_sub(uniq_sub_id) - local sub_filename = sha1.hex(uniq_sub_id) - return utils.join_path(get_temp_dir(), "mpv-subtitle-search-extracted-" .. sub_filename .. ".srt") + local sub_filename = sha1.hex(uniq_sub_id) + return utils.join_path(get_temp_dir(), "mpv-subtitle-search-extracted-" .. sub_filename .. ".srt") end function download_subtitle_async(url, on_done) - local sub_path = get_path_to_extract_sub(mp.get_property_native("path") .. "#" .. url) - - if subs_cache[sub_path] then - on_done(sub_path) - return - end - - local extract_overlay = mp.create_osd_overlay("ass-events") - extract_overlay.data = "{\\a3\\fs20}Fetching remote subtitles, wait..." - extract_overlay:update() - - mp.command_native_async({ - name = "subprocess", - capture_stdout = true, - args = { "ffmpeg", "-y", "-hide_banner", "-loglevel", "error", "-i", url, "-vn", "-an", "-c:s", "srt", sub_path } - }, function(ok) - if not ok then - extract_overlay.data = "{\\a3\\fs20\\c&HFF&}Extraction failed" - extract_overlay:update() - - mp.add_timeout(2, function() - extract_overlay:remove() - end) - - on_done(nil) - else - extract_overlay:remove() - - on_done(sub_path) - end - end) + local sub_path = get_path_to_extract_sub(mp.get_property_native("path") .. "#" .. url) + + if subs_cache[sub_path] then + on_done(sub_path) + return + end + + local extract_overlay = mp.create_osd_overlay("ass-events") + extract_overlay.data = "{\\a3\\fs20}Fetching remote subtitles, wait..." + extract_overlay:update() + + mp.command_native_async({ + name = "subprocess", + capture_stdout = true, + args = { + "ffmpeg", + "-y", + "-hide_banner", + "-loglevel", + "error", + "-i", + url, + "-vn", + "-an", + "-c:s", + "srt", + sub_path, + }, + }, function(ok) + if not ok then + extract_overlay.data = "{\\a3\\fs20\\c&HFF&}Extraction failed" + extract_overlay:update() + + mp.add_timeout(2, function() + extract_overlay:remove() + end) + + on_done(nil) + else + extract_overlay:remove() + + on_done(sub_path) + end + end) end function extract_subtitle_track_async(track, on_done) - if track.external then - on_done(nil) - return - end - - local video_file = mp.get_property_native("path") - local working_dir = mp.get_property_native("working-directory") - local full_path = utils.join_path(working_dir, video_file) - - local track_index = track["ff-index"] - local sub_path = get_path_to_extract_sub(full_path .. "#" .. track_index) - - -- check if file already exists - if open_file(sub_path) then - msg.info("Reusing extracted subtitle track from " .. sub_path) - - on_done(sub_path) - return - end - - msg.info("Extracting embedded subtitle track to " .. sub_path) - - local extract_overlay = mp.create_osd_overlay("ass-events") - extract_overlay.data = "{\\a3\\fs20}Extracting embedded subtitles, wait..." - extract_overlay:update() - - mp.command_native_async({ - name = "subprocess", - capture_stdout = true, - args = { "ffmpeg", "-y", "-hide_banner", "-loglevel", "error", "-i", full_path, "-map", "0:" .. track_index, "-vn", "-an", "-c:s", "srt", sub_path } - }, function(ok) - if not ok then - extract_overlay.data = "{\\a3\\fs20\\c&HFF&}Extraction failed" - extract_overlay:update() - - mp.add_timeout(2, function() - extract_overlay:remove() - end) - - on_done(nil) - else - extract_overlay:remove() - - on_done(sub_path) - end - end) + if track.external then + on_done(nil) + return + end + + local video_file = mp.get_property_native("path") + local working_dir = mp.get_property_native("working-directory") + local full_path = utils.join_path(working_dir, video_file) + + local track_index = track["ff-index"] + local sub_path = get_path_to_extract_sub(full_path .. "#" .. track_index) + + -- check if file already exists + if open_file(sub_path) then + msg.info("Reusing extracted subtitle track from " .. sub_path) + + on_done(sub_path) + return + end + + msg.info("Extracting embedded subtitle track to " .. sub_path) + + local extract_overlay = mp.create_osd_overlay("ass-events") + extract_overlay.data = "{\\a3\\fs20}Extracting embedded subtitles, wait..." + extract_overlay:update() + + mp.command_native_async({ + name = "subprocess", + capture_stdout = true, + args = { + "ffmpeg", + "-y", + "-hide_banner", + "-loglevel", + "error", + "-i", + full_path, + "-map", + "0:" .. track_index, + "-vn", + "-an", + "-c:s", + "srt", + sub_path, + }, + }, function(ok) + if not ok then + extract_overlay.data = "{\\a3\\fs20\\c&HFF&}Extraction failed" + extract_overlay:update() + + mp.add_timeout(2, function() + extract_overlay:remove() + end) + + on_done(nil) + else + extract_overlay:remove() + + on_done(sub_path) + end + end) end function get_temp_dir() - local temp_dir = os.getenv("TMPDIR") - if temp_dir == nil then - temp_dir = os.getenv("TEMP") - end + local temp_dir = os.getenv("TMPDIR") + if temp_dir == nil then + temp_dir = os.getenv("TEMP") + end - if temp_dir == nil then - temp_dir = os.getenv("TMP") - end + if temp_dir == nil then + temp_dir = os.getenv("TMP") + end - if temp_dir == nil then - temp_dir = "/tmp" - end + if temp_dir == nil then + temp_dir = "/tmp" + end - return temp_dir + return temp_dir end function get_lines(input) - local lines = {} - - local tail = 1 - for head = 1, #input do - local ch = input:sub(head, head) - if ch == "\n" then - table.insert(lines, input:sub(tail, head - 1)) - tail = head + 1 - elseif head == #input then - table.insert(lines, input:sub(tail, head)) - end - end - - return lines + local lines = {} + + local tail = 1 + for head = 1, #input do + local ch = input:sub(head, head) + if ch == "\n" then + table.insert(lines, input:sub(tail, head - 1)) + tail = head + 1 + elseif head == #input then + table.insert(lines, input:sub(tail, head)) + end + end + + return lines end function trim(s) - return s:gsub("^%s*(.-)%s*$", "%1") + return s:gsub("^%s*(.-)%s*$", "%1") end function parse_vtt_sub(data) - local result = {} - local state = "header" - - local cur_line = {} - for _, line in ipairs(get_lines(data)) do - line = trim(line) - if state == "header" then - if line == "" then - state = "body" - end - elseif state == "body" then - if line == "" then - state = "header" - elseif line:match("^NOTE") or line:match("^STYLE") then - state = "comment" - else - local time_text = line:match("^(%d%d:%d%d:%d%d%.%d%d%d)") or line:match("^(%d%d:%d%d%.%d%d%d)") - if time_text then - cur_line.time = sub_time_to_seconds(time_text, ".") - state = "waiting_text" - else - state = "body" - end - end - elseif state == "comment" then - if #line == 0 then - state = "body" - end - elseif state == "waiting_text" then - if #line == 0 or line == nil then - if cur_line.text ~= nil then - table.insert(result, cur_line) - end - - cur_line = {} - state = "body" - else - line = remove_tags(line) - if cur_line.text then - cur_line.text = cur_line.text .. "\n" .. line - else - cur_line.text = line - end - end - end - end - - return result + local result = {} + local state = "header" + + local cur_line = {} + for _, line in ipairs(get_lines(data)) do + line = trim(line) + if state == "header" then + if line == "" then + state = "body" + end + elseif state == "body" then + if line == "" then + state = "header" + elseif line:match("^NOTE") or line:match("^STYLE") then + state = "comment" + else + local time_text = line:match("^(%d%d:%d%d:%d%d%.%d%d%d)") or line:match("^(%d%d:%d%d%.%d%d%d)") + if time_text then + cur_line.time = sub_time_to_seconds(time_text, ".") + state = "waiting_text" + else + state = "body" + end + end + elseif state == "comment" then + if #line == 0 then + state = "body" + end + elseif state == "waiting_text" then + if #line == 0 or line == nil then + if cur_line.text ~= nil then + table.insert(result, cur_line) + end + + cur_line = {} + state = "body" + else + line = remove_tags(line) + if cur_line.text then + cur_line.text = cur_line.text .. "\n" .. line + else + cur_line.text = line + end + end + end + end + + return result end function remove_tags(text) - function remove_tag(tag_to_remove) - return string.gsub(text, "</?" .. tag_to_remove .. ">", "") - end + function remove_tag(tag_to_remove) + return string.gsub(text, "</?" .. tag_to_remove .. ">", "") + end - text = remove_tag("b") - text = remove_tag("i") - text = remove_tag("u") - text = remove_tag("ruby") - text = remove_tag("rt") + text = remove_tag("b") + text = remove_tag("i") + text = remove_tag("u") + text = remove_tag("ruby") + text = remove_tag("rt") - -- remove class tag - text = remove_tag("c") - text = string.gsub(text, "<c.[^>]*>", "") + -- remove class tag + text = remove_tag("c") + text = string.gsub(text, "<c.[^>]*>", "") - -- remove voice tag - text = remove_tag("v") - text = string.gsub(text, "<v [^>]*>", "") + -- remove voice tag + text = remove_tag("v") + text = string.gsub(text, "<v [^>]*>", "") - -- remove karaoke karaoke tags - text = string.gsub(text, "</?%d%d:%d%d.%d%d%d>", "") - text = string.gsub(text, "</?%d%d:%d%d:%d%d.%d%d%d>", "") + -- remove karaoke karaoke tags + text = string.gsub(text, "</?%d%d:%d%d.%d%d%d>", "") + text = string.gsub(text, "</?%d%d:%d%d:%d%d.%d%d%d>", "") - -- remove font tag - text = string.gsub(text, '<font color="#?[%d%a]+">', "") - text = string.gsub(text, '</font>', "") + -- remove font tag + text = string.gsub(text, '<font color="#?[%d%a]+">', "") + text = string.gsub(text, "</font>", "") - return text + return text end - -- detects only most common encodings function get_encoding_from_bom(data) - -- utf8 - local bom = data:sub(1, 3) - if bom == "\xEF\xBB\xBF" then - return "utf-8" - end - - -- utf16 - bom = data:sub(1, 2) - if bom == "\xFF\xFE" or bom == "\xFE\xFF" then - return "utf-16" - end - - -- utf32 - bom = data:sub(1, 4) - if bom == "\xFF\xFE\x00\x00" or bom == "\x00\x00\xFE\xFF" then - return "utf-32" - end - - return nil + -- utf8 + local bom = data:sub(1, 3) + if bom == "\xEF\xBB\xBF" then + return "utf-8" + end + + -- utf16 + bom = data:sub(1, 2) + if bom == "\xFF\xFE" or bom == "\xFE\xFF" then + return "utf-16" + end + + -- utf32 + bom = data:sub(1, 4) + if bom == "\xFF\xFE\x00\x00" or bom == "\x00\x00\xFE\xFF" then + return "utf-32" + end + + return nil end function is_microdvd_sub(data) - return data:match("{%d+}{%d+}") + return data:match("{%d+}{%d+}") end function parse_microdvd_sub(data) - local result = {} - local lines = get_lines(data) - - -- if the first line contains only number, it's a subtitle fps - local subtitle_fps = tonumber(lines[1]) - if subtitle_fps == nil or subtitle_fps == 0 then - subtitle_fps = mp.get_property_native("container-fps") - if subtitle_fps == nil or subtitle_fps == 0 then - subtitle_fps = 24 - end - end - - msg.info("Using " .. subtitle_fps .. "fps for microdvd subtitle") - - for _, line in ipairs(lines) do - local time_text = line:match("^{(%d+)}{(%d+)}") - if time_text then - local start_frame = tonumber(time_text:match("^(%d+)")) - - local text = line:match("^{%d+}{%d+}(.*)") - text = text:gsub("|", " ") - if text then - table.insert(result, { - time = frame_to_secs(start_frame, subtitle_fps), - text = text - }) - end - end - end - - return result + local result = {} + local lines = get_lines(data) + + -- if the first line contains only number, it's a subtitle fps + local subtitle_fps = tonumber(lines[1]) + if subtitle_fps == nil or subtitle_fps == 0 then + subtitle_fps = mp.get_property_native("container-fps") + if subtitle_fps == nil or subtitle_fps == 0 then + subtitle_fps = 24 + end + end + + msg.info("Using " .. subtitle_fps .. "fps for microdvd subtitle") + + for _, line in ipairs(lines) do + local time_text = line:match("^{(%d+)}{(%d+)}") + if time_text then + local start_frame = tonumber(time_text:match("^(%d+)")) + + local text = line:match("^{%d+}{%d+}(.*)") + text = text:gsub("|", " ") + if text then + table.insert(result, { + time = frame_to_secs(start_frame, subtitle_fps), + text = text, + }) + end + end + end + + return result end function frame_to_secs(frame, subtitle_fps) - return frame / subtitle_fps + return frame / subtitle_fps end function parse_sub(data) - bom_encoding = get_encoding_from_bom(data) - if bom_encoding ~= nil then - if bom_encoding == "utf-8" then - data = data:sub(3) - else - local error_overlay = mp.create_osd_overlay("ass-events") - error_overlay.data = "{\\a3\\fs20\\c&HFF&}Unsupported subtitle encoding: " .. bom_encoding .. ", please re-encode subtitle file to utf-8 to search" - error_overlay:update() - - msg.error("Unsupported subtitle encoding: " .. bom_encoding .. ", please re-encode subtitle file to utf-8 to search") - - mp.add_timeout(10, function() - error_overlay:remove() - end) - - return {} - end - end - - data = string.gsub(data, "\r\n", "\n") - - if data:sub(1, 6) == "WEBVTT" then - return parse_vtt_sub(data) - end - - if is_microdvd_sub(data) then - return parse_microdvd_sub(data) - end - - local result = {} - local state = "waiting_index" - local cur_line = {} - for _, line in ipairs(get_lines(data)) do - line = trim(line) - if state == "waiting_index" then - if cur_line.text then - table.insert(result, cur_line) - cur_line = {} - end - - if line:match("^%d+$") then - state = "waiting_time" - end - elseif state == "waiting_time" then - local time_text = line:match("^(%d%d:%d%d:%d%d,%d%d%d) ") - if time_text then - cur_line.time = sub_time_to_seconds(time_text, ",") - state = "waiting_text" - else - state = "waiting_index" - end - elseif state == "waiting_text" then - line = remove_tags(line) - if #line == 0 then - if cur_line.text then - table.insert(result, cur_line) - end - cur_line = {} - state = "waiting_index" - elseif cur_line.text then - cur_line.text = cur_line.text .. " " .. line - else - cur_line.text = line - end - end - end - - if cur_line.text then - table.insert(result, cur_line) - end - - return result + bom_encoding = get_encoding_from_bom(data) + if bom_encoding ~= nil then + if bom_encoding == "utf-8" then + data = data:sub(3) + else + local error_overlay = mp.create_osd_overlay("ass-events") + error_overlay.data = "{\\a3\\fs20\\c&HFF&}Unsupported subtitle encoding: " + .. bom_encoding + .. ", please re-encode subtitle file to utf-8 to search" + error_overlay:update() + + msg.error( + "Unsupported subtitle encoding: " + .. bom_encoding + .. ", please re-encode subtitle file to utf-8 to search" + ) + + mp.add_timeout(10, function() + error_overlay:remove() + end) + + return {} + end + end + + data = string.gsub(data, "\r\n", "\n") + + if data:sub(1, 6) == "WEBVTT" then + return parse_vtt_sub(data) + end + + if is_microdvd_sub(data) then + return parse_microdvd_sub(data) + end + + local result = {} + local state = "waiting_index" + local cur_line = {} + for _, line in ipairs(get_lines(data)) do + line = trim(line) + if state == "waiting_index" then + if cur_line.text then + table.insert(result, cur_line) + cur_line = {} + end + + if line:match("^%d+$") then + state = "waiting_time" + end + elseif state == "waiting_time" then + local time_text = line:match("^(%d%d:%d%d:%d%d,%d%d%d) ") + if time_text then + cur_line.time = sub_time_to_seconds(time_text, ",") + state = "waiting_text" + else + state = "waiting_index" + end + elseif state == "waiting_text" then + line = remove_tags(line) + if #line == 0 then + if cur_line.text then + table.insert(result, cur_line) + end + cur_line = {} + state = "waiting_index" + elseif cur_line.text then + cur_line.text = cur_line.text .. " " .. line + else + cur_line.text = line + end + end + end + + if cur_line.text then + table.insert(result, cur_line) + end + + return result end function load_sub(path, prefix) - if not path then - return nil - end - - local cached = subs_cache[path] - if cached then - return cached - end - - local f = open_file(path) - if not f then - return nil - end - - local data = f:read("*all") - f:close() - - local sub = { - prefix = prefix, - lines = parse_sub(data) - } - subs_cache[path] = sub - return sub + if not path then + return nil + end + + local cached = subs_cache[path] + if cached then + return cached + end + + local f = open_file(path) + if not f then + return nil + end + + local data = f:read("*all") + f:close() + + local sub = { + prefix = prefix, + lines = parse_sub(data), + } + subs_cache[path] = sub + return sub end function make_nocase_pattern(s) - local result = "" - for _, code in utf8.codes(s) do - local c = utf8.char(code) - result = result .. string.format("[%s%s]", utf8.lower(c), utf8.upper(c)) - end - return result + local result = "" + for _, code in utf8.codes(s) do + local c = utf8.char(code) + result = result .. string.format("[%s%s]", utf8.lower(c), utf8.upper(c)) + end + return result end -- highlight found text with colored text in ass syntax function highlight_match(text, match_text, style_reset) - local match_start, match_end = utf8.find(utf8.lower(text), utf8.lower(match_text)) - if match_start == nil then - return text - end + local match_start, match_end = utf8.find(utf8.lower(text), utf8.lower(match_text)) + if match_start == nil then + return text + end - local before = result_list.ass_escape(utf8.sub(text, 1, match_start - 1)) - local match = result_list.ass_escape(utf8.sub(text, match_start, match_end)) - local after = result_list.ass_escape(utf8.sub(text, match_end + 1)) + local before = result_list.ass_escape(utf8.sub(text, 1, match_start - 1)) + local match = result_list.ass_escape(utf8.sub(text, match_start, match_end)) + local after = result_list.ass_escape(utf8.sub(text, match_end + 1)) - if style_reset == "" then - style_reset = "{\\c&HFFFFFF&}" - end + if style_reset == "" then + style_reset = "{\\c&HFFFFFF&}" + end - return before .. "{\\c&HFF00&}" .. match .. style_reset .. after + return before .. "{\\c&HFF00&}" .. match .. style_reset .. after end function adjust_sub_time(time) - local delay = mp.get_property_native("sub-delay") - if delay == nil then - return time - end - return time + delay + local delay = mp.get_property_native("sub-delay") + if delay == nil then + return time + end + return time + delay end -function divmod (a, b) - return math.floor(a / b), a % b +function divmod(a, b) + return math.floor(a / b), a % b end function format_time(time) - decimals = 3 - sep = "." - local s = time - local h, s = divmod(s, 60 * 60) - local m, s = divmod(s, 60) + decimals = 3 + sep = "." + local s = time + local h, s = divmod(s, 60 * 60) + local m, s = divmod(s, 60) - local second_format = string.format("%%0%d.%df", 2 + (decimals > 0 and decimals + 1 or 0), decimals) + local second_format = string.format("%%0%d.%df", 2 + (decimals > 0 and decimals + 1 or 0), decimals) - return string.format("%02d" .. sep .. "%02d" .. sep .. second_format, h, m, s) + return string.format("%02d" .. sep .. "%02d" .. sep .. second_format, h, m, s) end function get_subs_to_search_in_async(on_done) - local result = {} - - get_sub_filename_async("sub", function(primary_filename) - local sub = load_sub(primary_filename, "P") - if sub then - table.insert(result, sub) - end - - get_sub_filename_async("sub2", function(secondary_filename) - sub = load_sub(secondary_filename, "S") - if sub then - table.insert(result, sub) - end - - on_done(result) - end) - end) + local result = {} + + get_sub_filename_async("sub", function(primary_filename) + local sub = load_sub(primary_filename, "P") + if sub then + table.insert(result, sub) + end + + get_sub_filename_async("sub2", function(secondary_filename) + sub = load_sub(secondary_filename, "S") + if sub then + table.insert(result, sub) + end + + on_done(result) + end) + end) end function update_search_results_async(query, live) - get_subs_to_search_in_async(function(subs) - if #subs == 0 then - mp.osd_message("External subtitles not found") - return - end - - result_list.list = { - { - sub = nil, - time = mp.get_property_native("time-pos"), - ass = "Original position" - } - } - result_list.selected = 1 - result_list.live = live - - local closest_lower_index = 1 - local closest_lower_time = nil - local cur_time = mp.get_property_native("time-pos") - - local pat = "(" .. make_nocase_pattern(query) .. ")" - for _, sub in ipairs(subs) do - for _, sub_line in ipairs(sub.lines) do - if query == "*" or utf8.match(sub_line.text, pat) then - local sub_time = adjust_sub_time(sub_line.time) - - table.insert(result_list.list, { - sub = sub, - original_time = sub_line.time, - time = sub_time + 0.01, -- to ensure that the subtitle is visible - formatter = function(style_reset) - local sub_text = result_list.ass_escape(format_time(sub_time) .. ": ") .. - highlight_match(sub_line.text, query, style_reset) - - if #subs > 1 then - sub_text = "[" .. sub.prefix .. "] " .. sub_text - end - - return sub_text - end - }) - - if sub_time <= cur_time and (closest_lower_time == nil or closest_lower_time < sub_time) then - closest_lower_time = sub_time - closest_lower_index = #result_list.list - end - end - end - end - - result_list.selected = closest_lower_index - result_list.header = "Search results for \"" .. query .. "\"\\N ------------------------------------" - result_list.header = result_list.header .. "\\NENTER to jump to subtitle, Ctrl+Shift+Enter to adjust subtitle timing to selected line" - - result_list:update() - result_list:open() - end) + get_subs_to_search_in_async(function(subs) + if #subs == 0 then + mp.osd_message("External subtitles not found") + return + end + + result_list.list = { + { + sub = nil, + time = mp.get_property_native("time-pos"), + ass = "Original position", + }, + } + result_list.selected = 1 + result_list.live = live + + local closest_lower_index = 1 + local closest_lower_time = nil + local cur_time = mp.get_property_native("time-pos") + + local pat = "(" .. make_nocase_pattern(query) .. ")" + for _, sub in ipairs(subs) do + for _, sub_line in ipairs(sub.lines) do + if query == "*" or utf8.match(sub_line.text, pat) then + local sub_time = adjust_sub_time(sub_line.time) + + table.insert(result_list.list, { + sub = sub, + original_time = sub_line.time, + time = sub_time + 0.01, -- to ensure that the subtitle is visible + formatter = function(style_reset) + local sub_text = result_list.ass_escape(format_time(sub_time) .. ": ") + .. highlight_match(sub_line.text, query, style_reset) + + if #subs > 1 then + sub_text = "[" .. sub.prefix .. "] " .. sub_text + end + + return sub_text + end, + }) + + if sub_time <= cur_time and (closest_lower_time == nil or closest_lower_time < sub_time) then + closest_lower_time = sub_time + closest_lower_index = #result_list.list + end + end + end + end + + result_list.selected = closest_lower_index + result_list.header = 'Search results for "' .. query .. '"\\N ------------------------------------' + result_list.header = result_list.header + .. "\\NENTER to jump to subtitle, Ctrl+Shift+Enter to adjust subtitle timing to selected line" + + result_list:update() + result_list:open() + end) end -mp.register_script_message('start-search', function() - if input_console.is_repl_active() then - input_console.set_active(false) - else - input_console.set_enter_handler(function(query) - update_search_results_async(query, false) - end) - input_console.set_active(true) - end +mp.register_script_message("start-search", function() + if input_console.is_repl_active() then + input_console.set_active(false) + else + input_console.set_enter_handler(function(query) + update_search_results_async(query, false) + end) + input_console.set_active(true) + end end) -mp.register_script_message('show-all-lines', function() - update_search_results_async("*", true) +mp.register_script_message("show-all-lines", function() + update_search_results_async("*", true) end) local function get_current_subtitle_index(list, pos) - local closest_lower_index = 1 - local closest_lower_time = nil - for i, item in ipairs(list) do - if item.time <= pos and (closest_lower_time == nil or closest_lower_time < item.time) then - closest_lower_time = item.time - closest_lower_index = i - end - end - return closest_lower_index + local closest_lower_index = 1 + local closest_lower_time = nil + for i, item in ipairs(list) do + if item.time <= pos and (closest_lower_time == nil or closest_lower_time < item.time) then + closest_lower_time = item.time + closest_lower_index = i + end + end + return closest_lower_index end mp.observe_property("time-pos", "native", function(_, pos) - if not result_list.hidden and result_list.live and pos ~= nil then - local index = get_current_subtitle_index(result_list.list, pos) - if index > 1 then - result_list.selected = index - result_list:update() - end - end + if not result_list.hidden and result_list.live and pos ~= nil then + local index = get_current_subtitle_index(result_list.list, pos) + if index > 1 then + result_list.selected = index + result_list:update() + end + end end) diff --git a/ar/.config/mpv/scripts/thumbfast.lua b/ar/.config/mpv/scripts/thumbfast.lua index 58d1870..70c192b 100644 --- a/ar/.config/mpv/scripts/thumbfast.lua +++ b/ar/.config/mpv/scripts/thumbfast.lua @@ -11,51 +11,51 @@ file, You can obtain one at https://mozilla.org/MPL/2.0/. ]] local options = { - -- Socket path (leave empty for auto) - socket = "", + -- Socket path (leave empty for auto) + socket = "", - -- Thumbnail path (leave empty for auto) - thumbnail = "", + -- Thumbnail path (leave empty for auto) + thumbnail = "", - -- Maximum thumbnail generation size in pixels (scaled down to fit) - -- Values are scaled when hidpi is enabled - max_height = 200, - max_width = 200, + -- Maximum thumbnail generation size in pixels (scaled down to fit) + -- Values are scaled when hidpi is enabled + max_height = 200, + max_width = 200, - -- Scale factor for thumbnail display size (requires mpv 0.38+) - -- Note that this is lower quality than increasing max_height and max_width - scale_factor = 1, + -- Scale factor for thumbnail display size (requires mpv 0.38+) + -- Note that this is lower quality than increasing max_height and max_width + scale_factor = 1, - -- Apply tone-mapping, no to disable - tone_mapping = "auto", + -- Apply tone-mapping, no to disable + tone_mapping = "auto", - -- Overlay id - overlay_id = 42, + -- Overlay id + overlay_id = 42, - -- Spawn thumbnailer on file load for faster initial thumbnails - spawn_first = false, + -- Spawn thumbnailer on file load for faster initial thumbnails + spawn_first = false, - -- Close thumbnailer process after an inactivity period in seconds, 0 to disable - quit_after_inactivity = 0, + -- Close thumbnailer process after an inactivity period in seconds, 0 to disable + quit_after_inactivity = 0, - -- Enable on network playback - network = false, + -- Enable on network playback + network = false, - -- Enable on audio playback - audio = false, + -- Enable on audio playback + audio = false, - -- Enable hardware decoding - hwdec = false, + -- Enable hardware decoding + hwdec = false, - -- Windows only: use native Windows API to write to pipe (requires LuaJIT) - direct_io = false, + -- Windows only: use native Windows API to write to pipe (requires LuaJIT) + direct_io = false, - -- Custom path to the mpv executable - mpv_path = "mpv" + -- Custom path to the mpv executable + mpv_path = "mpv", } -mp.utils = require "mp.utils" -mp.options = require "mp.options" +mp.utils = require("mp.utils") +mp.options = require("mp.options") mp.options.read_options(options, "thumbfast") local properties = {} @@ -64,73 +64,72 @@ local pre_0_33_0 = true local support_media_control = mp.get_property_native("media-controls") ~= nil function subprocess(args, async, callback) - callback = callback or function() end - - if not pre_0_30_0 then - if async then - return mp.command_native_async({name = "subprocess", playback_only = true, args = args}, callback) - else - return mp.command_native({name = "subprocess", playback_only = false, capture_stdout = true, args = args}) - end - else - if async then - return mp.utils.subprocess_detached({args = args}, callback) - else - return mp.utils.subprocess({args = args}) - end - end + callback = callback or function() end + + if not pre_0_30_0 then + if async then + return mp.command_native_async({ name = "subprocess", playback_only = true, args = args }, callback) + else + return mp.command_native({ name = "subprocess", playback_only = false, capture_stdout = true, args = args }) + end + else + if async then + return mp.utils.subprocess_detached({ args = args }, callback) + else + return mp.utils.subprocess({ args = args }) + end + end end local winapi = {} if options.direct_io then - local ffi_loaded, ffi = pcall(require, "ffi") - if ffi_loaded then - winapi = { - ffi = ffi, - C = ffi.C, - bit = require("bit"), - socket_wc = "", - - -- WinAPI constants - CP_UTF8 = 65001, - GENERIC_WRITE = 0x40000000, - OPEN_EXISTING = 3, - FILE_FLAG_WRITE_THROUGH = 0x80000000, - FILE_FLAG_NO_BUFFERING = 0x20000000, - PIPE_NOWAIT = ffi.new("unsigned long[1]", 0x00000001), - - INVALID_HANDLE_VALUE = ffi.cast("void*", -1), - - -- don't care about how many bytes WriteFile wrote, so allocate something to store the result once - _lpNumberOfBytesWritten = ffi.new("unsigned long[1]"), - } - -- cache flags used in run() to avoid bor() call - winapi._createfile_pipe_flags = winapi.bit.bor(winapi.FILE_FLAG_WRITE_THROUGH, winapi.FILE_FLAG_NO_BUFFERING) - - ffi.cdef[[ + local ffi_loaded, ffi = pcall(require, "ffi") + if ffi_loaded then + winapi = { + ffi = ffi, + C = ffi.C, + bit = require("bit"), + socket_wc = "", + + -- WinAPI constants + CP_UTF8 = 65001, + GENERIC_WRITE = 0x40000000, + OPEN_EXISTING = 3, + FILE_FLAG_WRITE_THROUGH = 0x80000000, + FILE_FLAG_NO_BUFFERING = 0x20000000, + PIPE_NOWAIT = ffi.new("unsigned long[1]", 0x00000001), + + INVALID_HANDLE_VALUE = ffi.cast("void*", -1), + + -- don't care about how many bytes WriteFile wrote, so allocate something to store the result once + _lpNumberOfBytesWritten = ffi.new("unsigned long[1]"), + } + -- cache flags used in run() to avoid bor() call + winapi._createfile_pipe_flags = winapi.bit.bor(winapi.FILE_FLAG_WRITE_THROUGH, winapi.FILE_FLAG_NO_BUFFERING) + + ffi.cdef([[ void* __stdcall CreateFileW(const wchar_t *lpFileName, unsigned long dwDesiredAccess, unsigned long dwShareMode, void *lpSecurityAttributes, unsigned long dwCreationDisposition, unsigned long dwFlagsAndAttributes, void *hTemplateFile); bool __stdcall WriteFile(void *hFile, const void *lpBuffer, unsigned long nNumberOfBytesToWrite, unsigned long *lpNumberOfBytesWritten, void *lpOverlapped); bool __stdcall CloseHandle(void *hObject); bool __stdcall SetNamedPipeHandleState(void *hNamedPipe, unsigned long *lpMode, unsigned long *lpMaxCollectionCount, unsigned long *lpCollectDataTimeout); int __stdcall MultiByteToWideChar(unsigned int CodePage, unsigned long dwFlags, const char *lpMultiByteStr, int cbMultiByte, wchar_t *lpWideCharStr, int cchWideChar); - ]] - - winapi.MultiByteToWideChar = function(MultiByteStr) - if MultiByteStr then - local utf16_len = winapi.C.MultiByteToWideChar(winapi.CP_UTF8, 0, MultiByteStr, -1, nil, 0) - if utf16_len > 0 then - local utf16_str = winapi.ffi.new("wchar_t[?]", utf16_len) - if winapi.C.MultiByteToWideChar(winapi.CP_UTF8, 0, MultiByteStr, -1, utf16_str, utf16_len) > 0 then - return utf16_str - end - end - end - return "" - end - - else - options.direct_io = false - end + ]]) + + winapi.MultiByteToWideChar = function(MultiByteStr) + if MultiByteStr then + local utf16_len = winapi.C.MultiByteToWideChar(winapi.CP_UTF8, 0, MultiByteStr, -1, nil, 0) + if utf16_len > 0 then + local utf16_str = winapi.ffi.new("wchar_t[?]", utf16_len) + if winapi.C.MultiByteToWideChar(winapi.CP_UTF8, 0, MultiByteStr, -1, utf16_str, utf16_len) > 0 then + return utf16_str + end + end + end + return "" + end + else + options.direct_io = false + end end local file @@ -157,11 +156,19 @@ local script_name local show_thumbnail = false -local filters_reset = {["lavfi-crop"]=true, ["crop"]=true} -local filters_runtime = {["hflip"]=true, ["vflip"]=true} -local filters_all = {["hflip"]=true, ["vflip"]=true, ["lavfi-crop"]=true, ["crop"]=true} - -local tone_mappings = {["none"]=true, ["clip"]=true, ["linear"]=true, ["gamma"]=true, ["reinhard"]=true, ["hable"]=true, ["mobius"]=true} +local filters_reset = { ["lavfi-crop"] = true, ["crop"] = true } +local filters_runtime = { ["hflip"] = true, ["vflip"] = true } +local filters_all = { ["hflip"] = true, ["vflip"] = true, ["lavfi-crop"] = true, ["crop"] = true } + +local tone_mappings = { + ["none"] = true, + ["clip"] = true, + ["linear"] = true, + ["gamma"] = true, + ["reinhard"] = true, + ["hable"] = true, + ["mobius"] = true, +} local last_tone_mapping local last_vf_reset = "" @@ -178,7 +185,7 @@ local last_has_vid = 0 local has_vid = 0 local file_timer -local file_check_period = 1/60 +local file_check_period = 1 / 60 local allow_fast_seek = true @@ -191,50 +198,50 @@ if echo "print-text thumbfast" >&"$MPV_IPC_FD"; then echo -n > "$MPV_IPC_PATH"; ]=] local function get_os() - local raw_os_name = "" - - if jit and jit.os and jit.arch then - raw_os_name = jit.os - else - if package.config:sub(1,1) == "\\" then - -- Windows - local env_OS = os.getenv("OS") - if env_OS then - raw_os_name = env_OS - end - else - raw_os_name = subprocess({"uname", "-s"}).stdout - end - end - - raw_os_name = (raw_os_name):lower() - - local os_patterns = { - ["windows"] = "windows", - ["linux"] = "linux", - - ["osx"] = "darwin", - ["mac"] = "darwin", - ["darwin"] = "darwin", - - ["^mingw"] = "windows", - ["^cygwin"] = "windows", - - ["bsd$"] = "darwin", - ["sunos"] = "darwin" - } - - -- Default to linux - local str_os_name = "linux" - - for pattern, name in pairs(os_patterns) do - if raw_os_name:match(pattern) then - str_os_name = name - break - end - end - - return str_os_name + local raw_os_name = "" + + if jit and jit.os and jit.arch then + raw_os_name = jit.os + else + if package.config:sub(1, 1) == "\\" then + -- Windows + local env_OS = os.getenv("OS") + if env_OS then + raw_os_name = env_OS + end + else + raw_os_name = subprocess({ "uname", "-s" }).stdout + end + end + + raw_os_name = (raw_os_name):lower() + + local os_patterns = { + ["windows"] = "windows", + ["linux"] = "linux", + + ["osx"] = "darwin", + ["mac"] = "darwin", + ["darwin"] = "darwin", + + ["^mingw"] = "windows", + ["^cygwin"] = "windows", + + ["bsd$"] = "darwin", + ["sunos"] = "darwin", + } + + -- Default to linux + local str_os_name = "linux" + + for pattern, name in pairs(os_patterns) do + if raw_os_name:match(pattern) then + str_os_name = name + break + end + end + + return str_os_name end local os_name = mp.get_property("platform") or get_os() @@ -242,19 +249,19 @@ local os_name = mp.get_property("platform") or get_os() local path_separator = os_name == "windows" and "\\" or "/" if options.socket == "" then - if os_name == "windows" then - options.socket = "thumbfast" - else - options.socket = "/tmp/thumbfast" - end + if os_name == "windows" then + options.socket = "thumbfast" + else + options.socket = "/tmp/thumbfast" + end end if options.thumbnail == "" then - if os_name == "windows" then - options.thumbnail = os.getenv("TEMP").."\\thumbfast.out" - else - options.thumbnail = "/tmp/thumbfast.out" - end + if os_name == "windows" then + options.thumbnail = os.getenv("TEMP") .. "\\thumbfast.out" + else + options.thumbnail = "/tmp/thumbfast.out" + end end local unique = mp.utils.getpid() @@ -263,13 +270,13 @@ options.socket = options.socket .. unique options.thumbnail = options.thumbnail .. unique if options.direct_io then - if os_name == "windows" then - winapi.socket_wc = winapi.MultiByteToWideChar("\\\\.\\pipe\\" .. options.socket) - end + if os_name == "windows" then + winapi.socket_wc = winapi.MultiByteToWideChar("\\\\.\\pipe\\" .. options.socket) + end - if winapi.socket_wc == "" then - options.direct_io = false - end + if winapi.socket_wc == "" then + options.direct_io = false + end end options.scale_factor = math.floor(options.scale_factor) @@ -278,651 +285,837 @@ local mpv_path = options.mpv_path local frontend_path if mpv_path == "mpv" and os_name == "windows" then - frontend_path = mp.get_property_native("user-data/frontend/process-path") - mpv_path = frontend_path or mpv_path + frontend_path = mp.get_property_native("user-data/frontend/process-path") + mpv_path = frontend_path or mpv_path end if mpv_path == "mpv" and os_name == "darwin" and unique then - -- TODO: look into ~~osxbundle/ - mpv_path = string.gsub(subprocess({"ps", "-o", "comm=", "-p", tostring(unique)}).stdout, "[\n\r]", "") - if mpv_path ~= "mpv" then - mpv_path = string.gsub(mpv_path, "/mpv%-bundle$", "/mpv") - local mpv_bin = mp.utils.file_info("/usr/local/mpv") - if mpv_bin and mpv_bin.is_file then - mpv_path = "/usr/local/mpv" - else - local mpv_app = mp.utils.file_info("/Applications/mpv.app/Contents/MacOS/mpv") - if mpv_app and mpv_app.is_file then - mp.msg.warn("symlink mpv to fix Dock icons: `sudo ln -s /Applications/mpv.app/Contents/MacOS/mpv /usr/local/mpv`") - else - mp.msg.warn("drag to your Applications folder and symlink mpv to fix Dock icons: `sudo ln -s /Applications/mpv.app/Contents/MacOS/mpv /usr/local/mpv`") - end - end - end + -- TODO: look into ~~osxbundle/ + mpv_path = string.gsub(subprocess({ "ps", "-o", "comm=", "-p", tostring(unique) }).stdout, "[\n\r]", "") + if mpv_path ~= "mpv" then + mpv_path = string.gsub(mpv_path, "/mpv%-bundle$", "/mpv") + local mpv_bin = mp.utils.file_info("/usr/local/mpv") + if mpv_bin and mpv_bin.is_file then + mpv_path = "/usr/local/mpv" + else + local mpv_app = mp.utils.file_info("/Applications/mpv.app/Contents/MacOS/mpv") + if mpv_app and mpv_app.is_file then + mp.msg.warn( + "symlink mpv to fix Dock icons: `sudo ln -s /Applications/mpv.app/Contents/MacOS/mpv /usr/local/mpv`" + ) + else + mp.msg.warn( + "drag to your Applications folder and symlink mpv to fix Dock icons: `sudo ln -s /Applications/mpv.app/Contents/MacOS/mpv /usr/local/mpv`" + ) + end + end + end end local function vo_tone_mapping() - local passes = mp.get_property_native("vo-passes") - if passes and passes["fresh"] then - for k, v in pairs(passes["fresh"]) do - for k2, v2 in pairs(v) do - if k2 == "desc" and v2 then - local tone_mapping = string.match(v2, "([0-9a-z.-]+) tone map") - if tone_mapping then - return tone_mapping - end - end - end - end - end + local passes = mp.get_property_native("vo-passes") + if passes and passes["fresh"] then + for k, v in pairs(passes["fresh"]) do + for k2, v2 in pairs(v) do + if k2 == "desc" and v2 then + local tone_mapping = string.match(v2, "([0-9a-z.-]+) tone map") + if tone_mapping then + return tone_mapping + end + end + end + end + end end local function vf_string(filters, full) - local vf = "" - local vf_table = properties["vf"] - - if (properties["video-crop"] or "") ~= "" then - vf = "lavfi-crop="..string.gsub(properties["video-crop"], "(%d*)x?(%d*)%+(%d+)%+(%d+)", "w=%1:h=%2:x=%3:y=%4").."," - local width = properties["video-out-params"] and properties["video-out-params"]["dw"] - local height = properties["video-out-params"] and properties["video-out-params"]["dh"] - if width and height then - vf = string.gsub(vf, "w=:h=:", "w="..width..":h="..height..":") - end - end - - if vf_table and #vf_table > 0 then - for i = #vf_table, 1, -1 do - if filters[vf_table[i].name] then - local args = "" - for key, value in pairs(vf_table[i].params) do - if args ~= "" then - args = args .. ":" - end - args = args .. key .. "=" .. value - end - vf = vf .. vf_table[i].name .. "=" .. args .. "," - end - end - end - - if (full and options.tone_mapping ~= "no") or options.tone_mapping == "auto" then - if properties["video-params"] and properties["video-params"]["primaries"] == "bt.2020" then - local tone_mapping = options.tone_mapping - if tone_mapping == "auto" then - tone_mapping = last_tone_mapping or properties["tone-mapping"] - if tone_mapping == "auto" and properties["current-vo"] == "gpu-next" then - tone_mapping = vo_tone_mapping() - end - end - if not tone_mappings[tone_mapping] then - tone_mapping = "hable" - end - last_tone_mapping = tone_mapping - vf = vf .. "zscale=transfer=linear,format=gbrpf32le,tonemap="..tone_mapping..",zscale=transfer=bt709," - end - end - - if full then - vf = vf.."scale=w="..effective_w..":h="..effective_h..par..",pad=w="..effective_w..":h="..effective_h..":x=-1:y=-1,format=bgra" - end - - return vf + local vf = "" + local vf_table = properties["vf"] + + if (properties["video-crop"] or "") ~= "" then + vf = "lavfi-crop=" + .. string.gsub(properties["video-crop"], "(%d*)x?(%d*)%+(%d+)%+(%d+)", "w=%1:h=%2:x=%3:y=%4") + .. "," + local width = properties["video-out-params"] and properties["video-out-params"]["dw"] + local height = properties["video-out-params"] and properties["video-out-params"]["dh"] + if width and height then + vf = string.gsub(vf, "w=:h=:", "w=" .. width .. ":h=" .. height .. ":") + end + end + + if vf_table and #vf_table > 0 then + for i = #vf_table, 1, -1 do + if filters[vf_table[i].name] then + local args = "" + for key, value in pairs(vf_table[i].params) do + if args ~= "" then + args = args .. ":" + end + args = args .. key .. "=" .. value + end + vf = vf .. vf_table[i].name .. "=" .. args .. "," + end + end + end + + if (full and options.tone_mapping ~= "no") or options.tone_mapping == "auto" then + if properties["video-params"] and properties["video-params"]["primaries"] == "bt.2020" then + local tone_mapping = options.tone_mapping + if tone_mapping == "auto" then + tone_mapping = last_tone_mapping or properties["tone-mapping"] + if tone_mapping == "auto" and properties["current-vo"] == "gpu-next" then + tone_mapping = vo_tone_mapping() + end + end + if not tone_mappings[tone_mapping] then + tone_mapping = "hable" + end + last_tone_mapping = tone_mapping + vf = vf .. "zscale=transfer=linear,format=gbrpf32le,tonemap=" .. tone_mapping .. ",zscale=transfer=bt709," + end + end + + if full then + vf = vf + .. "scale=w=" + .. effective_w + .. ":h=" + .. effective_h + .. par + .. ",pad=w=" + .. effective_w + .. ":h=" + .. effective_h + .. ":x=-1:y=-1,format=bgra" + end + + return vf end local function calc_dimensions() - local width = properties["video-out-params"] and properties["video-out-params"]["dw"] - local height = properties["video-out-params"] and properties["video-out-params"]["dh"] - if not width or not height then return end - - local scale = properties["display-hidpi-scale"] or 1 - - if width / height > options.max_width / options.max_height then - effective_w = math.floor(options.max_width * scale + 0.5) - effective_h = math.floor(height / width * effective_w + 0.5) - else - effective_h = math.floor(options.max_height * scale + 0.5) - effective_w = math.floor(width / height * effective_h + 0.5) - end - - local v_par = properties["video-out-params"] and properties["video-out-params"]["par"] or 1 - if v_par == 1 then - par = ":force_original_aspect_ratio=decrease" - else - par = "" - end + local width = properties["video-out-params"] and properties["video-out-params"]["dw"] + local height = properties["video-out-params"] and properties["video-out-params"]["dh"] + if not width or not height then + return + end + + local scale = properties["display-hidpi-scale"] or 1 + + if width / height > options.max_width / options.max_height then + effective_w = math.floor(options.max_width * scale + 0.5) + effective_h = math.floor(height / width * effective_w + 0.5) + else + effective_h = math.floor(options.max_height * scale + 0.5) + effective_w = math.floor(width / height * effective_h + 0.5) + end + + local v_par = properties["video-out-params"] and properties["video-out-params"]["par"] or 1 + if v_par == 1 then + par = ":force_original_aspect_ratio=decrease" + else + par = "" + end end local info_timer = nil local function info(w, h) - local rotate = properties["video-params"] and properties["video-params"]["rotate"] - local image = properties["current-tracks/video"] and properties["current-tracks/video"]["image"] - local albumart = image and properties["current-tracks/video"]["albumart"] - - disabled = (w or 0) == 0 or (h or 0) == 0 or - has_vid == 0 or - (properties["demuxer-via-network"] and not options.network) or - (albumart and not options.audio) or - (image and not albumart) or - force_disabled - - if info_timer then - info_timer:kill() - info_timer = nil - elseif has_vid == 0 or (rotate == nil and not disabled) then - info_timer = mp.add_timeout(0.05, function() info(w, h) end) - end - - local json, err = mp.utils.format_json({width=w * options.scale_factor, height=h * options.scale_factor, scale_factor=options.scale_factor, disabled=disabled, available=true, socket=options.socket, thumbnail=options.thumbnail, overlay_id=options.overlay_id}) - if pre_0_30_0 then - mp.command_native({"script-message", "thumbfast-info", json}) - else - mp.command_native_async({"script-message", "thumbfast-info", json}, function() end) - end + local rotate = properties["video-params"] and properties["video-params"]["rotate"] + local image = properties["current-tracks/video"] and properties["current-tracks/video"]["image"] + local albumart = image and properties["current-tracks/video"]["albumart"] + + disabled = (w or 0) == 0 + or (h or 0) == 0 + or has_vid == 0 + or (properties["demuxer-via-network"] and not options.network) + or (albumart and not options.audio) + or (image and not albumart) + or force_disabled + + if info_timer then + info_timer:kill() + info_timer = nil + elseif has_vid == 0 or (rotate == nil and not disabled) then + info_timer = mp.add_timeout(0.05, function() + info(w, h) + end) + end + + local json, err = mp.utils.format_json({ + width = w * options.scale_factor, + height = h * options.scale_factor, + scale_factor = options.scale_factor, + disabled = disabled, + available = true, + socket = options.socket, + thumbnail = options.thumbnail, + overlay_id = options.overlay_id, + }) + if pre_0_30_0 then + mp.command_native({ "script-message", "thumbfast-info", json }) + else + mp.command_native_async({ "script-message", "thumbfast-info", json }, function() end) + end end local function remove_thumbnail_files() - if file then - file:close() - file = nil - file_bytes = 0 - end - os.remove(options.thumbnail) - os.remove(options.thumbnail..".bgra") + if file then + file:close() + file = nil + file_bytes = 0 + end + os.remove(options.thumbnail) + os.remove(options.thumbnail .. ".bgra") end local activity_timer local function spawn(time) - if disabled then return end - - local path = properties["path"] - if path == nil then return end - - if options.quit_after_inactivity > 0 then - if show_thumbnail or activity_timer:is_enabled() then - activity_timer:kill() - end - activity_timer:resume() - end - - local open_filename = properties["stream-open-filename"] - local ytdl = open_filename and properties["demuxer-via-network"] and path ~= open_filename - if ytdl then - path = open_filename - end - - remove_thumbnail_files() - - local vid = properties["vid"] - has_vid = vid or 0 - - local args = { - mpv_path, "--no-config", "--msg-level=all=no", "--idle", "--pause", "--keep-open=always", "--really-quiet", "--no-terminal", - "--load-scripts=no", "--osc=no", "--ytdl=no", "--load-stats-overlay=no", "--load-osd-console=no", "--load-auto-profiles=no", - "--edition="..(properties["edition"] or "auto"), "--vid="..(vid or "auto"), "--no-sub", "--no-audio", - "--start="..time, allow_fast_seek and "--hr-seek=no" or "--hr-seek=yes", - "--ytdl-format=worst", "--demuxer-readahead-secs=0", "--demuxer-max-bytes=128KiB", - "--vd-lavc-skiploopfilter=all", "--vd-lavc-software-fallback=1", "--vd-lavc-fast", "--vd-lavc-threads=2", "--hwdec="..(options.hwdec and "auto" or "no"), - "--vf="..vf_string(filters_all, true), - "--sws-scaler=fast-bilinear", - "--video-rotate="..last_rotate, - "--ovc=rawvideo", "--of=image2", "--ofopts=update=1", "--o="..options.thumbnail - } - - if not pre_0_30_0 then - table.insert(args, "--sws-allow-zimg=no") - end - - if support_media_control then - table.insert(args, "--media-controls=no") - end - - if os_name == "darwin" and properties["macos-app-activation-policy"] then - table.insert(args, "--macos-app-activation-policy=accessory") - end - - if os_name == "windows" or pre_0_33_0 then - table.insert(args, "--input-ipc-server="..options.socket) - elseif not script_written then - local client_script_path = options.socket..".run" - local script = io.open(client_script_path, "w+") - if script == nil then - mp.msg.error("client script write failed") - return - else - script_written = true - script:write(string.format(client_script, options.socket)) - script:close() - subprocess({"chmod", "+x", client_script_path}, true) - table.insert(args, "--scripts="..client_script_path) - end - else - local client_script_path = options.socket..".run" - table.insert(args, "--scripts="..client_script_path) - end - - table.insert(args, "--") - table.insert(args, path) - - spawned = true - spawn_waiting = true - - subprocess(args, true, - function(success, result) - if spawn_waiting and (success == false or (result.status ~= 0 and result.status ~= -2)) then - spawned = false - spawn_waiting = false - options.tone_mapping = "no" - mp.msg.error("mpv subprocess create failed") - if not spawn_working then -- notify users of required configuration - if options.mpv_path == "mpv" then - if properties["current-vo"] == "libmpv" then - if options.mpv_path == mpv_path then -- attempt to locate ImPlay - mpv_path = "ImPlay" - spawn(time) - else -- ImPlay not in path - if os_name ~= "darwin" then - force_disabled = true - info(real_w or effective_w, real_h or effective_h) - end - mp.commandv("show-text", "thumbfast: ERROR! cannot create mpv subprocess", 5000) - mp.commandv("script-message-to", "implay", "show-message", "thumbfast initial setup", "Set mpv_path=PATH_TO_ImPlay in thumbfast config:\n" .. string.gsub(mp.command_native({"expand-path", "~~/script-opts/thumbfast.conf"}), "[/\\]", path_separator).."\nand restart ImPlay") - end - else - mp.commandv("show-text", "thumbfast: ERROR! cannot create mpv subprocess", 5000) - if os_name == "windows" and frontend_path == nil then - mp.commandv("script-message-to", "mpvnet", "show-text", "thumbfast: ERROR! install standalone mpv, see README", 5000, 20) - mp.commandv("script-message", "mpv.net", "show-text", "thumbfast: ERROR! install standalone mpv, see README", 5000, 20) - end - end - else - mp.commandv("show-text", "thumbfast: ERROR! cannot create mpv subprocess", 5000) - -- found ImPlay but not defined in config - mp.commandv("script-message-to", "implay", "show-message", "thumbfast", "Set mpv_path=PATH_TO_ImPlay in thumbfast config:\n" .. string.gsub(mp.command_native({"expand-path", "~~/script-opts/thumbfast.conf"}), "[/\\]", path_separator).."\nand restart ImPlay") - end - end - elseif success == true and (result.status == 0 or result.status == -2) then - if not spawn_working and properties["current-vo"] == "libmpv" and options.mpv_path ~= mpv_path then - mp.commandv("script-message-to", "implay", "show-message", "thumbfast initial setup", "Set mpv_path=ImPlay in thumbfast config:\n" .. string.gsub(mp.command_native({"expand-path", "~~/script-opts/thumbfast.conf"}), "[/\\]", path_separator).."\nand restart ImPlay") - end - spawn_working = true - spawn_waiting = false - end - end - ) + if disabled then + return + end + + local path = properties["path"] + if path == nil then + return + end + + if options.quit_after_inactivity > 0 then + if show_thumbnail or activity_timer:is_enabled() then + activity_timer:kill() + end + activity_timer:resume() + end + + local open_filename = properties["stream-open-filename"] + local ytdl = open_filename and properties["demuxer-via-network"] and path ~= open_filename + if ytdl then + path = open_filename + end + + remove_thumbnail_files() + + local vid = properties["vid"] + has_vid = vid or 0 + + local args = { + mpv_path, + "--no-config", + "--msg-level=all=no", + "--idle", + "--pause", + "--keep-open=always", + "--really-quiet", + "--no-terminal", + "--load-scripts=no", + "--osc=no", + "--ytdl=no", + "--load-stats-overlay=no", + "--load-osd-console=no", + "--load-auto-profiles=no", + "--edition=" .. (properties["edition"] or "auto"), + "--vid=" .. (vid or "auto"), + "--no-sub", + "--no-audio", + "--start=" .. time, + allow_fast_seek and "--hr-seek=no" or "--hr-seek=yes", + "--ytdl-format=worst", + "--demuxer-readahead-secs=0", + "--demuxer-max-bytes=128KiB", + "--vd-lavc-skiploopfilter=all", + "--vd-lavc-software-fallback=1", + "--vd-lavc-fast", + "--vd-lavc-threads=2", + "--hwdec=" .. (options.hwdec and "auto" or "no"), + "--vf=" .. vf_string(filters_all, true), + "--sws-scaler=fast-bilinear", + "--video-rotate=" .. last_rotate, + "--ovc=rawvideo", + "--of=image2", + "--ofopts=update=1", + "--o=" .. options.thumbnail, + } + + if not pre_0_30_0 then + table.insert(args, "--sws-allow-zimg=no") + end + + if support_media_control then + table.insert(args, "--media-controls=no") + end + + if os_name == "darwin" and properties["macos-app-activation-policy"] then + table.insert(args, "--macos-app-activation-policy=accessory") + end + + if os_name == "windows" or pre_0_33_0 then + table.insert(args, "--input-ipc-server=" .. options.socket) + elseif not script_written then + local client_script_path = options.socket .. ".run" + local script = io.open(client_script_path, "w+") + if script == nil then + mp.msg.error("client script write failed") + return + else + script_written = true + script:write(string.format(client_script, options.socket)) + script:close() + subprocess({ "chmod", "+x", client_script_path }, true) + table.insert(args, "--scripts=" .. client_script_path) + end + else + local client_script_path = options.socket .. ".run" + table.insert(args, "--scripts=" .. client_script_path) + end + + table.insert(args, "--") + table.insert(args, path) + + spawned = true + spawn_waiting = true + + subprocess(args, true, function(success, result) + if spawn_waiting and (success == false or (result.status ~= 0 and result.status ~= -2)) then + spawned = false + spawn_waiting = false + options.tone_mapping = "no" + mp.msg.error("mpv subprocess create failed") + if not spawn_working then -- notify users of required configuration + if options.mpv_path == "mpv" then + if properties["current-vo"] == "libmpv" then + if options.mpv_path == mpv_path then -- attempt to locate ImPlay + mpv_path = "ImPlay" + spawn(time) + else -- ImPlay not in path + if os_name ~= "darwin" then + force_disabled = true + info(real_w or effective_w, real_h or effective_h) + end + mp.commandv("show-text", "thumbfast: ERROR! cannot create mpv subprocess", 5000) + mp.commandv( + "script-message-to", + "implay", + "show-message", + "thumbfast initial setup", + "Set mpv_path=PATH_TO_ImPlay in thumbfast config:\n" + .. string.gsub( + mp.command_native({ "expand-path", "~~/script-opts/thumbfast.conf" }), + "[/\\]", + path_separator + ) + .. "\nand restart ImPlay" + ) + end + else + mp.commandv("show-text", "thumbfast: ERROR! cannot create mpv subprocess", 5000) + if os_name == "windows" and frontend_path == nil then + mp.commandv( + "script-message-to", + "mpvnet", + "show-text", + "thumbfast: ERROR! install standalone mpv, see README", + 5000, + 20 + ) + mp.commandv( + "script-message", + "mpv.net", + "show-text", + "thumbfast: ERROR! install standalone mpv, see README", + 5000, + 20 + ) + end + end + else + mp.commandv("show-text", "thumbfast: ERROR! cannot create mpv subprocess", 5000) + -- found ImPlay but not defined in config + mp.commandv( + "script-message-to", + "implay", + "show-message", + "thumbfast", + "Set mpv_path=PATH_TO_ImPlay in thumbfast config:\n" + .. string.gsub( + mp.command_native({ "expand-path", "~~/script-opts/thumbfast.conf" }), + "[/\\]", + path_separator + ) + .. "\nand restart ImPlay" + ) + end + end + elseif success == true and (result.status == 0 or result.status == -2) then + if not spawn_working and properties["current-vo"] == "libmpv" and options.mpv_path ~= mpv_path then + mp.commandv( + "script-message-to", + "implay", + "show-message", + "thumbfast initial setup", + "Set mpv_path=ImPlay in thumbfast config:\n" + .. string.gsub( + mp.command_native({ "expand-path", "~~/script-opts/thumbfast.conf" }), + "[/\\]", + path_separator + ) + .. "\nand restart ImPlay" + ) + end + spawn_working = true + spawn_waiting = false + end + end) end local function run(command) - if not spawned then return end - - if options.direct_io then - local hPipe = winapi.C.CreateFileW(winapi.socket_wc, winapi.GENERIC_WRITE, 0, nil, winapi.OPEN_EXISTING, winapi._createfile_pipe_flags, nil) - if hPipe ~= winapi.INVALID_HANDLE_VALUE then - local buf = command .. "\n" - winapi.C.SetNamedPipeHandleState(hPipe, winapi.PIPE_NOWAIT, nil, nil) - winapi.C.WriteFile(hPipe, buf, #buf + 1, winapi._lpNumberOfBytesWritten, nil) - winapi.C.CloseHandle(hPipe) - end - - return - end - - local command_n = command.."\n" - - if os_name == "windows" then - if file and file_bytes + #command_n >= 4096 then - file:close() - file = nil - file_bytes = 0 - end - if not file then - file = io.open("\\\\.\\pipe\\"..options.socket, "r+b") - end - elseif pre_0_33_0 then - subprocess({"/usr/bin/env", "sh", "-c", "echo '" .. command .. "' | socat - " .. options.socket}) - return - elseif not file then - file = io.open(options.socket, "r+") - end - if file then - file_bytes = file:seek("end") - file:write(command_n) - file:flush() - end + if not spawned then + return + end + + if options.direct_io then + local hPipe = winapi.C.CreateFileW( + winapi.socket_wc, + winapi.GENERIC_WRITE, + 0, + nil, + winapi.OPEN_EXISTING, + winapi._createfile_pipe_flags, + nil + ) + if hPipe ~= winapi.INVALID_HANDLE_VALUE then + local buf = command .. "\n" + winapi.C.SetNamedPipeHandleState(hPipe, winapi.PIPE_NOWAIT, nil, nil) + winapi.C.WriteFile(hPipe, buf, #buf + 1, winapi._lpNumberOfBytesWritten, nil) + winapi.C.CloseHandle(hPipe) + end + + return + end + + local command_n = command .. "\n" + + if os_name == "windows" then + if file and file_bytes + #command_n >= 4096 then + file:close() + file = nil + file_bytes = 0 + end + if not file then + file = io.open("\\\\.\\pipe\\" .. options.socket, "r+b") + end + elseif pre_0_33_0 then + subprocess({ "/usr/bin/env", "sh", "-c", "echo '" .. command .. "' | socat - " .. options.socket }) + return + elseif not file then + file = io.open(options.socket, "r+") + end + if file then + file_bytes = file:seek("end") + file:write(command_n) + file:flush() + end end local function draw(w, h, script) - if not w or not show_thumbnail then return end - if x ~= nil then - local scale_w, scale_h = options.scale_factor ~= 1 and (w * options.scale_factor) or nil, options.scale_factor ~= 1 and (h * options.scale_factor) or nil - if pre_0_30_0 then - mp.command_native({"overlay-add", options.overlay_id, x, y, options.thumbnail..".bgra", 0, "bgra", w, h, (4*w), scale_w, scale_h}) - else - mp.command_native_async({"overlay-add", options.overlay_id, x, y, options.thumbnail..".bgra", 0, "bgra", w, h, (4*w), scale_w, scale_h}, function() end) - end - elseif script then - local json, err = mp.utils.format_json({width=w, height=h, scale_factor=options.scale_factor, x=x, y=y, socket=options.socket, thumbnail=options.thumbnail, overlay_id=options.overlay_id}) - mp.commandv("script-message-to", script, "thumbfast-render", json) - end + if not w or not show_thumbnail then + return + end + if x ~= nil then + local scale_w, scale_h = + options.scale_factor ~= 1 and (w * options.scale_factor) or nil, + options.scale_factor ~= 1 and (h * options.scale_factor) or nil + if pre_0_30_0 then + mp.command_native({ + "overlay-add", + options.overlay_id, + x, + y, + options.thumbnail .. ".bgra", + 0, + "bgra", + w, + h, + (4 * w), + scale_w, + scale_h, + }) + else + mp.command_native_async( + { + "overlay-add", + options.overlay_id, + x, + y, + options.thumbnail .. ".bgra", + 0, + "bgra", + w, + h, + (4 * w), + scale_w, + scale_h, + }, + function() end + ) + end + elseif script then + local json, err = mp.utils.format_json({ + width = w, + height = h, + scale_factor = options.scale_factor, + x = x, + y = y, + socket = options.socket, + thumbnail = options.thumbnail, + overlay_id = options.overlay_id, + }) + mp.commandv("script-message-to", script, "thumbfast-render", json) + end end local function real_res(req_w, req_h, filesize) - local count = filesize / 4 - local diff = (req_w * req_h) - count - - if (properties["video-params"] and properties["video-params"]["rotate"] or 0) % 180 == 90 then - req_w, req_h = req_h, req_w - end - - if diff == 0 then - return req_w, req_h - else - local threshold = 5 -- throw out results that change too much - local long_side, short_side = req_w, req_h - if req_h > req_w then - long_side, short_side = req_h, req_w - end - for a = short_side, short_side - threshold, -1 do - if count % a == 0 then - local b = count / a - if long_side - b < threshold then - if req_h < req_w then return b, a else return a, b end - end - end - end - return nil - end + local count = filesize / 4 + local diff = (req_w * req_h) - count + + if (properties["video-params"] and properties["video-params"]["rotate"] or 0) % 180 == 90 then + req_w, req_h = req_h, req_w + end + + if diff == 0 then + return req_w, req_h + else + local threshold = 5 -- throw out results that change too much + local long_side, short_side = req_w, req_h + if req_h > req_w then + long_side, short_side = req_h, req_w + end + for a = short_side, short_side - threshold, -1 do + if count % a == 0 then + local b = count / a + if long_side - b < threshold then + if req_h < req_w then + return b, a + else + return a, b + end + end + end + end + return nil + end end local function move_file(from, to) - if os_name == "windows" then - os.remove(to) - end - -- move the file because it can get overwritten while overlay-add is reading it, and crash the player - os.rename(from, to) + if os_name == "windows" then + os.remove(to) + end + -- move the file because it can get overwritten while overlay-add is reading it, and crash the player + os.rename(from, to) end local function seek(fast) - if last_seek_time then - run("async seek " .. last_seek_time .. (fast and " absolute+keyframes" or " absolute+exact")) - end + if last_seek_time then + run("async seek " .. last_seek_time .. (fast and " absolute+keyframes" or " absolute+exact")) + end end -local seek_period = 3/60 +local seek_period = 3 / 60 local seek_period_counter = 0 local seek_timer seek_timer = mp.add_periodic_timer(seek_period, function() - if seek_period_counter == 0 then - seek(allow_fast_seek) - seek_period_counter = 1 - else - if seek_period_counter == 2 then - if allow_fast_seek then - seek_timer:kill() - seek() - end - else seek_period_counter = seek_period_counter + 1 end - end + if seek_period_counter == 0 then + seek(allow_fast_seek) + seek_period_counter = 1 + else + if seek_period_counter == 2 then + if allow_fast_seek then + seek_timer:kill() + seek() + end + else + seek_period_counter = seek_period_counter + 1 + end + end end) seek_timer:kill() local function request_seek() - if seek_timer:is_enabled() then - seek_period_counter = 0 - else - seek_timer:resume() - seek(allow_fast_seek) - seek_period_counter = 1 - end + if seek_timer:is_enabled() then + seek_period_counter = 0 + else + seek_timer:resume() + seek(allow_fast_seek) + seek_period_counter = 1 + end end local function check_new_thumb() - -- the slave might start writing to the file after checking existance and - -- validity but before actually moving the file, so move to a temporary - -- location before validity check to make sure everything stays consistant - -- and valid thumbnails don't get overwritten by invalid ones - local tmp = options.thumbnail..".tmp" - move_file(options.thumbnail, tmp) - local finfo = mp.utils.file_info(tmp) - if not finfo then return false end - spawn_waiting = false - local w, h = real_res(effective_w, effective_h, finfo.size) - if w then -- only accept valid thumbnails - move_file(tmp, options.thumbnail..".bgra") - - real_w, real_h = w, h - if real_w and (real_w ~= last_real_w or real_h ~= last_real_h) then - last_real_w, last_real_h = real_w, real_h - info(real_w, real_h) - end - if not show_thumbnail then - file_timer:kill() - end - return true - end - - return false + -- the slave might start writing to the file after checking existance and + -- validity but before actually moving the file, so move to a temporary + -- location before validity check to make sure everything stays consistant + -- and valid thumbnails don't get overwritten by invalid ones + local tmp = options.thumbnail .. ".tmp" + move_file(options.thumbnail, tmp) + local finfo = mp.utils.file_info(tmp) + if not finfo then + return false + end + spawn_waiting = false + local w, h = real_res(effective_w, effective_h, finfo.size) + if w then -- only accept valid thumbnails + move_file(tmp, options.thumbnail .. ".bgra") + + real_w, real_h = w, h + if real_w and (real_w ~= last_real_w or real_h ~= last_real_h) then + last_real_w, last_real_h = real_w, real_h + info(real_w, real_h) + end + if not show_thumbnail then + file_timer:kill() + end + return true + end + + return false end file_timer = mp.add_periodic_timer(file_check_period, function() - if check_new_thumb() then - draw(real_w, real_h, script_name) - end + if check_new_thumb() then + draw(real_w, real_h, script_name) + end end) file_timer:kill() local function clear() - file_timer:kill() - seek_timer:kill() - if options.quit_after_inactivity > 0 then - if show_thumbnail or activity_timer:is_enabled() then - activity_timer:kill() - end - activity_timer:resume() - end - last_seek_time = nil - show_thumbnail = false - last_x = nil - last_y = nil - if script_name then return end - if pre_0_30_0 then - mp.command_native({"overlay-remove", options.overlay_id}) - else - mp.command_native_async({"overlay-remove", options.overlay_id}, function() end) - end + file_timer:kill() + seek_timer:kill() + if options.quit_after_inactivity > 0 then + if show_thumbnail or activity_timer:is_enabled() then + activity_timer:kill() + end + activity_timer:resume() + end + last_seek_time = nil + show_thumbnail = false + last_x = nil + last_y = nil + if script_name then + return + end + if pre_0_30_0 then + mp.command_native({ "overlay-remove", options.overlay_id }) + else + mp.command_native_async({ "overlay-remove", options.overlay_id }, function() end) + end end local function quit() - activity_timer:kill() - if show_thumbnail then - activity_timer:resume() - return - end - run("quit") - spawned = false - real_w, real_h = nil, nil - clear() + activity_timer:kill() + if show_thumbnail then + activity_timer:resume() + return + end + run("quit") + spawned = false + real_w, real_h = nil, nil + clear() end activity_timer = mp.add_timeout(options.quit_after_inactivity, quit) activity_timer:kill() local function thumb(time, r_x, r_y, script) - if disabled then return end - - time = tonumber(time) - if time == nil then return end - - if r_x == "" or r_y == "" then - x, y = nil, nil - else - x, y = math.floor(r_x + 0.5), math.floor(r_y + 0.5) - end - - script_name = script - if last_x ~= x or last_y ~= y or not show_thumbnail then - show_thumbnail = true - last_x, last_y = x, y - draw(real_w, real_h, script) - end - - if options.quit_after_inactivity > 0 then - if show_thumbnail or activity_timer:is_enabled() then - activity_timer:kill() - end - activity_timer:resume() - end - - if time == last_seek_time then return end - last_seek_time = time - if not spawned then spawn(time) end - request_seek() - if not file_timer:is_enabled() then file_timer:resume() end + if disabled then + return + end + + time = tonumber(time) + if time == nil then + return + end + + if r_x == "" or r_y == "" then + x, y = nil, nil + else + x, y = math.floor(r_x + 0.5), math.floor(r_y + 0.5) + end + + script_name = script + if last_x ~= x or last_y ~= y or not show_thumbnail then + show_thumbnail = true + last_x, last_y = x, y + draw(real_w, real_h, script) + end + + if options.quit_after_inactivity > 0 then + if show_thumbnail or activity_timer:is_enabled() then + activity_timer:kill() + end + activity_timer:resume() + end + + if time == last_seek_time then + return + end + last_seek_time = time + if not spawned then + spawn(time) + end + request_seek() + if not file_timer:is_enabled() then + file_timer:resume() + end end local function watch_changes() - if not dirty or not properties["video-out-params"] then return end - dirty = false - - local old_w = effective_w - local old_h = effective_h - - calc_dimensions() - - local vf_reset = vf_string(filters_reset) - local rotate = properties["video-rotate"] or 0 - - local resized = old_w ~= effective_w or - old_h ~= effective_h or - last_vf_reset ~= vf_reset or - (last_rotate % 180) ~= (rotate % 180) or - par ~= last_par or last_crop ~= properties["video-crop"] - - if resized then - last_rotate = rotate - info(effective_w, effective_h) - elseif last_has_vid ~= has_vid and has_vid ~= 0 then - info(effective_w, effective_h) - end - - if spawned then - if resized then - -- mpv doesn't allow us to change output size - local seek_time = last_seek_time - run("quit") - clear() - spawned = false - spawn(seek_time or mp.get_property_number("time-pos", 0)) - file_timer:resume() - else - if rotate ~= last_rotate then - run("set video-rotate "..rotate) - end - local vf_runtime = vf_string(filters_runtime) - if vf_runtime ~= last_vf_runtime then - run("vf set "..vf_string(filters_all, true)) - last_vf_runtime = vf_runtime - end - end - else - last_vf_runtime = vf_string(filters_runtime) - end - - last_vf_reset = vf_reset - last_rotate = rotate - last_par = par - last_crop = properties["video-crop"] - last_has_vid = has_vid - - if not spawned and not disabled and options.spawn_first and resized then - spawn(mp.get_property_number("time-pos", 0)) - file_timer:resume() - end + if not dirty or not properties["video-out-params"] then + return + end + dirty = false + + local old_w = effective_w + local old_h = effective_h + + calc_dimensions() + + local vf_reset = vf_string(filters_reset) + local rotate = properties["video-rotate"] or 0 + + local resized = old_w ~= effective_w + or old_h ~= effective_h + or last_vf_reset ~= vf_reset + or (last_rotate % 180) ~= (rotate % 180) + or par ~= last_par + or last_crop ~= properties["video-crop"] + + if resized then + last_rotate = rotate + info(effective_w, effective_h) + elseif last_has_vid ~= has_vid and has_vid ~= 0 then + info(effective_w, effective_h) + end + + if spawned then + if resized then + -- mpv doesn't allow us to change output size + local seek_time = last_seek_time + run("quit") + clear() + spawned = false + spawn(seek_time or mp.get_property_number("time-pos", 0)) + file_timer:resume() + else + if rotate ~= last_rotate then + run("set video-rotate " .. rotate) + end + local vf_runtime = vf_string(filters_runtime) + if vf_runtime ~= last_vf_runtime then + run("vf set " .. vf_string(filters_all, true)) + last_vf_runtime = vf_runtime + end + end + else + last_vf_runtime = vf_string(filters_runtime) + end + + last_vf_reset = vf_reset + last_rotate = rotate + last_par = par + last_crop = properties["video-crop"] + last_has_vid = has_vid + + if not spawned and not disabled and options.spawn_first and resized then + spawn(mp.get_property_number("time-pos", 0)) + file_timer:resume() + end end local function update_property(name, value) - properties[name] = value + properties[name] = value end local function update_property_dirty(name, value) - properties[name] = value - dirty = true - if name == "tone-mapping" then - last_tone_mapping = nil - end + properties[name] = value + dirty = true + if name == "tone-mapping" then + last_tone_mapping = nil + end end local function update_tracklist(name, value) - -- current-tracks shim - for _, track in ipairs(value) do - if track.type == "video" and track.selected then - properties["current-tracks/video"] = track - return - end - end + -- current-tracks shim + for _, track in ipairs(value) do + if track.type == "video" and track.selected then + properties["current-tracks/video"] = track + return + end + end end local function sync_changes(prop, val) - update_property(prop, val) - if val == nil then return end - - if type(val) == "boolean" then - if prop == "vid" then - has_vid = 0 - last_has_vid = 0 - info(effective_w, effective_h) - clear() - return - end - val = val and "yes" or "no" - end - - if prop == "vid" then - has_vid = 1 - end - - if not spawned then return end - - run("set "..prop.." "..val) - dirty = true + update_property(prop, val) + if val == nil then + return + end + + if type(val) == "boolean" then + if prop == "vid" then + has_vid = 0 + last_has_vid = 0 + info(effective_w, effective_h) + clear() + return + end + val = val and "yes" or "no" + end + + if prop == "vid" then + has_vid = 1 + end + + if not spawned then + return + end + + run("set " .. prop .. " " .. val) + dirty = true end local function file_load() - clear() - spawned = false - real_w, real_h = nil, nil - last_real_w, last_real_h = nil, nil - last_tone_mapping = nil - last_seek_time = nil - if info_timer then - info_timer:kill() - info_timer = nil - end - - calc_dimensions() - info(effective_w, effective_h) + clear() + spawned = false + real_w, real_h = nil, nil + last_real_w, last_real_h = nil, nil + last_tone_mapping = nil + last_seek_time = nil + if info_timer then + info_timer:kill() + info_timer = nil + end + + calc_dimensions() + info(effective_w, effective_h) end local function shutdown() - run("quit") - remove_thumbnail_files() - if os_name ~= "windows" then - os.remove(options.socket) - os.remove(options.socket..".run") - end + run("quit") + remove_thumbnail_files() + if os_name ~= "windows" then + os.remove(options.socket) + os.remove(options.socket .. ".run") + end end local function on_duration(prop, val) - allow_fast_seek = (val or 30) >= 30 + allow_fast_seek = (val or 30) >= 30 end mp.observe_property("current-tracks/video", "native", function(name, value) - if pre_0_33_0 then - mp.unobserve_property(update_tracklist) - pre_0_33_0 = false - end - update_property(name, value) + if pre_0_33_0 then + mp.unobserve_property(update_tracklist) + pre_0_33_0 = false + end + update_property(name, value) end) mp.observe_property("track-list", "native", update_tracklist) |
