mpv and xfce configs
This commit is contained in:
parent
fde3657808
commit
38f2cc85b6
BIN
xfce-custom/dots/home/neko/.config/dconf/user
Normal file
BIN
xfce-custom/dots/home/neko/.config/dconf/user
Normal file
Binary file not shown.
14
xfce-custom/dots/home/neko/.config/mimeapps.list
Normal file
14
xfce-custom/dots/home/neko/.config/mimeapps.list
Normal file
@ -0,0 +1,14 @@
|
||||
[Default Applications]
|
||||
x-scheme-handler/http=xfce4-web-browser.desktop
|
||||
x-scheme-handler/https=xfce4-web-browser.desktop
|
||||
image/jpeg=sxiv.desktop
|
||||
image/png=sxiv.desktop
|
||||
image/gif=sxiv.desktop
|
||||
|
||||
[Added Associations]
|
||||
x-scheme-handler/http=xfce4-web-browser.desktop;
|
||||
x-scheme-handler/https=xfce4-web-browser.desktop;
|
||||
image/jpeg=librewolf.desktop;sxiv.desktop;
|
||||
image/png=sxiv.desktop;
|
||||
image/gif=sxiv.desktop;
|
||||
application/x-desktop=librewolf.desktop;
|
BIN
xfce-custom/dots/home/neko/.config/mpv/fonts/uosc_icons.otf
Normal file
BIN
xfce-custom/dots/home/neko/.config/mpv/fonts/uosc_icons.otf
Normal file
Binary file not shown.
BIN
xfce-custom/dots/home/neko/.config/mpv/fonts/uosc_textures.ttf
Normal file
BIN
xfce-custom/dots/home/neko/.config/mpv/fonts/uosc_textures.ttf
Normal file
Binary file not shown.
10
xfce-custom/dots/home/neko/.config/mpv/input.conf
Normal file
10
xfce-custom/dots/home/neko/.config/mpv/input.conf
Normal file
@ -0,0 +1,10 @@
|
||||
F1 script-message-to command_palette show-command-palette bindings # Show bindings
|
||||
F2 script-message-to command_palette show-command-palette commands # Show commands
|
||||
F3 script-message-to command_palette show-command-palette properties # Show properties
|
||||
F4 script-message-to command_palette show-command-palette options # Show options
|
||||
F8 script-message-to command_palette show-command-palette playlist # Show playlist
|
||||
Alt+c script-message-to command_palette show-command-palette chapters # Show chapters
|
||||
Alt+a script-message-to command_palette show-command-palette audio # Show audio tracks
|
||||
Alt+s script-message-to command_palette show-command-palette subtitle # Show subtitle tracks
|
||||
Alt+v script-message-to command_palette show-command-palette video # Show video tracks
|
||||
Alt+p script-message-to command_palette show-command-palette profiles # Show profiles
|
7
xfce-custom/dots/home/neko/.config/mpv/mpv.conf
Normal file
7
xfce-custom/dots/home/neko/.config/mpv/mpv.conf
Normal file
@ -0,0 +1,7 @@
|
||||
# required so that the 2 UIs don't fight each other
|
||||
osc=no
|
||||
# uosc provides its own seeking/volume indicators, so you also don't need this
|
||||
osd-bar=no
|
||||
# uosc will draw its own window controls if you disable window border
|
||||
border=yes
|
||||
profile=pseudo-gui
|
@ -0,0 +1,897 @@
|
||||
local mp = require 'mp'
|
||||
local utils = require 'mp.utils'
|
||||
local assdraw = require 'mp.assdraw'
|
||||
|
||||
-- create namespace with default values
|
||||
local em = {
|
||||
|
||||
-- customisable values ------------------------------------------------------
|
||||
|
||||
lines_to_show = 17, -- NOT including search line
|
||||
pause_on_open = true,
|
||||
resume_on_exit = "only-if-was-paused", -- another possible value is true
|
||||
|
||||
-- styles (earlyer it was a table, but required many more steps to pass def-s
|
||||
-- here from .conf file)
|
||||
font_size = 21,
|
||||
-- cursor 'width', useful to change if you have hidpi monitor
|
||||
cursor_x_border = 0.3,
|
||||
line_bottom_margin = 1, -- basically space between lines
|
||||
text_color = {
|
||||
default = 'ffffff',
|
||||
accent = 'd8a07b',
|
||||
current = 'aaaaaa',
|
||||
comment = '636363',
|
||||
},
|
||||
menu_x_padding = 5, -- this padding for now applies only to 'left', not x
|
||||
menu_y_padding = 2, -- but this one applies to both - top & bottom
|
||||
|
||||
|
||||
-- values that should be passed from main script ----------------------------
|
||||
|
||||
search_heading = 'Default search heading',
|
||||
-- 'full' is required from main script, 'current_i' is optional
|
||||
-- others are 'private'
|
||||
list = {
|
||||
full = {}, filtered = {}, current_i = nil, pointer_i = 1, show_from_to = {}
|
||||
},
|
||||
-- field to compare with when searching for 'current value' by 'current_i'
|
||||
index_field = 'index',
|
||||
-- fields to use when searching for string match / any other custom searching
|
||||
-- if value has 0 length, then search list item itself
|
||||
filter_by_fields = {},
|
||||
|
||||
|
||||
-- 'private' values that are not supposed to be changed from the outside ----
|
||||
|
||||
is_active = false,
|
||||
-- https://mpv.io/manual/master/#lua-scripting-mp-create-osd-overlay(format)
|
||||
ass = mp.create_osd_overlay("ass-events"),
|
||||
was_paused = false, -- flag that indicates that vid was paused by this script
|
||||
|
||||
line = '',
|
||||
-- if there was no cursor it wouldn't have been needed, but for now we need
|
||||
-- variable below only to compare it with 'line' and see if we need to filter
|
||||
prev_line = '',
|
||||
cursor = 1,
|
||||
history = {},
|
||||
history_pos = 1,
|
||||
key_bindings = {},
|
||||
insert_mode = false,
|
||||
|
||||
-- used only in 'update' func to get error text msgs
|
||||
error_codes = {
|
||||
no_match = 'Match required',
|
||||
no_submit_provided = 'No submit function provided'
|
||||
}
|
||||
}
|
||||
|
||||
-- PRIVATE METHODS ------------------------------------------------------------
|
||||
|
||||
-- declare constructor function
|
||||
function em:new(o)
|
||||
o = o or {}
|
||||
setmetatable(o, self)
|
||||
self.__index = self
|
||||
|
||||
-- some options might be customised by user in .conf file and read as strings
|
||||
-- in that case parse those
|
||||
if type(o.filter_by_fields) == 'string' then
|
||||
o.filter_by_fields = utils.parse_json(o.filter_by_fields)
|
||||
end
|
||||
|
||||
if type(o.text_color) == 'string' then
|
||||
o.text_color = utils.parse_json(o.text_color)
|
||||
end
|
||||
|
||||
return o
|
||||
end
|
||||
|
||||
-- this func is just a getter of a current list depending on search line
|
||||
function em:current()
|
||||
return self.line == '' and self.list.full or self.list.filtered
|
||||
end
|
||||
|
||||
-- REVIEW: how to get rid of this wrapper and handle filter func sideeffects
|
||||
-- in a more elegant way?
|
||||
function em:filter_wrapper()
|
||||
-- handles sideeffect that are needed to be run on filtering list
|
||||
-- cuz the filter func may be redefined in main script and therefore needs
|
||||
-- to be straight forward - only doing filtering and returning the table
|
||||
|
||||
-- passing current query just in case, so ppl can use it in their custom funcs
|
||||
self.list.filtered = self:filter(self.line)
|
||||
|
||||
self.prev_line = self.line
|
||||
self.list.pointer_i = 1
|
||||
self:set_from_to(true)
|
||||
end
|
||||
|
||||
function em:set_from_to(reset_flag)
|
||||
-- additional variables just for shorter var name
|
||||
local i = self.list.pointer_i
|
||||
local to_show = self.lines_to_show
|
||||
local total = #self:current()
|
||||
|
||||
if reset_flag or to_show >= total then
|
||||
self.list.show_from_to = { 1, math.min(to_show, total) }
|
||||
return
|
||||
end
|
||||
|
||||
-- If menu is opened with something already selected we want this 'selected'
|
||||
-- to be displayed close to the middle of the menu. That's why 'show_from_to'
|
||||
-- is not initially set, so we can know - if show_from_to length is 0 - it is
|
||||
-- first call of this func in cur. init
|
||||
if #self.list.show_from_to == 0 then
|
||||
-- set show_from_to so chosen item will be displayed close to middle
|
||||
local half_list = math.ceil(to_show / 2)
|
||||
if i < half_list then
|
||||
self.list.show_from_to = { 1, to_show }
|
||||
elseif total - i < half_list then
|
||||
self.list.show_from_to = { total - to_show + 1, total }
|
||||
else
|
||||
self.list.show_from_to = { i - half_list + 1, i - half_list + to_show }
|
||||
end
|
||||
else
|
||||
local first, last = table.unpack(self.list.show_from_to)
|
||||
|
||||
-- handle cursor moving towards start / end bondary
|
||||
if first ~= 1 and i - first < 2 then
|
||||
self.list.show_from_to = { first - 1, last - 1 }
|
||||
end
|
||||
if last ~= total and last - i < 2 then
|
||||
self.list.show_from_to = { first + 1, last + 1 }
|
||||
end
|
||||
|
||||
-- handle index jumps from beginning to end and backwards
|
||||
if i > last then
|
||||
self.list.show_from_to = { i - to_show + 1, i }
|
||||
end
|
||||
if i < first then self.list.show_from_to = { 1, to_show } end
|
||||
end
|
||||
end
|
||||
|
||||
function em:change_selected_index(num)
|
||||
self.list.pointer_i = self.list.pointer_i + num
|
||||
if self.list.pointer_i < 1 then
|
||||
self.list.pointer_i = #self:current()
|
||||
elseif self.list.pointer_i > #self:current() then
|
||||
self.list.pointer_i = 1
|
||||
end
|
||||
self:set_from_to()
|
||||
self:update()
|
||||
end
|
||||
|
||||
-- Render the REPL and console as an ASS OSD
|
||||
function em:update(err_code)
|
||||
-- ASS tags documentation here - https://aegi.vmoe.info/docs/3.0/ASS_Tags/
|
||||
|
||||
-- do not bother if function was called to close the menu..
|
||||
if not self.is_active then
|
||||
em.ass:remove()
|
||||
return
|
||||
end
|
||||
|
||||
local line_height = self.font_size + self.line_bottom_margin
|
||||
local ww, wh = mp.get_osd_size() -- window width & height
|
||||
-- '+ 1' below is a search string
|
||||
local menu_y_pos =
|
||||
wh - (line_height * (self.lines_to_show + 1) + self.menu_y_padding * 2)
|
||||
|
||||
-- didn't find better place to handle filtered list update
|
||||
if self.line ~= self.prev_line then self:filter_wrapper() end
|
||||
|
||||
local function get_background()
|
||||
local a = self:ass_new_wrapper()
|
||||
a:append('{\\1c&H1c1c1c\\1a&H19}') -- background color & opacity
|
||||
a:pos(0, 0)
|
||||
a:draw_start()
|
||||
a:rect_cw(0, menu_y_pos, ww, wh)
|
||||
a:draw_stop()
|
||||
return a.text
|
||||
end
|
||||
|
||||
local function get_search_header()
|
||||
local a = self:ass_new_wrapper()
|
||||
|
||||
a:pos(self.menu_x_padding, menu_y_pos + self.menu_y_padding)
|
||||
|
||||
local search_prefix = table.concat({
|
||||
self:get_font_color('accent'),
|
||||
(#self:current() ~= 0 and self.list.pointer_i or '!'),
|
||||
'/', #self:current(), '\\h\\h', self.search_heading, ':\\h'
|
||||
});
|
||||
|
||||
a:append(search_prefix)
|
||||
-- reset font color after search prefix
|
||||
a:append(self:get_font_color 'default')
|
||||
|
||||
-- Create the cursor glyph as an ASS drawing. ASS will draw the cursor
|
||||
-- inline with the surrounding text, but it sets the advance to the width
|
||||
-- of the drawing. So the cursor doesn't affect layout too much, make it as
|
||||
-- thin as possible and make it appear to be 1px wide by giving it 0.5px
|
||||
-- horizontal borders.
|
||||
local cheight = self.font_size * 8
|
||||
-- TODO: maybe do it using draw_rect from ass?
|
||||
local cglyph = '{\\r' .. -- styles reset
|
||||
'\\1c&Hffffff&\\3c&Hffffff' .. -- font color and border color
|
||||
'\\xbord' .. self.cursor_x_border .. '\\p4\\pbo24}' .. -- xborder, scale x8 and baseline offset
|
||||
'm 0 0 l 0 ' .. cheight .. -- drawing just a line
|
||||
'{\\p0\\r}' -- finish drawing and reset styles
|
||||
local before_cur = self:ass_escape(self.line:sub(1, self.cursor - 1))
|
||||
local after_cur = self:ass_escape(self.line:sub(self.cursor))
|
||||
|
||||
a:append(table.concat({
|
||||
before_cur, cglyph, self:reset_styles(),
|
||||
self:get_font_color('default'), after_cur,
|
||||
(err_code and '\\h' .. self.error_codes[err_code] or "")
|
||||
}))
|
||||
|
||||
return a.text
|
||||
|
||||
-- NOTE: perhaps this commented code will some day help me in coding cursor
|
||||
-- like in M-x emacs menu:
|
||||
-- Redraw the cursor with the REPL text invisible. This will make the
|
||||
-- cursor appear in front of the text.
|
||||
-- ass:new_event()
|
||||
-- ass:an(1)
|
||||
-- ass:append(style .. '{\\alpha&HFF&}> ' .. before_cur)
|
||||
-- ass:append(cglyph)
|
||||
-- ass:append(style .. '{\\alpha&HFF&}' .. after_cur)
|
||||
end
|
||||
|
||||
local function get_list()
|
||||
local a = assdraw.ass_new()
|
||||
|
||||
local function apply_highlighting(y)
|
||||
a:new_event()
|
||||
a:append(self:reset_styles())
|
||||
a:append('{\\1c&Hffffff\\1a&HE6}') -- background color & opacity
|
||||
a:pos(0, 0)
|
||||
a:draw_start()
|
||||
a:rect_cw(0, y, ww, y + self.font_size)
|
||||
a:draw_stop()
|
||||
end
|
||||
|
||||
-- REVIEW: maybe make another function 'get_line_str' and move there
|
||||
-- everything from this for loop?
|
||||
-- REVIEW: how to use something like table.unpack below?
|
||||
for i = self.list.show_from_to[1], self.list.show_from_to[2] do
|
||||
local value = assert(self:current()[i], 'no value with index ' .. i)
|
||||
local y_offset = menu_y_pos + self.menu_y_padding +
|
||||
(line_height * (i - self.list.show_from_to[1] + 1))
|
||||
|
||||
if i == self.list.pointer_i then apply_highlighting(y_offset) end
|
||||
|
||||
a:new_event()
|
||||
a:append(self:reset_styles())
|
||||
a:pos(self.menu_x_padding, y_offset)
|
||||
a:append(self:get_line(i, value))
|
||||
end
|
||||
|
||||
return a.text
|
||||
end
|
||||
|
||||
em.ass.res_x = ww
|
||||
em.ass.res_y = wh
|
||||
em.ass.data = table.concat({
|
||||
get_background(),
|
||||
get_search_header(),
|
||||
get_list()
|
||||
}, "\n")
|
||||
|
||||
em.ass:update()
|
||||
|
||||
end
|
||||
|
||||
-- params:
|
||||
-- - data : {list: {}, [current_i] : num}
|
||||
function em:init(data)
|
||||
self.list.full = data.list or {}
|
||||
self.list.current_i = data.current_i or nil
|
||||
self.list.pointer_i = data.current_i or 1
|
||||
self:set_active(true)
|
||||
end
|
||||
|
||||
function em:exit()
|
||||
self:undefine_key_bindings()
|
||||
collectgarbage()
|
||||
end
|
||||
|
||||
-- TODO: write some idle func like this
|
||||
-- function idle()
|
||||
-- if pending_selection then
|
||||
-- gallery:set_selection(pending_selection)
|
||||
-- pending_selection = nil
|
||||
-- end
|
||||
-- if ass_changed or geometry_changed then
|
||||
-- local ww, wh = mp.get_osd_size()
|
||||
-- if geometry_changed then
|
||||
-- geometry_changed = false
|
||||
-- compute_geometry(ww, wh)
|
||||
-- end
|
||||
-- if ass_changed then
|
||||
-- ass_changed = false
|
||||
-- mp.set_osd_ass(ww, wh, ass)
|
||||
-- end
|
||||
-- end
|
||||
-- end
|
||||
-- ...
|
||||
-- and handle it as follows
|
||||
-- init():
|
||||
-- mp.register_idle(idle)
|
||||
-- idle()
|
||||
-- exit():
|
||||
-- mp.unregister_idle(idle)
|
||||
-- idle()
|
||||
-- And in these observers he is setting a flag, that's being checked in func above
|
||||
-- mp.observe_property("osd-width", "native", mark_geometry_stale)
|
||||
-- mp.observe_property("osd-height", "native", mark_geometry_stale)
|
||||
|
||||
-- PRIVATE METHODS END --------------------------------------------------------
|
||||
|
||||
-- PUBLIC METHODS -------------------------------------------------------------
|
||||
|
||||
function em:filter()
|
||||
-- default filter func, might be redefined in main script
|
||||
local result = {}
|
||||
|
||||
local function get_full_search_str(v)
|
||||
local str = ''
|
||||
for _, key in ipairs(self.filter_by_fields) do str = str .. (v[key] or '') end
|
||||
return str
|
||||
end
|
||||
|
||||
for _, v in ipairs(self.list.full) do
|
||||
-- if filter_by_fields has 0 length, then search list item itself
|
||||
if #self.filter_by_fields == 0 then
|
||||
if self:search_method(v) then table.insert(result, v) end
|
||||
else
|
||||
-- NOTE: we might use search_method on fiels separately like this:
|
||||
-- for _,key in ipairs(self.filter_by_fields) do
|
||||
-- if self:search_method(v[key]) then table.insert(result, v) end
|
||||
-- end
|
||||
-- But since im planning to implement fuzzy search in future i need full
|
||||
-- search string here
|
||||
if self:search_method(get_full_search_str(v)) then
|
||||
table.insert(result, v)
|
||||
end
|
||||
end
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
-- TODO: implement fuzzy search and maybe match highlights
|
||||
function em:search_method(str)
|
||||
-- also might be redefined by main script
|
||||
|
||||
-- convert to string just to make sure..
|
||||
return tostring(str):lower():find(self.line:lower(), 1, true)
|
||||
end
|
||||
|
||||
-- this module requires submit function to be defined in main script
|
||||
function em:submit() self:update('no_submit_provided') end
|
||||
|
||||
function em:update_list(list)
|
||||
-- for now this func doesn't handle cases when we have 'current_i' to update
|
||||
-- it
|
||||
self.list.full = list
|
||||
if self.line ~= self.prev_line then self:filter_wrapper() end
|
||||
end
|
||||
|
||||
-- PUBLIC METHODS END ---------------------------------------------------------
|
||||
|
||||
-- HELPER METHODS -------------------------------------------------------------
|
||||
|
||||
function em:get_line(_, v) -- [i]ndex, [v]alue
|
||||
-- this func might be redefined in main script to get a custom-formatted line
|
||||
-- default implementation of this func supposes that value.content field is a
|
||||
-- String
|
||||
local a = assdraw.ass_new()
|
||||
local style = (self.list.current_i == v[self.index_field])
|
||||
and 'current' or 'default'
|
||||
|
||||
a:append(self:reset_styles())
|
||||
a:append(self:get_font_color(style))
|
||||
-- content as default field, which is holding string
|
||||
-- no point in moving it to main object since content itself is being
|
||||
-- composed in THIS function, that might (and most likely, should) be
|
||||
-- redefined in main script
|
||||
a:append(v.content or 'Something is off in `get_line` func')
|
||||
return a.text
|
||||
end
|
||||
|
||||
-- REVIEW: for now i don't see normal way of mergin this func with below one
|
||||
-- but it's being used only once
|
||||
function em:reset_styles()
|
||||
local a = assdraw.ass_new()
|
||||
-- alignment top left, no word wrapping, border 0, shadow 0
|
||||
a:append('{\\an7\\q2\\bord0\\shad0}')
|
||||
a:append('{\\fs' .. self.font_size .. '}')
|
||||
return a.text
|
||||
end
|
||||
|
||||
-- function to get rid of some copypaste
|
||||
function em:ass_new_wrapper()
|
||||
local a = assdraw.ass_new()
|
||||
a:new_event()
|
||||
a:append(self:reset_styles())
|
||||
return a
|
||||
end
|
||||
|
||||
function em:get_font_color(style)
|
||||
return '{\\1c&H' .. self.text_color[style] .. '}'
|
||||
end
|
||||
|
||||
-- HELPER METHODS END ---------------------------------------------------------
|
||||
|
||||
|
||||
--[[
|
||||
The below code is a modified implementation of text input from mpv's console.lua:
|
||||
https://github.com/mpv-player/mpv/blob/87c9eefb2928252497f6141e847b74ad1158bc61/player/lua/console.lua
|
||||
|
||||
I was too lazy to list all modifications i've done to the script, but if u
|
||||
rly need to see those - do diff with the original code
|
||||
]] --
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- START ORIGINAL MPV CODE --
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
-- Copyright (C) 2019 the mpv developers
|
||||
--
|
||||
-- Permission to use, copy, modify, and/or distribute this software for any
|
||||
-- purpose with or without fee is hereby granted, provided that the above
|
||||
-- copyright notice and this permission notice appear in all copies.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
-- WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
-- MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
||||
-- SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
-- WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
|
||||
-- OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
function em:detect_platform()
|
||||
local o = {}
|
||||
-- Kind of a dumb way of detecting the platform but whatever
|
||||
if mp.get_property_native('options/vo-mmcss-profile', o) ~= o then
|
||||
return 'windows'
|
||||
elseif mp.get_property_native('options/macos-force-dedicated-gpu', o) ~= o then
|
||||
return 'macos'
|
||||
elseif os.getenv('WAYLAND_DISPLAY') then
|
||||
return 'wayland'
|
||||
end
|
||||
return 'x11'
|
||||
end
|
||||
|
||||
-- Escape a string for verbatim display on the OSD
|
||||
function em:ass_escape(str)
|
||||
-- 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
|
||||
str = str:gsub('\\', '\\\239\187\191')
|
||||
str = str:gsub('{', '\\{')
|
||||
str = str:gsub('}', '\\}')
|
||||
-- Precede newlines with a ZWNBSP to prevent ASS's weird collapsing of
|
||||
-- consecutive newlines
|
||||
str = str:gsub('\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')
|
||||
return str
|
||||
end
|
||||
|
||||
-- Set the REPL visibility ("enable", Esc)
|
||||
function em:set_active(active)
|
||||
if active == self.is_active then return end
|
||||
if active then
|
||||
self.is_active = true
|
||||
self.insert_mode = false
|
||||
mp.enable_messages('terminal-default')
|
||||
self:define_key_bindings()
|
||||
|
||||
-- set flag 'was_paused' only if vid wasn't paused before EM init
|
||||
if self.pause_on_open and not mp.get_property_bool("pause", false) then
|
||||
mp.set_property_bool("pause", true)
|
||||
self.was_paused = true
|
||||
end
|
||||
|
||||
self:set_from_to()
|
||||
self:update()
|
||||
else
|
||||
-- no need to call 'update' in this block cuz 'clear' method is calling it
|
||||
self.is_active = false
|
||||
self:undefine_key_bindings()
|
||||
|
||||
if self.resume_on_exit == true or
|
||||
(self.resume_on_exit == "only-if-was-paused" and self.was_paused) then
|
||||
mp.set_property_bool("pause", false)
|
||||
end
|
||||
|
||||
self:clear()
|
||||
collectgarbage()
|
||||
end
|
||||
end
|
||||
|
||||
-- Naive helper function to find the next UTF-8 character in 'str' after 'pos'
|
||||
-- by skipping continuation bytes. Assumes 'str' contains valid UTF-8.
|
||||
function em:next_utf8(str, pos)
|
||||
if pos > str:len() then return pos end
|
||||
repeat
|
||||
pos = pos + 1
|
||||
until pos > str:len() or str:byte(pos) < 0x80 or str:byte(pos) > 0xbf
|
||||
return pos
|
||||
end
|
||||
|
||||
-- As above, but finds the previous UTF-8 charcter in 'str' before 'pos'
|
||||
function em:prev_utf8(str, pos)
|
||||
if pos <= 1 then return pos end
|
||||
repeat
|
||||
pos = pos - 1
|
||||
until pos <= 1 or str:byte(pos) < 0x80 or str:byte(pos) > 0xbf
|
||||
return pos
|
||||
end
|
||||
|
||||
-- Insert a character at the current cursor position (any_unicode)
|
||||
function em:handle_char_input(c)
|
||||
if self.insert_mode then
|
||||
self.line = self.line:sub(1, self.cursor - 1) .. c .. self.line:sub(self:next_utf8(self.line, self.cursor))
|
||||
else
|
||||
self.line = self.line:sub(1, self.cursor - 1) .. c .. self.line:sub(self.cursor)
|
||||
end
|
||||
self.cursor = self.cursor + #c
|
||||
self:update()
|
||||
end
|
||||
|
||||
-- Remove the character behind the cursor (Backspace)
|
||||
function em:handle_backspace()
|
||||
if self.cursor <= 1 then return end
|
||||
local prev = self:prev_utf8(self.line, self.cursor)
|
||||
self.line = self.line:sub(1, prev - 1) .. self.line:sub(self.cursor)
|
||||
self.cursor = prev
|
||||
self:update()
|
||||
end
|
||||
|
||||
-- Remove the character in front of the cursor (Del)
|
||||
function em:handle_del()
|
||||
if self.cursor > self.line:len() then return end
|
||||
self.line = self.line:sub(1, self.cursor - 1) .. self.line:sub(self:next_utf8(self.line, self.cursor))
|
||||
self:update()
|
||||
end
|
||||
|
||||
-- Toggle insert mode (Ins)
|
||||
function em:handle_ins()
|
||||
self.insert_mode = not self.insert_mode
|
||||
end
|
||||
|
||||
-- Move the cursor to the next character (Right)
|
||||
function em:next_char()
|
||||
self.cursor = self:next_utf8(self.line, self.cursor)
|
||||
self:update()
|
||||
end
|
||||
|
||||
-- Move the cursor to the previous character (Left)
|
||||
function em:prev_char()
|
||||
self.cursor = self:prev_utf8(self.line, self.cursor)
|
||||
self:update()
|
||||
end
|
||||
|
||||
-- Clear the current line (Ctrl+C)
|
||||
function em:clear()
|
||||
self.line = ''
|
||||
self.prev_line = ''
|
||||
|
||||
self.list.current_i = nil
|
||||
self.list.pointer_i = 1
|
||||
self.list.filtered = {}
|
||||
self.list.show_from_to = {}
|
||||
|
||||
self.was_paused = false
|
||||
|
||||
self.cursor = 1
|
||||
self.insert_mode = false
|
||||
self.history_pos = #self.history + 1
|
||||
|
||||
self:update()
|
||||
end
|
||||
|
||||
-- Run the current command and clear the line (Enter)
|
||||
function em:handle_enter()
|
||||
if #self:current() == 0 then
|
||||
self:update('no_match')
|
||||
return
|
||||
end
|
||||
|
||||
if self.history[#self.history] ~= self.line then
|
||||
self.history[#self.history + 1] = self.line
|
||||
end
|
||||
|
||||
self:submit(self:current()[self.list.pointer_i])
|
||||
self:set_active(false)
|
||||
end
|
||||
|
||||
-- Go to the specified position in the command history
|
||||
function em:go_history(new_pos)
|
||||
local old_pos = self.history_pos
|
||||
self.history_pos = new_pos
|
||||
|
||||
-- Restrict the position to a legal value
|
||||
if self.history_pos > #self.history + 1 then
|
||||
self.history_pos = #self.history + 1
|
||||
elseif self.history_pos < 1 then
|
||||
self.history_pos = 1
|
||||
end
|
||||
|
||||
-- Do nothing if the history position didn't actually change
|
||||
if self.history_pos == old_pos then
|
||||
return
|
||||
end
|
||||
|
||||
-- If the user was editing a non-history line, save it as the last history
|
||||
-- entry. This makes it much less frustrating to accidentally hit Up/Down
|
||||
-- while editing a line.
|
||||
if old_pos == #self.history + 1 and self.line ~= '' and self.history[#self.history] ~= self.line then
|
||||
self.history[#self.history + 1] = self.line
|
||||
end
|
||||
|
||||
-- Now show the history line (or a blank line for #history + 1)
|
||||
if self.history_pos <= #self.history then
|
||||
self.line = self.history[self.history_pos]
|
||||
else
|
||||
self.line = ''
|
||||
end
|
||||
self.cursor = self.line:len() + 1
|
||||
self.insert_mode = false
|
||||
self:update()
|
||||
end
|
||||
|
||||
-- Go to the specified relative position in the command history (Up, Down)
|
||||
function em:move_history(amount)
|
||||
self:go_history(self.history_pos + amount)
|
||||
end
|
||||
|
||||
-- Go to the first command in the command history (PgUp)
|
||||
function em:handle_pgup()
|
||||
self:go_history(1)
|
||||
end
|
||||
|
||||
-- Stop browsing history and start editing a blank line (PgDown)
|
||||
function em:handle_pgdown()
|
||||
self:go_history(#self.history + 1)
|
||||
end
|
||||
|
||||
-- Move to the start of the current word, or if already at the start, the start
|
||||
-- of the previous word. (Ctrl+Left)
|
||||
function em:prev_word()
|
||||
-- This is basically the same as next_word() but backwards, so reverse the
|
||||
-- string in order to do a "backwards" find. This wouldn't be as annoying
|
||||
-- to do if Lua didn't insist on 1-based indexing.
|
||||
self.cursor = self.line:len() - select(2, self.line:reverse():find('%s*[^%s]*', self.line:len() - self.cursor + 2)) + 1
|
||||
self:update()
|
||||
end
|
||||
|
||||
-- Move to the end of the current word, or if already at the end, the end of
|
||||
-- the next word. (Ctrl+Right)
|
||||
function em:next_word()
|
||||
self.cursor = select(2, self.line:find('%s*[^%s]*', self.cursor)) + 1
|
||||
self:update()
|
||||
end
|
||||
|
||||
-- Move the cursor to the beginning of the line (HOME)
|
||||
function em:go_home()
|
||||
self.cursor = 1
|
||||
self:update()
|
||||
end
|
||||
|
||||
-- Move the cursor to the end of the line (END)
|
||||
function em:go_end()
|
||||
self.cursor = self.line:len() + 1
|
||||
self:update()
|
||||
end
|
||||
|
||||
-- Delete from the cursor to the beginning of the word (Ctrl+Backspace)
|
||||
function em:del_word()
|
||||
local before_cur = self.line:sub(1, self.cursor - 1)
|
||||
local after_cur = self.line:sub(self.cursor)
|
||||
|
||||
before_cur = before_cur:gsub('[^%s]+%s*$', '', 1)
|
||||
self.line = before_cur .. after_cur
|
||||
self.cursor = before_cur:len() + 1
|
||||
self:update()
|
||||
end
|
||||
|
||||
-- Delete from the cursor to the end of the word (Ctrl+Del)
|
||||
function em:del_next_word()
|
||||
if self.cursor > self.line:len() then return end
|
||||
|
||||
local before_cur = self.line:sub(1, self.cursor - 1)
|
||||
local after_cur = self.line:sub(self.cursor)
|
||||
|
||||
after_cur = after_cur:gsub('^%s*[^%s]+', '', 1)
|
||||
self.line = before_cur .. after_cur
|
||||
self:update()
|
||||
end
|
||||
|
||||
-- Delete from the cursor to the end of the line (Ctrl+K)
|
||||
function em:del_to_eol()
|
||||
self.line = self.line:sub(1, self.cursor - 1)
|
||||
self:update()
|
||||
end
|
||||
|
||||
-- Delete from the cursor back to the start of the line (Ctrl+U)
|
||||
function em:del_to_start()
|
||||
self.line = self.line:sub(self.cursor)
|
||||
self.cursor = 1
|
||||
self:update()
|
||||
end
|
||||
|
||||
-- Returns a string of UTF-8 text from the clipboard (or the primary selection)
|
||||
function em:get_clipboard(clip)
|
||||
-- Pick a better default font for Windows and macOS
|
||||
local platform = self:detect_platform()
|
||||
|
||||
if platform == 'x11' then
|
||||
local res = utils.subprocess({
|
||||
args = { 'xclip', '-selection', clip and 'clipboard' or 'primary', '-out' },
|
||||
playback_only = false,
|
||||
})
|
||||
if not res.error then
|
||||
return res.stdout
|
||||
end
|
||||
elseif platform == 'wayland' then
|
||||
local res = utils.subprocess({
|
||||
args = { 'wl-paste', clip and '-n' or '-np' },
|
||||
playback_only = false,
|
||||
})
|
||||
if not res.error then
|
||||
return res.stdout
|
||||
end
|
||||
elseif platform == 'windows' then
|
||||
local res = utils.subprocess({
|
||||
args = { 'powershell', '-NoProfile', '-Command', [[& {
|
||||
Trap {
|
||||
Write-Error -ErrorRecord $_
|
||||
Exit 1
|
||||
}
|
||||
|
||||
$clip = ""
|
||||
if (Get-Command "Get-Clipboard" -errorAction SilentlyContinue) {
|
||||
$clip = Get-Clipboard -Raw -Format Text -TextFormatType UnicodeText
|
||||
} else {
|
||||
Add-Type -AssemblyName PresentationCore
|
||||
$clip = [Windows.Clipboard]::GetText()
|
||||
}
|
||||
|
||||
$clip = $clip -Replace "`r",""
|
||||
$u8clip = [System.Text.Encoding]::UTF8.GetBytes($clip)
|
||||
[Console]::OpenStandardOutput().Write($u8clip, 0, $u8clip.Length)
|
||||
}]] },
|
||||
playback_only = false,
|
||||
})
|
||||
if not res.error then
|
||||
return res.stdout
|
||||
end
|
||||
elseif platform == 'macos' then
|
||||
local res = utils.subprocess({
|
||||
args = { 'pbpaste' },
|
||||
playback_only = false,
|
||||
})
|
||||
if not res.error then
|
||||
return res.stdout
|
||||
end
|
||||
end
|
||||
return ''
|
||||
end
|
||||
|
||||
-- Paste text from the window-system's clipboard. 'clip' determines whether the
|
||||
-- clipboard or the primary selection buffer is used (on X11 and Wayland only.)
|
||||
function em:paste(clip)
|
||||
local text = self:get_clipboard(clip)
|
||||
local before_cur = self.line:sub(1, self.cursor - 1)
|
||||
local after_cur = self.line:sub(self.cursor)
|
||||
self.line = before_cur .. text .. after_cur
|
||||
self.cursor = self.cursor + text:len()
|
||||
self:update()
|
||||
end
|
||||
|
||||
-- List of input bindings. This is a weird mashup between common GUI text-input
|
||||
-- bindings and readline bindings.
|
||||
function em:get_bindings()
|
||||
local bindings = {
|
||||
{ 'ctrl+[', function() self:set_active(false) end },
|
||||
{ 'ctrl+g', function() self:set_active(false) end },
|
||||
{ 'esc', function() self:set_active(false) end },
|
||||
{ 'enter', function() self:handle_enter() end },
|
||||
{ 'kp_enter', function() self:handle_enter() end },
|
||||
{ 'ctrl+m', function() self:handle_enter() end },
|
||||
{ 'bs', function() self:handle_backspace() end },
|
||||
{ 'shift+bs', function() self:handle_backspace() end },
|
||||
{ 'ctrl+h', function() self:handle_backspace() end },
|
||||
{ 'del', function() self:handle_del() end },
|
||||
{ 'shift+del', function() self:handle_del() end },
|
||||
{ 'ins', function() self:handle_ins() end },
|
||||
{ 'shift+ins', function() self:paste(false) end },
|
||||
{ 'mbtn_mid', function() self:paste(false) end },
|
||||
{ 'left', function() self:prev_char() end },
|
||||
{ 'ctrl+b', function() self:prev_char() end },
|
||||
{ 'right', function() self:next_char() end },
|
||||
{ 'ctrl+f', function() self:next_char() end },
|
||||
{ 'ctrl+k', function() self:change_selected_index(-1) end },
|
||||
{ 'ctrl+p', function() self:change_selected_index(-1) end },
|
||||
{ 'ctrl+j', function() self:change_selected_index(1) end },
|
||||
{ 'ctrl+n', function() self:change_selected_index(1) end },
|
||||
{ 'up', function() self:move_history(-1) end },
|
||||
{ 'alt+p', function() self:move_history(-1) end },
|
||||
{ 'wheel_up', function() self:move_history(-1) end },
|
||||
{ 'down', function() self:move_history(1) end },
|
||||
{ 'alt+n', function() self:move_history(1) end },
|
||||
{ 'wheel_down', function() self:move_history(1) end },
|
||||
{ 'wheel_left', function() end },
|
||||
{ 'wheel_right', function() end },
|
||||
{ 'ctrl+left', function() self:prev_word() end },
|
||||
{ 'alt+b', function() self:prev_word() end },
|
||||
{ 'ctrl+right', function() self:next_word() end },
|
||||
{ 'alt+f', function() self:next_word() end },
|
||||
{ 'ctrl+a', function() self:go_home() end },
|
||||
{ 'home', function() self:go_home() end },
|
||||
{ 'ctrl+e', function() self:go_end() end },
|
||||
{ 'end', function() self:go_end() end },
|
||||
{ 'pgup', function() self:handle_pgup() end },
|
||||
{ 'pgdwn', function() self:handle_pgdown() end },
|
||||
{ 'ctrl+c', function() self:clear() end },
|
||||
{ 'ctrl+d', function() self:handle_del() end },
|
||||
{ 'ctrl+u', function() self:del_to_start() end },
|
||||
{ 'ctrl+v', function() self:paste(true) end },
|
||||
{ 'meta+v', function() self:paste(true) end },
|
||||
{ 'ctrl+bs', function() self:del_word() end },
|
||||
{ 'ctrl+w', function() self:del_word() end },
|
||||
{ 'ctrl+del', function() self:del_next_word() end },
|
||||
{ 'alt+d', function() self:del_next_word() end },
|
||||
{ 'kp_dec', function() self:handle_char_input('.') end },
|
||||
}
|
||||
|
||||
for i = 0, 9 do
|
||||
bindings[#bindings + 1] =
|
||||
{ 'kp' .. i, function() self:handle_char_input('' .. i) end }
|
||||
end
|
||||
|
||||
return bindings
|
||||
end
|
||||
|
||||
function em:text_input(info)
|
||||
if info.key_text and (info.event == "press" or info.event == "down"
|
||||
or info.event == "repeat")
|
||||
then
|
||||
self:handle_char_input(info.key_text)
|
||||
end
|
||||
end
|
||||
|
||||
function em:define_key_bindings()
|
||||
if #self.key_bindings > 0 then
|
||||
return
|
||||
end
|
||||
for _, bind in ipairs(self:get_bindings()) do
|
||||
-- Generate arbitrary name for removing the bindings later.
|
||||
local name = "search_" .. (#self.key_bindings + 1)
|
||||
self.key_bindings[#self.key_bindings + 1] = name
|
||||
mp.add_forced_key_binding(bind[1], name, bind[2], { repeatable = true })
|
||||
end
|
||||
mp.add_forced_key_binding("any_unicode", "search_input", function(...)
|
||||
self:text_input(...)
|
||||
end, { repeatable = true, complex = true })
|
||||
self.key_bindings[#self.key_bindings + 1] = "search_input"
|
||||
end
|
||||
|
||||
function em:undefine_key_bindings()
|
||||
for _, name in ipairs(self.key_bindings) do
|
||||
mp.remove_key_binding(name)
|
||||
end
|
||||
self.key_bindings = {}
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- END ORIGINAL MPV CODE --
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
return em
|
@ -0,0 +1,445 @@
|
||||
|
||||
-- https://github.com/stax76/mpv-scripts
|
||||
|
||||
----- options
|
||||
|
||||
local o = {
|
||||
lines_to_show = 10,
|
||||
pause_on_open = false, -- does not work on my system when enabled, menu won't show
|
||||
resume_on_exit = "only-if-was-paused",
|
||||
|
||||
-- styles
|
||||
font_size = 50,
|
||||
line_bottom_margin = 1,
|
||||
menu_x_padding = 5,
|
||||
menu_y_padding = 2,
|
||||
}
|
||||
|
||||
local opt = require "mp.options"
|
||||
opt.read_options(o)
|
||||
|
||||
----- string
|
||||
|
||||
function is_empty(input)
|
||||
if input == nil or input == "" then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
function contains(input, find)
|
||||
if not is_empty(input) and not is_empty(find) then
|
||||
return input:find(find, 1, true)
|
||||
end
|
||||
end
|
||||
|
||||
function starts_with(str, start)
|
||||
return str:sub(1, #start) == start
|
||||
end
|
||||
|
||||
function split(input, sep)
|
||||
assert(#sep == 1) -- supports only single character separator
|
||||
local tbl = {}
|
||||
|
||||
if input ~= nil then
|
||||
for str in string.gmatch(input, "([^" .. sep .. "]+)") do
|
||||
table.insert(tbl, str)
|
||||
end
|
||||
end
|
||||
|
||||
return tbl
|
||||
end
|
||||
|
||||
function first_to_upper(str)
|
||||
return (str:gsub("^%l", string.upper))
|
||||
end
|
||||
|
||||
----- list
|
||||
|
||||
function list_contains(list, value)
|
||||
for _, v in pairs(list) do
|
||||
if v == value then
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
----- path
|
||||
|
||||
function get_temp_dir()
|
||||
local is_windows = package.config:sub(1,1) == "\\"
|
||||
|
||||
if is_windows then
|
||||
return os.getenv("TEMP") .. "\\"
|
||||
else
|
||||
return "/tmp/"
|
||||
end
|
||||
end
|
||||
|
||||
---- file
|
||||
|
||||
function file_exists(path)
|
||||
if is_empty(path) then return false end
|
||||
local file = io.open(path, "r")
|
||||
|
||||
if file ~= nil then
|
||||
io.close(file)
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
function file_write(path, content)
|
||||
local file = assert(io.open(path, "w"))
|
||||
file:write(content)
|
||||
file:close()
|
||||
end
|
||||
|
||||
----- mpv
|
||||
|
||||
local utils = require "mp.utils"
|
||||
local assdraw = require 'mp.assdraw'
|
||||
local msg = require "mp.msg"
|
||||
|
||||
----- path mpv
|
||||
|
||||
function file_name(value)
|
||||
local _, filename = utils.split_path(value)
|
||||
return filename
|
||||
end
|
||||
|
||||
----- main
|
||||
|
||||
package.path = mp.command_native({ "expand-path", "~~/script-modules/?.lua;" }) .. package.path
|
||||
|
||||
local em = require "extended-menu"
|
||||
local menu = em:new(o)
|
||||
local menu_content = { list = {}, current_i = nil }
|
||||
local osc_visibility = nil
|
||||
local media_info_cache = {}
|
||||
local original_set_active_func = em.set_active
|
||||
local original_get_line_func = em.get_line
|
||||
|
||||
function em:get_bindings()
|
||||
local bindings = {
|
||||
{ 'esc', function() self:set_active(false) end },
|
||||
{ 'enter', function() self:handle_enter() end },
|
||||
{ 'bs', function() self:handle_backspace() end },
|
||||
{ 'del', function() self:handle_del() end },
|
||||
{ 'ins', function() self:handle_ins() end },
|
||||
{ 'left', function() self:prev_char() end },
|
||||
{ 'right', function() self:next_char() end },
|
||||
{ 'ctrl+f', function() self:next_char() end },
|
||||
{ 'up', function() self:change_selected_index(-1) end },
|
||||
{ 'down', function() self:change_selected_index(1) end },
|
||||
{ 'ctrl+up', function() self:move_history(-1) end },
|
||||
{ 'ctrl+down', function() self:move_history(1) end },
|
||||
{ 'ctrl+left', function() self:prev_word() end },
|
||||
{ 'ctrl+right', function() self:next_word() end },
|
||||
{ 'home', function() self:go_home() end },
|
||||
{ 'end', function() self:go_end() end },
|
||||
{ 'pgup', function() self:change_selected_index(-o.lines_to_show) end },
|
||||
{ 'pgdwn', function() self:change_selected_index(o.lines_to_show) end },
|
||||
{ 'ctrl+u', function() self:del_to_start() end },
|
||||
{ 'ctrl+v', function() self:paste(true) end },
|
||||
{ 'ctrl+bs', function() self:del_word() end },
|
||||
{ 'ctrl+del', function() self:del_next_word() end },
|
||||
{ 'kp_dec', function() self:handle_char_input('.') end },
|
||||
}
|
||||
|
||||
for i = 0, 9 do
|
||||
bindings[#bindings + 1] =
|
||||
{'kp' .. i, function() self:handle_char_input('' .. i) end}
|
||||
end
|
||||
|
||||
return bindings
|
||||
end
|
||||
|
||||
function em:set_active(active)
|
||||
original_set_active_func(self, active)
|
||||
|
||||
if not active and osc_visibility then
|
||||
mp.command("script-message osc-visibility " .. osc_visibility .. " no_osd")
|
||||
osc_visibility = nil
|
||||
end
|
||||
end
|
||||
|
||||
menu.index_field = "index"
|
||||
|
||||
function get_media_info()
|
||||
local path = mp.get_property("path")
|
||||
|
||||
if media_info_cache[path] then
|
||||
return media_info_cache[path]
|
||||
end
|
||||
|
||||
local format_file = get_temp_dir() .. mp.get_script_name() .. " media-info-format-v1.txt"
|
||||
|
||||
if not file_exists(format_file) then
|
||||
media_info_format = [[General;N: %FileNameExtension%\\nG: %Format%, %FileSize/String%, %Duration/String%, %OverallBitRate/String%, %Recorded_Date%\\n
|
||||
Video;V: %Format%, %Format_Profile%, %Width%x%Height%, %BitRate/String%, %FrameRate% FPS\\n
|
||||
Audio;A: %Language/String%, %Format%, %Format_Profile%, %BitRate/String%, %Channel(s)% ch, %SamplingRate/String%, %Title%\\n
|
||||
Text;S: %Language/String%, %Format%, %Format_Profile%, %Title%\\n]]
|
||||
|
||||
file_write(format_file, media_info_format)
|
||||
end
|
||||
|
||||
if contains(path, "://") or not file_exists(path) then
|
||||
return
|
||||
end
|
||||
|
||||
local proc_result = mp.command_native({
|
||||
name = "subprocess",
|
||||
playback_only = false,
|
||||
capture_stdout = true,
|
||||
args = {"mediainfo", "--inform=file://" .. format_file, path},
|
||||
})
|
||||
|
||||
if proc_result.status == 0 then
|
||||
local output = proc_result.stdout
|
||||
|
||||
output = string.gsub(output, ", , ,", ",")
|
||||
output = string.gsub(output, ", ,", ",")
|
||||
output = string.gsub(output, ": , ", ": ")
|
||||
output = string.gsub(output, ", \\n\r*\n", "\\n")
|
||||
output = string.gsub(output, "\\n\r*\n", "\\n")
|
||||
output = string.gsub(output, ", \\n", "\\n")
|
||||
output = string.gsub(output, "\\n", "\n")
|
||||
output = string.gsub(output, "%.000 FPS", " FPS")
|
||||
output = string.gsub(output, "MPEG Audio, Layer 3", "MP3")
|
||||
|
||||
media_info_cache[path] = output
|
||||
|
||||
return output
|
||||
end
|
||||
end
|
||||
|
||||
function binding_get_line(self, _, v)
|
||||
local a = assdraw.ass_new()
|
||||
local cmd = self:ass_escape(v.cmd)
|
||||
local key = self:ass_escape(v.key)
|
||||
local comment = self:ass_escape(v.comment or '')
|
||||
|
||||
if v.priority == -1 or v.priority == -2 then
|
||||
local why_inactive = (v.priority == -1) and 'Inactive' or 'Shadowed'
|
||||
a:append(self:get_font_color('comment'))
|
||||
|
||||
if comment ~= "" then
|
||||
a:append(comment .. '\\h')
|
||||
end
|
||||
|
||||
a:append(key .. '\\h(' .. why_inactive .. ')' .. '\\h' .. cmd)
|
||||
return a.text
|
||||
end
|
||||
|
||||
if comment ~= "" then
|
||||
a:append(self:get_font_color('default'))
|
||||
a:append(comment .. '\\h')
|
||||
end
|
||||
|
||||
a:append(self:get_font_color('accent'))
|
||||
a:append(key)
|
||||
a:append(self:get_font_color('comment'))
|
||||
a:append(' ' .. cmd)
|
||||
return a.text
|
||||
end
|
||||
|
||||
mp.register_script_message("show-command-palette", function (name)
|
||||
menu_content.list = {}
|
||||
menu_content.current_i = 1
|
||||
menu.search_heading = first_to_upper(name)
|
||||
menu.filter_by_fields = { "content" }
|
||||
em.get_line = original_get_line_func
|
||||
|
||||
if name == "bindings" then
|
||||
local bindings = utils.parse_json(mp.get_property("input-bindings"))
|
||||
|
||||
for _, v in ipairs(bindings) do
|
||||
v.key = "(" .. v.key .. ")"
|
||||
|
||||
if not is_empty(v.comment) then
|
||||
v.comment = first_to_upper(v.comment)
|
||||
end
|
||||
end
|
||||
|
||||
for _, v in ipairs(bindings) do
|
||||
for _, v2 in ipairs(bindings) do
|
||||
if v.key == v2.key and v.priority < v2.priority then
|
||||
v.priority = -2
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
table.sort(bindings, function(i, j)
|
||||
return i.priority > j.priority
|
||||
end)
|
||||
|
||||
menu_content.list = bindings
|
||||
|
||||
function menu:submit(val)
|
||||
mp.command(val.cmd)
|
||||
end
|
||||
|
||||
menu.filter_by_fields = {'cmd', 'key', 'comment'}
|
||||
em.get_line = binding_get_line
|
||||
elseif name == "chapters" then
|
||||
local count = mp.get_property("chapter-list/count")
|
||||
if count == 0 then return end
|
||||
|
||||
for i = 0, count do
|
||||
local title = mp.get_property("chapter-list/" .. i .. "/title")
|
||||
|
||||
if title then
|
||||
table.insert(menu_content.list, { index = i + 1, content = title })
|
||||
end
|
||||
end
|
||||
|
||||
menu_content.current_i = mp.get_property_number("chapter") + 1
|
||||
|
||||
function menu:submit(val)
|
||||
mp.set_property_number("chapter", val.index - 1)
|
||||
end
|
||||
elseif name == "playlist" then
|
||||
local count = mp.get_property_number("playlist-count")
|
||||
if count == 0 then return end
|
||||
|
||||
for i = 0, (count - 1) do
|
||||
local text = mp.get_property("playlist/" .. i .. "/title")
|
||||
|
||||
if text == nil then
|
||||
text = file_name(mp.get_property("playlist/" .. i .. "/filename"))
|
||||
end
|
||||
|
||||
table.insert(menu_content.list, { index = i + 1, content = text })
|
||||
end
|
||||
|
||||
menu_content.current_i = mp.get_property_number("playlist-pos") + 1
|
||||
|
||||
function menu:submit(val)
|
||||
mp.set_property_number("playlist-pos", val.index - 1)
|
||||
end
|
||||
elseif name == "commands" then
|
||||
local commands = utils.parse_json(mp.get_property("command-list"))
|
||||
|
||||
for k, v in ipairs(commands) do
|
||||
local text = v.name
|
||||
|
||||
for _, arg in ipairs(v.args) do
|
||||
if arg.optional then
|
||||
text = text .. " [<" .. arg.name .. ">]"
|
||||
else
|
||||
text = text .. " <" .. arg.name .. ">"
|
||||
end
|
||||
end
|
||||
|
||||
table.insert(menu_content.list, { index = k, content = text })
|
||||
end
|
||||
|
||||
function menu:submit(val)
|
||||
print(val.content)
|
||||
local cmd = string.match(val.content, '%S+')
|
||||
mp.commandv("script-message-to", "console", "type", cmd .. " ")
|
||||
end
|
||||
elseif name == "properties" then
|
||||
local properties = split(mp.get_property("property-list"), ",")
|
||||
|
||||
for k, v in ipairs(properties) do
|
||||
table.insert(menu_content.list, { index = k, content = v })
|
||||
end
|
||||
|
||||
function menu:submit(val)
|
||||
mp.commandv('script-message-to', 'console', 'type', "print-text ${" .. val.content .. "}")
|
||||
end
|
||||
elseif name == "options" then
|
||||
local options = split(mp.get_property("options"), ",")
|
||||
|
||||
for k, v in ipairs(options) do
|
||||
local type = mp.get_property_osd("option-info/" .. v .. "/type", "")
|
||||
local default =mp.get_property_osd("option-info/" .. v .. "/default-value", "")
|
||||
v = v .. " (type: " .. type .. ", default: " .. default .. ")"
|
||||
table.insert(menu_content.list, { index = k, content = v })
|
||||
end
|
||||
|
||||
function menu:submit(val)
|
||||
print(val.content)
|
||||
local prop = string.match(val.content, '%S+')
|
||||
mp.commandv("script-message-to", "console", "type", "set " .. prop .. " ")
|
||||
end
|
||||
elseif name == "audio" then
|
||||
local tracks = split(get_media_info() .. "\nA: None", "\n")
|
||||
local id = 0
|
||||
|
||||
for _, v in ipairs(tracks) do
|
||||
if starts_with(v, "A: ") then
|
||||
id = id + 1
|
||||
table.insert(menu_content.list, { index = id, content = string.sub(v, 4) })
|
||||
end
|
||||
end
|
||||
|
||||
menu_content.current_i = mp.get_property_number("aid") or id
|
||||
|
||||
function menu:submit(val)
|
||||
mp.command("set aid " .. ((val.index == id) and 'no' or val.index))
|
||||
end
|
||||
elseif name == "subtitle" then
|
||||
local tracks = split(get_media_info() .. "\nS: None", "\n")
|
||||
local id = 0
|
||||
|
||||
for _, v in ipairs(tracks) do
|
||||
if starts_with(v, "S: ") then
|
||||
id = id + 1
|
||||
table.insert(menu_content.list, { index = id, content = string.sub(v, 4) })
|
||||
end
|
||||
end
|
||||
|
||||
menu_content.current_i = mp.get_property_number("sid") or id
|
||||
|
||||
function menu:submit(val)
|
||||
mp.command("set sid " .. ((val.index == id) and 'no' or val.index))
|
||||
end
|
||||
elseif name == "video" then
|
||||
local tracks = split(get_media_info() .. "\nV: None", "\n")
|
||||
local id = 0
|
||||
|
||||
for _, v in ipairs(tracks) do
|
||||
if starts_with(v, "V: ") then
|
||||
id = id + 1
|
||||
table.insert(menu_content.list, { index = id, content = string.sub(v, 4) })
|
||||
end
|
||||
end
|
||||
|
||||
menu_content.current_i = mp.get_property_number("vid") or id
|
||||
|
||||
function menu:submit(val)
|
||||
mp.command("set vid " .. ((val.index == id) and 'no' or val.index))
|
||||
end
|
||||
elseif name == "profiles" then
|
||||
local profiles = utils.parse_json(mp.get_property("profile-list"))
|
||||
local ignore_list = {"builtin-pseudo-gui", "encoding", "libmpv", "pseudo-gui", "default"}
|
||||
|
||||
for k, v in ipairs(profiles) do
|
||||
if not list_contains(ignore_list, v.name) then
|
||||
table.insert(menu_content.list, { index = k, content = v.name })
|
||||
end
|
||||
end
|
||||
|
||||
function menu:submit(val)
|
||||
mp.command("show-text " .. val.content);
|
||||
mp.command("apply-profile " .. val.content);
|
||||
end
|
||||
else
|
||||
msg.error("Unknown mode " .. name)
|
||||
return
|
||||
end
|
||||
|
||||
if is_empty(mp.get_property("path")) then
|
||||
osc_visibility = utils.shared_script_property_get("osc-visibility")
|
||||
|
||||
if osc_visibility then
|
||||
mp.command("script-message osc-visibility never no_osd")
|
||||
end
|
||||
else
|
||||
osc_visibility = nil
|
||||
end
|
||||
|
||||
menu:init(menu_content)
|
||||
end)
|
@ -0,0 +1,553 @@
|
||||
(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
|
||||
// referene: http://docs.aegisub.org/3.2/ASS_Tags/
|
||||
// based on mpv's assdraw.lua
|
||||
|
||||
var c = 0.551915024494 // circle approximation
|
||||
|
||||
function Assdraw() {
|
||||
this.scale = 4;
|
||||
this.text = ""
|
||||
}
|
||||
|
||||
Assdraw.SMART_WRAPPING = 0
|
||||
Assdraw.EOL_WRAPPING = 1
|
||||
Assdraw.NO_WORD_WRAPPING = 2
|
||||
Assdraw.SMART_WRAPPING_WIDER = 3
|
||||
Assdraw.BOTTOM_LEFT = 1
|
||||
Assdraw.BOTTOM_CENTER = 2
|
||||
Assdraw.BOTTOM_RIGHT = 3
|
||||
Assdraw.MIDDLE_LEFT = 4
|
||||
Assdraw.MIDDLE_CENTER = 5
|
||||
Assdraw.MIDDLE_RIGHT = 6
|
||||
Assdraw.TOP_LEFT = 7
|
||||
Assdraw.TOP_CENTER = 8
|
||||
Assdraw.TOP_RIGHT = 9
|
||||
|
||||
Assdraw.escape = function(s) {
|
||||
return s.replace(/([{}])/g, "\\$1")
|
||||
}
|
||||
|
||||
Assdraw.bolden = function(s) {
|
||||
return '{\\b1}' + s + '{\\b0}'
|
||||
}
|
||||
|
||||
Assdraw.prototype.newEvent = function() {
|
||||
// osd_libass.c adds an event per line
|
||||
if (this.text.length > 0) {
|
||||
this.text = this.text + "\n"
|
||||
}
|
||||
}
|
||||
|
||||
Assdraw.prototype.override = function(callback) {
|
||||
this.append('{')
|
||||
callback.call(this);
|
||||
this.append('}')
|
||||
}
|
||||
|
||||
Assdraw.prototype.primaryFillColor = function(hex) {
|
||||
this.append('\\1c&H' + hex + '&')
|
||||
return this;
|
||||
}
|
||||
|
||||
Assdraw.prototype.secondaryFillColor = function(hex) {
|
||||
this.append('\\2c&H' + hex + '&')
|
||||
return this;
|
||||
}
|
||||
|
||||
Assdraw.prototype.borderColor = function(hex) {
|
||||
this.append('\\3c&H' + hex + '&')
|
||||
return this;
|
||||
}
|
||||
|
||||
Assdraw.prototype.shadowColor = function(hex) {
|
||||
this.append('\\4c&H' + hex + '&')
|
||||
return this;
|
||||
}
|
||||
|
||||
Assdraw.prototype.primaryFillAlpha = function(hex) {
|
||||
this.append('\\1a&H' + hex + '&')
|
||||
return this;
|
||||
}
|
||||
|
||||
Assdraw.prototype.secondaryFillAlpha = function(hex) {
|
||||
this.append('\\2a&H' + hex + '&')
|
||||
return this;
|
||||
}
|
||||
|
||||
Assdraw.prototype.borderAlpha = function(hex) {
|
||||
this.append('\\3a&H' + hex + '&')
|
||||
return this;
|
||||
}
|
||||
|
||||
Assdraw.prototype.shadowAlpha = function(hex) {
|
||||
this.append('\\4a&H' + hex + '&')
|
||||
return this;
|
||||
}
|
||||
|
||||
Assdraw.prototype.fontName = function(s) {
|
||||
this.append('\\fn' + s)
|
||||
return this;
|
||||
}
|
||||
|
||||
Assdraw.prototype.fontSize = function(s) {
|
||||
this.append('\\fs' + s)
|
||||
return this;
|
||||
}
|
||||
|
||||
Assdraw.prototype.borderSize = function(n) {
|
||||
this.append('\\bord' + n)
|
||||
return this;
|
||||
}
|
||||
|
||||
Assdraw.prototype.xShadowDistance = function(n) {
|
||||
this.append('\\xshad' + n)
|
||||
return this;
|
||||
}
|
||||
|
||||
Assdraw.prototype.yShadowDistance = function(n) {
|
||||
this.append('\\yshad' + n)
|
||||
return this;
|
||||
}
|
||||
|
||||
Assdraw.prototype.letterSpacing = function(n) {
|
||||
this.append('\\fsp' + n)
|
||||
return this;
|
||||
}
|
||||
|
||||
Assdraw.prototype.wrapStyle = function(n) {
|
||||
this.append('\\q' + n)
|
||||
return this;
|
||||
}
|
||||
|
||||
Assdraw.prototype.drawStart = function() {
|
||||
this.text = this.text + "{\\p"+ this.scale + "}"
|
||||
}
|
||||
|
||||
Assdraw.prototype.drawStop = function() {
|
||||
this.text = this.text + "{\\p0}"
|
||||
}
|
||||
|
||||
Assdraw.prototype.coord = function(x, y) {
|
||||
var scale = Math.pow(2, (this.scale - 1))
|
||||
var ix = Math.ceil(x * scale)
|
||||
var iy = Math.ceil(y * scale)
|
||||
this.text = this.text + " " + ix + " " + iy
|
||||
}
|
||||
|
||||
Assdraw.prototype.append = function(s) {
|
||||
this.text = this.text + s
|
||||
}
|
||||
|
||||
Assdraw.prototype.appendLn = function(s) {
|
||||
this.append(s + '\\n')
|
||||
}
|
||||
|
||||
Assdraw.prototype.appendLN = function(s) {
|
||||
this.append(s + '\\N')
|
||||
}
|
||||
|
||||
Assdraw.prototype.merge = function(other) {
|
||||
this.text = this.text + other.text
|
||||
}
|
||||
|
||||
Assdraw.prototype.pos = function(x, y) {
|
||||
this.append("\\pos(" + x.toFixed(0) + "," + y.toFixed(0) + ")")
|
||||
}
|
||||
|
||||
Assdraw.prototype.lineAlignment = function(an) {
|
||||
this.append("\\an" + an)
|
||||
}
|
||||
|
||||
Assdraw.prototype.moveTo = function(x, y) {
|
||||
this.append(" m")
|
||||
this.coord(x, y)
|
||||
}
|
||||
|
||||
Assdraw.prototype.lineTo = function(x, y) {
|
||||
this.append(" l")
|
||||
this.coord(x, y)
|
||||
}
|
||||
|
||||
Assdraw.prototype.bezierCurve = function(x1, y1, x2, y2, x3, y3) {
|
||||
this.append(" b")
|
||||
this.coord(x1, y1)
|
||||
this.coord(x2, y2)
|
||||
this.coord(x3, y3)
|
||||
}
|
||||
|
||||
|
||||
Assdraw.prototype.rectCcw = function(x0, y0, x1, y1) {
|
||||
this.moveTo(x0, y0)
|
||||
this.lineTo(x0, y1)
|
||||
this.lineTo(x1, y1)
|
||||
this.lineTo(x1, y0)
|
||||
}
|
||||
|
||||
Assdraw.prototype.rectCw = function(x0, y0, x1, y1) {
|
||||
this.moveTo(x0, y0)
|
||||
this.lineTo(x1, y0)
|
||||
this.lineTo(x1, y1)
|
||||
this.lineTo(x0, y1)
|
||||
}
|
||||
|
||||
Assdraw.prototype.hexagonCw = function(x0, y0, x1, y1, r1, r2) {
|
||||
if (typeof r2 === 'undefined') {
|
||||
r2 = r1
|
||||
}
|
||||
this.moveTo(x0 + r1, y0)
|
||||
if (x0 != x1) {
|
||||
this.lineTo(x1 - r2, y0)
|
||||
}
|
||||
this.lineTo(x1, y0 + r2)
|
||||
if (x0 != x1) {
|
||||
this.lineTo(x1 - r2, y1)
|
||||
}
|
||||
this.lineTo(x0 + r1, y1)
|
||||
this.lineTo(x0, y0 + r1)
|
||||
}
|
||||
|
||||
Assdraw.prototype.hexagonCcw = function(x0, y0, x1, y1, r1, r2) {
|
||||
if (typeof r2 === 'undefined') {
|
||||
r2 = r1
|
||||
}
|
||||
this.moveTo(x0 + r1, y0)
|
||||
this.lineTo(x0, y0 + r1)
|
||||
this.lineTo(x0 + r1, y1)
|
||||
if (x0 != x1) {
|
||||
this.lineTo(x1 - r2, y1)
|
||||
}
|
||||
this.lineTo(x1, y0 + r2)
|
||||
if (x0 != x1) {
|
||||
this.lineTo(x1 - r2, y0)
|
||||
}
|
||||
}
|
||||
|
||||
Assdraw.prototype.roundRectCw = function(ass, x0, y0, x1, y1, r1, r2) {
|
||||
if (typeof r2 === 'undefined') {
|
||||
r2 = r1
|
||||
}
|
||||
var c1 = c * r1 // circle approximation
|
||||
var c2 = c * r2 // circle approximation
|
||||
this.moveTo(x0 + r1, y0)
|
||||
this.lineTo(x1 - r2, y0) // top line
|
||||
if (r2 > 0) {
|
||||
this.bezierCurve(x1 - r2 + c2, y0, x1, y0 + r2 - c2, x1, y0 + r2) // top right corner
|
||||
}
|
||||
this.lineTo(x1, y1 - r2) // right line
|
||||
if (r2 > 0) {
|
||||
this.bezierCurve(x1, y1 - r2 + c2, x1 - r2 + c2, y1, x1 - r2, y1) // bottom right corner
|
||||
}
|
||||
this.lineTo(x0 + r1, y1) // bottom line
|
||||
if (r1 > 0) {
|
||||
this.bezierCurve(x0 + r1 - c1, y1, x0, y1 - r1 + c1, x0, y1 - r1) // bottom left corner
|
||||
}
|
||||
this.lineTo(x0, y0 + r1) // left line
|
||||
if (r1 > 0) {
|
||||
this.bezierCurve(x0, y0 + r1 - c1, x0 + r1 - c1, y0, x0 + r1, y0) // top left corner
|
||||
}
|
||||
}
|
||||
|
||||
Assdraw.prototype.roundRectCcw = function(ass, x0, y0, x1, y1, r1, r2) {
|
||||
if (typeof r2 === 'undefined') {
|
||||
r2 = r1
|
||||
}
|
||||
var c1 = c * r1 // circle approximation
|
||||
var c2 = c * r2 // circle approximation
|
||||
this.moveTo(x0 + r1, y0)
|
||||
if (r1 > 0) {
|
||||
this.bezierCurve(x0 + r1 - c1, y0, x0, y0 + r1 - c1, x0, y0 + r1) // top left corner
|
||||
}
|
||||
this.lineTo(x0, y1 - r1) // left line
|
||||
if (r1 > 0) {
|
||||
this.bezierCurve(x0, y1 - r1 + c1, x0 + r1 - c1, y1, x0 + r1, y1) // bottom left corner
|
||||
}
|
||||
this.lineTo(x1 - r2, y1) // bottom line
|
||||
if (r2 > 0) {
|
||||
this.bezierCurve(x1 - r2 + c2, y1, x1, y1 - r2 + c2, x1, y1 - r2) // bottom right corner
|
||||
}
|
||||
this.lineTo(x1, y0 + r2) // right line
|
||||
if (r2 > 0) {
|
||||
this.bezierCurve(x1, y0 + r2 - c2, x1 - r2 + c2, y0, x1 - r2, y0) // top right corner
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Assdraw
|
||||
|
||||
},{}],2:[function(require,module,exports){
|
||||
var assdraw = require('./assdraw.js')
|
||||
var shortcuts = [
|
||||
{
|
||||
category: 'Navigation',
|
||||
shortcuts: [
|
||||
{keys: ', / .', effect: 'Seek by frame'},
|
||||
{keys: '← / →', effect: 'Seek by 5 seconds'},
|
||||
{keys: '↓ / ↑', effect: 'Seek by 1 minute'},
|
||||
{keys: '[Shift] PGDWN / PGUP', effect: 'Seek by 10 minutes'},
|
||||
{keys: '[Shift] ← / →', effect: 'Seek by 1 second (exact)'},
|
||||
{keys: '[Shift] ↓ / ↑', effect: 'Seek by 5 seconds (exact)'},
|
||||
{keys: '[Ctrl] ← / →', effect: 'Seek by subtitle'},
|
||||
{keys: '[Shift] BACKSPACE', effect: 'Undo last seek'},
|
||||
{keys: '[Ctrl+Shift] BACKSPACE', effect: 'Mark current position'},
|
||||
{keys: 'l', effect: 'Set/clear A-B loop points'},
|
||||
{keys: 'L', effect: 'Toggle infinite looping'},
|
||||
{keys: 'PGDWN / PGUP', effect: 'Previous/next chapter'},
|
||||
{keys: '< / >', effect: 'Go backward/forward in the playlist'},
|
||||
{keys: 'ENTER', effect: 'Go forward in the playlist'},
|
||||
{keys: 'F8', effect: 'Show playlist [UI]'},
|
||||
]
|
||||
},
|
||||
{
|
||||
category: 'Playback',
|
||||
shortcuts: [
|
||||
{keys: 'p / SPACE', effect: 'Pause/unpause'},
|
||||
{keys: '[ / ]', effect: 'Decrease/increase speed [10%]'},
|
||||
{keys: '{ / }', effect: 'Halve/double speed'},
|
||||
{keys: 'BACKSPACE', effect: 'Reset speed'},
|
||||
{keys: 'o / P', effect: 'Show progress'},
|
||||
{keys: 'O', effect: 'Toggle progress'},
|
||||
{keys: 'i / I', effect: 'Show/toggle stats'},
|
||||
]
|
||||
},
|
||||
{
|
||||
category: 'Subtitle',
|
||||
shortcuts: [
|
||||
{keys: '[Ctrl+Shift] ← / →', effect: 'Adjust subtitle delay [subtitle]'},
|
||||
{keys: 'z / Z', effect: 'Adjust subtitle delay [0.1sec]'},
|
||||
{keys: 'v', effect: 'Toggle subtitle visibility'},
|
||||
{keys: 'u', effect: 'Toggle subtitle style overrides'},
|
||||
{keys: 'V', effect: 'Toggle subtitle VSFilter aspect compatibility mode'},
|
||||
{keys: 'r / R', effect: 'Move subtitles up/down'},
|
||||
{keys: 'j / J', effect: 'Cycle subtitle'},
|
||||
{keys: 'F9', effect: 'Show audio/subtitle list [UI]'},
|
||||
]
|
||||
},
|
||||
{
|
||||
category: 'Audio',
|
||||
shortcuts: [
|
||||
{keys: 'm', effect: 'Mute sound'},
|
||||
{keys: '#', effect: 'Cycle audio track'},
|
||||
{keys: '/ / *', effect: 'Decrease/increase volume'},
|
||||
{keys: '9 / 0', effect: 'Decrease/increase volume'},
|
||||
{keys: '[Ctrl] - / +', effect: 'Decrease/increase audio delay [0.1sec]'},
|
||||
{keys: 'F9', effect: 'Show audio/subtitle list [UI]'},
|
||||
]
|
||||
},
|
||||
{
|
||||
category: 'Video',
|
||||
shortcuts: [
|
||||
{keys: '_', effect: 'Cycle video track'},
|
||||
{keys: 'A', effect: 'Cycle aspect ratio'},
|
||||
{keys: 'd', effect: 'Toggle deinterlacer'},
|
||||
{keys: '[Ctrl] h', effect: 'Toggle hardware video decoding'},
|
||||
{keys: 'w / W', effect: 'Decrease/increase pan-and-scan range'},
|
||||
{keys: '[Alt] - / +', effect: 'Zoom out/in'},
|
||||
{keys: '[Alt] ARROWS', effect: 'Move the video rectangle'},
|
||||
{keys: '[Alt] BACKSPACE', effect: 'Reset pan/zoom'},
|
||||
{keys: '1 / 2', effect: 'Decrease/increase contrast'},
|
||||
{keys: '3 / 4', effect: 'Decrease/increase brightness'},
|
||||
{keys: '5 / 6', effect: 'Decrease/increase gamma'},
|
||||
{keys: '7 / 8', effect: 'Decrease/increase saturation'},
|
||||
]
|
||||
},
|
||||
{
|
||||
category: 'Application',
|
||||
shortcuts: [
|
||||
{keys: 'q', effect: 'Quit'},
|
||||
{keys: 'Q', effect: 'Save position and quit'},
|
||||
{keys: 's', effect: 'Take a screenshot'},
|
||||
{keys: 'S', effect: 'Take a screenshot without subtitles'},
|
||||
{keys: '[Ctrl] s', effect: 'Take a screenshot as rendered'},
|
||||
]
|
||||
},
|
||||
{
|
||||
category: 'Window',
|
||||
shortcuts: [
|
||||
{keys: 'f', effect: 'Toggle fullscreen'},
|
||||
{keys: '[Command] f', effect: 'Toggle fullscreen [macOS]'},
|
||||
{keys: 'ESC', effect: 'Exit fullscreen'},
|
||||
{keys: 'T', effect: 'Toggle stay-on-top'},
|
||||
{keys: '[Alt] 0', effect: 'Resize window to 0.5x [macOS]'},
|
||||
{keys: '[Alt] 1', effect: 'Reset window size [macOS]'},
|
||||
{keys: '[Alt] 2', effect: 'Resize window to 2x [macOS]'},
|
||||
]
|
||||
},
|
||||
{
|
||||
category: 'Multimedia keys',
|
||||
shortcuts: [
|
||||
{keys: 'PAUSE', effect: 'Pause'}, // keyboard with multimedia keys
|
||||
{keys: 'STOP', effect: 'Quit'}, // keyboard with multimedia keys
|
||||
{keys: 'PREVIOUS / NEXT', effect: 'Seek 1 minute'}, // keyboard with multimedia keys
|
||||
]
|
||||
},
|
||||
]
|
||||
|
||||
var State = {
|
||||
active: false,
|
||||
startLine: 0,
|
||||
startCategory: 0
|
||||
}
|
||||
|
||||
var opts = {
|
||||
font: 'monospace',
|
||||
'font-size': 8,
|
||||
'usage-font-size': 6,
|
||||
}
|
||||
|
||||
function repeat(s, num) {
|
||||
var ret = '';
|
||||
for (var i = 0; i < num; i++) {
|
||||
ret = ret + s;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
function renderCategory(category) {
|
||||
var lines = []
|
||||
lines.push(assdraw.bolden(category.category))
|
||||
var maxKeysLength = 0;
|
||||
category.shortcuts.forEach(function(shortcut) {
|
||||
if (shortcut.keys.length > maxKeysLength) maxKeysLength = shortcut.keys.length
|
||||
})
|
||||
category.shortcuts.forEach(function(shortcut) {
|
||||
var padding = repeat(" ", maxKeysLength - shortcut.keys.length)
|
||||
lines.push(assdraw.escape(shortcut.keys + padding + " " + shortcut.effect))
|
||||
})
|
||||
return lines
|
||||
}
|
||||
|
||||
function render() {
|
||||
var screen = mp.get_osd_size()
|
||||
if (!State.active) {
|
||||
mp.set_osd_ass(0, 0, '{}')
|
||||
return
|
||||
}
|
||||
var ass = new assdraw()
|
||||
ass.newEvent()
|
||||
ass.override(function() {
|
||||
this.lineAlignment(assdraw.TOP_LEFT)
|
||||
this.primaryFillAlpha('00')
|
||||
this.borderAlpha('00')
|
||||
this.shadowAlpha('99')
|
||||
this.primaryFillColor('eeeeee')
|
||||
this.borderColor('111111')
|
||||
this.shadowColor('000000')
|
||||
this.fontName(opts.font)
|
||||
this.fontSize(opts['font-size'])
|
||||
this.borderSize(1)
|
||||
this.xShadowDistance(0)
|
||||
this.yShadowDistance(1)
|
||||
this.letterSpacing(0)
|
||||
this.wrapStyle(assdraw.EOL_WRAPPING)
|
||||
})
|
||||
var mainLines = [];
|
||||
var pushedCategory = false
|
||||
shortcuts.forEach(function(category, i) {
|
||||
if (i < State.startCategory) {
|
||||
return;
|
||||
}
|
||||
pushedCategory = true;
|
||||
if (pushedCategory) {
|
||||
mainLines.push("")
|
||||
}
|
||||
mainLines.push.apply(mainLines, renderCategory(category))
|
||||
})
|
||||
mainLines.slice(State.startLine).forEach(function(line) {
|
||||
ass.appendLN(line);
|
||||
})
|
||||
|
||||
ass.newEvent()
|
||||
var sideLines = renderCategory({
|
||||
category: 'usage',
|
||||
shortcuts: Keybindings
|
||||
})
|
||||
ass.override(function() {
|
||||
this.lineAlignment(assdraw.TOP_RIGHT)
|
||||
this.fontSize(opts['usage-font-size'])
|
||||
})
|
||||
sideLines.forEach(function(line) {
|
||||
ass.appendLN(line);
|
||||
})
|
||||
|
||||
mp.set_osd_ass(0, 0, ass.text)
|
||||
}
|
||||
|
||||
function setActive(active) {
|
||||
if (active == State.active) return
|
||||
if (active) {
|
||||
State.active = true
|
||||
updateBindings(Keybindings, true)
|
||||
} else {
|
||||
State.active = false
|
||||
updateBindings(Keybindings, false)
|
||||
}
|
||||
render()
|
||||
}
|
||||
|
||||
function updateBindings(bindings, enable) {
|
||||
bindings.forEach(function(binding, i) {
|
||||
var name = '__cheatsheet_binding_' + i
|
||||
if (enable) {
|
||||
mp.add_forced_key_binding(binding.keys, name, binding.callback, binding.options)
|
||||
} else {
|
||||
mp.remove_key_binding(name)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
var Keybindings = [
|
||||
{
|
||||
keys: 'esc',
|
||||
effect: 'close',
|
||||
callback: function() { setActive(false) }
|
||||
},
|
||||
{
|
||||
keys: '?',
|
||||
effect: 'close',
|
||||
callback: function() { setActive(false) }
|
||||
},
|
||||
{
|
||||
keys: 'j',
|
||||
effect: 'next line',
|
||||
callback: function() {
|
||||
State.startLine += 1
|
||||
render()
|
||||
},
|
||||
options: 'repeatable'
|
||||
},
|
||||
{
|
||||
keys: 'k',
|
||||
effect: 'prev line',
|
||||
callback: function() {
|
||||
State.startLine = Math.max(0, State.startine - 1)
|
||||
render()
|
||||
},
|
||||
options: 'repeatable'
|
||||
},
|
||||
{
|
||||
keys: 'n',
|
||||
effect: 'next category',
|
||||
callback: function() {
|
||||
State.startCategory += 1
|
||||
State.startLine = 0
|
||||
render()
|
||||
},
|
||||
options: 'repeatable'
|
||||
},
|
||||
{
|
||||
keys: 'p',
|
||||
effect: 'prev category',
|
||||
callback: function() {
|
||||
State.startCategory = Math.max(0, State.startCategory - 1)
|
||||
State.startLine = 0
|
||||
render()
|
||||
},
|
||||
options: 'repeatable'
|
||||
},
|
||||
]
|
||||
|
||||
mp.add_key_binding('?', 'cheatsheet-enable', function() { setActive(true) })
|
||||
|
||||
mp.observe_property('osd-width', 'native', render)
|
||||
mp.observe_property('osd-height', 'native', render)
|
||||
|
||||
},{"./assdraw.js":1}]},{},[2]);
|
422
xfce-custom/dots/home/neko/.config/mpv/scripts/reload.lua
Normal file
422
xfce-custom/dots/home/neko/.config/mpv/scripts/reload.lua
Normal file
@ -0,0 +1,422 @@
|
||||
-- reload.lua
|
||||
--
|
||||
-- When an online video is stuck buffering or got very slow CDN
|
||||
-- source, restarting often helps. This script provides automatic
|
||||
-- reloading of videos that doesn't have buffering progress for some
|
||||
-- time while keeping the current time position. It also adds `Ctrl+r`
|
||||
-- keybinding to reload video manually.
|
||||
--
|
||||
-- SETTINGS
|
||||
--
|
||||
-- To override default setting put the `lua-settings/reload.conf` file in
|
||||
-- mpv user folder, on linux it is `~/.config/mpv`. NOTE: config file
|
||||
-- name should match the name of the script.
|
||||
--
|
||||
-- Default `reload.conf` settings:
|
||||
--
|
||||
-- ```
|
||||
-- # enable automatic reload on timeout
|
||||
-- # when paused-for-cache event fired, we will wait
|
||||
-- # paused_for_cache_timer_timeout sedonds and then reload the video
|
||||
-- paused_for_cache_timer_enabled=yes
|
||||
--
|
||||
-- # checking paused_for_cache property interval in seconds,
|
||||
-- # can not be less than 0.05 (50 ms)
|
||||
-- paused_for_cache_timer_interval=1
|
||||
--
|
||||
-- # time in seconds to wait until reload
|
||||
-- paused_for_cache_timer_timeout=10
|
||||
--
|
||||
-- # enable automatic reload based on demuxer cache
|
||||
-- # if demuxer-cache-time property didn't change in demuxer_cache_timer_timeout
|
||||
-- # time interval, the video will be reloaded as soon as demuxer cache depleated
|
||||
-- demuxer_cache_timer_enabled=yes
|
||||
--
|
||||
-- # checking demuxer-cache-time property interval in seconds,
|
||||
-- # can not be less than 0.05 (50 ms)
|
||||
-- demuxer_cache_timer_interval=2
|
||||
--
|
||||
-- # if demuxer cache didn't receive any data during demuxer_cache_timer_timeout
|
||||
-- # we decide that it has no progress and will reload the stream when
|
||||
-- # paused_for_cache event happens
|
||||
-- demuxer_cache_timer_timeout=20
|
||||
--
|
||||
-- # when the end-of-file is reached, reload the stream to check
|
||||
-- # if there is more content available.
|
||||
-- reload_eof_enabled=no
|
||||
--
|
||||
-- # keybinding to reload stream from current time position
|
||||
-- # you can disable keybinding by setting it to empty value
|
||||
-- # reload_key_binding=
|
||||
-- reload_key_binding=Ctrl+r
|
||||
-- ```
|
||||
--
|
||||
-- DEBUGGING
|
||||
--
|
||||
-- Debug messages will be printed to stdout with mpv command line option
|
||||
-- `--msg-level='reload=debug'`. You may also need to add the `--no-msg-color`
|
||||
-- option to make the debug logs visible if you are using a dark colorscheme
|
||||
-- in terminal.
|
||||
|
||||
local msg = require 'mp.msg'
|
||||
local options = require 'mp.options'
|
||||
local utils = require 'mp.utils'
|
||||
|
||||
|
||||
local settings = {
|
||||
paused_for_cache_timer_enabled = true,
|
||||
paused_for_cache_timer_interval = 1,
|
||||
paused_for_cache_timer_timeout = 10,
|
||||
demuxer_cache_timer_enabled = true,
|
||||
demuxer_cache_timer_interval = 2,
|
||||
demuxer_cache_timer_timeout = 20,
|
||||
reload_eof_enabled = false,
|
||||
reload_key_binding = "Ctrl+r",
|
||||
}
|
||||
|
||||
-- global state stores properties between reloads
|
||||
local property_path = nil
|
||||
local property_time_pos = 0
|
||||
local property_keep_open = nil
|
||||
|
||||
-- FSM managing the demuxer cache.
|
||||
--
|
||||
-- States:
|
||||
--
|
||||
-- * fetch - fetching new data
|
||||
-- * stale - unable to fetch new data for time < 'demuxer_cache_timer_timeout'
|
||||
-- * stuck - unable to fetch new data for time >= 'demuxer_cache_timer_timeout'
|
||||
--
|
||||
-- State transitions:
|
||||
--
|
||||
-- +---------------------------+
|
||||
-- v |
|
||||
-- +-------+ +-------+ +-------+
|
||||
-- + fetch +<--->+ stale +---->+ stuck |
|
||||
-- +-------+ +-------+ +-------+
|
||||
-- | ^ | ^ | ^
|
||||
-- +---+ +---+ +---+
|
||||
local demuxer_cache = {
|
||||
timer = nil,
|
||||
|
||||
state = {
|
||||
name = 'uninitialized',
|
||||
demuxer_cache_time = 0,
|
||||
in_state_time = 0,
|
||||
},
|
||||
|
||||
events = {
|
||||
continue_fetch = { name = 'continue_fetch', from = 'fetch', to = 'fetch' },
|
||||
continue_stale = { name = 'continue_stale', from = 'stale', to = 'stale' },
|
||||
continue_stuck = { name = 'continue_stuck', from = 'stuck', to = 'stuck' },
|
||||
fetch_to_stale = { name = 'fetch_to_stale', from = 'fetch', to = 'stale' },
|
||||
stale_to_fetch = { name = 'stale_to_fetch', from = 'stale', to = 'fetch' },
|
||||
stale_to_stuck = { name = 'stale_to_stuck', from = 'stale', to = 'stuck' },
|
||||
stuck_to_fetch = { name = 'stuck_to_fetch', from = 'stuck', to = 'fetch' },
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
-- Always start with 'fetch' state
|
||||
function demuxer_cache.reset_state()
|
||||
demuxer_cache.state = {
|
||||
name = demuxer_cache.events.continue_fetch.to,
|
||||
demuxer_cache_time = 0,
|
||||
in_state_time = 0,
|
||||
}
|
||||
end
|
||||
|
||||
-- Has 'demuxer_cache_time' changed
|
||||
function demuxer_cache.has_progress_since(t)
|
||||
return demuxer_cache.state.demuxer_cache_time ~= t
|
||||
end
|
||||
|
||||
function demuxer_cache.is_state_fetch()
|
||||
return demuxer_cache.state.name == demuxer_cache.events.continue_fetch.to
|
||||
end
|
||||
|
||||
function demuxer_cache.is_state_stale()
|
||||
return demuxer_cache.state.name == demuxer_cache.events.continue_stale.to
|
||||
end
|
||||
|
||||
function demuxer_cache.is_state_stuck()
|
||||
return demuxer_cache.state.name == demuxer_cache.events.continue_stuck.to
|
||||
end
|
||||
|
||||
function demuxer_cache.transition(event)
|
||||
if demuxer_cache.state.name == event.from then
|
||||
|
||||
-- state setup
|
||||
demuxer_cache.state.demuxer_cache_time = event.demuxer_cache_time
|
||||
|
||||
if event.name == 'continue_fetch' then
|
||||
demuxer_cache.state.in_state_time = demuxer_cache.state.in_state_time + event.interval
|
||||
elseif event.name == 'continue_stale' then
|
||||
demuxer_cache.state.in_state_time = demuxer_cache.state.in_state_time + event.interval
|
||||
elseif event.name == 'continue_stuck' then
|
||||
demuxer_cache.state.in_state_time = demuxer_cache.state.in_state_time + event.interval
|
||||
elseif event.name == 'fetch_to_stale' then
|
||||
demuxer_cache.state.in_state_time = 0
|
||||
elseif event.name == 'stale_to_fetch' then
|
||||
demuxer_cache.state.in_state_time = 0
|
||||
elseif event.name == 'stale_to_stuck' then
|
||||
demuxer_cache.state.in_state_time = 0
|
||||
elseif event.name == 'stuck_to_fetch' then
|
||||
demuxer_cache.state.in_state_time = 0
|
||||
end
|
||||
|
||||
-- state transition
|
||||
demuxer_cache.state.name = event.to
|
||||
|
||||
msg.debug('demuxer_cache.transition', event.name, utils.to_string(demuxer_cache.state))
|
||||
else
|
||||
msg.error(
|
||||
'demuxer_cache.transition',
|
||||
'illegal transition', event.name,
|
||||
'from state', demuxer_cache.state.name)
|
||||
end
|
||||
end
|
||||
|
||||
function demuxer_cache.initialize(demuxer_cache_timer_interval)
|
||||
demuxer_cache.reset_state()
|
||||
demuxer_cache.timer = mp.add_periodic_timer(
|
||||
demuxer_cache_timer_interval,
|
||||
function()
|
||||
demuxer_cache.demuxer_cache_timer_tick(
|
||||
mp.get_property_native('demuxer-cache-time'),
|
||||
demuxer_cache_timer_interval)
|
||||
end
|
||||
)
|
||||
end
|
||||
|
||||
-- If there is no progress of demuxer_cache_time in
|
||||
-- settings.demuxer_cache_timer_timeout time interval switch state to
|
||||
-- 'stuck' and switch back to 'fetch' as soon as any progress is made
|
||||
function demuxer_cache.demuxer_cache_timer_tick(demuxer_cache_time, demuxer_cache_timer_interval)
|
||||
local event = nil
|
||||
local cache_has_progress = demuxer_cache.has_progress_since(demuxer_cache_time)
|
||||
|
||||
-- I miss pattern matching so much
|
||||
if demuxer_cache.is_state_fetch() then
|
||||
if cache_has_progress then
|
||||
event = demuxer_cache.events.continue_fetch
|
||||
else
|
||||
event = demuxer_cache.events.fetch_to_stale
|
||||
end
|
||||
elseif demuxer_cache.is_state_stale() then
|
||||
if cache_has_progress then
|
||||
event = demuxer_cache.events.stale_to_fetch
|
||||
elseif demuxer_cache.state.in_state_time < settings.demuxer_cache_timer_timeout then
|
||||
event = demuxer_cache.events.continue_stale
|
||||
else
|
||||
event = demuxer_cache.events.stale_to_stuck
|
||||
end
|
||||
elseif demuxer_cache.is_state_stuck() then
|
||||
if cache_has_progress then
|
||||
event = demuxer_cache.events.stuck_to_fetch
|
||||
else
|
||||
event = demuxer_cache.events.continue_stuck
|
||||
end
|
||||
end
|
||||
|
||||
event.demuxer_cache_time = demuxer_cache_time
|
||||
event.interval = demuxer_cache_timer_interval
|
||||
demuxer_cache.transition(event)
|
||||
end
|
||||
|
||||
|
||||
local paused_for_cache = {
|
||||
timer = nil,
|
||||
time = 0,
|
||||
}
|
||||
|
||||
function paused_for_cache.reset_timer()
|
||||
msg.debug('paused_for_cache.reset_timer', paused_for_cache.time)
|
||||
if paused_for_cache.timer then
|
||||
paused_for_cache.timer:kill()
|
||||
paused_for_cache.timer = nil
|
||||
paused_for_cache.time = 0
|
||||
end
|
||||
end
|
||||
|
||||
function paused_for_cache.start_timer(interval_seconds, timeout_seconds)
|
||||
msg.debug('paused_for_cache.start_timer', paused_for_cache.time)
|
||||
if not paused_for_cache.timer then
|
||||
paused_for_cache.timer = mp.add_periodic_timer(
|
||||
interval_seconds,
|
||||
function()
|
||||
paused_for_cache.time = paused_for_cache.time + interval_seconds
|
||||
if paused_for_cache.time >= timeout_seconds then
|
||||
paused_for_cache.reset_timer()
|
||||
reload_resume()
|
||||
end
|
||||
msg.debug('paused_for_cache', 'tick', paused_for_cache.time)
|
||||
end
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
function paused_for_cache.handler(property, is_paused)
|
||||
if is_paused then
|
||||
|
||||
if demuxer_cache.is_state_stuck() then
|
||||
msg.info("demuxer cache has no progress")
|
||||
-- reset demuxer state to avoid immediate reload if
|
||||
-- paused_for_cache event triggered right after reload
|
||||
demuxer_cache.reset_state()
|
||||
reload_resume()
|
||||
end
|
||||
|
||||
paused_for_cache.start_timer(
|
||||
settings.paused_for_cache_timer_interval,
|
||||
settings.paused_for_cache_timer_timeout)
|
||||
else
|
||||
paused_for_cache.reset_timer()
|
||||
end
|
||||
end
|
||||
|
||||
function read_settings()
|
||||
options.read_options(settings, mp.get_script_name())
|
||||
msg.debug(utils.to_string(settings))
|
||||
end
|
||||
|
||||
function reload(path, time_pos)
|
||||
msg.debug("reload", path, time_pos)
|
||||
if time_pos == nil then
|
||||
mp.commandv("loadfile", path, "replace")
|
||||
else
|
||||
mp.commandv("loadfile", path, "replace", "start=+" .. time_pos)
|
||||
end
|
||||
end
|
||||
|
||||
function reload_resume()
|
||||
local path = mp.get_property("path", property_path)
|
||||
local time_pos = mp.get_property("time-pos")
|
||||
local reload_duration = mp.get_property_native("duration")
|
||||
|
||||
local playlist_count = mp.get_property_number("playlist/count")
|
||||
local playlist_pos = mp.get_property_number("playlist-pos")
|
||||
local playlist = {}
|
||||
for i = 0, playlist_count-1 do
|
||||
playlist[i] = mp.get_property("playlist/" .. i .. "/filename")
|
||||
end
|
||||
-- Tries to determine live stream vs. pre-recordered VOD. VOD has non-zero
|
||||
-- duration property. When reloading VOD, to keep the current time position
|
||||
-- we should provide offset from the start. Stream doesn't have fixed start.
|
||||
-- Decent choice would be to reload stream from it's current 'live' positon.
|
||||
-- That's the reason we don't pass the offset when reloading streams.
|
||||
if reload_duration and reload_duration > 0 then
|
||||
msg.info("reloading video from", time_pos, "second")
|
||||
reload(path, time_pos)
|
||||
-- VODs get stuck when reload is called without a time_pos
|
||||
-- this is most noticeable in youtube videos whenever download gets stuck in the first frames
|
||||
-- video would stay paused without being actually paused
|
||||
-- issue surfaced in mpv 0.33, afaik
|
||||
elseif reload_duration and reload_duration == 0 then
|
||||
msg.info("reloading video from", time_pos, "second")
|
||||
reload(path, time_pos)
|
||||
else
|
||||
msg.info("reloading stream")
|
||||
reload(path, nil)
|
||||
end
|
||||
msg.info("file", playlist_pos+1, "of", playlist_count, "in playlist")
|
||||
for i = 0, playlist_pos-1 do
|
||||
mp.commandv("loadfile", playlist[i], "append")
|
||||
end
|
||||
mp.commandv("playlist-move", 0, playlist_pos+1)
|
||||
for i = playlist_pos+1, playlist_count-1 do
|
||||
mp.commandv("loadfile", playlist[i], "append")
|
||||
end
|
||||
end
|
||||
|
||||
function reload_eof(property, eof_reached)
|
||||
msg.debug("reload_eof", property, eof_reached)
|
||||
local time_pos = mp.get_property_number("time-pos")
|
||||
local duration = mp.get_property_number("duration")
|
||||
|
||||
if eof_reached and round(time_pos) == round(duration) then
|
||||
msg.debug("property_time_pos", property_time_pos, "time_pos", time_pos)
|
||||
|
||||
-- Check that playback time_pos made progress after the last reload. When
|
||||
-- eof is reached we try to reload the video, in case there is more content
|
||||
-- available. If time_pos stayed the same after reload, it means that the
|
||||
-- video length stayed the same, and we can end the playback.
|
||||
if round(property_time_pos) == round(time_pos) then
|
||||
msg.info("eof reached, playback ended")
|
||||
mp.set_property("keep-open", property_keep_open)
|
||||
else
|
||||
msg.info("eof reached, checking if more content available")
|
||||
reload_resume()
|
||||
mp.set_property_bool("pause", false)
|
||||
property_time_pos = time_pos
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function on_file_loaded(event)
|
||||
local debug_info = {
|
||||
event = event,
|
||||
time_pos = mp.get_property("time-pos"),
|
||||
stream_pos = mp.get_property("stream-pos"),
|
||||
stream_end = mp.get_property("stream-end"),
|
||||
duration = mp.get_property("duration"),
|
||||
seekable = mp.get_property("seekable"),
|
||||
pause = mp.get_property("pause"),
|
||||
paused_for_cache = mp.get_property("paused-for-cache"),
|
||||
cache_buffering_state = mp.get_property("cache-buffering-state"),
|
||||
}
|
||||
msg.debug("debug_info", utils.to_string(debug_info))
|
||||
|
||||
-- When the video is reloaded after being paused for cache, it won't start
|
||||
-- playing again while all properties looks fine:
|
||||
-- `pause=no`, `paused-for-cache=no` and `cache-buffering-state=100`.
|
||||
-- As a workaround, we cycle through the paused state by sending two SPACE
|
||||
-- keypresses.
|
||||
-- What didn't work:
|
||||
-- - Cycling through the `pause` property.
|
||||
-- - Run the `playlist-play-index current` command.
|
||||
mp.commandv("keypress", 'SPACE')
|
||||
mp.commandv("keypress", 'SPACE')
|
||||
end
|
||||
|
||||
-- Round positive numbers.
|
||||
function round(num)
|
||||
return math.floor(num + 0.5)
|
||||
end
|
||||
|
||||
-- main
|
||||
|
||||
read_settings()
|
||||
|
||||
if settings.reload_key_binding ~= "" then
|
||||
mp.add_key_binding(settings.reload_key_binding, "reload_resume", reload_resume)
|
||||
end
|
||||
|
||||
if settings.paused_for_cache_timer_enabled then
|
||||
mp.observe_property("paused-for-cache", "bool", paused_for_cache.handler)
|
||||
end
|
||||
|
||||
if settings.demuxer_cache_timer_enabled then
|
||||
demuxer_cache.initialize(settings.demuxer_cache_timer_interval)
|
||||
end
|
||||
|
||||
if settings.reload_eof_enabled then
|
||||
-- vo-configured == video output created && its configuration went ok
|
||||
mp.observe_property(
|
||||
"vo-configured",
|
||||
"bool",
|
||||
function(name, vo_configured)
|
||||
msg.debug(name, vo_configured)
|
||||
if vo_configured then
|
||||
property_path = mp.get_property("path")
|
||||
property_keep_open = mp.get_property("keep-open")
|
||||
mp.set_property("keep-open", "yes")
|
||||
mp.set_property("keep-open-pause", "no")
|
||||
end
|
||||
end
|
||||
)
|
||||
|
||||
mp.observe_property("eof-reached", "bool", reload_eof)
|
||||
end
|
||||
|
||||
mp.register_event("file-loaded", on_file_loaded)
|
569
xfce-custom/dots/home/neko/.config/mpv/scripts/sponsorblock.lua
Normal file
569
xfce-custom/dots/home/neko/.config/mpv/scripts/sponsorblock.lua
Normal file
@ -0,0 +1,569 @@
|
||||
-- sponsorblock.lua
|
||||
--
|
||||
-- This script skips sponsored segments of YouTube videos
|
||||
-- using data from https://github.com/ajayyy/SponsorBlock
|
||||
|
||||
local ON_WINDOWS = package.config:sub(1,1) ~= "/"
|
||||
|
||||
local options = {
|
||||
server_address = "https://sponsor.ajay.app",
|
||||
|
||||
python_path = ON_WINDOWS and "python" or "python3",
|
||||
|
||||
-- Categories to fetch
|
||||
categories = "sponsor,intro,outro,interaction,selfpromo,filler",
|
||||
|
||||
-- Categories to skip automatically
|
||||
skip_categories = "sponsor",
|
||||
|
||||
-- If true, sponsored segments will only be skipped once
|
||||
skip_once = true,
|
||||
|
||||
-- Note that sponsored segments may ocasionally be inaccurate if this is turned off
|
||||
-- see https://blog.ajay.app/voting-and-pseudo-randomness-or-sponsorblock-or-youtube-sponsorship-segment-blocker
|
||||
local_database = false,
|
||||
|
||||
-- Update database on first run, does nothing if local_database is false
|
||||
auto_update = true,
|
||||
|
||||
-- How long to wait between local database updates
|
||||
-- Format: "X[d,h,m]", leave blank to update on every mpv run
|
||||
auto_update_interval = "6h",
|
||||
|
||||
-- User ID used to submit sponsored segments, leave blank for random
|
||||
user_id = "",
|
||||
|
||||
-- Name to display on the stats page https://sponsor.ajay.app/stats/ leave blank to keep current name
|
||||
display_name = "",
|
||||
|
||||
-- Tell the server when a skip happens
|
||||
report_views = true,
|
||||
|
||||
-- Auto upvote skipped sponsors
|
||||
auto_upvote = false,
|
||||
|
||||
-- Use sponsor times from server if they're more up to date than our local database
|
||||
server_fallback = true,
|
||||
|
||||
-- Create chapters at sponsor boundaries for OSC display and manual skipping
|
||||
make_chapters = true,
|
||||
|
||||
-- Minimum duration for sponsors (in seconds), segments under that threshold will be ignored
|
||||
min_duration = 1,
|
||||
|
||||
-- Fade audio for smoother transitions
|
||||
audio_fade = false,
|
||||
|
||||
-- Audio fade step, applied once every 100ms until cap is reached
|
||||
audio_fade_step = 10,
|
||||
|
||||
-- Audio fade cap
|
||||
audio_fade_cap = 0,
|
||||
|
||||
-- Fast forward through sponsors instead of skipping
|
||||
fast_forward = false,
|
||||
|
||||
-- Playback speed modifier when fast forwarding, applied once every second until cap is reached
|
||||
fast_forward_increase = .2,
|
||||
|
||||
-- Playback speed cap
|
||||
fast_forward_cap = 2,
|
||||
|
||||
-- Length of the sha256 prefix (3-32) when querying server, 0 to disable
|
||||
sha256_length = 4,
|
||||
|
||||
-- Pattern for video id in local files, ignored if blank
|
||||
-- Recommended value for base youtube-dl is "-([%w-_]+)%.[mw][kpe][v4b]m?$"
|
||||
local_pattern = "",
|
||||
|
||||
-- Legacy option, use skip_categories instead
|
||||
skip = true
|
||||
}
|
||||
|
||||
mp.options = require "mp.options"
|
||||
mp.options.read_options(options, "sponsorblock")
|
||||
|
||||
local legacy = mp.command_native_async == nil
|
||||
--[[
|
||||
if legacy then
|
||||
options.local_database = false
|
||||
end
|
||||
--]]
|
||||
options.local_database = false
|
||||
|
||||
local utils = require "mp.utils"
|
||||
scripts_dir = mp.find_config_file("scripts")
|
||||
|
||||
local sponsorblock = utils.join_path(scripts_dir, "sponsorblock_shared/sponsorblock.py")
|
||||
local uid_path = utils.join_path(scripts_dir, "sponsorblock_shared/sponsorblock.txt")
|
||||
local database_file = options.local_database and utils.join_path(scripts_dir, "sponsorblock_shared/sponsorblock.db") or ""
|
||||
local youtube_id = nil
|
||||
local ranges = {}
|
||||
local init = false
|
||||
local segment = {a = 0, b = 0, progress = 0, first = true}
|
||||
local retrying = false
|
||||
local last_skip = {uuid = "", dir = nil}
|
||||
local speed_timer = nil
|
||||
local fade_timer = nil
|
||||
local fade_dir = nil
|
||||
local volume_before = mp.get_property_number("volume")
|
||||
local categories = {}
|
||||
local all_categories = {"sponsor", "intro", "outro", "interaction", "selfpromo", "preview", "music_offtopic", "filler"}
|
||||
local chapter_cache = {}
|
||||
|
||||
for category in string.gmatch(options.skip_categories, "([^,]+)") do
|
||||
categories[category] = true
|
||||
end
|
||||
|
||||
function file_exists(name)
|
||||
local f = io.open(name,"r")
|
||||
if f ~= nil then io.close(f) return true else return false end
|
||||
end
|
||||
|
||||
function t_count(t)
|
||||
local count = 0
|
||||
for _ in pairs(t) do count = count + 1 end
|
||||
return count
|
||||
end
|
||||
|
||||
function time_sort(a, b)
|
||||
if a.time == b.time then
|
||||
return string.match(a.title, "segment end")
|
||||
end
|
||||
return a.time < b.time
|
||||
end
|
||||
|
||||
function parse_update_interval()
|
||||
local s = options.auto_update_interval
|
||||
if s == "" then return 0 end -- Interval Disabled
|
||||
|
||||
local num, mod = s:match "^(%d+)([hdm])$"
|
||||
|
||||
if num == nil or mod == nil then
|
||||
mp.osd_message("[sponsorblock] auto_update_interval " .. s .. " is invalid", 5)
|
||||
return nil
|
||||
end
|
||||
|
||||
local time_table = {
|
||||
m = 60,
|
||||
h = 60 * 60,
|
||||
d = 60 * 60 * 24,
|
||||
}
|
||||
|
||||
return num * time_table[mod]
|
||||
end
|
||||
|
||||
function clean_chapters()
|
||||
local chapters = mp.get_property_native("chapter-list")
|
||||
local new_chapters = {}
|
||||
for _, chapter in pairs(chapters) do
|
||||
if chapter.title ~= "Preview segment start" and chapter.title ~= "Preview segment end" then
|
||||
table.insert(new_chapters, chapter)
|
||||
end
|
||||
end
|
||||
mp.set_property_native("chapter-list", new_chapters)
|
||||
end
|
||||
|
||||
function create_chapter(chapter_title, chapter_time)
|
||||
local chapters = mp.get_property_native("chapter-list")
|
||||
local duration = mp.get_property_native("duration")
|
||||
table.insert(chapters, {title=chapter_title, time=(duration == nil or duration > chapter_time) and chapter_time or duration - .001})
|
||||
table.sort(chapters, time_sort)
|
||||
mp.set_property_native("chapter-list", chapters)
|
||||
end
|
||||
|
||||
function process(uuid, t, new_ranges)
|
||||
start_time = tonumber(string.match(t, "[^,]+"))
|
||||
end_time = tonumber(string.sub(string.match(t, ",[^,]+"), 2))
|
||||
for o_uuid, o_t in pairs(ranges) do
|
||||
if (start_time >= o_t.start_time and start_time <= o_t.end_time) or (o_t.start_time >= start_time and o_t.start_time <= end_time) then
|
||||
new_ranges[o_uuid] = o_t
|
||||
return
|
||||
end
|
||||
end
|
||||
category = string.match(t, "[^,]+$")
|
||||
if categories[category] and end_time - start_time >= options.min_duration then
|
||||
new_ranges[uuid] = {
|
||||
start_time = start_time,
|
||||
end_time = end_time,
|
||||
category = category,
|
||||
skipped = false
|
||||
}
|
||||
end
|
||||
if options.make_chapters and not chapter_cache[uuid] then
|
||||
chapter_cache[uuid] = true
|
||||
local category_title = (category:gsub("^%l", string.upper):gsub("_", " "))
|
||||
create_chapter(category_title .. " segment start (" .. string.sub(uuid, 1, 6) .. ")", start_time)
|
||||
create_chapter(category_title .. " segment end (" .. string.sub(uuid, 1, 6) .. ")", end_time)
|
||||
end
|
||||
end
|
||||
|
||||
function getranges(_, exists, db, more)
|
||||
if type(exists) == "table" and exists["status"] == "1" then
|
||||
if options.server_fallback then
|
||||
mp.add_timeout(0, function() getranges(true, true, "") end)
|
||||
else
|
||||
return mp.osd_message("[sponsorblock] database update failed, gave up")
|
||||
end
|
||||
end
|
||||
if db ~= "" and db ~= database_file then db = database_file end
|
||||
if exists ~= true and not file_exists(db) then
|
||||
if not retrying then
|
||||
mp.osd_message("[sponsorblock] database update failed, retrying...")
|
||||
retrying = true
|
||||
end
|
||||
return update()
|
||||
end
|
||||
if retrying then
|
||||
mp.osd_message("[sponsorblock] database update succeeded")
|
||||
retrying = false
|
||||
end
|
||||
local sponsors
|
||||
local args = {
|
||||
options.python_path,
|
||||
sponsorblock,
|
||||
"ranges",
|
||||
db,
|
||||
options.server_address,
|
||||
youtube_id,
|
||||
options.categories,
|
||||
tostring(options.sha256_length)
|
||||
}
|
||||
if not legacy then
|
||||
sponsors = mp.command_native({name = "subprocess", capture_stdout = true, playback_only = false, args = args})
|
||||
else
|
||||
sponsors = utils.subprocess({args = args})
|
||||
end
|
||||
mp.msg.debug("Got: " .. string.gsub(sponsors.stdout, "[\n\r]", ""))
|
||||
if not string.match(sponsors.stdout, "^%s*(.*%S)") then return end
|
||||
if string.match(sponsors.stdout, "error") then return getranges(true, true) end
|
||||
local new_ranges = {}
|
||||
local r_count = 0
|
||||
if more then r_count = -1 end
|
||||
for t in string.gmatch(sponsors.stdout, "[^:%s]+") do
|
||||
uuid = string.match(t, "([^,]+),[^,]+$")
|
||||
if ranges[uuid] then
|
||||
new_ranges[uuid] = ranges[uuid]
|
||||
else
|
||||
process(uuid, t, new_ranges)
|
||||
end
|
||||
r_count = r_count + 1
|
||||
end
|
||||
local c_count = t_count(ranges)
|
||||
if c_count == 0 or r_count >= c_count then
|
||||
ranges = new_ranges
|
||||
end
|
||||
end
|
||||
|
||||
function fast_forward()
|
||||
if options.fast_forward and options.fast_forward == true then
|
||||
speed_timer = nil
|
||||
mp.set_property("speed", 1)
|
||||
end
|
||||
local last_speed = mp.get_property_number("speed")
|
||||
local new_speed = math.min(last_speed + options.fast_forward_increase, options.fast_forward_cap)
|
||||
if new_speed <= last_speed then return end
|
||||
mp.set_property("speed", new_speed)
|
||||
end
|
||||
|
||||
function fade_audio(step)
|
||||
local last_volume = mp.get_property_number("volume")
|
||||
local new_volume = math.max(options.audio_fade_cap, math.min(last_volume + step, volume_before))
|
||||
if new_volume == last_volume then
|
||||
if step >= 0 then fade_dir = nil end
|
||||
if fade_timer ~= nil then fade_timer:kill() end
|
||||
fade_timer = nil
|
||||
return
|
||||
end
|
||||
mp.set_property("volume", new_volume)
|
||||
end
|
||||
|
||||
function skip_ads(name, pos)
|
||||
if pos == nil then return end
|
||||
local sponsor_ahead = false
|
||||
for uuid, t in pairs(ranges) do
|
||||
if (options.fast_forward == uuid or not options.skip_once or not t.skipped) and t.start_time <= pos and t.end_time > pos then
|
||||
if options.fast_forward == uuid then return end
|
||||
if options.fast_forward == false then
|
||||
mp.osd_message("[sponsorblock] " .. t.category .. " skipped")
|
||||
mp.set_property("time-pos", t.end_time)
|
||||
else
|
||||
mp.osd_message("[sponsorblock] skipping " .. t.category)
|
||||
end
|
||||
t.skipped = true
|
||||
last_skip = {uuid = uuid, dir = nil}
|
||||
if options.report_views or options.auto_upvote then
|
||||
local args = {
|
||||
options.python_path,
|
||||
sponsorblock,
|
||||
"stats",
|
||||
database_file,
|
||||
options.server_address,
|
||||
youtube_id,
|
||||
uuid,
|
||||
options.report_views and "1" or "",
|
||||
uid_path,
|
||||
options.user_id,
|
||||
options.auto_upvote and "1" or ""
|
||||
}
|
||||
if not legacy then
|
||||
mp.command_native_async({name = "subprocess", playback_only = false, args = args}, function () end)
|
||||
else
|
||||
utils.subprocess_detached({args = args})
|
||||
end
|
||||
end
|
||||
if options.fast_forward ~= false then
|
||||
options.fast_forward = uuid
|
||||
if speed_timer ~= nil then speed_timer:kill() end
|
||||
speed_timer = mp.add_periodic_timer(1, fast_forward)
|
||||
end
|
||||
return
|
||||
elseif (not options.skip_once or not t.skipped) and t.start_time <= pos + 1 and t.end_time > pos + 1 then
|
||||
sponsor_ahead = true
|
||||
end
|
||||
end
|
||||
if options.audio_fade then
|
||||
if sponsor_ahead then
|
||||
if fade_dir ~= false then
|
||||
if fade_dir == nil then volume_before = mp.get_property_number("volume") end
|
||||
if fade_timer ~= nil then fade_timer:kill() end
|
||||
fade_dir = false
|
||||
fade_timer = mp.add_periodic_timer(.1, function() fade_audio(-options.audio_fade_step) end)
|
||||
end
|
||||
elseif fade_dir == false then
|
||||
fade_dir = true
|
||||
if fade_timer ~= nil then fade_timer:kill() end
|
||||
fade_timer = mp.add_periodic_timer(.1, function() fade_audio(options.audio_fade_step) end)
|
||||
end
|
||||
end
|
||||
if options.fast_forward and options.fast_forward ~= true then
|
||||
options.fast_forward = true
|
||||
speed_timer:kill()
|
||||
speed_timer = nil
|
||||
mp.set_property("speed", 1)
|
||||
end
|
||||
end
|
||||
|
||||
function vote(dir)
|
||||
if last_skip.uuid == "" then return mp.osd_message("[sponsorblock] no sponsors skipped, can't submit vote") end
|
||||
local updown = dir == "1" and "up" or "down"
|
||||
if last_skip.dir == dir then return mp.osd_message("[sponsorblock] " .. updown .. "vote already submitted") end
|
||||
last_skip.dir = dir
|
||||
local args = {
|
||||
options.python_path,
|
||||
sponsorblock,
|
||||
"stats",
|
||||
database_file,
|
||||
options.server_address,
|
||||
youtube_id,
|
||||
last_skip.uuid,
|
||||
"",
|
||||
uid_path,
|
||||
options.user_id,
|
||||
dir
|
||||
}
|
||||
if not legacy then
|
||||
mp.command_native_async({name = "subprocess", playback_only = false, args = args}, function () end)
|
||||
else
|
||||
utils.subprocess({args = args})
|
||||
end
|
||||
mp.osd_message("[sponsorblock] " .. updown .. "vote submitted")
|
||||
end
|
||||
|
||||
function update()
|
||||
mp.command_native_async({name = "subprocess", playback_only = false, args = {
|
||||
options.python_path,
|
||||
sponsorblock,
|
||||
"update",
|
||||
database_file,
|
||||
options.server_address
|
||||
}}, getranges)
|
||||
end
|
||||
|
||||
function file_loaded()
|
||||
local initialized = init
|
||||
ranges = {}
|
||||
segment = {a = 0, b = 0, progress = 0, first = true}
|
||||
last_skip = {uuid = "", dir = nil}
|
||||
chapter_cache = {}
|
||||
local video_path = mp.get_property("path", "")
|
||||
mp.msg.debug("Path: " .. video_path)
|
||||
local video_referer = string.match(mp.get_property("http-header-fields", ""), "Referer:([^,]+)") or ""
|
||||
mp.msg.debug("Referer: " .. video_referer)
|
||||
|
||||
local urls = {
|
||||
"ytdl://([%w-_]+).*",
|
||||
"https?://youtu%.be/([%w-_]+).*",
|
||||
"https?://w?w?w?%.?youtube%.com/v/([%w-_]+).*",
|
||||
"/watch.*[?&]v=([%w-_]+).*",
|
||||
"/embed/([%w-_]+).*"
|
||||
}
|
||||
youtube_id = nil
|
||||
for i, url in ipairs(urls) do
|
||||
youtube_id = youtube_id or string.match(video_path, url) or string.match(video_referer, url)
|
||||
if youtube_id then break end
|
||||
end
|
||||
youtube_id = youtube_id or string.match(video_path, options.local_pattern)
|
||||
|
||||
if not youtube_id or string.len(youtube_id) < 11 or (local_pattern and string.len(youtube_id) ~= 11) then return end
|
||||
youtube_id = string.sub(youtube_id, 1, 11)
|
||||
mp.msg.debug("Found YouTube ID: " .. youtube_id)
|
||||
init = true
|
||||
if not options.local_database then
|
||||
getranges(true, true)
|
||||
else
|
||||
local exists = file_exists(database_file)
|
||||
if exists and options.server_fallback then
|
||||
getranges(true, true)
|
||||
mp.add_timeout(0, function() getranges(true, true, "", true) end)
|
||||
elseif exists then
|
||||
getranges(true, true)
|
||||
elseif options.server_fallback then
|
||||
mp.add_timeout(0, function() getranges(true, true, "") end)
|
||||
end
|
||||
end
|
||||
if initialized then return end
|
||||
if options.skip then
|
||||
mp.observe_property("time-pos", "native", skip_ads)
|
||||
end
|
||||
if options.display_name ~= "" then
|
||||
local args = {
|
||||
options.python_path,
|
||||
sponsorblock,
|
||||
"username",
|
||||
database_file,
|
||||
options.server_address,
|
||||
youtube_id,
|
||||
"",
|
||||
"",
|
||||
uid_path,
|
||||
options.user_id,
|
||||
options.display_name
|
||||
}
|
||||
if not legacy then
|
||||
mp.command_native_async({name = "subprocess", playback_only = false, args = args}, function () end)
|
||||
else
|
||||
utils.subprocess_detached({args = args})
|
||||
end
|
||||
end
|
||||
if not options.local_database or (not options.auto_update and file_exists(database_file)) then return end
|
||||
|
||||
if file_exists(database_file) then
|
||||
local db_info = utils.file_info(database_file)
|
||||
local cur_time = os.time(os.date("*t"))
|
||||
local upd_interval = parse_update_interval()
|
||||
if upd_interval == nil or os.difftime(cur_time, db_info.mtime) < upd_interval then return end
|
||||
end
|
||||
|
||||
update()
|
||||
end
|
||||
|
||||
function set_segment()
|
||||
if not youtube_id then return end
|
||||
local pos = mp.get_property_number("time-pos")
|
||||
if pos == nil then return end
|
||||
if segment.progress > 1 then
|
||||
segment.progress = segment.progress - 2
|
||||
end
|
||||
if segment.progress == 1 then
|
||||
segment.progress = 0
|
||||
segment.b = pos
|
||||
mp.osd_message("[sponsorblock] segment boundary B set, press again for boundary A", 3)
|
||||
else
|
||||
segment.progress = 1
|
||||
segment.a = pos
|
||||
mp.osd_message("[sponsorblock] segment boundary A set, press again for boundary B", 3)
|
||||
end
|
||||
if options.make_chapters and not segment.first then
|
||||
local start_time = math.min(segment.a, segment.b)
|
||||
local end_time = math.max(segment.a, segment.b)
|
||||
if end_time - start_time ~= 0 and end_time ~= 0 then
|
||||
clean_chapters()
|
||||
create_chapter("Preview segment start", start_time)
|
||||
create_chapter("Preview segment end", end_time)
|
||||
end
|
||||
end
|
||||
segment.first = false
|
||||
end
|
||||
|
||||
function select_category(selected)
|
||||
for category in string.gmatch(options.categories, "([^,]+)") do
|
||||
mp.remove_key_binding("select_category_"..category)
|
||||
mp.remove_key_binding("kp_select_category_"..category)
|
||||
end
|
||||
submit_segment(selected)
|
||||
end
|
||||
|
||||
function submit_segment(category)
|
||||
if not youtube_id then return end
|
||||
local start_time = math.min(segment.a, segment.b)
|
||||
local end_time = math.max(segment.a, segment.b)
|
||||
if end_time - start_time == 0 or end_time == 0 then
|
||||
mp.osd_message("[sponsorblock] empty segment, not submitting")
|
||||
elseif segment.progress <= 1 then
|
||||
segment.progress = segment.progress + 2
|
||||
local category_list = ""
|
||||
for category_id, category in pairs(all_categories) do
|
||||
local category_title = (category:gsub("^%l", string.upper):gsub("_", " "))
|
||||
category_list = category_list .. category_id .. ": " .. category_title .. "\n"
|
||||
mp.add_forced_key_binding(tostring(category_id), "select_category_"..category, function() select_category(category) end)
|
||||
mp.add_forced_key_binding("KP"..tostring(category_id), "kp_select_category_"..category, function() select_category(category) end)
|
||||
end
|
||||
mp.osd_message(string.format("[sponsorblock] press a number to select category for segment: %.2d:%.2d:%.2d to %.2d:%.2d:%.2d\n\n" .. category_list .. "\nyou can press Shift+G again for default (Sponsor) or hide this message with g", math.floor(start_time/(60*60)), math.floor(start_time/60%60), math.floor(start_time%60), math.floor(end_time/(60*60)), math.floor(end_time/60%60), math.floor(end_time%60)), 30)
|
||||
else
|
||||
mp.osd_message("[sponsorblock] submitting segment...", 30)
|
||||
local submit
|
||||
local args = {
|
||||
options.python_path,
|
||||
sponsorblock,
|
||||
"submit",
|
||||
database_file,
|
||||
options.server_address,
|
||||
youtube_id,
|
||||
tostring(start_time),
|
||||
tostring(end_time),
|
||||
uid_path,
|
||||
options.user_id,
|
||||
category or "sponsor"
|
||||
}
|
||||
if not legacy then
|
||||
submit = mp.command_native({name = "subprocess", capture_stdout = true, playback_only = false, args = args})
|
||||
else
|
||||
submit = utils.subprocess({args = args})
|
||||
end
|
||||
if string.match(submit.stdout, "success") then
|
||||
segment = {a = 0, b = 0, progress = 0, first = true}
|
||||
mp.osd_message("[sponsorblock] segment submitted")
|
||||
if options.make_chapters then
|
||||
clean_chapters()
|
||||
create_chapter("Submitted segment start", start_time)
|
||||
create_chapter("Submitted segment end", end_time)
|
||||
end
|
||||
elseif string.match(submit.stdout, "error") then
|
||||
mp.osd_message("[sponsorblock] segment submission failed, server may be down. try again", 5)
|
||||
elseif string.match(submit.stdout, "502") then
|
||||
mp.osd_message("[sponsorblock] segment submission failed, server is down. try again", 5)
|
||||
elseif string.match(submit.stdout, "400") then
|
||||
mp.osd_message("[sponsorblock] segment submission failed, impossible inputs", 5)
|
||||
segment = {a = 0, b = 0, progress = 0, first = true}
|
||||
elseif string.match(submit.stdout, "429") then
|
||||
mp.osd_message("[sponsorblock] segment submission failed, rate limited. try again", 5)
|
||||
elseif string.match(submit.stdout, "409") then
|
||||
mp.osd_message("[sponsorblock] segment already submitted", 3)
|
||||
segment = {a = 0, b = 0, progress = 0, first = true}
|
||||
else
|
||||
mp.osd_message("[sponsorblock] segment submission failed", 5)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
mp.register_event("file-loaded", file_loaded)
|
||||
mp.add_key_binding("g", "set_segment", set_segment)
|
||||
mp.add_key_binding("G", "submit_segment", submit_segment)
|
||||
mp.add_key_binding("h", "upvote_segment", function() return vote("1") end)
|
||||
mp.add_key_binding("H", "downvote_segment", function() return vote("0") end)
|
||||
-- Bindings below are for backwards compatibility and could be removed at any time
|
||||
mp.add_key_binding(nil, "sponsorblock_set_segment", set_segment)
|
||||
mp.add_key_binding(nil, "sponsorblock_submit_segment", submit_segment)
|
||||
mp.add_key_binding(nil, "sponsorblock_upvote", function() return vote("1") end)
|
||||
mp.add_key_binding(nil, "sponsorblock_downvote", function() return vote("0") end)
|
@ -0,0 +1,3 @@
|
||||
-- This is a dummy main.lua
|
||||
-- required for mpv 0.33
|
||||
-- do not delete
|
@ -0,0 +1,122 @@
|
||||
import urllib.request
|
||||
import urllib.parse
|
||||
import hashlib
|
||||
import sqlite3
|
||||
import random
|
||||
import string
|
||||
import json
|
||||
import sys
|
||||
import os
|
||||
|
||||
if sys.argv[1] in ["submit", "stats", "username"]:
|
||||
if not sys.argv[8]:
|
||||
if os.path.isfile(sys.argv[7]):
|
||||
with open(sys.argv[7]) as f:
|
||||
uid = f.read()
|
||||
else:
|
||||
uid = "".join(random.choices(string.ascii_letters + string.digits, k=36))
|
||||
with open(sys.argv[7], "w") as f:
|
||||
f.write(uid)
|
||||
else:
|
||||
uid = sys.argv[8]
|
||||
|
||||
opener = urllib.request.build_opener()
|
||||
opener.addheaders = [("User-Agent", "mpv_sponsorblock/1.0 (https://github.com/po5/mpv_sponsorblock)")]
|
||||
urllib.request.install_opener(opener)
|
||||
|
||||
if sys.argv[1] == "ranges" and (not sys.argv[2] or not os.path.isfile(sys.argv[2])):
|
||||
sha = None
|
||||
if 3 <= int(sys.argv[6]) <= 32:
|
||||
sha = hashlib.sha256(sys.argv[4].encode()).hexdigest()[:int(sys.argv[6])]
|
||||
times = []
|
||||
try:
|
||||
response = urllib.request.urlopen(sys.argv[3] + "/api/skipSegments" + ("/" + sha + "?" if sha else "?videoID=" + sys.argv[4] + "&") + urllib.parse.urlencode([("categories", json.dumps(sys.argv[5].split(",")))]))
|
||||
segments = json.load(response)
|
||||
for segment in segments:
|
||||
if sha and sys.argv[4] != segment["videoID"]:
|
||||
continue
|
||||
if sha:
|
||||
for s in segment["segments"]:
|
||||
times.append(str(s["segment"][0]) + "," + str(s["segment"][1]) + "," + s["UUID"] + "," + s["category"])
|
||||
else:
|
||||
times.append(str(segment["segment"][0]) + "," + str(segment["segment"][1]) + "," + segment["UUID"] + "," + segment["category"])
|
||||
print(":".join(times))
|
||||
except (TimeoutError, urllib.error.URLError) as e:
|
||||
print("error")
|
||||
except urllib.error.HTTPError as e:
|
||||
if e.code == 404:
|
||||
print("")
|
||||
else:
|
||||
print("error")
|
||||
elif sys.argv[1] == "ranges":
|
||||
conn = sqlite3.connect(sys.argv[2])
|
||||
conn.row_factory = sqlite3.Row
|
||||
c = conn.cursor()
|
||||
times = []
|
||||
for category in sys.argv[5].split(","):
|
||||
c.execute("SELECT startTime, endTime, votes, UUID, category FROM sponsorTimes WHERE videoID = ? AND shadowHidden = 0 AND votes > -1 AND category = ?", (sys.argv[4], category))
|
||||
sponsors = c.fetchall()
|
||||
best = list(sponsors)
|
||||
dealtwith = []
|
||||
similar = []
|
||||
for sponsor_a in sponsors:
|
||||
for sponsor_b in sponsors:
|
||||
if sponsor_a is not sponsor_b and sponsor_a["startTime"] >= sponsor_b["startTime"] and sponsor_a["startTime"] <= sponsor_b["endTime"]:
|
||||
similar.append([sponsor_a, sponsor_b])
|
||||
if sponsor_a in best:
|
||||
best.remove(sponsor_a)
|
||||
if sponsor_b in best:
|
||||
best.remove(sponsor_b)
|
||||
for sponsors_a in similar:
|
||||
if sponsors_a in dealtwith:
|
||||
continue
|
||||
group = set(sponsors_a)
|
||||
for sponsors_b in similar:
|
||||
if sponsors_b[0] in group or sponsors_b[1] in group:
|
||||
group.add(sponsors_b[0])
|
||||
group.add(sponsors_b[1])
|
||||
dealtwith.append(sponsors_b)
|
||||
best.append(max(group, key=lambda x:x["votes"]))
|
||||
for time in best:
|
||||
times.append(str(time["startTime"]) + "," + str(time["endTime"]) + "," + time["UUID"] + "," + time["category"])
|
||||
print(":".join(times))
|
||||
elif sys.argv[1] == "update":
|
||||
try:
|
||||
urllib.request.urlretrieve(sys.argv[3] + "/database.db", sys.argv[2] + ".tmp")
|
||||
os.replace(sys.argv[2] + ".tmp", sys.argv[2])
|
||||
except PermissionError:
|
||||
print("database update failed, file currently in use", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
except ConnectionResetError:
|
||||
print("database update failed, connection reset", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
except TimeoutError:
|
||||
print("database update failed, timed out", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
except urllib.error.URLError:
|
||||
print("database update failed", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
elif sys.argv[1] == "submit":
|
||||
try:
|
||||
req = urllib.request.Request(sys.argv[3] + "/api/skipSegments", data=json.dumps({"videoID": sys.argv[4], "segments": [{"segment": [float(sys.argv[5]), float(sys.argv[6])], "category": sys.argv[9]}], "userID": uid}).encode(), headers={"Content-Type": "application/json"})
|
||||
response = urllib.request.urlopen(req)
|
||||
print("success")
|
||||
except urllib.error.HTTPError as e:
|
||||
print(e.code)
|
||||
except:
|
||||
print("error")
|
||||
elif sys.argv[1] == "stats":
|
||||
try:
|
||||
if sys.argv[6]:
|
||||
urllib.request.urlopen(sys.argv[3] + "/api/viewedVideoSponsorTime?UUID=" + sys.argv[5])
|
||||
if sys.argv[9]:
|
||||
urllib.request.urlopen(sys.argv[3] + "/api/voteOnSponsorTime?UUID=" + sys.argv[5] + "&userID=" + uid + "&type=" + sys.argv[9])
|
||||
except:
|
||||
pass
|
||||
elif sys.argv[1] == "username":
|
||||
try:
|
||||
data = urllib.parse.urlencode({"userID": uid, "userName": sys.argv[9]}).encode()
|
||||
req = urllib.request.Request(sys.argv[3] + "/api/setUsername", data=data)
|
||||
urllib.request.urlopen(req)
|
||||
except:
|
||||
pass
|
@ -0,0 +1 @@
|
||||
aMn4i07nXmgiLpjJPE235QcbVv3QxdXdH1xB
|
515
xfce-custom/dots/home/neko/.config/mpv/scripts/thumbfast.lua
Normal file
515
xfce-custom/dots/home/neko/.config/mpv/scripts/thumbfast.lua
Normal file
@ -0,0 +1,515 @@
|
||||
-- thumbfast.lua
|
||||
--
|
||||
-- High-performance on-the-fly thumbnailer
|
||||
--
|
||||
-- Built for easy integration in third-party UIs.
|
||||
|
||||
local options = {
|
||||
-- Socket path (leave empty for auto)
|
||||
socket = "",
|
||||
|
||||
-- Thumbnail path (leave empty for auto)
|
||||
thumbnail = "",
|
||||
|
||||
-- Maximum thumbnail size in pixels (scaled down to fit)
|
||||
-- Values are scaled when hidpi is enabled
|
||||
max_height = 200,
|
||||
max_width = 200,
|
||||
|
||||
-- Overlay id
|
||||
overlay_id = 42,
|
||||
|
||||
-- Thumbnail interval in seconds, set to 0 to disable (warning: high cpu usage)
|
||||
-- Clamped to min_thumbnails and max_thumbnails
|
||||
interval = 6,
|
||||
|
||||
-- Number of thumbnails
|
||||
min_thumbnails = 6,
|
||||
max_thumbnails = 120,
|
||||
|
||||
-- Spawn thumbnailer on file load for faster initial thumbnails
|
||||
spawn_first = false,
|
||||
|
||||
-- Enable on network playback
|
||||
network = false,
|
||||
|
||||
-- Enable on audio playback
|
||||
audio = false
|
||||
}
|
||||
|
||||
mp.utils = require "mp.utils"
|
||||
mp.options = require "mp.options"
|
||||
mp.options.read_options(options, "thumbfast")
|
||||
|
||||
if options.min_thumbnails < 1 then
|
||||
options.min_thumbnails = 1
|
||||
end
|
||||
|
||||
local os_name = ""
|
||||
|
||||
math.randomseed(os.time())
|
||||
local unique = math.random(10000000)
|
||||
local init = false
|
||||
|
||||
local spawned = false
|
||||
local can_generate = true
|
||||
local network = false
|
||||
local disabled = false
|
||||
local interval = 0
|
||||
|
||||
local x = nil
|
||||
local y = nil
|
||||
local last_x = x
|
||||
local last_y = y
|
||||
|
||||
local last_index = nil
|
||||
local last_request = nil
|
||||
local last_request_time = nil
|
||||
local last_display_time = 0
|
||||
|
||||
local effective_w = options.max_width
|
||||
local effective_h = options.max_height
|
||||
local thumb_size = effective_w * effective_h * 4
|
||||
|
||||
local filters_reset = {["lavfi-crop"]=true, crop=true}
|
||||
local filters_runtime = {hflip=true, vflip=true}
|
||||
local filters_all = filters_runtime
|
||||
for k,v in pairs(filters_reset) do filters_all[k] = v end
|
||||
|
||||
local last_vf_reset = ""
|
||||
local last_vf_runtime = ""
|
||||
|
||||
local last_rotate = 0
|
||||
|
||||
local par = ""
|
||||
local last_par = ""
|
||||
|
||||
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 = mp.command_native({name = "subprocess", playback_only = false, capture_stdout = true, args = {"uname", "-s"}}).stdout
|
||||
end
|
||||
end
|
||||
|
||||
raw_os_name = (raw_os_name):lower()
|
||||
|
||||
local os_patterns = {
|
||||
["windows"] = "Windows",
|
||||
|
||||
-- Uses socat
|
||||
["linux"] = "Linux",
|
||||
|
||||
["osx"] = "Mac",
|
||||
["mac"] = "Mac",
|
||||
["darwin"] = "Mac",
|
||||
|
||||
["^mingw"] = "Windows",
|
||||
["^cygwin"] = "Windows",
|
||||
|
||||
-- Because they have the good netcat (with -U)
|
||||
["bsd$"] = "Mac",
|
||||
["sunos"] = "Mac"
|
||||
}
|
||||
|
||||
-- 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 function vf_string(filters, full)
|
||||
local vf = ""
|
||||
local vf_table = mp.get_property_native("vf")
|
||||
|
||||
if #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 then
|
||||
vf = vf.."scale=w="..effective_w..":h="..effective_h..par..",pad=w="..effective_w..":h="..effective_h..":x=(ow-iw)/2:y=(oh-ih)/2,format=bgra"
|
||||
end
|
||||
|
||||
return vf
|
||||
end
|
||||
|
||||
local function calc_dimensions()
|
||||
local width = mp.get_property_number("video-out-params/dw")
|
||||
local height = mp.get_property_number("video-out-params/dh")
|
||||
if not width or not height then return end
|
||||
|
||||
local scale = mp.get_property_number("display-hidpi-scale", 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
|
||||
|
||||
thumb_size = effective_w * effective_h * 4
|
||||
|
||||
local v_par = mp.get_property_number("video-out-params/par", 1)
|
||||
if v_par == 1 then
|
||||
par = ":force_original_aspect_ratio=decrease"
|
||||
else
|
||||
par = ""
|
||||
end
|
||||
end
|
||||
|
||||
local function info()
|
||||
local display_w, display_h = effective_w, effective_h
|
||||
if mp.get_property_number("video-params/rotate", 0) % 180 == 90 then
|
||||
display_w, display_h = effective_h, effective_w
|
||||
end
|
||||
|
||||
local json, err = mp.utils.format_json({width=display_w, height=display_h, disabled=disabled, socket=options.socket, thumbnail=options.thumbnail, overlay_id=options.overlay_id})
|
||||
mp.commandv("script-message", "thumbfast-info", json)
|
||||
end
|
||||
|
||||
local function remove_thumbnail_files()
|
||||
os.remove(options.thumbnail)
|
||||
os.remove(options.thumbnail..".bgra")
|
||||
end
|
||||
|
||||
local function spawn(time)
|
||||
if disabled then return end
|
||||
|
||||
local path = mp.get_property("path")
|
||||
if path == nil then return end
|
||||
|
||||
spawned = true
|
||||
|
||||
local open_filename = mp.get_property("stream-open-filename")
|
||||
local ytdl = open_filename and network and path ~= open_filename
|
||||
if ytdl then
|
||||
path = open_filename
|
||||
end
|
||||
|
||||
if os_name == "" then
|
||||
os_name = get_os()
|
||||
end
|
||||
|
||||
if options.socket == "" then
|
||||
if os_name == "Windows" then
|
||||
options.socket = "thumbfast"
|
||||
elseif os_name == "Mac" then
|
||||
options.socket = "/tmp/thumbfast"
|
||||
else
|
||||
options.socket = "/tmp/thumbfast"
|
||||
end
|
||||
end
|
||||
|
||||
if options.thumbnail == "" then
|
||||
if os_name == "Windows" then
|
||||
options.thumbnail = os.getenv("TEMP").."\\thumbfast.out"
|
||||
elseif os_name == "Mac" then
|
||||
options.thumbnail = "/tmp/thumbfast.out"
|
||||
else
|
||||
options.thumbnail = "/tmp/thumbfast.out"
|
||||
end
|
||||
end
|
||||
|
||||
if not init then
|
||||
-- ensure uniqueness
|
||||
options.socket = options.socket .. unique
|
||||
options.thumbnail = options.thumbnail .. unique
|
||||
init = true
|
||||
end
|
||||
|
||||
remove_thumbnail_files()
|
||||
|
||||
calc_dimensions()
|
||||
|
||||
info()
|
||||
|
||||
mp.command_native_async(
|
||||
{name = "subprocess", playback_only = true, args = {
|
||||
"mpv", path, "--no-config", "--msg-level=all=no", "--idle", "--pause", "--keep-open=always",
|
||||
"--edition="..(mp.get_property_number("edition") or "auto"), "--vid="..(mp.get_property_number("vid") or "auto"), "--no-sub", "--no-audio",
|
||||
"--input-ipc-server="..options.socket,
|
||||
"--start="..time, "--hr-seek=no",
|
||||
"--ytdl-format=worst", "--demuxer-readahead-secs=0", "--demuxer-max-bytes=128KiB",
|
||||
"--vd-lavc-skiploopfilter=all", "--vd-lavc-software-fallback=1", "--vd-lavc-fast",
|
||||
"--vf="..vf_string(filters_all, true),
|
||||
"--sws-allow-zimg=no", "--sws-fast=yes", "--sws-scaler=fast-bilinear",
|
||||
"--video-rotate="..last_rotate,
|
||||
"--ovc=rawvideo", "--of=image2", "--ofopts=update=1", "--o="..options.thumbnail
|
||||
}},
|
||||
function() end
|
||||
)
|
||||
end
|
||||
|
||||
local function run(command, callback)
|
||||
if not spawned then return end
|
||||
|
||||
callback = callback or function() end
|
||||
|
||||
local seek_command
|
||||
if os_name == "Windows" then
|
||||
seek_command = {"cmd", "/c", "echo "..command.." > \\\\.\\pipe\\" .. options.socket}
|
||||
elseif os_name == "Mac" then
|
||||
-- this doesn't work, on my system. not sure why.
|
||||
seek_command = {"/usr/bin/env", "sh", "-c", "echo '"..command.."' | nc -w0 -U " .. options.socket}
|
||||
else
|
||||
seek_command = {"/usr/bin/env", "sh", "-c", "echo '" .. command .. "' | socat - " .. options.socket}
|
||||
end
|
||||
|
||||
mp.command_native_async(
|
||||
{name = "subprocess", playback_only = true, capture_stdout = true, args = seek_command},
|
||||
callback
|
||||
)
|
||||
end
|
||||
|
||||
local function thumb_index(thumbtime)
|
||||
return math.floor(thumbtime / interval)
|
||||
end
|
||||
|
||||
local function index_time(index, thumbtime)
|
||||
if interval > 0 then
|
||||
local time = index * interval
|
||||
return time + interval / 3
|
||||
else
|
||||
return thumbtime
|
||||
end
|
||||
end
|
||||
|
||||
local function draw(w, h, thumbtime, display_time, script)
|
||||
local display_w, display_h = w, h
|
||||
if mp.get_property_number("video-params/rotate", 0) % 180 == 90 then
|
||||
display_w, display_h = h, w
|
||||
end
|
||||
|
||||
if x ~= nil then
|
||||
mp.command_native(
|
||||
{name = "overlay-add", id=options.overlay_id, x=x, y=y, file=options.thumbnail..".bgra", offset=0, fmt="bgra", w=display_w, h=display_h, stride=(4*display_w)}
|
||||
)
|
||||
elseif script then
|
||||
local json, err = mp.utils.format_json({width=display_w, height=display_h, 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 display_img(w, h, thumbtime, display_time, script, redraw)
|
||||
if last_display_time > display_time or disabled then return end
|
||||
|
||||
if not redraw then
|
||||
can_generate = false
|
||||
|
||||
local info = mp.utils.file_info(options.thumbnail)
|
||||
if not info or info.size ~= thumb_size then
|
||||
if thumbtime == -1 then
|
||||
can_generate = true
|
||||
return
|
||||
end
|
||||
|
||||
if thumbtime < 0 then
|
||||
thumbtime = thumbtime + 1
|
||||
end
|
||||
|
||||
-- display last successful thumbnail if one exists
|
||||
local info2 = mp.utils.file_info(options.thumbnail..".bgra")
|
||||
if info2 and info2.size == thumb_size then
|
||||
draw(w, h, thumbtime, display_time, script)
|
||||
end
|
||||
|
||||
-- retry up to 5 times
|
||||
return mp.add_timeout(0.05, function() display_img(w, h, thumbtime < 0 and thumbtime or -5, display_time, script) end)
|
||||
end
|
||||
|
||||
if last_display_time > display_time then return end
|
||||
|
||||
-- os.rename can't replace files on windows
|
||||
if os_name == "Windows" then
|
||||
os.remove(options.thumbnail..".bgra")
|
||||
end
|
||||
-- move the file because it can get overwritten while overlay-add is reading it, and crash the player
|
||||
os.rename(options.thumbnail, options.thumbnail..".bgra")
|
||||
|
||||
last_display_time = display_time
|
||||
else
|
||||
local info = mp.utils.file_info(options.thumbnail..".bgra")
|
||||
if not info or info.size ~= thumb_size then
|
||||
-- still waiting on intial thumbnail
|
||||
return mp.add_timeout(0.05, function() display_img(w, h, thumbtime, display_time, script) end)
|
||||
end
|
||||
if not can_generate then
|
||||
return draw(w, h, thumbtime, display_time, script)
|
||||
end
|
||||
end
|
||||
|
||||
draw(w, h, thumbtime, display_time, script)
|
||||
|
||||
can_generate = true
|
||||
|
||||
if not redraw then
|
||||
-- often, the file we read will be the last requested thumbnail
|
||||
-- retry after a small delay to ensure we got the latest image
|
||||
if thumbtime ~= -1 then
|
||||
mp.add_timeout(0.05, function() display_img(w, h, -1, display_time, script) end)
|
||||
mp.add_timeout(0.1, function() display_img(w, h, -1, display_time, script) end)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
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 == nil or r_y == nil then
|
||||
x, y = nil, nil
|
||||
else
|
||||
x, y = math.floor(r_x + 0.5), math.floor(r_y + 0.5)
|
||||
end
|
||||
|
||||
local index = thumb_index(time)
|
||||
local seek_time = index_time(index, time)
|
||||
|
||||
if last_request == seek_time or (interval > 0 and index == last_index) then
|
||||
last_index = index
|
||||
if x ~= last_x or y ~= last_y then
|
||||
last_x, last_y = x, y
|
||||
display_img(effective_w, effective_h, time, mp.get_time(), script, true)
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
local cur_request_time = mp.get_time()
|
||||
|
||||
last_index = index
|
||||
last_request_time = cur_request_time
|
||||
last_request = seek_time
|
||||
|
||||
if not spawned then
|
||||
spawn(seek_time)
|
||||
if can_generate then
|
||||
display_img(effective_w, effective_h, time, cur_request_time, script)
|
||||
mp.add_timeout(0.15, function() display_img(effective_w, effective_h, time, cur_request_time, script) end)
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
run("async seek "..seek_time.." absolute+keyframes", function() if can_generate then display_img(effective_w, effective_h, time, cur_request_time, script) end end)
|
||||
end
|
||||
|
||||
local function clear()
|
||||
last_display_time = mp.get_time()
|
||||
can_generate = true
|
||||
last_x = nil
|
||||
last_y = nil
|
||||
mp.command_native(
|
||||
{name = "overlay-remove", id=options.overlay_id}
|
||||
)
|
||||
end
|
||||
|
||||
local function watch_changes()
|
||||
local old_w = effective_w
|
||||
local old_h = effective_h
|
||||
|
||||
calc_dimensions()
|
||||
|
||||
local vf_reset = vf_string(filters_reset)
|
||||
local rotate = mp.get_property_number("video-rotate", 0)
|
||||
|
||||
if spawned then
|
||||
if 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 then
|
||||
last_rotate = rotate
|
||||
-- mpv doesn't allow us to change output size
|
||||
run("quit")
|
||||
clear()
|
||||
info()
|
||||
spawned = false
|
||||
spawn(last_request or mp.get_property_number("time-pos", 0))
|
||||
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
|
||||
if 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 then
|
||||
last_rotate = rotate
|
||||
info()
|
||||
end
|
||||
last_vf_runtime = vf_string(filters_runtime)
|
||||
end
|
||||
|
||||
last_vf_reset = vf_reset
|
||||
last_rotate = rotate
|
||||
last_par = par
|
||||
end
|
||||
|
||||
local function sync_changes(prop, val)
|
||||
if spawned and val then
|
||||
run("set "..prop.." "..val)
|
||||
end
|
||||
end
|
||||
|
||||
local function file_load()
|
||||
clear()
|
||||
|
||||
network = mp.get_property_bool("demuxer-via-network", false)
|
||||
local image = mp.get_property_native('current-tracks/video/image', true)
|
||||
local albumart = image and mp.get_property_native("current-tracks/video/albumart", false)
|
||||
|
||||
disabled = (network and not options.network) or (albumart and not options.audio) or (image and not albumart)
|
||||
info()
|
||||
if disabled then return end
|
||||
|
||||
interval = math.min(math.max(mp.get_property_number("duration", 1) / options.max_thumbnails, options.interval), mp.get_property_number("duration", options.interval * options.min_thumbnails) / options.min_thumbnails)
|
||||
|
||||
spawned = false
|
||||
if options.spawn_first then spawn(mp.get_property_number("time-pos", 0)) end
|
||||
end
|
||||
|
||||
local function shutdown()
|
||||
run("quit")
|
||||
remove_thumbnail_files()
|
||||
os.remove(options.socket)
|
||||
end
|
||||
|
||||
mp.observe_property("display-hidpi-scale", "native", watch_changes)
|
||||
mp.observe_property("video-out-params", "native", watch_changes)
|
||||
mp.observe_property("vf", "native", watch_changes)
|
||||
mp.observe_property("vid", "native", sync_changes)
|
||||
mp.observe_property("edition", "native", sync_changes)
|
||||
|
||||
mp.register_script_message("thumb", thumb)
|
||||
mp.register_script_message("clear", clear)
|
||||
|
||||
mp.register_event("file-loaded", file_load)
|
||||
mp.register_event("shutdown", shutdown)
|
4708
xfce-custom/dots/home/neko/.config/mpv/scripts/uosc.lua
Normal file
4708
xfce-custom/dots/home/neko/.config/mpv/scripts/uosc.lua
Normal file
File diff suppressed because it is too large
Load Diff
2913
xfce-custom/dots/home/neko/.config/mpv/scripts/webm.lua
Normal file
2913
xfce-custom/dots/home/neko/.config/mpv/scripts/webm.lua
Normal file
File diff suppressed because it is too large
Load Diff
3
xfce-custom/dots/home/neko/.config/xfce4/helpers.rc
Normal file
3
xfce-custom/dots/home/neko/.config/xfce4/helpers.rc
Normal file
@ -0,0 +1,3 @@
|
||||
WebBrowser=custom-WebBrowser
|
||||
TerminalEmulator=xfce4-terminal
|
||||
|
@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<channel name="displays" version="1.0">
|
||||
<property name="ActiveProfile" type="string" value="Default"/>
|
||||
</channel>
|
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<channel name="thunar" version="1.0">
|
||||
<property name="last-view" type="string" value="ThunarIconView"/>
|
||||
<property name="last-icon-view-zoom-level" type="string" value="THUNAR_ZOOM_LEVEL_100_PERCENT"/>
|
||||
<property name="last-separator-position" type="int" value="170"/>
|
||||
<property name="last-show-hidden" type="bool" value="true"/>
|
||||
</channel>
|
@ -0,0 +1,194 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<channel name="xfce4-keyboard-shortcuts" version="1.0">
|
||||
<property name="commands" type="empty">
|
||||
<property name="default" type="empty">
|
||||
<property name="<Alt>F1" type="empty"/>
|
||||
<property name="<Alt>F2" type="empty">
|
||||
<property name="startup-notify" type="empty"/>
|
||||
</property>
|
||||
<property name="<Alt>F3" type="empty">
|
||||
<property name="startup-notify" type="empty"/>
|
||||
</property>
|
||||
<property name="<Primary><Alt>Delete" type="empty"/>
|
||||
<property name="<Primary><Alt>l" type="empty"/>
|
||||
<property name="<Primary><Alt>t" type="empty"/>
|
||||
<property name="XF86Display" type="empty"/>
|
||||
<property name="<Super>p" type="empty"/>
|
||||
<property name="<Primary>Escape" type="empty"/>
|
||||
<property name="XF86WWW" type="empty"/>
|
||||
<property name="HomePage" type="empty"/>
|
||||
<property name="XF86Mail" type="empty"/>
|
||||
<property name="Print" type="empty"/>
|
||||
<property name="<Alt>Print" type="empty"/>
|
||||
<property name="<Shift>Print" type="empty"/>
|
||||
<property name="<Super>e" type="empty"/>
|
||||
<property name="<Primary><Alt>f" type="empty"/>
|
||||
<property name="<Primary><Alt>Escape" type="empty"/>
|
||||
<property name="<Primary><Shift>Escape" type="empty"/>
|
||||
<property name="<Super>r" type="empty">
|
||||
<property name="startup-notify" type="empty"/>
|
||||
</property>
|
||||
</property>
|
||||
<property name="custom" type="empty">
|
||||
<property name="<Alt>F2" type="string" value="xfce4-appfinder --collapsed">
|
||||
<property name="startup-notify" type="bool" value="true"/>
|
||||
</property>
|
||||
<property name="<Alt>Print" type="string" value="xfce4-screenshooter -w"/>
|
||||
<property name="<Super>r" type="string" value="xfce4-appfinder -c">
|
||||
<property name="startup-notify" type="bool" value="true"/>
|
||||
</property>
|
||||
<property name="XF86WWW" type="string" value="exo-open --launch WebBrowser"/>
|
||||
<property name="XF86Mail" type="string" value="exo-open --launch MailReader"/>
|
||||
<property name="<Alt>F3" type="string" value="xfce4-appfinder">
|
||||
<property name="startup-notify" type="bool" value="true"/>
|
||||
</property>
|
||||
<property name="Print" type="string" value="xfce4-screenshooter"/>
|
||||
<property name="<Primary>Escape" type="string" value="xfdesktop --menu"/>
|
||||
<property name="<Shift>Print" type="string" value="xfce4-screenshooter -r"/>
|
||||
<property name="<Primary><Alt>Delete" type="string" value="xfce4-session-logout"/>
|
||||
<property name="<Primary><Alt>t" type="string" value="exo-open --launch TerminalEmulator"/>
|
||||
<property name="<Primary><Alt>f" type="string" value="thunar"/>
|
||||
<property name="<Primary><Alt>l" type="string" value="xflock4"/>
|
||||
<property name="<Alt>F1" type="string" value="xfce4-popup-applicationsmenu"/>
|
||||
<property name="<Super>p" type="string" value="xfce4-display-settings --minimal"/>
|
||||
<property name="<Primary><Shift>Escape" type="string" value="xfce4-taskmanager"/>
|
||||
<property name="<Super>e" type="string" value="thunar"/>
|
||||
<property name="<Primary><Alt>Escape" type="string" value="xkill"/>
|
||||
<property name="HomePage" type="string" value="exo-open --launch WebBrowser"/>
|
||||
<property name="XF86Display" type="string" value="xfce4-display-settings --minimal"/>
|
||||
<property name="override" type="bool" value="true"/>
|
||||
</property>
|
||||
</property>
|
||||
<property name="xfwm4" type="empty">
|
||||
<property name="default" type="empty">
|
||||
<property name="<Alt>Insert" type="empty"/>
|
||||
<property name="Escape" type="empty"/>
|
||||
<property name="Left" type="empty"/>
|
||||
<property name="Right" type="empty"/>
|
||||
<property name="Up" type="empty"/>
|
||||
<property name="Down" type="empty"/>
|
||||
<property name="<Alt>Tab" type="empty"/>
|
||||
<property name="<Alt><Shift>Tab" type="empty"/>
|
||||
<property name="<Alt>Delete" type="empty"/>
|
||||
<property name="<Primary><Alt>Down" type="empty"/>
|
||||
<property name="<Primary><Alt>Left" type="empty"/>
|
||||
<property name="<Shift><Alt>Page_Down" type="empty"/>
|
||||
<property name="<Alt>F4" type="empty"/>
|
||||
<property name="<Alt>F6" type="empty"/>
|
||||
<property name="<Alt>F7" type="empty"/>
|
||||
<property name="<Alt>F8" type="empty"/>
|
||||
<property name="<Alt>F9" type="empty"/>
|
||||
<property name="<Alt>F10" type="empty"/>
|
||||
<property name="<Alt>F11" type="empty"/>
|
||||
<property name="<Alt>F12" type="empty"/>
|
||||
<property name="<Primary><Shift><Alt>Left" type="empty"/>
|
||||
<property name="<Primary><Alt>End" type="empty"/>
|
||||
<property name="<Primary><Alt>Home" type="empty"/>
|
||||
<property name="<Primary><Shift><Alt>Right" type="empty"/>
|
||||
<property name="<Primary><Shift><Alt>Up" type="empty"/>
|
||||
<property name="<Primary><Alt>KP_1" type="empty"/>
|
||||
<property name="<Primary><Alt>KP_2" type="empty"/>
|
||||
<property name="<Primary><Alt>KP_3" type="empty"/>
|
||||
<property name="<Primary><Alt>KP_4" type="empty"/>
|
||||
<property name="<Primary><Alt>KP_5" type="empty"/>
|
||||
<property name="<Primary><Alt>KP_6" type="empty"/>
|
||||
<property name="<Primary><Alt>KP_7" type="empty"/>
|
||||
<property name="<Primary><Alt>KP_8" type="empty"/>
|
||||
<property name="<Primary><Alt>KP_9" type="empty"/>
|
||||
<property name="<Alt>space" type="empty"/>
|
||||
<property name="<Shift><Alt>Page_Up" type="empty"/>
|
||||
<property name="<Primary><Alt>Right" type="empty"/>
|
||||
<property name="<Primary><Alt>d" type="empty"/>
|
||||
<property name="<Primary><Alt>Up" type="empty"/>
|
||||
<property name="<Super>Tab" type="empty"/>
|
||||
<property name="<Primary>F1" type="empty"/>
|
||||
<property name="<Primary>F2" type="empty"/>
|
||||
<property name="<Primary>F3" type="empty"/>
|
||||
<property name="<Primary>F4" type="empty"/>
|
||||
<property name="<Primary>F5" type="empty"/>
|
||||
<property name="<Primary>F6" type="empty"/>
|
||||
<property name="<Primary>F7" type="empty"/>
|
||||
<property name="<Primary>F8" type="empty"/>
|
||||
<property name="<Primary>F9" type="empty"/>
|
||||
<property name="<Primary>F10" type="empty"/>
|
||||
<property name="<Primary>F11" type="empty"/>
|
||||
<property name="<Primary>F12" type="empty"/>
|
||||
<property name="<Super>KP_Left" type="empty"/>
|
||||
<property name="<Super>KP_Right" type="empty"/>
|
||||
<property name="<Super>KP_Up" type="empty"/>
|
||||
<property name="<Super>KP_Down" type="empty"/>
|
||||
<property name="<Super>KP_Page_Up" type="empty"/>
|
||||
<property name="<Super>KP_Home" type="empty"/>
|
||||
<property name="<Super>KP_End" type="empty"/>
|
||||
<property name="<Super>KP_Next" type="empty"/>
|
||||
</property>
|
||||
<property name="custom" type="empty">
|
||||
<property name="<Primary>F12" type="string" value="workspace_12_key"/>
|
||||
<property name="<Super>KP_Down" type="string" value="tile_up_key"/>
|
||||
<property name="<Alt>F4" type="string" value="close_window_key"/>
|
||||
<property name="<Primary><Alt>KP_3" type="string" value="move_window_workspace_3_key"/>
|
||||
<property name="<Primary>F2" type="string" value="workspace_2_key"/>
|
||||
<property name="<Primary>F6" type="string" value="workspace_6_key"/>
|
||||
<property name="<Primary><Alt>Down" type="string" value="down_workspace_key"/>
|
||||
<property name="<Primary><Alt>KP_9" type="string" value="move_window_workspace_9_key"/>
|
||||
<property name="<Super>KP_Up" type="string" value="tile_down_key"/>
|
||||
<property name="<Primary><Alt>End" type="string" value="move_window_next_workspace_key"/>
|
||||
<property name="<Primary>F8" type="string" value="workspace_8_key"/>
|
||||
<property name="<Primary><Shift><Alt>Left" type="string" value="move_window_left_key"/>
|
||||
<property name="<Super>KP_Right" type="string" value="tile_right_key"/>
|
||||
<property name="<Primary><Alt>KP_4" type="string" value="move_window_workspace_4_key"/>
|
||||
<property name="Right" type="string" value="right_key"/>
|
||||
<property name="Down" type="string" value="down_key"/>
|
||||
<property name="<Primary>F3" type="string" value="workspace_3_key"/>
|
||||
<property name="<Shift><Alt>Page_Down" type="string" value="lower_window_key"/>
|
||||
<property name="<Primary>F9" type="string" value="workspace_9_key"/>
|
||||
<property name="<Alt>Tab" type="string" value="cycle_windows_key"/>
|
||||
<property name="<Primary><Shift><Alt>Right" type="string" value="move_window_right_key"/>
|
||||
<property name="<Primary><Alt>Right" type="string" value="right_workspace_key"/>
|
||||
<property name="<Alt>F6" type="string" value="stick_window_key"/>
|
||||
<property name="<Primary><Alt>KP_5" type="string" value="move_window_workspace_5_key"/>
|
||||
<property name="<Primary>F11" type="string" value="workspace_11_key"/>
|
||||
<property name="<Alt>F10" type="string" value="maximize_window_key"/>
|
||||
<property name="<Alt>Delete" type="string" value="del_workspace_key"/>
|
||||
<property name="<Super>Tab" type="string" value="switch_window_key"/>
|
||||
<property name="<Primary><Alt>d" type="string" value="show_desktop_key"/>
|
||||
<property name="<Primary>F4" type="string" value="workspace_4_key"/>
|
||||
<property name="<Super>KP_Page_Up" type="string" value="tile_up_right_key"/>
|
||||
<property name="<Alt>F7" type="string" value="move_window_key"/>
|
||||
<property name="Up" type="string" value="up_key"/>
|
||||
<property name="<Primary><Alt>KP_6" type="string" value="move_window_workspace_6_key"/>
|
||||
<property name="<Alt>F11" type="string" value="fullscreen_key"/>
|
||||
<property name="<Alt>space" type="string" value="popup_menu_key"/>
|
||||
<property name="<Super>KP_Home" type="string" value="tile_up_left_key"/>
|
||||
<property name="Escape" type="string" value="cancel_key"/>
|
||||
<property name="<Primary><Alt>KP_1" type="string" value="move_window_workspace_1_key"/>
|
||||
<property name="<Super>KP_Next" type="string" value="tile_down_right_key"/>
|
||||
<property name="<Super>KP_Left" type="string" value="tile_left_key"/>
|
||||
<property name="<Shift><Alt>Page_Up" type="string" value="raise_window_key"/>
|
||||
<property name="<Primary><Alt>Home" type="string" value="move_window_prev_workspace_key"/>
|
||||
<property name="<Alt><Shift>Tab" type="string" value="cycle_reverse_windows_key"/>
|
||||
<property name="<Primary><Alt>Left" type="string" value="left_workspace_key"/>
|
||||
<property name="<Alt>F12" type="string" value="above_key"/>
|
||||
<property name="<Primary><Shift><Alt>Up" type="string" value="move_window_up_key"/>
|
||||
<property name="<Primary>F5" type="string" value="workspace_5_key"/>
|
||||
<property name="<Alt>F8" type="string" value="resize_window_key"/>
|
||||
<property name="<Primary><Alt>KP_7" type="string" value="move_window_workspace_7_key"/>
|
||||
<property name="<Primary><Alt>KP_2" type="string" value="move_window_workspace_2_key"/>
|
||||
<property name="<Super>KP_End" type="string" value="tile_down_left_key"/>
|
||||
<property name="<Primary><Alt>Up" type="string" value="up_workspace_key"/>
|
||||
<property name="<Alt>F9" type="string" value="hide_window_key"/>
|
||||
<property name="<Primary>F7" type="string" value="workspace_7_key"/>
|
||||
<property name="<Primary>F10" type="string" value="workspace_10_key"/>
|
||||
<property name="Left" type="string" value="left_key"/>
|
||||
<property name="<Primary><Alt>KP_8" type="string" value="move_window_workspace_8_key"/>
|
||||
<property name="<Alt>Insert" type="string" value="add_workspace_key"/>
|
||||
<property name="<Primary>F1" type="string" value="workspace_1_key"/>
|
||||
<property name="override" type="bool" value="true"/>
|
||||
</property>
|
||||
</property>
|
||||
<property name="providers" type="array">
|
||||
<value type="string" value="xfwm4"/>
|
||||
<value type="string" value="commands"/>
|
||||
</property>
|
||||
</channel>
|
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<channel name="xfce4-mime-settings" version="1.0">
|
||||
<property name="last" type="empty">
|
||||
<property name="window-width" type="int" value="550"/>
|
||||
<property name="window-height" type="int" value="400"/>
|
||||
<property name="mime-width" type="int" value="300"/>
|
||||
<property name="status-width" type="int" value="75"/>
|
||||
<property name="default-width" type="int" value="133"/>
|
||||
</property>
|
||||
</channel>
|
108
xfce-custom/dots/home/neko/.config/xfce4/xfconf/xfce4-panel.xml
Normal file
108
xfce-custom/dots/home/neko/.config/xfce4/xfconf/xfce4-panel.xml
Normal file
@ -0,0 +1,108 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<channel name="xfce4-panel" version="1.0">
|
||||
<property name="configver" type="int" value="2"/>
|
||||
<property name="panels" type="array">
|
||||
<value type="int" value="1"/>
|
||||
<value type="int" value="2"/>
|
||||
<property name="dark-mode" type="bool" value="true"/>
|
||||
<property name="panel-1" type="empty">
|
||||
<property name="position" type="string" value="p=6;x=0;y=0"/>
|
||||
<property name="length" type="uint" value="100"/>
|
||||
<property name="position-locked" type="bool" value="true"/>
|
||||
<property name="icon-size" type="uint" value="16"/>
|
||||
<property name="size" type="uint" value="26"/>
|
||||
<property name="plugin-ids" type="array">
|
||||
<value type="int" value="1"/>
|
||||
<value type="int" value="2"/>
|
||||
<value type="int" value="3"/>
|
||||
<value type="int" value="4"/>
|
||||
<value type="int" value="5"/>
|
||||
<value type="int" value="6"/>
|
||||
<value type="int" value="8"/>
|
||||
<value type="int" value="11"/>
|
||||
<value type="int" value="12"/>
|
||||
<value type="int" value="13"/>
|
||||
<value type="int" value="14"/>
|
||||
</property>
|
||||
</property>
|
||||
<property name="panel-2" type="empty">
|
||||
<property name="autohide-behavior" type="uint" value="1"/>
|
||||
<property name="position" type="string" value="p=10;x=0;y=0"/>
|
||||
<property name="position-locked" type="bool" value="true"/>
|
||||
<property name="size" type="uint" value="48"/>
|
||||
<property name="plugin-ids" type="array">
|
||||
<value type="int" value="15"/>
|
||||
<value type="int" value="16"/>
|
||||
<value type="int" value="17"/>
|
||||
<value type="int" value="18"/>
|
||||
<value type="int" value="19"/>
|
||||
<value type="int" value="20"/>
|
||||
<value type="int" value="21"/>
|
||||
<value type="int" value="22"/>
|
||||
</property>
|
||||
</property>
|
||||
</property>
|
||||
<property name="plugins" type="empty">
|
||||
<property name="plugin-1" type="string" value="applicationsmenu"/>
|
||||
<property name="plugin-2" type="string" value="tasklist">
|
||||
<property name="grouping" type="uint" value="1"/>
|
||||
</property>
|
||||
<property name="plugin-3" type="string" value="separator">
|
||||
<property name="expand" type="bool" value="true"/>
|
||||
<property name="style" type="uint" value="0"/>
|
||||
</property>
|
||||
<property name="plugin-4" type="string" value="pager"/>
|
||||
<property name="plugin-5" type="string" value="separator">
|
||||
<property name="style" type="uint" value="0"/>
|
||||
</property>
|
||||
<property name="plugin-6" type="string" value="systray">
|
||||
<property name="square-icons" type="bool" value="true"/>
|
||||
</property>
|
||||
<property name="plugin-8" type="string" value="pulseaudio">
|
||||
<property name="enable-keyboard-shortcuts" type="bool" value="true"/>
|
||||
<property name="show-notifications" type="bool" value="true"/>
|
||||
<property name="mpris-players" type="string" value="firefox.instance_1_35"/>
|
||||
</property>
|
||||
<property name="plugin-9" type="string" value="power-manager-plugin"/>
|
||||
<property name="plugin-10" type="string" value="notification-plugin"/>
|
||||
<property name="plugin-11" type="string" value="separator">
|
||||
<property name="style" type="uint" value="0"/>
|
||||
</property>
|
||||
<property name="plugin-12" type="string" value="clock"/>
|
||||
<property name="plugin-13" type="string" value="separator">
|
||||
<property name="style" type="uint" value="0"/>
|
||||
</property>
|
||||
<property name="plugin-14" type="string" value="actions"/>
|
||||
<property name="plugin-15" type="string" value="showdesktop"/>
|
||||
<property name="plugin-16" type="string" value="separator"/>
|
||||
<property name="plugin-17" type="string" value="launcher">
|
||||
<property name="items" type="array">
|
||||
<value type="string" value="17117663751.desktop"/>
|
||||
<value type="string" value="17089428881.desktop"/>
|
||||
</property>
|
||||
</property>
|
||||
<property name="plugin-18" type="string" value="launcher">
|
||||
<property name="items" type="array">
|
||||
<value type="string" value="17117663752.desktop"/>
|
||||
<value type="string" value="17089428882.desktop"/>
|
||||
</property>
|
||||
</property>
|
||||
<property name="plugin-19" type="string" value="launcher">
|
||||
<property name="items" type="array">
|
||||
<value type="string" value="17117663753.desktop"/>
|
||||
<value type="string" value="17089428883.desktop"/>
|
||||
</property>
|
||||
</property>
|
||||
<property name="plugin-20" type="string" value="launcher">
|
||||
<property name="items" type="array">
|
||||
<value type="string" value="17117663754.desktop"/>
|
||||
<value type="string" value="17089428884.desktop"/>
|
||||
</property>
|
||||
</property>
|
||||
<property name="plugin-21" type="string" value="separator"/>
|
||||
<property name="plugin-22" type="string" value="directorymenu">
|
||||
<property name="base-directory" type="string" value="/home/neko"/>
|
||||
</property>
|
||||
</property>
|
||||
</channel>
|
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<channel name="xfce4-settings-manager" version="1.0">
|
||||
<property name="last" type="empty">
|
||||
<property name="window-width" type="int" value="640"/>
|
||||
<property name="window-height" type="int" value="500"/>
|
||||
</property>
|
||||
</channel>
|
91
xfce-custom/dots/home/neko/.config/xfce4/xfconf/xfwm4.xml
Normal file
91
xfce-custom/dots/home/neko/.config/xfce4/xfconf/xfwm4.xml
Normal file
@ -0,0 +1,91 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<channel name="xfwm4" version="1.0">
|
||||
<property name="general" type="empty">
|
||||
<property name="activate_action" type="string" value="bring"/>
|
||||
<property name="borderless_maximize" type="bool" value="true"/>
|
||||
<property name="box_move" type="bool" value="false"/>
|
||||
<property name="box_resize" type="bool" value="false"/>
|
||||
<property name="button_layout" type="string" value="O|SHMC"/>
|
||||
<property name="button_offset" type="int" value="0"/>
|
||||
<property name="button_spacing" type="int" value="0"/>
|
||||
<property name="click_to_focus" type="bool" value="true"/>
|
||||
<property name="cycle_apps_only" type="bool" value="false"/>
|
||||
<property name="cycle_draw_frame" type="bool" value="true"/>
|
||||
<property name="cycle_raise" type="bool" value="false"/>
|
||||
<property name="cycle_hidden" type="bool" value="true"/>
|
||||
<property name="cycle_minimum" type="bool" value="true"/>
|
||||
<property name="cycle_minimized" type="bool" value="false"/>
|
||||
<property name="cycle_preview" type="bool" value="true"/>
|
||||
<property name="cycle_tabwin_mode" type="int" value="0"/>
|
||||
<property name="cycle_workspaces" type="bool" value="false"/>
|
||||
<property name="double_click_action" type="string" value="maximize"/>
|
||||
<property name="double_click_distance" type="int" value="5"/>
|
||||
<property name="double_click_time" type="int" value="250"/>
|
||||
<property name="easy_click" type="string" value="Alt"/>
|
||||
<property name="focus_delay" type="int" value="250"/>
|
||||
<property name="focus_hint" type="bool" value="true"/>
|
||||
<property name="focus_new" type="bool" value="true"/>
|
||||
<property name="frame_opacity" type="int" value="100"/>
|
||||
<property name="frame_border_top" type="int" value="0"/>
|
||||
<property name="full_width_title" type="bool" value="true"/>
|
||||
<property name="horiz_scroll_opacity" type="bool" value="false"/>
|
||||
<property name="inactive_opacity" type="int" value="100"/>
|
||||
<property name="maximized_offset" type="int" value="0"/>
|
||||
<property name="mousewheel_rollup" type="bool" value="true"/>
|
||||
<property name="move_opacity" type="int" value="100"/>
|
||||
<property name="placement_mode" type="string" value="center"/>
|
||||
<property name="placement_ratio" type="int" value="20"/>
|
||||
<property name="popup_opacity" type="int" value="100"/>
|
||||
<property name="prevent_focus_stealing" type="bool" value="false"/>
|
||||
<property name="raise_delay" type="int" value="250"/>
|
||||
<property name="raise_on_click" type="bool" value="true"/>
|
||||
<property name="raise_on_focus" type="bool" value="false"/>
|
||||
<property name="raise_with_any_button" type="bool" value="true"/>
|
||||
<property name="repeat_urgent_blink" type="bool" value="false"/>
|
||||
<property name="resize_opacity" type="int" value="100"/>
|
||||
<property name="scroll_workspaces" type="bool" value="true"/>
|
||||
<property name="shadow_delta_height" type="int" value="0"/>
|
||||
<property name="shadow_delta_width" type="int" value="0"/>
|
||||
<property name="shadow_delta_x" type="int" value="0"/>
|
||||
<property name="shadow_delta_y" type="int" value="-3"/>
|
||||
<property name="shadow_opacity" type="int" value="50"/>
|
||||
<property name="show_app_icon" type="bool" value="false"/>
|
||||
<property name="show_dock_shadow" type="bool" value="true"/>
|
||||
<property name="show_frame_shadow" type="bool" value="true"/>
|
||||
<property name="show_popup_shadow" type="bool" value="false"/>
|
||||
<property name="snap_resist" type="bool" value="false"/>
|
||||
<property name="snap_to_border" type="bool" value="true"/>
|
||||
<property name="snap_to_windows" type="bool" value="false"/>
|
||||
<property name="snap_width" type="int" value="10"/>
|
||||
<property name="vblank_mode" type="string" value="auto"/>
|
||||
<property name="theme" type="string" value="Default"/>
|
||||
<property name="tile_on_move" type="bool" value="true"/>
|
||||
<property name="title_alignment" type="string" value="center"/>
|
||||
<property name="title_font" type="string" value="Sans Bold 9"/>
|
||||
<property name="title_horizontal_offset" type="int" value="0"/>
|
||||
<property name="titleless_maximize" type="bool" value="false"/>
|
||||
<property name="title_shadow_active" type="string" value="false"/>
|
||||
<property name="title_shadow_inactive" type="string" value="false"/>
|
||||
<property name="title_vertical_offset_active" type="int" value="0"/>
|
||||
<property name="title_vertical_offset_inactive" type="int" value="0"/>
|
||||
<property name="toggle_workspaces" type="bool" value="false"/>
|
||||
<property name="unredirect_overlays" type="bool" value="true"/>
|
||||
<property name="urgent_blink" type="bool" value="false"/>
|
||||
<property name="use_compositing" type="bool" value="true"/>
|
||||
<property name="workspace_count" type="int" value="4"/>
|
||||
<property name="wrap_cycle" type="bool" value="true"/>
|
||||
<property name="wrap_layout" type="bool" value="true"/>
|
||||
<property name="wrap_resistance" type="int" value="10"/>
|
||||
<property name="wrap_windows" type="bool" value="true"/>
|
||||
<property name="wrap_workspaces" type="bool" value="false"/>
|
||||
<property name="zoom_desktop" type="bool" value="true"/>
|
||||
<property name="zoom_pointer" type="bool" value="true"/>
|
||||
<property name="workspace_names" type="array">
|
||||
<value type="string" value="Workspace 1"/>
|
||||
<value type="string" value="Workspace 2"/>
|
||||
<value type="string" value="Workspace 3"/>
|
||||
<value type="string" value="Workspace 4"/>
|
||||
</property>
|
||||
</property>
|
||||
</channel>
|
@ -0,0 +1,44 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<channel name="xsettings" version="1.0">
|
||||
<property name="Net" type="empty">
|
||||
<property name="ThemeName" type="string" value="Catppuccin-Mocha-Standard-Blue-Dark"/>
|
||||
<property name="IconThemeName" type="string" value="Adwaita"/>
|
||||
<property name="DoubleClickTime" type="empty"/>
|
||||
<property name="DoubleClickDistance" type="empty"/>
|
||||
<property name="DndDragThreshold" type="empty"/>
|
||||
<property name="CursorBlink" type="empty"/>
|
||||
<property name="CursorBlinkTime" type="empty"/>
|
||||
<property name="SoundThemeName" type="empty"/>
|
||||
<property name="EnableEventSounds" type="empty"/>
|
||||
<property name="EnableInputFeedbackSounds" type="empty"/>
|
||||
</property>
|
||||
<property name="Xft" type="empty">
|
||||
<property name="DPI" type="empty"/>
|
||||
<property name="Antialias" type="empty"/>
|
||||
<property name="Hinting" type="empty"/>
|
||||
<property name="HintStyle" type="empty"/>
|
||||
<property name="RGBA" type="empty"/>
|
||||
</property>
|
||||
<property name="Gtk" type="empty">
|
||||
<property name="CanChangeAccels" type="empty"/>
|
||||
<property name="ColorPalette" type="empty"/>
|
||||
<property name="FontName" type="empty"/>
|
||||
<property name="MonospaceFontName" type="empty"/>
|
||||
<property name="IconSizes" type="empty"/>
|
||||
<property name="KeyThemeName" type="empty"/>
|
||||
<property name="ToolbarStyle" type="empty"/>
|
||||
<property name="ToolbarIconSize" type="empty"/>
|
||||
<property name="MenuImages" type="empty"/>
|
||||
<property name="ButtonImages" type="empty"/>
|
||||
<property name="MenuBarAccel" type="empty"/>
|
||||
<property name="CursorThemeName" type="empty"/>
|
||||
<property name="CursorThemeSize" type="empty"/>
|
||||
<property name="DecorationLayout" type="empty"/>
|
||||
<property name="DialogsUseHeader" type="empty"/>
|
||||
<property name="TitlebarMiddleClick" type="empty"/>
|
||||
</property>
|
||||
<property name="Gdk" type="empty">
|
||||
<property name="WindowScalingFactor" type="empty"/>
|
||||
</property>
|
||||
</channel>
|
@ -0,0 +1,11 @@
|
||||
[Desktop Entry]
|
||||
NoDisplay=true
|
||||
Version=1.0
|
||||
Encoding=UTF-8
|
||||
Type=X-XFCE-Helper
|
||||
X-XFCE-Category=WebBrowser
|
||||
X-XFCE-CommandsWithParameter=librewolf "%s"
|
||||
Icon=librewolf
|
||||
Name=librewolf
|
||||
X-XFCE-Commands=librewolf
|
||||
|
@ -1 +1,3 @@
|
||||
hello from the other side :3 <3
|
||||
|
||||
meow - starlight
|
||||
|
Loading…
Reference in New Issue
Block a user