Commit: 506a9b76af090167f9dea4944c9db5d9b5cf0279
Parent: bf7ac34cc206397266b4b5c3d1d985a6b4dd2d37
Author: 0x766F6964
Date: Sun, 10 Nov 2019 11:45:46 -0700
update .kshrc; add ncmpcpp and mpv settings
Diffstat:
11 files changed, 3887 insertions(+), 16 deletions(-)
diff --git a/.config/mpv/image-input.conf b/.config/mpv/image-input.conf
@@ -0,0 +1,11 @@
+MOUSE_BTN3 add video-zoom 0.1
+MOUSE_BTN4 add video-zoom -0.1
+MOUSE_BTN7 playlist-prev
+MOUSE_BTN8 playlist-next
+
+Alt+RIGHT add video-rotate 90
+Alt+LEFT add video-rotate -90
+
+h cycle-values video-unscaled "yes" "no"
+
+l cycle-values loop "inf" "no"
diff --git a/.config/mpv/input.conf b/.config/mpv/input.conf
@@ -0,0 +1,11 @@
+MOUSE_BTN3 add volume 1
+MOUSE_BTN4 add volume -1
+MOUSE_BTN7 add chapter -1
+MOUSE_BTN8 add chapter 1
+
+Alt+RIGHT add video-rotate 90
+Alt+LEFT add video-rotate -90
+
+H cycle-values video-unscaled "yes" "no"
+
+L cycle-values loop "inf" "no"
diff --git a/.config/mpv/mpv.conf b/.config/mpv/mpv.conf
@@ -0,0 +1,71 @@
+# Video Output
+profile=opengl-hq
+hwdec=auto
+
+# Audio
+audio-device=alsa/mpv
+volume=70
+volume-max=200
+
+# Output channels (stereo)
+audio-channels=2
+af=format=channels=2
+
+# Default lang
+slang=jp,jpn,ja,eng,en,zh-Hant
+alang=jp,jpn,ja,eng,en
+
+# UI
+keep-open
+#ontop
+
+screenshot-format="png"
+screenshot-template="[%tY.%tm.%td_%tH:%tM:%tS]_%F_%P"
+screenshot-directory="~/media/pic/screengrabs/animu/"
+screenshot-png-compression=9
+
+# OSD rice (for file info)
+sub-pos=95
+osd-font="Kozuka Gothic Pro M"
+sub-font="Kozuka Gothic Pro M"
+osd-font-size=40
+sub-font-size=54
+osd-color="#eeeeee"
+sub-color="#eeeeee"
+osd-border-size=2
+sub-border-size=2.5
+osd-border-color="#262626"
+sub-border-color="#262626"
+msg-level=osd/libass=warn
+
+# Misc
+script-opts=osc-minmousemove=3
+hr-seek=yes
+#ytdl-format='(bestvideo+bestaudio)/best'
+ytdl-format='(bestvideo[height<=?1440]+bestaudio)/best'
+ytdl-raw-options='yes-playlist='
+
+[image]
+image-display-duration=inf
+loop-file=inf
+term-status-msg=
+scale=ewa_lanczossharp
+dscale=mitchell
+
+[extension.gif]
+loop-file=inf
+[extension.swf]
+loop-file=inf
+[extension.webm]
+loop-file=inf
+[extension.png]
+profile=image
+[extension.jpg]
+profile=image
+[extension.jpeg]
+profile=image
+
+[protocol.http]
+force-window=immediate
+[protocol.https]
+force-window=immediate
diff --git a/.config/mpv/scripts/autoload.lua b/.config/mpv/scripts/autoload.lua
@@ -0,0 +1,168 @@
+-- This script automatically loads playlist entries before and after the
+-- the currently played file. It does so by scanning the directory a file is
+-- located in when starting playback. It sorts the directory entries
+-- alphabetically, and adds entries before and after the current file to
+-- the internal playlist. (It stops if the it would add an already existing
+-- playlist entry at the same position - this makes it "stable".)
+-- Add at most 5000 * 2 files when starting a file (before + after).
+MAXENTRIES = 5000
+
+local options = require 'mp.options'
+
+o = {
+ disabled = false
+}
+options.read_options(o)
+
+function Set (t)
+ local set = {}
+ for _, v in pairs(t) do set[v] = true end
+ return set
+end
+
+EXTENSIONS = Set {
+ 'mkv', 'avi', 'mp4', 'ogv', 'webm', 'rmvb', 'flv', 'wmv', 'mpeg', 'mpg', 'm4v', '3gp',
+ 'mp3', 'wav', 'ogm', 'flac', 'm4a', 'wma', 'ogg', 'opus',
+}
+
+mputils = require 'mp.utils'
+
+function add_files_at(index, files)
+ index = index - 1
+ local oldcount = mp.get_property_number("playlist-count", 1)
+ for i = 1, #files do
+ mp.commandv("loadfile", files[i], "append")
+ mp.commandv("playlist-move", oldcount + i - 1, index + i - 1)
+ end
+end
+
+function get_extension(path)
+ match = string.match(path, "%.([^%.]+)$" )
+ if match == nil then
+ return "nomatch"
+ else
+ return match
+ end
+end
+
+table.filter = function(t, iter)
+ for i = #t, 1, -1 do
+ if not iter(t[i]) then
+ table.remove(t, i)
+ end
+ end
+end
+
+-- splitbynum and alnumcomp from alphanum.lua (C) Andre Bogus
+-- Released under the MIT License
+-- http://www.davekoelle.com/files/alphanum.lua
+
+-- split a string into a table of number and string values
+function splitbynum(s)
+ local result = {}
+ for x, y in (s or ""):gmatch("(%d*)(%D*)") do
+ if x ~= "" then table.insert(result, tonumber(x)) end
+ if y ~= "" then table.insert(result, y) end
+ end
+ return result
+end
+
+function clean_key(k)
+ k = (' '..k..' '):gsub("%s+", " "):sub(2, -2):lower()
+ return splitbynum(k)
+end
+
+-- compare two strings
+function alnumcomp(x, y)
+ local xt, yt = clean_key(x), clean_key(y)
+ for i = 1, math.min(#xt, #yt) do
+ local xe, ye = xt[i], yt[i]
+ if type(xe) == "string" then ye = tostring(ye)
+ elseif type(ye) == "string" then xe = tostring(xe) end
+ if xe ~= ye then return xe < ye end
+ end
+ return #xt < #yt
+end
+
+function find_and_add_entries()
+ local path = mp.get_property("path", "")
+ local dir, filename = mputils.split_path(path)
+ if o.disabled or #dir == 0 then
+ return
+ end
+ local pl_count = mp.get_property_number("playlist-count", 1)
+ if (pl_count > 1 and autoload == nil) or
+ (pl_count == 1 and EXTENSIONS[string.lower(get_extension(filename))] == nil) then
+ return
+ else
+ autoload = true
+ end
+
+ local files = mputils.readdir(dir, "files")
+ if files == nil then
+ return
+ end
+ table.filter(files, function (v, k)
+ if string.match(v, "^%.") then
+ return false
+ end
+ local ext = get_extension(v)
+ if ext == nil then
+ return false
+ end
+ return EXTENSIONS[string.lower(ext)]
+ end)
+ table.sort(files, alnumcomp)
+
+ if dir == "." then
+ dir = ""
+ end
+
+ local pl = mp.get_property_native("playlist", {})
+ local pl_current = mp.get_property_number("playlist-pos", 0) + 1
+ -- Find the current pl entry (dir+"/"+filename) in the sorted dir list
+ local current
+ for i = 1, #files do
+ if files[i] == filename then
+ current = i
+ break
+ end
+ end
+ if current == nil then
+ return
+ end
+
+ local append = {[-1] = {}, [1] = {}}
+ for direction = -1, 1, 2 do -- 2 iterations, with direction = -1 and +1
+ for i = 1, MAXENTRIES do
+ local file = files[current + i * direction]
+ local pl_e = pl[pl_current + i * direction]
+ if file == nil or file[1] == "." then
+ break
+ end
+
+ local filepath = dir .. file
+ if pl_e then
+ -- If there's a playlist entry, and it's the same file, stop.
+ if pl_e.filename == filepath then
+ break
+ end
+ end
+
+ if direction == -1 then
+ if pl_current == 1 then -- never add additional entries in the middle
+ mp.msg.info("Prepending " .. file)
+ table.insert(append[-1], 1, filepath)
+ end
+ else
+ mp.msg.info("Adding " .. file)
+ table.insert(append[1], filepath)
+ end
+ end
+ end
+
+ add_files_at(pl_current + 1, append[1])
+ add_files_at(pl_current, append[-1])
+end
+
+mp.register_event("start-file", find_and_add_entries)
diff --git a/.config/mpv/scripts/gallery-thumbgen.lua b/.config/mpv/scripts/gallery-thumbgen.lua
@@ -0,0 +1,289 @@
+local utils = require 'mp.utils'
+local msg = require 'mp.msg'
+
+local jobs_queue = {} -- queue of thumbnail jobs
+local failed = {} -- list of failed output paths, to avoid redoing them
+
+local opts = {
+ ytdl_exclude = "",
+}
+(require 'mp.options').read_options(opts, "gallery_worker")
+
+local ytdl = {
+ path = "youtube-dl",
+ searched = false,
+ blacklisted = {} -- Add patterns of URLs you want blacklisted from youtube-dl,
+ -- see gallery_worker.conf or ytdl_hook-exclude in the mpv manpage for more info
+}
+
+function append_table(lhs, rhs)
+ for i = 1,#rhs do
+ lhs[#lhs+1] = rhs[i]
+ end
+ return lhs
+end
+
+function file_exists(path)
+ local f = io.open(path, "r")
+ if f ~= nil then
+ io.close(f)
+ return true
+ else
+ return false
+ end
+end
+
+local video_extensions = { "mkv", "webm", "mp4", "avi", "wmv" }
+
+function is_video(input_path)
+ local extension = string.match(input_path, "%.([^.]+)$")
+ if extension then
+ extension = string.lower(extension)
+ for _, ext in ipairs(video_extensions) do
+ if extension == ext then
+ return true
+ end
+ end
+ end
+ return false
+end
+
+function is_blacklisted(url)
+ if opts.ytdl_exclude == "" then return false end
+ if #ytdl.blacklisted == 0 then
+ local joined = opts.ytdl_exclude
+ while joined:match('%|?[^|]+') do
+ local _, e, substring = joined:find('%|?([^|]+)')
+ table.insert(ytdl.blacklisted, substring)
+ joined = joined:sub(e+1)
+ end
+ end
+ if #ytdl.blacklisted > 0 then
+ url = url:match('https?://(.+)')
+ for _, exclude in ipairs(ytdl.blacklisted) do
+ if url:match(exclude) then
+ msg.verbose('URL matches excluded substring. Skipping.')
+ return true
+ end
+ end
+ end
+ return false
+end
+
+
+function ytdl_thumbnail_url(input_path)
+ local function exec(args)
+ local ret = utils.subprocess({args = args, cancellable=false})
+ return ret.status, ret.stdout, ret
+ end
+ local function first_non_nil(x, ...)
+ if x ~= nil then return x end
+ return first_non_nil(...)
+ end
+
+ -- if input_path is youtube, generate our own URL
+ youtube_id1 = string.match(input_path, "https?://youtu%.be/([%a%d%-_]+).*")
+ youtube_id2 = string.match(input_path, "https?://w?w?w?%.?youtube%.com/v/([%a%d%-_]+).*")
+ youtube_id3 = string.match(input_path, "https?://w?w?w?%.?youtube%.com/watch%?v=([%a%d%-_]+).*")
+ youtube_id4 = string.match(input_path, "https?://w?w?w?%.?youtube%.com/embed/([%a%d%-_]+).*")
+ youtube_id = youtube_id1 or youtube_id2 or youtube_id3 or youtube_id4
+
+ if youtube_id then
+ -- the hqdefault.jpg thumbnail should always exist, since it's used on the search result page
+ return "https://i.ytimg.com/vi/" .. youtube_id .. "/hqdefault.jpg"
+ end
+
+ --otherwise proceed with the slower `youtube-dl -J` method
+ if not (ytdl.searched) then --search for youtude-dl in mpv's config directory
+ local exesuf = (package.config:sub(1,1) == '\\') and '.exe' or ''
+ local ytdl_mcd = mp.find_config_file("youtube-dl")
+ if not (ytdl_mcd == nil) then
+ msg.error("found youtube-dl at: " .. ytdl_mcd)
+ ytdl.path = ytdl_mcd
+ end
+ ytdl.searched = true
+ end
+ local command = {ytdl.path, "--no-warnings", "--no-playlist", "-J", input_path}
+ local es, json, result = exec(command)
+
+ if (es < 0) or (json == nil) or (json == "") then
+ msg.error("fetching thumbnail url with youtube-dl failed for" .. input_path)
+ return input_path
+ end
+ local json, err = utils.parse_json(json)
+ if (json == nil) then
+ msg.error("failed to parse json for youtube-dl thumbnail: " .. err)
+ return input_path
+ end
+
+ if (json.thumbnail == nil) or (json.thumbnail == "") then
+ msg.error("no thumbnail url from youtube-dl.")
+ return input_path
+ end
+ return json.thumbnail
+end
+
+function thumbnail_command(input_path, width, height, take_thumbnail_at, output_path, with_mpv)
+ local vf = string.format("%s,%s",
+ string.format("scale=iw*min(1\\,min(%d/iw\\,%d/ih)):-2", width, height),
+ string.format("pad=%d:%d:(%d-iw)/2:(%d-ih)/2:color=0x00000000", width, height, width, height)
+ )
+ local out = {}
+ local add = function(table) out = append_table(out, table) end
+
+
+ if input_path:find("https?://") == 1 and not is_blacklisted(input_path) then
+ -- returns the original input_path on failure
+ input_path = ytdl_thumbnail_url(input_path)
+ end
+
+
+ if not with_mpv then
+ out = { "ffmpeg" }
+ if is_video(input_path) then
+ if string.sub(take_thumbnail_at, -1) == "%" then
+ --if only fucking ffmpeg supported percent-style seeking
+ local res = utils.subprocess({ args = {
+ "ffprobe", "-v", "error",
+ "-show_entries", "format=duration", "-of",
+ "default=noprint_wrappers=1:nokey=1", input_path
+ }, cancellable = false })
+ if res.status == 0 then
+ local duration = tonumber(string.match(res.stdout, "^%s*(.-)%s*$"))
+ if duration then
+ local percent = tonumber(string.sub(take_thumbnail_at, 1, -2))
+ local start = tostring(duration * percent / 100)
+ add({ "-ss", start, "-noaccurate_seek" })
+ end
+ end
+ else
+ add({ "-ss", tonumber(take_thumbnail_at), "-noaccurate_seek" })
+ end
+ end
+ add({
+ "-i", input_path,
+ "-vf", vf,
+ "-map", "v:0",
+ "-f", "rawvideo",
+ "-pix_fmt", "bgra",
+ "-c:v", "rawvideo",
+ "-frames:v", "1",
+ "-y", "-loglevel", "quiet",
+ output_path
+ })
+ else
+ out = { "mpv", input_path }
+ if take_thumbnail_at ~= "0" and is_video(input_path) then
+ add({ "--hr-seek=no", "--start", take_thumbnail_at })
+ end
+ add({
+ "--no-config", "--msg-level=all=no",
+ "--vf", "lavfi=[" .. vf .. ",format=bgra]",
+ "--audio", "no",
+ "--sub", "no",
+ "--frames", "1",
+ "--image-display-duration", "0",
+ "--of", "rawvideo", "--ovc", "rawvideo",
+ "--o", output_path
+ })
+ end
+ return out
+end
+
+function generate_thumbnail(thumbnail_job)
+ if file_exists(thumbnail_job.output_path) then return true end
+
+ local dir, _ = utils.split_path(thumbnail_job.output_path)
+ local tmp_output_path = utils.join_path(dir, mp.get_script_name())
+
+ local command = thumbnail_command(
+ thumbnail_job.input_path,
+ thumbnail_job.width,
+ thumbnail_job.height,
+ thumbnail_job.take_thumbnail_at,
+ tmp_output_path,
+ thumbnail_job.with_mpv
+ )
+
+ local res = utils.subprocess({ args = command, cancellable = false })
+ --"atomically" generate the output to avoid loading half-generated thumbnails (results in crashes)
+ if res.status == 0 then
+ local info = utils.file_info(tmp_output_path)
+ if not info or info.size == 0 then
+ return false
+ end
+ if os.rename(tmp_output_path, thumbnail_job.output_path) then
+ return true
+ end
+ end
+ return false
+end
+
+function handle_events(wait)
+ e = mp.wait_event(wait)
+ while e.event ~= "none" do
+ if e.event == "shutdown" then
+ return false
+ elseif e.event == "client-message" then
+ if e.args[1] == "push-thumbnail-front" or e.args[1] == "push-thumbnail-back" then
+ local thumbnail_job = {
+ requester = e.args[2],
+ input_path = e.args[3],
+ width = tonumber(e.args[4]),
+ height = tonumber(e.args[5]),
+ take_thumbnail_at = e.args[6],
+ output_path = e.args[7],
+ with_mpv = (e.args[8] == "true"),
+ }
+ if e.args[1] == "push-thumbnail-front" then
+ jobs_queue[#jobs_queue + 1] = thumbnail_job
+ else
+ table.insert(jobs_queue, 1, thumbnail_job)
+ end
+ end
+ end
+ e = mp.wait_event(0)
+ end
+ return true
+end
+
+local registration_timeout = 2 -- seconds
+local registration_period = 0.2
+
+-- shitty custom event loop because I can't figure out a better way
+-- works pretty well though
+function mp_event_loop()
+ local start_time = mp.get_time()
+ local sleep_time = registration_period
+ local last_broadcast_time = -registration_period
+ local broadcast_func
+ broadcast_func = function()
+ local now = mp.get_time()
+ if now >= start_time + registration_timeout then
+ mp.commandv("script-message", "thumbnails-generator-broadcast", mp.get_script_name())
+ sleep_time = 1e20
+ broadcast_func = function() end
+ elseif now >= last_broadcast_time + registration_period then
+ mp.commandv("script-message", "thumbnails-generator-broadcast", mp.get_script_name())
+ last_broadcast_time = now
+ end
+ end
+
+ while true do
+ if not handle_events(sleep_time) then return end
+ broadcast_func()
+ while #jobs_queue > 0 do
+ local thumbnail_job = jobs_queue[#jobs_queue]
+ if not failed[thumbnail_job.output_path] then
+ if generate_thumbnail(thumbnail_job) then
+ mp.commandv("script-message-to", thumbnail_job.requester, "thumbnail-generated", thumbnail_job.output_path)
+ else
+ failed[thumbnail_job.output_path] = true
+ end
+ end
+ jobs_queue[#jobs_queue] = nil
+ if not handle_events(0) then return end
+ broadcast_func()
+ end
+ end
+end
diff --git a/.config/mpv/scripts/gallery.lua b/.config/mpv/scripts/gallery.lua
@@ -0,0 +1,840 @@
+local utils = require 'mp.utils'
+local msg = require 'mp.msg'
+local assdraw = require 'mp.assdraw'
+
+local on_windows = (package.config:sub(1,1) ~= "/")
+
+local opts = {
+ thumbs_dir = "/tmp/thumb",
+ auto_generate_thumbnails = true,
+ generate_thumbnails_with_mpv = true,
+
+ thumbnail_width = 192,
+ thumbnail_height = 108,
+ dynamic_thumbnail_size = "",
+
+ take_thumbnail_at = "20%",
+
+ resume_when_picking = true,
+ start_gallery_on_startup = false,
+ start_gallery_on_file_end = false,
+ toggle_behaves_as_accept = true,
+
+ margin_x = 15,
+ margin_y = 15,
+ max_thumbnails = 64,
+
+ show_scrollbar = true,
+ scrollbar_side = "left",
+ scrollbar_min_size = 10,
+
+ show_placeholders = true,
+ placeholder_color = "222222",
+ always_show_placeholders = false,
+ background = "0.1",
+
+ show_filename = true,
+ show_title = true,
+ strip_directory = true,
+ strip_extension = true,
+ text_size = 28,
+
+ selected_frame_color = "DDDDDD",
+ frame_roundness = 5,
+ flagged_frame_color = "5B9769",
+ selected_flagged_frame_color = "BAFFCA",
+ flagged_file_path = "/tmp/mpv.list",
+
+ max_generators = 8,
+
+ mouse_support = true,
+ UP = "UP",
+ DOWN = "DOWN",
+ LEFT = "LEFT",
+ RIGHT = "RIGHT",
+ PAGE_UP = "PGUP",
+ PAGE_DOWN = "PGDWN",
+ FIRST = "HOME",
+ LAST = "END",
+ RANDOM = "r",
+ ACCEPT = "ENTER",
+ CANCEL = "ESC",
+ REMOVE = "DEL",
+ FLAG = "m",
+}
+(require 'mp.options').read_options(opts)
+
+function split(input, char, tonum)
+ local ret = {}
+ for str in string.gmatch(input, "([^" .. char .. "]+)") do
+ ret[#ret + 1] = (not tonum and str) or tonumber(str)
+ end
+ return ret
+end
+opts.dynamic_thumbnail_size = split(opts.dynamic_thumbnail_size, ";", false)
+for i = 1, #opts.dynamic_thumbnail_size do
+ local preset = split(opts.dynamic_thumbnail_size[i], ",", true)
+ if (#preset ~= 3) or not (preset[1] and preset[2] and preset[3]) then
+ msg.error(opts.dynamic_thumbnail_size[i] .. " is not a valid preset")
+ return
+ end
+ opts.dynamic_thumbnail_size[i] = preset
+end
+
+if on_windows then
+ opts.thumbs_dir = string.gsub(opts.thumbs_dir, "^%%APPDATA%%", os.getenv("APPDATA") or "%APPDATA%")
+else
+ opts.thumbs_dir = string.gsub(opts.thumbs_dir, "^~", os.getenv("HOME") or "~")
+end
+opts.max_thumbnails = math.min(opts.max_thumbnails, 64)
+
+local sha256
+--[[
+minified code below is a combination of:
+-sha256 implementation from
+http://lua-users.org/wiki/SecureHashAlgorithm
+-lua implementation of bit32 (used as fallback on lua5.1) from
+https://www.snpedia.com/extensions/Scribunto/engines/LuaCommon/lualib/bit32.lua
+both are licensed under the MIT below:
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+--]]
+do local b,c,d,e,f;if bit32 then b,c,d,e,f=bit32.band,bit32.rrotate,bit32.bxor,bit32.rshift,bit32.bnot else f=function(g)g=math.floor(tonumber(g))%0x100000000;return(-g-1)%0x100000000 end;local h={[0]={[0]=0,0,0,0},[1]={[0]=0,1,0,1},[2]={[0]=0,0,2,2},[3]={[0]=0,1,2,3}}local i={[0]={[0]=0,1,2,3},[1]={[0]=1,0,3,2},[2]={[0]=2,3,0,1},[3]={[0]=3,2,1,0}}local function j(k,l,m,n,o)for p=1,m do l[p]=math.floor(tonumber(l[p]))%0x100000000 end;local q=1;local r=0;for s=0,31,2 do local t=n;for p=1,m do t=o[t][l[p]%4]l[p]=math.floor(l[p]/4)end;r=r+t*q;q=q*4 end;return r end;b=function(...)return j('band',{...},select('#',...),3,h)end;d=function(...)return j('bxor',{...},select('#',...),0,i)end;e=function(g,u)g=math.floor(tonumber(g))%0x100000000;u=math.floor(tonumber(u))u=math.min(math.max(-32,u),32)return math.floor(g/2^u)%0x100000000 end;c=function(g,u)g=math.floor(tonumber(g))%0x100000000;u=-math.floor(tonumber(u))%32;local g=g*2^u;return g%0x100000000+math.floor(g/0x100000000)end end;local v={0x428a2f98,0x71374491,0xb5c0fbcf,0xe9b5dba5,0x3956c25b,0x59f111f1,0x923f82a4,0xab1c5ed5,0xd807aa98,0x12835b01,0x243185be,0x550c7dc3,0x72be5d74,0x80deb1fe,0x9bdc06a7,0xc19bf174,0xe49b69c1,0xefbe4786,0x0fc19dc6,0x240ca1cc,0x2de92c6f,0x4a7484aa,0x5cb0a9dc,0x76f988da,0x983e5152,0xa831c66d,0xb00327c8,0xbf597fc7,0xc6e00bf3,0xd5a79147,0x06ca6351,0x14292967,0x27b70a85,0x2e1b2138,0x4d2c6dfc,0x53380d13,0x650a7354,0x766a0abb,0x81c2c92e,0x92722c85,0xa2bfe8a1,0xa81a664b,0xc24b8b70,0xc76c51a3,0xd192e819,0xd6990624,0xf40e3585,0x106aa070,0x19a4c116,0x1e376c08,0x2748774c,0x34b0bcb5,0x391c0cb3,0x4ed8aa4a,0x5b9cca4f,0x682e6ff3,0x748f82ee,0x78a5636f,0x84c87814,0x8cc70208,0x90befffa,0xa4506ceb,0xbef9a3f7,0xc67178f2}local function w(n)return string.gsub(n,".",function(t)return string.format("%02x",string.byte(t))end)end;local function x(y,z)local n=""for p=1,z do local A=y%256;n=string.char(A)..n;y=(y-A)/256 end;return n end;local function B(n,p)local z=0;for p=p,p+3 do z=z*256+string.byte(n,p)end;return z end;local function C(D,E)local F=-(E+1+8)%64;E=x(8*E,8)D=D.."\128"..string.rep("\0",F)..E;return D end;local function G(H)H[1]=0x6a09e667;H[2]=0xbb67ae85;H[3]=0x3c6ef372;H[4]=0xa54ff53a;H[5]=0x510e527f;H[6]=0x9b05688c;H[7]=0x1f83d9ab;H[8]=0x5be0cd19;return H end;local function I(D,p,H)local J={}for K=1,16 do J[K]=B(D,p+(K-1)*4)end;for K=17,64 do local L=J[K-15]local M=d(c(L,7),c(L,18),e(L,3))L=J[K-2]local N=d(c(L,17),c(L,19),e(L,10))J[K]=J[K-16]+M+J[K-7]+N end;local O,s,t,P,Q,R,S,T=H[1],H[2],H[3],H[4],H[5],H[6],H[7],H[8]for p=1,64 do local M=d(c(O,2),c(O,13),c(O,22))local U=d(b(O,s),b(O,t),b(s,t))local V=M+U;local N=d(c(Q,6),c(Q,11),c(Q,25))local W=d(b(Q,R),b(f(Q),S))local X=T+N+W+v[p]+J[p]T=S;S=R;R=Q;Q=P+X;P=t;t=s;s=O;O=X+V end;H[1]=b(H[1]+O)H[2]=b(H[2]+s)H[3]=b(H[3]+t)H[4]=b(H[4]+P)H[5]=b(H[5]+Q)H[6]=b(H[6]+R)H[7]=b(H[7]+S)H[8]=b(H[8]+T)end;local function Y(H)return w(x(H[1],4)..x(H[2],4)..x(H[3],4)..x(H[4],4)..x(H[5],4)..x(H[6],4)..x(H[7],4)..x(H[8],4))end;local Z={}sha256=function(D)D=C(D,#D)local H=G(Z)for p=1,#D,64 do I(D,p,H)end;return Y(H)end end
+-- end of sha code
+
+active = false
+playlist = {} -- copy of the current "playlist" property
+geometry = {
+ window_w = 0,
+ window_h = 0,
+ rows = 0,
+ columns = 0,
+ size_x = 0,
+ size_y = 0,
+ margin_x = 0,
+ margin_y = 0,
+}
+view = { -- 1-based indices into the "playlist" array
+ first = 0, -- must be equal to N*columns
+ last = 0, -- must be > first and <= first + rows*columns
+}
+hash_cache = {}
+overlays = {
+ active = {}, -- array of 64 strings indicating the file associated to the current thumbnail (empty if no file)
+ missing = {}, -- maps hashes of missing thumbnails to the index they should be shown at
+}
+selection = {
+ old = 0, -- the playlist element selected when entering the gallery
+ now = 0, -- the currently selected element
+}
+pending = {
+ selection = -1,
+ window_size_changed = false,
+ deletion = false,
+}
+ass = {
+ selection = "",
+ scrollbar = "",
+ placeholders = "",
+}
+flags = {}
+resume = {} -- maps filenames to a {time=,vid=,aid=,sid=} tuple
+misc = {
+ old_force_window = "",
+ old_geometry = "",
+ old_osd_level = "",
+ old_background = "",
+ old_idle = "",
+}
+generators = {} -- list of generator scripts that have registered themselves
+
+do
+ local inited = false
+ function init()
+ if not inited then
+ inited = true
+ if utils.file_info then -- 0.28+
+ local res = utils.file_info(opts.thumbs_dir)
+ if not res or not res.is_dir then
+ msg.error(string.format("Thumbnail directory \"%s\" does not exist", opts.thumbs_dir))
+ os.execute("mkdir " .. opts.thumbs_dir)
+ end
+ end
+ end
+ end
+end
+
+function file_exists(path)
+ if utils.file_info then -- 0.28+
+ local info = utils.file_info(path)
+ return info ~= nil and info.is_file
+ else
+ local f = io.open(path, "r")
+ if f ~= nil then
+ io.close(f)
+ return true
+ end
+ return false
+ end
+end
+
+function thumbnail_size_from_presets(window_w, window_h)
+ local size = window_w * window_h
+ local picked = nil
+ for _, preset in ipairs(opts.dynamic_thumbnail_size) do
+ picked = { preset[2], preset[3] }
+ if size <= preset[1] then
+ break
+ end
+ end
+ return picked
+end
+
+function select_under_cursor()
+ local g = geometry
+ local mx, my = mp.get_mouse_pos()
+ if mx < 0 or my < 0 or mx > g.window_w or my > g.window_h then return end
+ local mx, my = mx - g.margin_x, my - g.margin_y
+ local on_column = (mx % (g.size_x + g.margin_x)) < g.size_x
+ local on_row = (my % (g.size_y + g.margin_y)) < g.size_y
+ if on_column and on_row then
+ local column = math.floor(mx / (g.size_x + g.margin_x))
+ local row = math.floor(my / (g.size_y + g.margin_y))
+ local new_sel = view.first + row * g.columns + column
+ if new_sel > view.last then return end
+ if selection.now == new_sel then
+ quit_gallery_view(selection.now)
+ else
+ selection.now = new_sel
+ pending.selection = new_sel
+ ass_show(true, false, false)
+ end
+ end
+end
+
+function toggle_selection_flag()
+ local name = playlist[selection.now].filename
+ if flags[name] == nil then
+ flags[name] = true
+ else
+ flags[name] = nil
+ end
+ ass_show(true, false, false)
+end
+
+do
+ local function increment_func(increment, clamp)
+ local new = pending.selection == -1 and selection.now or pending.selection
+ new = new + increment
+ if new <= 0 or new > #playlist then
+ if not clamp then return end
+ new = math.max(1, math.min(new, #playlist))
+ end
+ pending.selection = new
+ end
+
+ local bindings_repeat = {}
+ bindings_repeat[opts.UP] = function() increment_func(- geometry.columns, false) end
+ bindings_repeat[opts.DOWN] = function() increment_func( geometry.columns, false) end
+ bindings_repeat[opts.LEFT] = function() increment_func(- 1, false) end
+ bindings_repeat[opts.RIGHT] = function() increment_func( 1, false) end
+ bindings_repeat[opts.PAGE_UP] = function() increment_func(- geometry.columns * geometry.rows, true) end
+ bindings_repeat[opts.PAGE_DOWN] = function() increment_func( geometry.columns * geometry.rows, true) end
+ bindings_repeat[opts.RANDOM] = function() pending.selection = math.random(1, #playlist) end
+ bindings_repeat[opts.REMOVE] = function() pending.deletion = true end
+
+ local bindings = {}
+ bindings[opts.FIRST] = function() pending.selection = 1 end
+ bindings[opts.LAST] = function() pending.selection = #playlist end
+ bindings[opts.ACCEPT] = function() quit_gallery_view(selection.now) end
+ bindings[opts.CANCEL] = function() quit_gallery_view(selection.old) end
+ bindings[opts.FLAG] = toggle_selection_flag
+ if opts.mouse_support then
+ bindings["MBTN_LEFT"] = select_under_cursor
+ bindings["WHEEL_UP"] = function() increment_func(- geometry.columns, true) end
+ bindings["WHEEL_DOWN"] = function() increment_func( geometry.columns, true) end
+ end
+
+ local function window_size_changed()
+ pending.window_size_changed = true
+ end
+
+ local function idle_handler()
+ if pending.selection ~= -1 then
+ selection.now = pending.selection
+ pending.selection = -1
+ ensure_view_valid()
+ refresh_overlays(false)
+ ass_show(true, true, true)
+ end
+ if pending.window_size_changed then
+ pending.window_size_changed = false
+ local window_w, window_h = mp.get_osd_size()
+ if window_w ~= geometry.window_w or window_h ~= geometry.window_h then
+ compute_geometry(window_w, window_h)
+ if geometry.rows <= 0 or geometry.columns <= 0 then
+ quit_gallery_view(selection.old)
+ return
+ end
+ ensure_view_valid()
+ refresh_overlays(true)
+ ass_show(true, true, true)
+ end
+ end
+ if pending.deletion then
+ pending.deletion = false
+ mp.commandv("playlist-remove", selection.now - 1)
+ selection.now = selection.now + (selection.now == #playlist and -1 or 1)
+ end
+ end
+
+ function setup_ui_handlers()
+ for key, func in pairs(bindings_repeat) do
+ mp.add_forced_key_binding(key, "gallery-view-"..key, func, {repeatable = true})
+ end
+ for key, func in pairs(bindings) do
+ mp.add_forced_key_binding(key, "gallery-view-"..key, func)
+ end
+ for _, prop in ipairs({ "osd-width", "osd-height" }) do
+ mp.observe_property(prop, "native", window_size_changed)
+ end
+ mp.register_idle(idle_handler)
+ end
+
+ function teardown_ui_handlers()
+ for key, _ in pairs(bindings_repeat) do
+ mp.remove_key_binding("gallery-view-"..key)
+ end
+ for key, _ in pairs(bindings) do
+ mp.remove_key_binding("gallery-view-"..key)
+ end
+ mp.unobserve_property(window_size_changed)
+ mp.unregister_idle(idle_handler)
+ end
+end
+
+function resume_playback(select)
+ -- what a mess
+ local s = resume[playlist[select].filename]
+ local pos = mp.get_property_number("playlist-pos-1")
+ if pos == select then
+ if s and opts.resume_when_picking then
+ mp.commandv("seek", s.time, "absolute")
+ end
+ mp.set_property("vid", s and s.vid or "1")
+ mp.set_property("aid", s and s.aid or "1")
+ mp.set_property("sid", s and s.sid or "1")
+ mp.set_property_bool("pause", false)
+ else
+ if s then
+ local func
+ func = function()
+ local change_maybe = function(prop, val)
+ if val ~= mp.get_property(prop) then
+ mp.set_property(prop,val)
+ end
+ end
+ change_maybe("vid", s.vid)
+ change_maybe("aid", s.aid)
+ change_maybe("sid", s.sid)
+ if opts.resume_when_picking then
+ mp.commandv("seek", s.time, "absolute")
+ end
+ mp.unregister_event(func)
+ end
+ mp.register_event("file-loaded", func)
+ end
+ mp.set_property("playlist-pos-1", select)
+ mp.set_property("vid", "1")
+ mp.set_property("aid", "1")
+ mp.set_property("sid", "1")
+ mp.set_property_bool("pause", false)
+ end
+end
+
+function restore_properties()
+ mp.set_property("force-window", misc.old_force_window)
+ mp.set_property("track-auto-selection", misc.old_track_auto_selection)
+ mp.set_property("geometry", misc.old_geometry)
+ mp.set_property("osd-level", misc.old_osd_level)
+ mp.set_property("background", misc.old_background)
+ mp.set_property("idle", misc.old_idle)
+ mp.commandv("script-message", "osc-visibility", "auto", "true")
+end
+
+function save_properties()
+ misc.old_force_window = mp.get_property("force-window")
+ misc.old_track_auto_selection = mp.get_property("track-auto-selection")
+ misc.old_geometry = mp.get_property("geometry")
+ misc.old_osd_level = mp.get_property("osd-level")
+ misc.old_background = mp.get_property("background")
+ misc.old_idle = mp.get_property("idle")
+ mp.set_property_bool("force-window", true)
+ mp.set_property_bool("track-auto-selection", false)
+ mp.set_property_number("osd-level", 0)
+ mp.set_property("background", opts.background)
+ mp.set_property_bool("idle", true)
+ mp.commandv("no-osd", "script-message", "osc-visibility", "never", "true")
+ mp.set_property("geometry", geometry.window_w .. "x" .. geometry.window_h)
+end
+
+function compute_geometry(ww, wh)
+ geometry.window_w, geometry.window_h = ww, wh
+
+ local dyn_thumb_size = thumbnail_size_from_presets(ww, wh)
+ if dyn_thumb_size then
+ geometry.size_x = dyn_thumb_size[1]
+ geometry.size_y = dyn_thumb_size[2]
+ else
+ geometry.size_x = opts.thumbnail_width
+ geometry.size_y = opts.thumbnail_height
+ end
+
+ local margin_y = opts.show_filename and math.max(opts.text_size, opts.margin_y) or opts.margin_y
+ geometry.rows = math.floor((wh - margin_y) / (geometry.size_y + margin_y))
+ geometry.columns = math.floor((ww - opts.margin_x) / (geometry.size_x + opts.margin_x))
+ if (geometry.rows * geometry.columns > opts.max_thumbnails) then
+ local r = math.sqrt(geometry.rows * geometry.columns / opts.max_thumbnails)
+ geometry.rows = math.floor(geometry.rows / r)
+ geometry.columns = math.floor(geometry.columns / r)
+ end
+ geometry.margin_x = (ww - geometry.columns * geometry.size_x) / (geometry.columns + 1)
+ geometry.margin_y = (wh - geometry.rows * geometry.size_y) / (geometry.rows + 1)
+end
+
+-- makes sure that view.first and view.last are valid with regards to the playlist
+-- and that selection.now is within the view
+-- to be called after the playlist, view or selection was modified somehow
+function ensure_view_valid()
+ local selection_row = math.floor((selection.now - 1) / geometry.columns)
+ local max_thumbs = geometry.rows * geometry.columns
+
+ if view.last >= #playlist then
+ view.last = #playlist
+ last_row = math.floor((view.last - 1) / geometry.columns)
+ first_row = math.max(0, last_row - geometry.rows + 1)
+ view.first = 1 + first_row * geometry.columns
+ elseif view.first == 0 or view.last == 0 or view.last - view.first + 1 ~= max_thumbs then
+ -- special case: the number of possible thumbnails was changed
+ -- just recreate the view such that the selection is in the middle row
+ local max_row = (#playlist - 1) / geometry.columns + 1
+ local row_first = selection_row - math.floor((geometry.rows - 1) / 2)
+ local row_last = selection_row + math.floor((geometry.rows - 1) / 2) + geometry.rows % 2
+ if row_first < 0 then
+ row_first = 0
+ elseif row_last > max_row then
+ row_first = max_row - geometry.rows + 1
+ end
+ view.first = 1 + row_first * geometry.columns
+ view.last = math.min(#playlist, view.first - 1 + max_thumbs)
+ return
+ end
+
+ if selection.now < view.first then
+ -- the selection is now on the first line
+ view.first = selection_row * geometry.columns + 1
+ view.last = math.min(#playlist, view.first + max_thumbs - 1)
+ elseif selection.now > view.last then
+ -- the selection is now on the last line
+ view.last = (selection_row + 1) * geometry.columns
+ view.first = math.max(1, view.last - max_thumbs + 1)
+ view.last = math.min(#playlist, view.last)
+ end
+end
+
+-- ass related stuff
+do
+ local function refresh_placeholders()
+ if not opts.show_placeholders then return end
+ local a = assdraw.ass_new()
+ a:new_event()
+ a:append('{\\bord0}')
+ a:append('{\\shad0}')
+ a:append('{\\1c&' .. opts.placeholder_color .. '}')
+ a:pos(0, 0)
+ a:draw_start()
+ for i = 0, view.last - view.first do
+ if opts.always_show_placeholders or overlays.active[i + 1] == "" then
+ local x = geometry.margin_x + (geometry.margin_x + geometry.size_x) * (i % geometry.columns)
+ local y = geometry.margin_y + (geometry.margin_y + geometry.size_y) * math.floor(i / geometry.columns)
+ a:rect_cw(x, y, x + geometry.size_x, y + geometry.size_y)
+ end
+ end
+ a:draw_stop()
+ ass.placeholders = a.text
+ end
+
+ local function refresh_scrollbar()
+ if not opts.show_scrollbar then return end
+ ass.scrollbar = ""
+ local before = (view.first - 1) / #playlist
+ local after = (#playlist - view.last) / #playlist
+ -- don't show the scrollbar if everything is visible
+ if before + after == 0 then return end
+ local p = opts.scrollbar_min_size / 100
+ if before + after > 1 - p then
+ if before == 0 then
+ after = (1 - p)
+ elseif after == 0 then
+ before = (1 - p)
+ else
+ before, after =
+ before / after * (1 - p) / (1 + before / after),
+ after / before * (1 - p) / (1 + after / before)
+ end
+ end
+ local y1 = geometry.margin_y + before * (geometry.window_h - 2 * geometry.margin_y)
+ local y2 = geometry.window_h - (geometry.margin_y + after * (geometry.window_h - 2 * geometry.margin_y))
+ local x1, x2
+ if opts.scrollbar_side == "left" then
+ x1, x2 = 4, 8
+ else
+ x1, x2 = geometry.window_w - 8, geometry.window_w - 4
+ end
+ local scrollbar = assdraw.ass_new()
+ scrollbar:new_event()
+ scrollbar:append('{\\bord0}')
+ scrollbar:append('{\\shad0}')
+ scrollbar:append('{\\1c&AAAAAA&}')
+ scrollbar:pos(0, 0)
+ scrollbar:draw_start()
+ scrollbar:round_rect_cw(x1, y1, x2, y2, opts.frame_roundness)
+ scrollbar:draw_stop()
+ ass.scrollbar = scrollbar.text
+ end
+
+ local function refresh_selection()
+ local selection_ass = assdraw.ass_new()
+ local draw_frame = function(index, color)
+ if index < view.first or index > view.last then return end
+ local i = index - view.first
+ local x = geometry.margin_x + (geometry.margin_x + geometry.size_x) * (i % geometry.columns)
+ local y = geometry.margin_y + (geometry.margin_y + geometry.size_y) * math.floor(i / geometry.columns)
+ selection_ass:new_event()
+ selection_ass:append('{\\bord5}')
+ selection_ass:append('{\\3c&'.. color ..'&}')
+ selection_ass:append('{\\1a&FF&}')
+ selection_ass:pos(0, 0)
+ selection_ass:draw_start()
+ selection_ass:round_rect_cw(x, y, x + geometry.size_x, y + geometry.size_y, opts.frame_roundness)
+ selection_ass:draw_stop()
+ end
+ for i = view.first, view.last do
+ local name = playlist[i].filename
+ if flags[name] then
+ if i == selection.now then
+ draw_frame(i, opts.selected_flagged_frame_color)
+ else
+ draw_frame(i, opts.flagged_frame_color)
+ end
+ elseif i == selection.now then
+ draw_frame(i, opts.selected_frame_color)
+ end
+ end
+
+ if opts.show_filename or opts.show_title then
+ selection_ass:new_event()
+ local i = (selection.now - view.first)
+ local an = 5
+ local x = geometry.margin_x + (geometry.margin_x + geometry.size_x) * (i % geometry.columns) + geometry.size_x / 2
+ local y = geometry.margin_y + (geometry.margin_y + geometry.size_y) * math.floor(i / geometry.columns) + geometry.size_y + geometry.margin_y / 2
+ local col = i % geometry.columns
+ if geometry.columns > 1 then
+ if col == 0 then
+ x = x - geometry.size_x / 2
+ an = 4
+ elseif col == geometry.columns - 1 then
+ x = x + geometry.size_x / 2
+ an = 6
+ end
+ end
+ selection_ass:an(an)
+ selection_ass:pos(x, y)
+ selection_ass:append(string.format("{\\fs%d}", opts.text_size))
+ selection_ass:append("{\\bord0}")
+ local f
+ local element = playlist[selection.now]
+ if opts.show_title and element.title then
+ f = element.title
+ else
+ f = element.filename
+ if opts.strip_directory then
+ if on_windows then
+ f = string.match(f, "([^\\/]+)$") or f
+ else
+ f = string.match(f, "([^/]+)$") or f
+ end
+ end
+ if opts.strip_extension then
+ f = string.match(f, "(.+)%.[^.]+$") or f
+ end
+ end
+ selection_ass:append(f)
+ end
+ ass.selection = selection_ass.text
+ end
+
+ function ass_show(selection, scrollbar, placeholders)
+ if selection then refresh_selection() end
+ if scrollbar then refresh_scrollbar() end
+ if placeholders then refresh_placeholders() end
+ local merge = function(a, b)
+ return b ~= "" and (a .. "\n" .. b) or a
+ end
+ mp.set_osd_ass(geometry.window_w, geometry.window_h,
+ merge(merge(ass.selection, ass.scrollbar), ass.placeholders)
+ )
+ end
+
+ function ass_hide()
+ mp.set_osd_ass(1280, 720, "")
+ end
+end
+
+function normalize_path(path)
+ if string.find(path, "://") then
+ return path
+ end
+ path = utils.join_path(utils.getcwd(), path)
+ if on_windows then
+ path = string.gsub(path, "\\", "/")
+ end
+ path = string.gsub(path, "/%./", "/")
+ local n
+ repeat
+ path, n = string.gsub(path, "/[^/]*/%.%./", "/", 1)
+ until n == 0
+ return path
+end
+
+function refresh_overlays(force)
+ local todo = {}
+ overlays.missing = {}
+ for i = 1, 64 do
+ local index = view.first + i - 1
+ if index <= view.last then
+ local filename = playlist[index].filename
+ if force or overlays.active[i] ~= filename then
+ local filename_hash = hash_cache[filename]
+ if filename_hash == nil then
+ filename_hash = string.sub(sha256(normalize_path(filename)), 1, 12)
+ hash_cache[filename] = filename_hash
+ end
+ local thumb_filename = string.format("%s_%d_%d", filename_hash, geometry.size_x, geometry.size_y)
+ local thumb_path = utils.join_path(opts.thumbs_dir, thumb_filename)
+ if file_exists(thumb_path) then
+ show_overlay(i, thumb_path)
+ overlays.active[i] = filename
+ else
+ overlays.missing[thumb_path] = { index = i, input = filename }
+ remove_overlay(i)
+ todo[#todo + 1] = { input = filename, output = thumb_path }
+ end
+ end
+ else
+ remove_overlay(i)
+ end
+ end
+ -- reverse iterate so that the first thumbnail is at the top of the stack
+ if opts.auto_generate_thumbnails and #generators >= 1 then
+ for i = #todo, 1, -1 do
+ local generator = generators[i % #generators + 1]
+ local t = todo[i]
+ mp.commandv("script-message-to", generator, "push-thumbnail-front",
+ mp.get_script_name(),
+ t.input,
+ tostring(geometry.size_x),
+ tostring(geometry.size_y),
+ opts.take_thumbnail_at,
+ t.output,
+ opts.generate_thumbnails_with_mpv and "true" or "false"
+ )
+ end
+ end
+end
+
+function show_overlay(index_1, thumb_path)
+ local g = geometry
+ local index_0 = index_1 - 1
+ mp.commandv("overlay-add",
+ tostring(index_0),
+ tostring(math.floor(0.5 + g.margin_x + (g.margin_x + g.size_x) * (index_0 % g.columns))),
+ tostring(math.floor(0.5 + g.margin_y + (g.margin_y + g.size_y) * math.floor(index_0 / g.columns))),
+ thumb_path,
+ "0",
+ "bgra",
+ tostring(g.size_x),
+ tostring(g.size_y),
+ tostring(4*g.size_x))
+ mp.osd_message("", 0.01)
+end
+
+function remove_overlays()
+ for i = 1, 64 do
+ remove_overlay(i)
+ end
+ overlays.missing = {}
+end
+
+function remove_overlay(index_1)
+ if overlays.active[index_1] == "" then return end
+ overlays.active[index_1] = ""
+ mp.command("overlay-remove " .. index_1 - 1)
+ mp.osd_message("", 0.01)
+end
+
+function playlist_changed(key, value)
+ local did_change = function()
+ if #playlist ~= #value then return true end
+ for i = 1, #playlist do
+ if playlist[i].filename ~= value[i].filename then return true end
+ end
+ return false
+ end
+ if not did_change() then return end
+ if #value == 0 then
+ quit_gallery_view()
+ return
+ end
+ local sel_old_file = playlist[selection.old].filename
+ local sel_new_file = playlist[selection.now].filename
+ playlist = value
+ selection.old = math.max(1, math.min(selection.old, #playlist))
+ selection.now = math.max(1, math.min(selection.now, #playlist))
+ for i, f in ipairs(playlist) do
+ if sel_old_file == f.filename then
+ selection.old = i
+ end
+ if sel_new_file == f.filename then
+ selection.now = i
+ end
+ end
+ ensure_view_valid()
+ refresh_overlays(false)
+ ass_show(true, true, true)
+end
+
+function start_gallery_view(record_time)
+ if active then return end
+ init()
+ playlist = mp.get_property_native("playlist")
+ if #playlist == 0 then return end
+
+ local ww, wh = mp.get_osd_size()
+ compute_geometry(ww, wh)
+ if geometry.rows <= 0 or geometry.columns <= 0 then return end
+
+ mp.observe_property("playlist", "native", playlist_changed)
+ save_properties()
+
+ local pos = mp.get_property_number("playlist-pos-1")
+ if pos then
+ local s = {}
+ mp.set_property_bool("pause", true)
+ if opts.resume_when_picking then
+ s.time = record_time and mp.get_property_number("time-pos") or 0
+ end
+ s.vid = mp.get_property_number("vid") or "1"
+ s.aid = mp.get_property_number("aid") or "1"
+ s.sid = mp.get_property_number("sid") or "1"
+ resume[playlist[pos].filename] = s
+ mp.set_property("vid", "no")
+ mp.set_property("aid", "no")
+ mp.set_property("sid", "no")
+ else
+ -- this may happen if we enter the gallery too fast
+ local func
+ func = function()
+ mp.set_property_bool("pause", true)
+ mp.set_property("vid", "no")
+ mp.set_property("aid", "no")
+ mp.set_property("sid", "no")
+ mp.unregister_event(func)
+ end
+ mp.register_event("file-loaded", func)
+ end
+ selection.old = pos or 1
+ selection.now = selection.old
+ ensure_view_valid()
+ setup_ui_handlers()
+ refresh_overlays(true)
+ ass_show(true, true, true)
+ active = true
+end
+
+function quit_gallery_view(select)
+ if not active then return end
+ teardown_ui_handlers()
+ remove_overlays()
+ mp.unobserve_property(playlist_changed)
+ ass_hide()
+ if select then
+ resume_playback(select)
+ end
+ restore_properties()
+ active = false
+end
+
+function toggle_gallery()
+ if not active then
+ start_gallery_view(true)
+ else
+ quit_gallery_view(opts.toggle_behaves_as_accept and selection.now or selection.old)
+ end
+end
+
+mp.register_script_message("thumbnail-generated", function(thumbnail_path)
+ if not active then return end
+ local missing = overlays.missing[thumbnail_path]
+ if missing == nil then return end
+ show_overlay(missing.index, thumbnail_path)
+ overlays.active[missing.index] = missing.input
+ if not opts.always_show_placeholders then
+ ass_show(false, false, true)
+ end
+ overlays.missing[thumbnail_path] = nil
+end)
+
+mp.register_script_message("thumbnails-generator-broadcast", function(generator_name)
+ if #generators >= opts.max_generators then return end
+ for _, g in ipairs(generators) do
+ if generator_name == g then return end
+ end
+ generators[#generators + 1] = generator_name
+end)
+
+function write_flag_file()
+ if next(flags) == nil then return end
+ local out = io.open(opts.flagged_file_path, "w")
+ for f, _ in pairs(flags) do
+ out:write(f .. "\n")
+ end
+ out:close()
+end
+
+mp.register_event("shutdown", write_flag_file)
+
+if opts.start_gallery_on_file_end then
+ mp.observe_property("eof-reached", "bool", function(_, val)
+ if val and mp.get_property_number("playlist-count") > 1 then
+ start_gallery_view(false)
+
+ end
+ end)
+end
+if opts.start_gallery_on_startup then
+ local autostart
+ autostart = function()
+ if mp.get_property_number("playlist-count") == 0 then return end
+ if mp.get_property_number("osd-width") <= 0 then return end
+ start_gallery_view(false)
+ mp.unobserve_property(autostart)
+ end
+ mp.observe_property("playlist-count", "number", autostart)
+ mp.observe_property("osd-width", "number", autostart)
+end
+
+mp.add_key_binding("g", "gallery-view", toggle_gallery)
+mp.add_key_binding(nil, "gallery-write-flag-file", write_flag_file)
diff --git a/.config/mpv/scripts/reload.lua b/.config/mpv/scripts/reload.lua
@@ -0,0 +1,395 @@
+-- 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 `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'`
+
+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 debug_info(event)
+ msg.debug("event =", utils.to_string(event))
+ msg.debug("path =", mp.get_property("path"))
+ msg.debug("time-pos =", mp.get_property("time-pos"))
+ msg.debug("paused-for-cache =", mp.get_property("paused-for-cache"))
+ msg.debug("stream-path =", mp.get_property("stream-path"))
+ msg.debug("stream-pos =", mp.get_property("stream-pos"))
+ msg.debug("stream-end =", mp.get_property("stream-end"))
+ msg.debug("duration =", mp.get_property("duration"))
+ msg.debug("seekable =", mp.get_property("seekable"))
+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)
+ 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 math.floor(time_pos) == math.floor(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 video, in case there is more content
+ -- available. If time_pos stayed the same after reload, it means that vidkk
+ -- to avoid infinite reload loop when playback ended
+ -- math.floor function rounds time_pos to a second, to avoid inane reloads
+ if math.floor(property_time_pos) == math.floor(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
+
+-- 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", debug_info)
diff --git a/.config/mpv/scripts/webm.lua b/.config/mpv/scripts/webm.lua
@@ -0,0 +1,2025 @@
+local mp = require("mp")
+local assdraw = require("mp.assdraw")
+local msg = require("mp.msg")
+local utils = require("mp.utils")
+local mpopts = require("mp.options")
+local options = {
+ -- Defaults to shift+w
+ keybind = "E",
+ -- If empty, saves on the same directory of the playing video.
+ -- A starting "~" will be replaced by the home dir.
+ -- This field is delimited by double-square-brackets - [[ and ]] - instead of
+ -- quotes, because Windows users might run into a issue when using
+ -- backslashes as a path separator. Examples of valid inputs for this field
+ -- would be: [[]] (the default, empty value), [[C:\Users\John]] (on Windows),
+ -- and [[/home/john]] (on Unix-like systems eg. Linux).
+ output_directory = [[]],
+ run_detached = false,
+ -- Template string for the output file
+ -- %f - Filename, with extension
+ -- %F - Filename, without extension
+ -- %T - Media title, if it exists, or filename, with extension (useful for some streams, such as YouTube).
+ -- %s, %e - Start and end time, with milliseconds
+ -- %S, %E - Start and time, without milliseconds
+ -- %M - "-audio", if audio is enabled, empty otherwise
+ output_template = "%F-[%S-%E]%M",
+ -- Scale video to a certain height, keeping the aspect ratio. -1 disables it.
+ scale_height = -1,
+ -- Target filesize, in kB. This will be used to calculate the bitrate
+ -- used on the encode. If this is set to <= 0, the video bitrate will be set
+ -- to 0, which might enable constant quality modes, depending on the
+ -- video codec that's used (VP8 and VP9, for example).
+ target_filesize = 2500,
+ -- If true, will use stricter flags to ensure the resulting file doesn't
+ -- overshoot the target filesize. Not recommended, as constrained quality
+ -- mode should work well, unless you're really having trouble hitting
+ -- the target size.
+ strict_filesize_constraint = false,
+ strict_bitrate_multiplier = 0.95,
+ -- In kilobits.
+ strict_audio_bitrate = 64,
+ -- Sets the output format, from a few predefined ones.
+ -- Currently we have webm-vp8 (libvpx/libvorbis), webm-vp9 (libvpx-vp9/libvorbis)
+ -- and raw (rawvideo/pcm_s16le).
+ output_format = "webm-vp8",
+ twopass = false,
+ -- If set, applies the video filters currently used on the playback to the encode.
+ apply_current_filters = true,
+ -- If set, writes the video's filename to the "Title" field on the metadata.
+ write_filename_on_metadata = false,
+ -- Set the number of encoding threads, for codecs libvpx and libvpx-vp9
+ libvpx_threads = 4,
+ additional_flags = "",
+ -- Useful for flags that may impact output filesize, such as crf, qmin, qmax etc
+ -- Won't be applied when strict_filesize_constraint is on.
+ non_strict_additional_flags = "--ovcopts-add=crf=10",
+ -- Display the encode progress, in %. Requires run_detached to be disabled.
+ -- On Windows, it shows a cmd popup. "auto" will display progress on non-Windows platforms.
+ display_progress = "auto",
+ -- The font size used in the menu. Isn't used for the notifications (started encode, finished encode etc)
+ font_size = 28,
+ margin = 10,
+ message_duration = 5
+}
+
+mpopts.read_options(options)
+local bold
+bold = function(text)
+ return "{\\b1}" .. tostring(text) .. "{\\b0}"
+end
+local message
+message = function(text, duration)
+ local ass = mp.get_property_osd("osd-ass-cc/0")
+ ass = ass .. text
+ return mp.osd_message(ass, duration or options.message_duration)
+end
+local append
+append = function(a, b)
+ for _, val in ipairs(b) do
+ a[#a + 1] = val
+ end
+ return a
+end
+local seconds_to_time_string
+seconds_to_time_string = function(seconds, no_ms, full)
+ if seconds < 0 then
+ return "unknown"
+ end
+ local ret = ""
+ if not (no_ms) then
+ ret = string.format(".%03d", seconds * 1000 % 1000)
+ end
+ ret = string.format("%02d:%02d%s", math.floor(seconds / 60) % 60, math.floor(seconds) % 60, ret)
+ if full or seconds > 3600 then
+ ret = string.format("%d:%s", math.floor(seconds / 3600), ret)
+ end
+ return ret
+end
+local seconds_to_path_element
+seconds_to_path_element = function(seconds, no_ms, full)
+ local time_string = seconds_to_time_string(seconds, no_ms, full)
+ local _
+ time_string, _ = time_string:gsub(":", ".")
+ return time_string
+end
+local file_exists
+file_exists = function(name)
+ local info, err = utils.file_info(name)
+ if info ~= nil then
+ return true
+ end
+ return false
+end
+local format_filename
+format_filename = function(startTime, endTime, videoFormat)
+ local replaceTable = {
+ ["%%f"] = mp.get_property("filename"),
+ ["%%F"] = mp.get_property("filename/no-ext"),
+ ["%%s"] = seconds_to_path_element(startTime),
+ ["%%S"] = seconds_to_path_element(startTime, true),
+ ["%%e"] = seconds_to_path_element(endTime),
+ ["%%E"] = seconds_to_path_element(endTime, true),
+ ["%%T"] = mp.get_property("media-title"),
+ ["%%M"] = (mp.get_property_native('aid') and not mp.get_property_native('mute')) and '-audio' or ''
+ }
+ local filename = options.output_template
+ for format, value in pairs(replaceTable) do
+ local _
+ filename, _ = filename:gsub(format, value)
+ end
+ local _
+ filename, _ = filename:gsub("[<>:\"/\\|?*]", "")
+ return tostring(filename) .. "." .. tostring(videoFormat.outputExtension)
+end
+local parse_directory
+parse_directory = function(dir)
+ local home_dir = os.getenv("HOME")
+ if not home_dir then
+ home_dir = os.getenv("USERPROFILE")
+ end
+ if not home_dir then
+ local drive = os.getenv("HOMEDRIVE")
+ local path = os.getenv("HOMEPATH")
+ if drive and path then
+ home_dir = utils.join_path(drive, path)
+ else
+ msg.warn("Couldn't find home dir.")
+ home_dir = ""
+ end
+ end
+ local _
+ dir, _ = dir:gsub("^~", home_dir)
+ return dir
+end
+local is_windows = type(package) == "table" and type(package.config) == "string" and package.config:sub(1, 1) == "\\"
+local trim
+trim = function(s)
+ return s:match("^%s*(.-)%s*$")
+end
+local get_null_path
+get_null_path = function()
+ if file_exists("/dev/null") then
+ return "/dev/null"
+ end
+ return "NUL"
+end
+local run_subprocess
+run_subprocess = function(params)
+ local res = utils.subprocess(params)
+ if res.status ~= 0 then
+ msg.verbose("Command failed! Reason: ", res.error, " Killed by us? ", res.killed_by_us and "yes" or "no")
+ msg.verbose("Command stdout: ")
+ msg.verbose(res.stdout)
+ return false
+ end
+ return true
+end
+local shell_escape
+shell_escape = function(args)
+ local ret = { }
+ for i, a in ipairs(args) do
+ local s = tostring(a)
+ if string.match(s, "[^A-Za-z0-9_/:=-]") then
+ if is_windows then
+ s = '"' .. string.gsub(s, '"', '"\\""') .. '"'
+ else
+ s = "'" .. string.gsub(s, "'", "'\\''") .. "'"
+ end
+ end
+ table.insert(ret, s)
+ end
+ local concat = table.concat(ret, " ")
+ if is_windows then
+ concat = '"' .. concat .. '"'
+ end
+ return concat
+end
+local run_subprocess_popen
+run_subprocess_popen = function(command_line)
+ local command_line_string = shell_escape(command_line)
+ command_line_string = command_line_string .. " 2>&1"
+ msg.verbose("run_subprocess_popen: running " .. tostring(command_line_string))
+ return io.popen(command_line_string)
+end
+local calculate_scale_factor
+calculate_scale_factor = function()
+ local baseResY = 720
+ local osd_w, osd_h = mp.get_osd_size()
+ return osd_h / baseResY
+end
+local should_display_progress
+should_display_progress = function()
+ if options.display_progress == "auto" then
+ return not is_windows
+ end
+ return options.display_progress
+end
+local dimensions_changed = true
+local _video_dimensions = { }
+local get_video_dimensions
+get_video_dimensions = function()
+ if not (dimensions_changed) then
+ return _video_dimensions
+ end
+ local video_params = mp.get_property_native("video-out-params")
+ if not video_params then
+ return nil
+ end
+ dimensions_changed = false
+ local keep_aspect = mp.get_property_bool("keepaspect")
+ local w = video_params["w"]
+ local h = video_params["h"]
+ local dw = video_params["dw"]
+ local dh = video_params["dh"]
+ if mp.get_property_number("video-rotate") % 180 == 90 then
+ w, h = h, w
+ dw, dh = dh, dw
+ end
+ _video_dimensions = {
+ top_left = { },
+ bottom_right = { },
+ ratios = { }
+ }
+ local window_w, window_h = mp.get_osd_size()
+ if keep_aspect then
+ local unscaled = mp.get_property_native("video-unscaled")
+ local panscan = mp.get_property_number("panscan")
+ local fwidth = window_w
+ local fheight = math.floor(window_w / dw * dh)
+ if fheight > window_h or fheight < h then
+ local tmpw = math.floor(window_h / dh * dw)
+ if tmpw <= window_w then
+ fheight = window_h
+ fwidth = tmpw
+ end
+ end
+ local vo_panscan_area = window_h - fheight
+ local f_w = fwidth / fheight
+ local f_h = 1
+ if vo_panscan_area == 0 then
+ vo_panscan_area = window_h - fwidth
+ f_w = 1
+ f_h = fheight / fwidth
+ end
+ if unscaled or unscaled == "downscale-big" then
+ vo_panscan_area = 0
+ if unscaled or (dw <= window_w and dh <= window_h) then
+ fwidth = dw
+ fheight = dh
+ end
+ end
+ local scaled_width = fwidth + math.floor(vo_panscan_area * panscan * f_w)
+ local scaled_height = fheight + math.floor(vo_panscan_area * panscan * f_h)
+ local split_scaling
+ split_scaling = function(dst_size, scaled_src_size, zoom, align, pan)
+ scaled_src_size = math.floor(scaled_src_size * 2 ^ zoom)
+ align = (align + 1) / 2
+ local dst_start = math.floor((dst_size - scaled_src_size) * align + pan * scaled_src_size)
+ if dst_start < 0 then
+ dst_start = dst_start + 1
+ end
+ local dst_end = dst_start + scaled_src_size
+ if dst_start >= dst_end then
+ dst_start = 0
+ dst_end = 1
+ end
+ return dst_start, dst_end
+ end
+ local zoom = mp.get_property_number("video-zoom")
+ local align_x = mp.get_property_number("video-align-x")
+ local pan_x = mp.get_property_number("video-pan-x")
+ _video_dimensions.top_left.x, _video_dimensions.bottom_right.x = split_scaling(window_w, scaled_width, zoom, align_x, pan_x)
+ local align_y = mp.get_property_number("video-align-y")
+ local pan_y = mp.get_property_number("video-pan-y")
+ _video_dimensions.top_left.y, _video_dimensions.bottom_right.y = split_scaling(window_h, scaled_height, zoom, align_y, pan_y)
+ else
+ _video_dimensions.top_left.x = 0
+ _video_dimensions.bottom_right.x = window_w
+ _video_dimensions.top_left.y = 0
+ _video_dimensions.bottom_right.y = window_h
+ end
+ _video_dimensions.ratios.w = w / (_video_dimensions.bottom_right.x - _video_dimensions.top_left.x)
+ _video_dimensions.ratios.h = h / (_video_dimensions.bottom_right.y - _video_dimensions.top_left.y)
+ return _video_dimensions
+end
+local set_dimensions_changed
+set_dimensions_changed = function()
+ dimensions_changed = true
+end
+local monitor_dimensions
+monitor_dimensions = function()
+ local properties = {
+ "keepaspect",
+ "video-out-params",
+ "video-unscaled",
+ "panscan",
+ "video-zoom",
+ "video-align-x",
+ "video-pan-x",
+ "video-align-y",
+ "video-pan-y",
+ "osd-width",
+ "osd-height"
+ }
+ for _, p in ipairs(properties) do
+ mp.observe_property(p, "native", set_dimensions_changed)
+ end
+end
+local clamp
+clamp = function(min, val, max)
+ if val <= min then
+ return min
+ end
+ if val >= max then
+ return max
+ end
+ return val
+end
+local clamp_point
+clamp_point = function(top_left, point, bottom_right)
+ return {
+ x = clamp(top_left.x, point.x, bottom_right.x),
+ y = clamp(top_left.y, point.y, bottom_right.y)
+ }
+end
+local VideoPoint
+do
+ local _class_0
+ local _base_0 = {
+ set_from_screen = function(self, sx, sy)
+ local d = get_video_dimensions()
+ local point = clamp_point(d.top_left, {
+ x = sx,
+ y = sy
+ }, d.bottom_right)
+ self.x = math.floor(d.ratios.w * (point.x - d.top_left.x) + 0.5)
+ self.y = math.floor(d.ratios.h * (point.y - d.top_left.y) + 0.5)
+ end,
+ to_screen = function(self)
+ local d = get_video_dimensions()
+ return {
+ x = math.floor(self.x / d.ratios.w + d.top_left.x + 0.5),
+ y = math.floor(self.y / d.ratios.h + d.top_left.y + 0.5)
+ }
+ end
+ }
+ _base_0.__index = _base_0
+ _class_0 = setmetatable({
+ __init = function(self)
+ self.x = -1
+ self.y = -1
+ end,
+ __base = _base_0,
+ __name = "VideoPoint"
+ }, {
+ __index = _base_0,
+ __call = function(cls, ...)
+ local _self_0 = setmetatable({}, _base_0)
+ cls.__init(_self_0, ...)
+ return _self_0
+ end
+ })
+ _base_0.__class = _class_0
+ VideoPoint = _class_0
+end
+local Region
+do
+ local _class_0
+ local _base_0 = {
+ is_valid = function(self)
+ return self.x > -1 and self.y > -1 and self.w > -1 and self.h > -1
+ end,
+ set_from_points = function(self, p1, p2)
+ self.x = math.min(p1.x, p2.x)
+ self.y = math.min(p1.y, p2.y)
+ self.w = math.abs(p1.x - p2.x)
+ self.h = math.abs(p1.y - p2.y)
+ end
+ }
+ _base_0.__index = _base_0
+ _class_0 = setmetatable({
+ __init = function(self)
+ self.x = -1
+ self.y = -1
+ self.w = -1
+ self.h = -1
+ end,
+ __base = _base_0,
+ __name = "Region"
+ }, {
+ __index = _base_0,
+ __call = function(cls, ...)
+ local _self_0 = setmetatable({}, _base_0)
+ cls.__init(_self_0, ...)
+ return _self_0
+ end
+ })
+ _base_0.__class = _class_0
+ Region = _class_0
+end
+local make_fullscreen_region
+make_fullscreen_region = function()
+ local r = Region()
+ local d = get_video_dimensions()
+ local a = VideoPoint()
+ local b = VideoPoint()
+ local xa, ya
+ do
+ local _obj_0 = d.top_left
+ xa, ya = _obj_0.x, _obj_0.y
+ end
+ a:set_from_screen(xa, ya)
+ local xb, yb
+ do
+ local _obj_0 = d.bottom_right
+ xb, yb = _obj_0.x, _obj_0.y
+ end
+ b:set_from_screen(xb, yb)
+ r:set_from_points(a, b)
+ return r
+end
+local formats = { }
+local Format
+do
+ local _class_0
+ local _base_0 = {
+ getPreFilters = function(self)
+ return { }
+ end,
+ getPostFilters = function(self)
+ return { }
+ end,
+ getFlags = function(self)
+ return { }
+ end
+ }
+ _base_0.__index = _base_0
+ _class_0 = setmetatable({
+ __init = function(self)
+ self.displayName = "Basic"
+ self.supportsTwopass = true
+ self.videoCodec = ""
+ self.audioCodec = ""
+ self.outputExtension = ""
+ self.acceptsBitrate = true
+ end,
+ __base = _base_0,
+ __name = "Format"
+ }, {
+ __index = _base_0,
+ __call = function(cls, ...)
+ local _self_0 = setmetatable({}, _base_0)
+ cls.__init(_self_0, ...)
+ return _self_0
+ end
+ })
+ _base_0.__class = _class_0
+ Format = _class_0
+end
+local RawVideo
+do
+ local _class_0
+ local _parent_0 = Format
+ local _base_0 = {
+ getColorspace = function(self)
+ local csp = mp.get_property("colormatrix")
+ local _exp_0 = csp
+ if "bt.601" == _exp_0 then
+ return "bt601"
+ elseif "bt.709" == _exp_0 then
+ return "bt709"
+ elseif "bt.2020" == _exp_0 then
+ return "bt2020"
+ elseif "smpte-240m" == _exp_0 then
+ return "smpte240m"
+ else
+ msg.info("Warning, unknown colorspace " .. tostring(csp) .. " detected, using bt.601.")
+ return "bt601"
+ end
+ end,
+ getPostFilters = function(self)
+ return {
+ "format=yuv444p16",
+ "lavfi-scale=in_color_matrix=" .. self:getColorspace(),
+ "format=bgr24"
+ }
+ end
+ }
+ _base_0.__index = _base_0
+ setmetatable(_base_0, _parent_0.__base)
+ _class_0 = setmetatable({
+ __init = function(self)
+ self.displayName = "Raw"
+ self.supportsTwopass = false
+ self.videoCodec = "rawvideo"
+ self.audioCodec = "pcm_s16le"
+ self.outputExtension = "avi"
+ self.acceptsBitrate = false
+ end,
+ __base = _base_0,
+ __name = "RawVideo",
+ __parent = _parent_0
+ }, {
+ __index = function(cls, name)
+ local val = rawget(_base_0, name)
+ if val == nil then
+ local parent = rawget(cls, "__parent")
+ if parent then
+ return parent[name]
+ end
+ else
+ return val
+ end
+ end,
+ __call = function(cls, ...)
+ local _self_0 = setmetatable({}, _base_0)
+ cls.__init(_self_0, ...)
+ return _self_0
+ end
+ })
+ _base_0.__class = _class_0
+ if _parent_0.__inherited then
+ _parent_0.__inherited(_parent_0, _class_0)
+ end
+ RawVideo = _class_0
+end
+formats["raw"] = RawVideo()
+local WebmVP8
+do
+ local _class_0
+ local _parent_0 = Format
+ local _base_0 = {
+ getPreFilters = function(self)
+ local colormatrixFilter = {
+ ["bt.709"] = "bt709",
+ ["bt.2020"] = "bt2020",
+ ["smpte-240m"] = "smpte240m"
+ }
+ local ret = { }
+ local colormatrix = mp.get_property_native("video-params/colormatrix")
+ if colormatrixFilter[colormatrix] then
+ append(ret, {
+ "lavfi-colormatrix=" .. tostring(colormatrixFilter[colormatrix]) .. ":bt601"
+ })
+ end
+ return ret
+ end,
+ getFlags = function(self)
+ return {
+ "--ovcopts-add=threads=" .. tostring(options.libvpx_threads)
+ }
+ end
+ }
+ _base_0.__index = _base_0
+ setmetatable(_base_0, _parent_0.__base)
+ _class_0 = setmetatable({
+ __init = function(self)
+ self.displayName = "WebM"
+ self.supportsTwopass = true
+ self.videoCodec = "libvpx"
+ self.audioCodec = "libvorbis"
+ self.outputExtension = "webm"
+ self.acceptsBitrate = true
+ end,
+ __base = _base_0,
+ __name = "WebmVP8",
+ __parent = _parent_0
+ }, {
+ __index = function(cls, name)
+ local val = rawget(_base_0, name)
+ if val == nil then
+ local parent = rawget(cls, "__parent")
+ if parent then
+ return parent[name]
+ end
+ else
+ return val
+ end
+ end,
+ __call = function(cls, ...)
+ local _self_0 = setmetatable({}, _base_0)
+ cls.__init(_self_0, ...)
+ return _self_0
+ end
+ })
+ _base_0.__class = _class_0
+ if _parent_0.__inherited then
+ _parent_0.__inherited(_parent_0, _class_0)
+ end
+ WebmVP8 = _class_0
+end
+formats["webm-vp8"] = WebmVP8()
+local WebmVP9
+do
+ local _class_0
+ local _parent_0 = Format
+ local _base_0 = {
+ getFlags = function(self)
+ return {
+ "--ovcopts-add=threads=" .. tostring(options.libvpx_threads)
+ }
+ end
+ }
+ _base_0.__index = _base_0
+ setmetatable(_base_0, _parent_0.__base)
+ _class_0 = setmetatable({
+ __init = function(self)
+ self.displayName = "WebM (VP9)"
+ self.supportsTwopass = true
+ self.videoCodec = "libvpx-vp9"
+ self.audioCodec = "libvorbis"
+ self.outputExtension = "webm"
+ self.acceptsBitrate = true
+ end,
+ __base = _base_0,
+ __name = "WebmVP9",
+ __parent = _parent_0
+ }, {
+ __index = function(cls, name)
+ local val = rawget(_base_0, name)
+ if val == nil then
+ local parent = rawget(cls, "__parent")
+ if parent then
+ return parent[name]
+ end
+ else
+ return val
+ end
+ end,
+ __call = function(cls, ...)
+ local _self_0 = setmetatable({}, _base_0)
+ cls.__init(_self_0, ...)
+ return _self_0
+ end
+ })
+ _base_0.__class = _class_0
+ if _parent_0.__inherited then
+ _parent_0.__inherited(_parent_0, _class_0)
+ end
+ WebmVP9 = _class_0
+end
+formats["webm-vp9"] = WebmVP9()
+local MP4
+do
+ local _class_0
+ local _parent_0 = Format
+ local _base_0 = { }
+ _base_0.__index = _base_0
+ setmetatable(_base_0, _parent_0.__base)
+ _class_0 = setmetatable({
+ __init = function(self)
+ self.displayName = "MP4 (h264/AAC)"
+ self.supportsTwopass = true
+ self.videoCodec = "libx264"
+ self.audioCodec = "aac"
+ self.outputExtension = "mp4"
+ self.acceptsBitrate = true
+ end,
+ __base = _base_0,
+ __name = "MP4",
+ __parent = _parent_0
+ }, {
+ __index = function(cls, name)
+ local val = rawget(_base_0, name)
+ if val == nil then
+ local parent = rawget(cls, "__parent")
+ if parent then
+ return parent[name]
+ end
+ else
+ return val
+ end
+ end,
+ __call = function(cls, ...)
+ local _self_0 = setmetatable({}, _base_0)
+ cls.__init(_self_0, ...)
+ return _self_0
+ end
+ })
+ _base_0.__class = _class_0
+ if _parent_0.__inherited then
+ _parent_0.__inherited(_parent_0, _class_0)
+ end
+ MP4 = _class_0
+end
+formats["mp4"] = MP4()
+local Page
+do
+ local _class_0
+ local _base_0 = {
+ add_keybinds = function(self)
+ if not self.keybinds then
+ return
+ end
+ for key, func in pairs(self.keybinds) do
+ mp.add_forced_key_binding(key, key, func, {
+ repeatable = true
+ })
+ end
+ end,
+ remove_keybinds = function(self)
+ if not self.keybinds then
+ return
+ end
+ for key, _ in pairs(self.keybinds) do
+ mp.remove_key_binding(key)
+ end
+ end,
+ observe_properties = function(self)
+ self.sizeCallback = function()
+ return self:draw()
+ end
+ local properties = {
+ "keepaspect",
+ "video-out-params",
+ "video-unscaled",
+ "panscan",
+ "video-zoom",
+ "video-align-x",
+ "video-pan-x",
+ "video-align-y",
+ "video-pan-y",
+ "osd-width",
+ "osd-height"
+ }
+ for _index_0 = 1, #properties do
+ local p = properties[_index_0]
+ mp.observe_property(p, "native", self.sizeCallback)
+ end
+ end,
+ unobserve_properties = function(self)
+ if self.sizeCallback then
+ mp.unobserve_property(self.sizeCallback)
+ self.sizeCallback = nil
+ end
+ end,
+ clear = function(self)
+ local window_w, window_h = mp.get_osd_size()
+ mp.set_osd_ass(window_w, window_h, "")
+ return mp.osd_message("", 0)
+ end,
+ prepare = function(self)
+ return nil
+ end,
+ dispose = function(self)
+ return nil
+ end,
+ show = function(self)
+ self.visible = true
+ self:observe_properties()
+ self:add_keybinds()
+ self:prepare()
+ self:clear()
+ return self:draw()
+ end,
+ hide = function(self)
+ self.visible = false
+ self:unobserve_properties()
+ self:remove_keybinds()
+ self:clear()
+ return self:dispose()
+ end,
+ setup_text = function(self, ass)
+ local scale = calculate_scale_factor()
+ local margin = options.margin * scale
+ ass:pos(margin, margin)
+ return ass:append("{\\fs" .. tostring(options.font_size * scale) .. "}")
+ end
+ }
+ _base_0.__index = _base_0
+ _class_0 = setmetatable({
+ __init = function() end,
+ __base = _base_0,
+ __name = "Page"
+ }, {
+ __index = _base_0,
+ __call = function(cls, ...)
+ local _self_0 = setmetatable({}, _base_0)
+ cls.__init(_self_0, ...)
+ return _self_0
+ end
+ })
+ _base_0.__class = _class_0
+ Page = _class_0
+end
+local EncodeWithProgress
+do
+ local _class_0
+ local _parent_0 = Page
+ local _base_0 = {
+ draw = function(self)
+ local progress = 100 * ((self.currentTime - self.startTime) / self.duration)
+ local progressText = string.format("%d%%", progress)
+ local window_w, window_h = mp.get_osd_size()
+ local ass = assdraw.ass_new()
+ ass:new_event()
+ self:setup_text(ass)
+ ass:append("Encoding (" .. tostring(bold(progressText)) .. ")\\N")
+ return mp.set_osd_ass(window_w, window_h, ass.text)
+ end,
+ parseLine = function(self, line)
+ local matchTime = string.match(line, "Encode time[-]pos: ([0-9.]+)")
+ local matchExit = string.match(line, "Exiting... [(]([%a ]+)[)]")
+ if matchTime == nil and matchExit == nil then
+ return
+ end
+ if matchTime ~= nil and tonumber(matchTime) > self.currentTime then
+ self.currentTime = tonumber(matchTime)
+ end
+ if matchExit ~= nil then
+ self.finished = true
+ self.finishedReason = matchExit
+ end
+ end,
+ startEncode = function(self, command_line)
+ local copy_command_line
+ do
+ local _accum_0 = { }
+ local _len_0 = 1
+ for _index_0 = 1, #command_line do
+ local arg = command_line[_index_0]
+ _accum_0[_len_0] = arg
+ _len_0 = _len_0 + 1
+ end
+ copy_command_line = _accum_0
+ end
+ append(copy_command_line, {
+ '--term-status-msg=Encode time-pos: ${=time-pos}'
+ })
+ self:show()
+ local processFd = run_subprocess_popen(copy_command_line)
+ for line in processFd:lines() do
+ msg.verbose(string.format('%q', line))
+ self:parseLine(line)
+ self:draw()
+ end
+ processFd:close()
+ self:hide()
+ if self.finishedReason == "End of file" then
+ return true
+ end
+ return false
+ end
+ }
+ _base_0.__index = _base_0
+ setmetatable(_base_0, _parent_0.__base)
+ _class_0 = setmetatable({
+ __init = function(self, startTime, endTime)
+ self.startTime = startTime
+ self.endTime = endTime
+ self.duration = endTime - startTime
+ self.currentTime = startTime
+ end,
+ __base = _base_0,
+ __name = "EncodeWithProgress",
+ __parent = _parent_0
+ }, {
+ __index = function(cls, name)
+ local val = rawget(_base_0, name)
+ if val == nil then
+ local parent = rawget(cls, "__parent")
+ if parent then
+ return parent[name]
+ end
+ else
+ return val
+ end
+ end,
+ __call = function(cls, ...)
+ local _self_0 = setmetatable({}, _base_0)
+ cls.__init(_self_0, ...)
+ return _self_0
+ end
+ })
+ _base_0.__class = _class_0
+ if _parent_0.__inherited then
+ _parent_0.__inherited(_parent_0, _class_0)
+ end
+ EncodeWithProgress = _class_0
+end
+local get_active_tracks
+get_active_tracks = function()
+ local accepted = {
+ video = true,
+ audio = not mp.get_property_bool("mute"),
+ sub = mp.get_property_bool("sub-visibility")
+ }
+ local active = { }
+ for _, track in ipairs(mp.get_property_native("track-list")) do
+ if track["selected"] and accepted[track["type"]] then
+ active[#active + 1] = track
+ end
+ end
+ return active
+end
+local get_scale_filters
+get_scale_filters = function()
+ if options.scale_height > 0 then
+ return {
+ "lavfi-scale=-2:" .. tostring(options.scale_height)
+ }
+ end
+ return { }
+end
+local append_property
+append_property = function(out, property_name, option_name)
+ option_name = option_name or property_name
+ local prop = mp.get_property(property_name)
+ if prop and prop ~= "" then
+ return append(out, {
+ "--" .. tostring(option_name) .. "=" .. tostring(prop)
+ })
+ end
+end
+local append_list_options
+append_list_options = function(out, property_name, option_prefix)
+ option_prefix = option_prefix or property_name
+ local prop = mp.get_property_native(property_name)
+ if prop then
+ for _index_0 = 1, #prop do
+ local value = prop[_index_0]
+ append(out, {
+ "--" .. tostring(option_prefix) .. "-append=" .. tostring(value)
+ })
+ end
+ end
+end
+local get_playback_options
+get_playback_options = function()
+ local ret = { }
+ append_property(ret, "sub-ass-override")
+ append_property(ret, "sub-ass-force-style")
+ append_property(ret, "sub-auto")
+ append_property(ret, "sub-delay")
+ append_property(ret, "video-rotate")
+ for _, track in ipairs(mp.get_property_native("track-list")) do
+ if track["type"] == "sub" and track["external"] then
+ append(ret, {
+ "--sub-files-append=" .. tostring(track['external-filename'])
+ })
+ end
+ end
+ return ret
+end
+local get_speed_flags
+get_speed_flags = function()
+ local ret = { }
+ local speed = mp.get_property_native("speed")
+ if speed ~= 1 then
+ append(ret, {
+ "--vf-add=setpts=PTS/" .. tostring(speed),
+ "--af-add=atempo=" .. tostring(speed),
+ "--sub-speed=1/" .. tostring(speed)
+ })
+ end
+ return ret
+end
+local get_metadata_flags
+get_metadata_flags = function()
+ local title = mp.get_property("filename/no-ext")
+ return {
+ "--oset-metadata=title=%" .. tostring(string.len(title)) .. "%" .. tostring(title)
+ }
+end
+local apply_current_filters
+apply_current_filters = function(filters)
+ local vf = mp.get_property_native("vf")
+ msg.verbose("apply_current_filters: got " .. tostring(#vf) .. " currently applied.")
+ for _index_0 = 1, #vf do
+ local _continue_0 = false
+ repeat
+ local filter = vf[_index_0]
+ msg.verbose("apply_current_filters: filter name: " .. tostring(filter['name']))
+ if filter["enabled"] == false then
+ _continue_0 = true
+ break
+ end
+ local str = filter["name"]
+ local params = filter["params"] or { }
+ for k, v in pairs(params) do
+ str = str .. ":" .. tostring(k) .. "=%" .. tostring(string.len(v)) .. "%" .. tostring(v)
+ end
+ append(filters, {
+ str
+ })
+ _continue_0 = true
+ until true
+ if not _continue_0 then
+ break
+ end
+ end
+end
+local encode
+encode = function(region, startTime, endTime)
+ local format = formats[options.output_format]
+ local path = mp.get_property("path")
+ if not path then
+ message("No file is being played")
+ return
+ end
+ local is_stream = not file_exists(path)
+ local command = {
+ "mpv",
+ path,
+ "--start=" .. seconds_to_time_string(startTime, false, true),
+ "--end=" .. seconds_to_time_string(endTime, false, true),
+ "--ovc=" .. tostring(format.videoCodec),
+ "--oac=" .. tostring(format.audioCodec),
+ "--loop-file=no"
+ }
+ local vid = -1
+ local aid = -1
+ local sid = -1
+ for _, track in ipairs(get_active_tracks()) do
+ local _exp_0 = track["type"]
+ if "video" == _exp_0 then
+ vid = track['id']
+ elseif "audio" == _exp_0 then
+ aid = track['id']
+ elseif "sub" == _exp_0 then
+ sid = track['id']
+ end
+ end
+ append(command, {
+ "--vid=" .. (vid >= 0 and tostring(vid) or "no"),
+ "--aid=" .. (aid >= 0 and tostring(aid) or "no"),
+ "--sid=" .. (sid >= 0 and tostring(sid) or "no")
+ })
+ append(command, get_playback_options())
+ local filters = { }
+ append(filters, format:getPreFilters())
+ if options.apply_current_filters then
+ apply_current_filters(filters)
+ end
+ if region and region:is_valid() then
+ append(filters, {
+ "lavfi-crop=" .. tostring(region.w) .. ":" .. tostring(region.h) .. ":" .. tostring(region.x) .. ":" .. tostring(region.y)
+ })
+ end
+ append(filters, get_scale_filters())
+ append(filters, format:getPostFilters())
+ for _index_0 = 1, #filters do
+ local f = filters[_index_0]
+ append(command, {
+ "--vf-add=" .. tostring(f)
+ })
+ end
+ append(command, get_speed_flags())
+ append(command, format:getFlags())
+ if options.write_filename_on_metadata then
+ append(command, get_metadata_flags())
+ end
+ if options.target_filesize > 0 and format.acceptsBitrate then
+ local dT = endTime - startTime
+ if options.strict_filesize_constraint then
+ local video_kilobits = options.target_filesize * 8
+ if aid >= 0 then
+ video_kilobits = video_kilobits - dT * options.strict_audio_bitrate
+ append(command, {
+ "--oacopts-add=b=" .. tostring(options.strict_audio_bitrate) .. "k"
+ })
+ end
+ video_kilobits = video_kilobits * options.strict_bitrate_multiplier
+ local bitrate = math.floor(video_kilobits / dT)
+ append(command, {
+ "--ovcopts-add=b=" .. tostring(bitrate) .. "k",
+ "--ovcopts-add=minrate=" .. tostring(bitrate) .. "k",
+ "--ovcopts-add=maxrate=" .. tostring(bitrate) .. "k"
+ })
+ else
+ local bitrate = math.floor(options.target_filesize * 8 / dT)
+ append(command, {
+ "--ovcopts-add=b=" .. tostring(bitrate) .. "k"
+ })
+ end
+ elseif options.target_filesize <= 0 and format.acceptsBitrate then
+ append(command, {
+ "--ovcopts-add=b=0"
+ })
+ end
+ for token in string.gmatch(options.additional_flags, "[^%s]+") do
+ command[#command + 1] = token
+ end
+ if not options.strict_filesize_constraint then
+ for token in string.gmatch(options.non_strict_additional_flags, "[^%s]+") do
+ command[#command + 1] = token
+ end
+ end
+ if options.twopass and format.supportsTwopass and not is_stream then
+ local first_pass_cmdline
+ do
+ local _accum_0 = { }
+ local _len_0 = 1
+ for _index_0 = 1, #command do
+ local arg = command[_index_0]
+ _accum_0[_len_0] = arg
+ _len_0 = _len_0 + 1
+ end
+ first_pass_cmdline = _accum_0
+ end
+ append(first_pass_cmdline, {
+ "--ovcopts-add=flags=+pass1",
+ "-of=" .. tostring(format.outputExtension),
+ "-o=" .. tostring(get_null_path())
+ })
+ message("Starting first pass...")
+ msg.verbose("First-pass command line: ", table.concat(first_pass_cmdline, " "))
+ local res = run_subprocess({
+ args = first_pass_cmdline,
+ cancellable = false
+ })
+ if not res then
+ message("First pass failed! Check the logs for details.")
+ return
+ end
+ append(command, {
+ "--ovcopts-add=flags=+pass2"
+ })
+ end
+ local dir = ""
+ if is_stream then
+ dir = parse_directory("~")
+ else
+ local _
+ dir, _ = utils.split_path(path)
+ end
+ if options.output_directory ~= "" then
+ dir = parse_directory(options.output_directory)
+ end
+ local formatted_filename = format_filename(startTime, endTime, format)
+ local out_path = utils.join_path(dir, formatted_filename)
+ append(command, {
+ "-o=" .. tostring(out_path)
+ })
+ msg.info("Encoding to", out_path)
+ msg.verbose("Command line:", table.concat(command, " "))
+ if options.run_detached then
+ message("Started encode, process was detached.")
+ return utils.subprocess_detached({
+ args = command
+ })
+ else
+ local res = false
+ if not should_display_progress() then
+ message("Started encode...")
+ res = run_subprocess({
+ args = command,
+ cancellable = false
+ })
+ else
+ local ewp = EncodeWithProgress(startTime, endTime)
+ res = ewp:startEncode(command)
+ end
+ if res then
+ return message("Encoded successfully! Saved to\\N" .. tostring(bold(out_path)))
+ else
+ return message("Encode failed! Check the logs for details.")
+ end
+ end
+end
+local CropPage
+do
+ local _class_0
+ local _parent_0 = Page
+ local _base_0 = {
+ reset = function(self)
+ local dimensions = get_video_dimensions()
+ local xa, ya
+ do
+ local _obj_0 = dimensions.top_left
+ xa, ya = _obj_0.x, _obj_0.y
+ end
+ self.pointA:set_from_screen(xa, ya)
+ local xb, yb
+ do
+ local _obj_0 = dimensions.bottom_right
+ xb, yb = _obj_0.x, _obj_0.y
+ end
+ self.pointB:set_from_screen(xb, yb)
+ if self.visible then
+ return self:draw()
+ end
+ end,
+ setPointA = function(self)
+ local posX, posY = mp.get_mouse_pos()
+ self.pointA:set_from_screen(posX, posY)
+ if self.visible then
+ return self:draw()
+ end
+ end,
+ setPointB = function(self)
+ local posX, posY = mp.get_mouse_pos()
+ self.pointB:set_from_screen(posX, posY)
+ if self.visible then
+ return self:draw()
+ end
+ end,
+ cancel = function(self)
+ self:hide()
+ return self.callback(false, nil)
+ end,
+ finish = function(self)
+ local region = Region()
+ region:set_from_points(self.pointA, self.pointB)
+ self:hide()
+ return self.callback(true, region)
+ end,
+ draw_box = function(self, ass)
+ local region = Region()
+ region:set_from_points(self.pointA:to_screen(), self.pointB:to_screen())
+ local d = get_video_dimensions()
+ ass:new_event()
+ ass:pos(0, 0)
+ ass:append('{\\bord0}')
+ ass:append('{\\shad0}')
+ ass:append('{\\c&H000000&}')
+ ass:append('{\\alpha&H77}')
+ ass:draw_start()
+ ass:rect_cw(d.top_left.x, d.top_left.y, region.x, region.y + region.h)
+ ass:rect_cw(region.x, d.top_left.y, d.bottom_right.x, region.y)
+ ass:rect_cw(d.top_left.x, region.y + region.h, region.x + region.w, d.bottom_right.y)
+ ass:rect_cw(region.x + region.w, region.y, d.bottom_right.x, d.bottom_right.y)
+ return ass:draw_stop()
+ end,
+ draw = function(self)
+ local window = { }
+ window.w, window.h = mp.get_osd_size()
+ local ass = assdraw.ass_new()
+ self:draw_box(ass)
+ ass:new_event()
+ self:setup_text(ass)
+ ass:append(tostring(bold('Crop:')) .. "\\N")
+ ass:append(tostring(bold('1:')) .. " change point A (" .. tostring(self.pointA.x) .. ", " .. tostring(self.pointA.y) .. ")\\N")
+ ass:append(tostring(bold('2:')) .. " change point B (" .. tostring(self.pointB.x) .. ", " .. tostring(self.pointB.y) .. ")\\N")
+ ass:append(tostring(bold('r:')) .. " reset to whole screen\\N")
+ ass:append(tostring(bold('ESC:')) .. " cancel crop\\N")
+ ass:append(tostring(bold('ENTER:')) .. " confirm crop\\N")
+ return mp.set_osd_ass(window.w, window.h, ass.text)
+ end
+ }
+ _base_0.__index = _base_0
+ setmetatable(_base_0, _parent_0.__base)
+ _class_0 = setmetatable({
+ __init = function(self, callback, region)
+ self.pointA = VideoPoint()
+ self.pointB = VideoPoint()
+ self.keybinds = {
+ ["1"] = (function()
+ local _base_1 = self
+ local _fn_0 = _base_1.setPointA
+ return function(...)
+ return _fn_0(_base_1, ...)
+ end
+ end)(),
+ ["2"] = (function()
+ local _base_1 = self
+ local _fn_0 = _base_1.setPointB
+ return function(...)
+ return _fn_0(_base_1, ...)
+ end
+ end)(),
+ ["r"] = (function()
+ local _base_1 = self
+ local _fn_0 = _base_1.reset
+ return function(...)
+ return _fn_0(_base_1, ...)
+ end
+ end)(),
+ ["ESC"] = (function()
+ local _base_1 = self
+ local _fn_0 = _base_1.cancel
+ return function(...)
+ return _fn_0(_base_1, ...)
+ end
+ end)(),
+ ["ENTER"] = (function()
+ local _base_1 = self
+ local _fn_0 = _base_1.finish
+ return function(...)
+ return _fn_0(_base_1, ...)
+ end
+ end)()
+ }
+ self:reset()
+ self.callback = callback
+ if region and region:is_valid() then
+ self.pointA.x = region.x
+ self.pointA.y = region.y
+ self.pointB.x = region.x + region.w
+ self.pointB.y = region.y + region.h
+ end
+ end,
+ __base = _base_0,
+ __name = "CropPage",
+ __parent = _parent_0
+ }, {
+ __index = function(cls, name)
+ local val = rawget(_base_0, name)
+ if val == nil then
+ local parent = rawget(cls, "__parent")
+ if parent then
+ return parent[name]
+ end
+ else
+ return val
+ end
+ end,
+ __call = function(cls, ...)
+ local _self_0 = setmetatable({}, _base_0)
+ cls.__init(_self_0, ...)
+ return _self_0
+ end
+ })
+ _base_0.__class = _class_0
+ if _parent_0.__inherited then
+ _parent_0.__inherited(_parent_0, _class_0)
+ end
+ CropPage = _class_0
+end
+local Option
+do
+ local _class_0
+ local _base_0 = {
+ hasPrevious = function(self)
+ local _exp_0 = self.optType
+ if "bool" == _exp_0 then
+ return true
+ elseif "int" == _exp_0 then
+ if self.opts.min then
+ return self.value > self.opts.min
+ else
+ return true
+ end
+ elseif "list" == _exp_0 then
+ return self.value > 1
+ end
+ end,
+ hasNext = function(self)
+ local _exp_0 = self.optType
+ if "bool" == _exp_0 then
+ return true
+ elseif "int" == _exp_0 then
+ if self.opts.max then
+ return self.value < self.opts.max
+ else
+ return true
+ end
+ elseif "list" == _exp_0 then
+ return self.value < #self.opts.possibleValues
+ end
+ end,
+ leftKey = function(self)
+ local _exp_0 = self.optType
+ if "bool" == _exp_0 then
+ self.value = not self.value
+ elseif "int" == _exp_0 then
+ self.value = self.value - self.opts.step
+ if self.opts.min and self.opts.min > self.value then
+ self.value = self.opts.min
+ end
+ elseif "list" == _exp_0 then
+ if self.value > 1 then
+ self.value = self.value - 1
+ end
+ end
+ end,
+ rightKey = function(self)
+ local _exp_0 = self.optType
+ if "bool" == _exp_0 then
+ self.value = not self.value
+ elseif "int" == _exp_0 then
+ self.value = self.value + self.opts.step
+ if self.opts.max and self.opts.max < self.value then
+ self.value = self.opts.max
+ end
+ elseif "list" == _exp_0 then
+ if self.value < #self.opts.possibleValues then
+ self.value = self.value + 1
+ end
+ end
+ end,
+ getValue = function(self)
+ local _exp_0 = self.optType
+ if "bool" == _exp_0 then
+ return self.value
+ elseif "int" == _exp_0 then
+ return self.value
+ elseif "list" == _exp_0 then
+ local value, _
+ do
+ local _obj_0 = self.opts.possibleValues[self.value]
+ value, _ = _obj_0[1], _obj_0[2]
+ end
+ return value
+ end
+ end,
+ setValue = function(self, value)
+ local _exp_0 = self.optType
+ if "bool" == _exp_0 then
+ self.value = value
+ elseif "int" == _exp_0 then
+ self.value = value
+ elseif "list" == _exp_0 then
+ local set = false
+ for i, possiblePair in ipairs(self.opts.possibleValues) do
+ local possibleValue, _
+ possibleValue, _ = possiblePair[1], possiblePair[2]
+ if possibleValue == value then
+ set = true
+ self.value = i
+ break
+ end
+ end
+ if not set then
+ return msg.warn("Tried to set invalid value " .. tostring(value) .. " to " .. tostring(self.displayText) .. " option.")
+ end
+ end
+ end,
+ getDisplayValue = function(self)
+ local _exp_0 = self.optType
+ if "bool" == _exp_0 then
+ return self.value and "yes" or "no"
+ elseif "int" == _exp_0 then
+ if self.opts.altDisplayNames and self.opts.altDisplayNames[self.value] then
+ return self.opts.altDisplayNames[self.value]
+ else
+ return tostring(self.value)
+ end
+ elseif "list" == _exp_0 then
+ local value, displayValue
+ do
+ local _obj_0 = self.opts.possibleValues[self.value]
+ value, displayValue = _obj_0[1], _obj_0[2]
+ end
+ return displayValue or value
+ end
+ end,
+ draw = function(self, ass, selected)
+ if selected then
+ ass:append(tostring(bold(self.displayText)) .. ": ")
+ else
+ ass:append(tostring(self.displayText) .. ": ")
+ end
+ if self:hasPrevious() then
+ ass:append("◀ ")
+ end
+ ass:append(self:getDisplayValue())
+ if self:hasNext() then
+ ass:append(" ▶")
+ end
+ return ass:append("\\N")
+ end
+ }
+ _base_0.__index = _base_0
+ _class_0 = setmetatable({
+ __init = function(self, optType, displayText, value, opts)
+ self.optType = optType
+ self.displayText = displayText
+ self.opts = opts
+ self.value = 1
+ return self:setValue(value)
+ end,
+ __base = _base_0,
+ __name = "Option"
+ }, {
+ __index = _base_0,
+ __call = function(cls, ...)
+ local _self_0 = setmetatable({}, _base_0)
+ cls.__init(_self_0, ...)
+ return _self_0
+ end
+ })
+ _base_0.__class = _class_0
+ Option = _class_0
+end
+local EncodeOptionsPage
+do
+ local _class_0
+ local _parent_0 = Page
+ local _base_0 = {
+ getCurrentOption = function(self)
+ return self.options[self.currentOption][2]
+ end,
+ leftKey = function(self)
+ (self:getCurrentOption()):leftKey()
+ return self:draw()
+ end,
+ rightKey = function(self)
+ (self:getCurrentOption()):rightKey()
+ return self:draw()
+ end,
+ prevOpt = function(self)
+ self.currentOption = math.max(1, self.currentOption - 1)
+ return self:draw()
+ end,
+ nextOpt = function(self)
+ self.currentOption = math.min(#self.options, self.currentOption + 1)
+ return self:draw()
+ end,
+ confirmOpts = function(self)
+ for _, optPair in ipairs(self.options) do
+ local optName, opt
+ optName, opt = optPair[1], optPair[2]
+ options[optName] = opt:getValue()
+ end
+ self:hide()
+ return self.callback(true)
+ end,
+ cancelOpts = function(self)
+ self:hide()
+ return self.callback(false)
+ end,
+ draw = function(self)
+ local window_w, window_h = mp.get_osd_size()
+ local ass = assdraw.ass_new()
+ ass:new_event()
+ self:setup_text(ass)
+ ass:append(tostring(bold('Options:')) .. "\\N\\N")
+ for i, optPair in ipairs(self.options) do
+ local opt = optPair[2]
+ opt:draw(ass, self.currentOption == i)
+ end
+ ass:append("\\N▲ / ▼: navigate\\N")
+ ass:append(tostring(bold('ENTER:')) .. " confirm options\\N")
+ ass:append(tostring(bold('ESC:')) .. " cancel\\N")
+ return mp.set_osd_ass(window_w, window_h, ass.text)
+ end
+ }
+ _base_0.__index = _base_0
+ setmetatable(_base_0, _parent_0.__base)
+ _class_0 = setmetatable({
+ __init = function(self, callback)
+ self.callback = callback
+ self.currentOption = 1
+ local scaleHeightOpts = {
+ possibleValues = {
+ {
+ -1,
+ "no"
+ },
+ {
+ 240
+ },
+ {
+ 360
+ },
+ {
+ 480
+ },
+ {
+ 720
+ },
+ {
+ 1080
+ },
+ {
+ 1440
+ },
+ {
+ 2160
+ }
+ }
+ }
+ local filesizeOpts = {
+ step = 250,
+ min = 0,
+ altDisplayNames = {
+ [0] = "0 (constant quality)"
+ }
+ }
+ local formatIds = {
+ "webm-vp8",
+ "webm-vp9",
+ "mp4",
+ "raw"
+ }
+ local formatOpts = {
+ possibleValues = (function()
+ local _accum_0 = { }
+ local _len_0 = 1
+ for _index_0 = 1, #formatIds do
+ local fId = formatIds[_index_0]
+ _accum_0[_len_0] = {
+ fId,
+ formats[fId].displayName
+ }
+ _len_0 = _len_0 + 1
+ end
+ return _accum_0
+ end)()
+ }
+ self.options = {
+ {
+ "output_format",
+ Option("list", "Output Format", options.output_format, formatOpts)
+ },
+ {
+ "twopass",
+ Option("bool", "Two Pass", options.twopass)
+ },
+ {
+ "apply_current_filters",
+ Option("bool", "Apply Current Video Filters", options.apply_current_filters)
+ },
+ {
+ "scale_height",
+ Option("list", "Scale Height", options.scale_height, scaleHeightOpts)
+ },
+ {
+ "strict_filesize_constraint",
+ Option("bool", "Strict Filesize Constraint", options.strict_filesize_constraint)
+ },
+ {
+ "write_filename_on_metadata",
+ Option("bool", "Write Filename on Metadata", options.write_filename_on_metadata)
+ },
+ {
+ "target_filesize",
+ Option("int", "Target Filesize", options.target_filesize, filesizeOpts)
+ }
+ }
+ self.keybinds = {
+ ["LEFT"] = (function()
+ local _base_1 = self
+ local _fn_0 = _base_1.leftKey
+ return function(...)
+ return _fn_0(_base_1, ...)
+ end
+ end)(),
+ ["RIGHT"] = (function()
+ local _base_1 = self
+ local _fn_0 = _base_1.rightKey
+ return function(...)
+ return _fn_0(_base_1, ...)
+ end
+ end)(),
+ ["UP"] = (function()
+ local _base_1 = self
+ local _fn_0 = _base_1.prevOpt
+ return function(...)
+ return _fn_0(_base_1, ...)
+ end
+ end)(),
+ ["DOWN"] = (function()
+ local _base_1 = self
+ local _fn_0 = _base_1.nextOpt
+ return function(...)
+ return _fn_0(_base_1, ...)
+ end
+ end)(),
+ ["ENTER"] = (function()
+ local _base_1 = self
+ local _fn_0 = _base_1.confirmOpts
+ return function(...)
+ return _fn_0(_base_1, ...)
+ end
+ end)(),
+ ["ESC"] = (function()
+ local _base_1 = self
+ local _fn_0 = _base_1.cancelOpts
+ return function(...)
+ return _fn_0(_base_1, ...)
+ end
+ end)()
+ }
+ end,
+ __base = _base_0,
+ __name = "EncodeOptionsPage",
+ __parent = _parent_0
+ }, {
+ __index = function(cls, name)
+ local val = rawget(_base_0, name)
+ if val == nil then
+ local parent = rawget(cls, "__parent")
+ if parent then
+ return parent[name]
+ end
+ else
+ return val
+ end
+ end,
+ __call = function(cls, ...)
+ local _self_0 = setmetatable({}, _base_0)
+ cls.__init(_self_0, ...)
+ return _self_0
+ end
+ })
+ _base_0.__class = _class_0
+ if _parent_0.__inherited then
+ _parent_0.__inherited(_parent_0, _class_0)
+ end
+ EncodeOptionsPage = _class_0
+end
+local PreviewPage
+do
+ local _class_0
+ local _parent_0 = Page
+ local _base_0 = {
+ prepare = function(self)
+ local vf = mp.get_property_native("vf")
+ vf[#vf + 1] = {
+ name = "sub"
+ }
+ if self.region:is_valid() then
+ vf[#vf + 1] = {
+ name = "crop",
+ params = {
+ w = tostring(self.region.w),
+ h = tostring(self.region.h),
+ x = tostring(self.region.x),
+ y = tostring(self.region.y)
+ }
+ }
+ end
+ mp.set_property_native("vf", vf)
+ if self.startTime > -1 and self.endTime > -1 then
+ mp.set_property_native("ab-loop-a", self.startTime)
+ mp.set_property_native("ab-loop-b", self.endTime)
+ mp.set_property_native("time-pos", self.startTime)
+ end
+ return mp.set_property_native("pause", false)
+ end,
+ dispose = function(self)
+ mp.set_property("ab-loop-a", "no")
+ mp.set_property("ab-loop-b", "no")
+ for prop, value in pairs(self.originalProperties) do
+ mp.set_property_native(prop, value)
+ end
+ end,
+ draw = function(self)
+ local window_w, window_h = mp.get_osd_size()
+ local ass = assdraw.ass_new()
+ ass:new_event()
+ self:setup_text(ass)
+ ass:append("Press " .. tostring(bold('ESC')) .. " to exit preview.\\N")
+ return mp.set_osd_ass(window_w, window_h, ass.text)
+ end,
+ cancel = function(self)
+ self:hide()
+ return self.callback()
+ end
+ }
+ _base_0.__index = _base_0
+ setmetatable(_base_0, _parent_0.__base)
+ _class_0 = setmetatable({
+ __init = function(self, callback, region, startTime, endTime)
+ self.callback = callback
+ self.originalProperties = {
+ ["vf"] = mp.get_property_native("vf"),
+ ["time-pos"] = mp.get_property_native("time-pos"),
+ ["pause"] = mp.get_property_native("pause")
+ }
+ self.keybinds = {
+ ["ESC"] = (function()
+ local _base_1 = self
+ local _fn_0 = _base_1.cancel
+ return function(...)
+ return _fn_0(_base_1, ...)
+ end
+ end)()
+ }
+ self.region = region
+ self.startTime = startTime
+ self.endTime = endTime
+ self.isLoop = false
+ end,
+ __base = _base_0,
+ __name = "PreviewPage",
+ __parent = _parent_0
+ }, {
+ __index = function(cls, name)
+ local val = rawget(_base_0, name)
+ if val == nil then
+ local parent = rawget(cls, "__parent")
+ if parent then
+ return parent[name]
+ end
+ else
+ return val
+ end
+ end,
+ __call = function(cls, ...)
+ local _self_0 = setmetatable({}, _base_0)
+ cls.__init(_self_0, ...)
+ return _self_0
+ end
+ })
+ _base_0.__class = _class_0
+ if _parent_0.__inherited then
+ _parent_0.__inherited(_parent_0, _class_0)
+ end
+ PreviewPage = _class_0
+end
+local MainPage
+do
+ local _class_0
+ local _parent_0 = Page
+ local _base_0 = {
+ setStartTime = function(self)
+ self.startTime = mp.get_property_number("time-pos")
+ if self.visible then
+ self:clear()
+ return self:draw()
+ end
+ end,
+ setEndTime = function(self)
+ self.endTime = mp.get_property_number("time-pos")
+ if self.visible then
+ self:clear()
+ return self:draw()
+ end
+ end,
+ setupStartAndEndTimes = function(self)
+ if mp.get_property_native("duration") then
+ self.startTime = 0
+ self.endTime = mp.get_property_native("duration")
+ else
+ self.startTime = -1
+ self.endTime = -1
+ end
+ if self.visible then
+ self:clear()
+ return self:draw()
+ end
+ end,
+ draw = function(self)
+ local window_w, window_h = mp.get_osd_size()
+ local ass = assdraw.ass_new()
+ ass:new_event()
+ self:setup_text(ass)
+ ass:append(tostring(bold('WebM maker')) .. "\\N\\N")
+ ass:append(tostring(bold('c:')) .. " crop\\N")
+ ass:append(tostring(bold('1:')) .. " set start time (current is " .. tostring(seconds_to_time_string(self.startTime)) .. ")\\N")
+ ass:append(tostring(bold('2:')) .. " set end time (current is " .. tostring(seconds_to_time_string(self.endTime)) .. ")\\N")
+ ass:append(tostring(bold('o:')) .. " change encode options\\N")
+ ass:append(tostring(bold('p:')) .. " preview\\N")
+ ass:append(tostring(bold('e:')) .. " encode\\N\\N")
+ ass:append(tostring(bold('ESC:')) .. " close\\N")
+ return mp.set_osd_ass(window_w, window_h, ass.text)
+ end,
+ onUpdateCropRegion = function(self, updated, newRegion)
+ if updated then
+ self.region = newRegion
+ end
+ return self:show()
+ end,
+ crop = function(self)
+ self:hide()
+ local cropPage = CropPage((function()
+ local _base_1 = self
+ local _fn_0 = _base_1.onUpdateCropRegion
+ return function(...)
+ return _fn_0(_base_1, ...)
+ end
+ end)(), self.region)
+ return cropPage:show()
+ end,
+ onOptionsChanged = function(self, updated)
+ return self:show()
+ end,
+ changeOptions = function(self)
+ self:hide()
+ local encodeOptsPage = EncodeOptionsPage((function()
+ local _base_1 = self
+ local _fn_0 = _base_1.onOptionsChanged
+ return function(...)
+ return _fn_0(_base_1, ...)
+ end
+ end)())
+ return encodeOptsPage:show()
+ end,
+ onPreviewEnded = function(self)
+ return self:show()
+ end,
+ preview = function(self)
+ self:hide()
+ local previewPage = PreviewPage((function()
+ local _base_1 = self
+ local _fn_0 = _base_1.onPreviewEnded
+ return function(...)
+ return _fn_0(_base_1, ...)
+ end
+ end)(), self.region, self.startTime, self.endTime)
+ return previewPage:show()
+ end,
+ encode = function(self)
+ self:hide()
+ if self.startTime < 0 then
+ message("No start time, aborting")
+ return
+ end
+ if self.endTime < 0 then
+ message("No end time, aborting")
+ return
+ end
+ if self.startTime >= self.endTime then
+ message("Start time is ahead of end time, aborting")
+ return
+ end
+ return encode(self.region, self.startTime, self.endTime)
+ end
+ }
+ _base_0.__index = _base_0
+ setmetatable(_base_0, _parent_0.__base)
+ _class_0 = setmetatable({
+ __init = function(self)
+ self.keybinds = {
+ ["c"] = (function()
+ local _base_1 = self
+ local _fn_0 = _base_1.crop
+ return function(...)
+ return _fn_0(_base_1, ...)
+ end
+ end)(),
+ ["1"] = (function()
+ local _base_1 = self
+ local _fn_0 = _base_1.setStartTime
+ return function(...)
+ return _fn_0(_base_1, ...)
+ end
+ end)(),
+ ["2"] = (function()
+ local _base_1 = self
+ local _fn_0 = _base_1.setEndTime
+ return function(...)
+ return _fn_0(_base_1, ...)
+ end
+ end)(),
+ ["o"] = (function()
+ local _base_1 = self
+ local _fn_0 = _base_1.changeOptions
+ return function(...)
+ return _fn_0(_base_1, ...)
+ end
+ end)(),
+ ["p"] = (function()
+ local _base_1 = self
+ local _fn_0 = _base_1.preview
+ return function(...)
+ return _fn_0(_base_1, ...)
+ end
+ end)(),
+ ["e"] = (function()
+ local _base_1 = self
+ local _fn_0 = _base_1.encode
+ return function(...)
+ return _fn_0(_base_1, ...)
+ end
+ end)(),
+ ["ESC"] = (function()
+ local _base_1 = self
+ local _fn_0 = _base_1.hide
+ return function(...)
+ return _fn_0(_base_1, ...)
+ end
+ end)()
+ }
+ self.startTime = -1
+ self.endTime = -1
+ self.region = Region()
+ end,
+ __base = _base_0,
+ __name = "MainPage",
+ __parent = _parent_0
+ }, {
+ __index = function(cls, name)
+ local val = rawget(_base_0, name)
+ if val == nil then
+ local parent = rawget(cls, "__parent")
+ if parent then
+ return parent[name]
+ end
+ else
+ return val
+ end
+ end,
+ __call = function(cls, ...)
+ local _self_0 = setmetatable({}, _base_0)
+ cls.__init(_self_0, ...)
+ return _self_0
+ end
+ })
+ _base_0.__class = _class_0
+ if _parent_0.__inherited then
+ _parent_0.__inherited(_parent_0, _class_0)
+ end
+ MainPage = _class_0
+end
+monitor_dimensions()
+local mainPage = MainPage()
+mp.add_key_binding(options.keybind, "display-webm-encoder", (function()
+ local _base_0 = mainPage
+ local _fn_0 = _base_0.show
+ return function(...)
+ return _fn_0(_base_0, ...)
+ end
+end)(), {
+ repeatable = false
+})
+return mp.register_event("file-loaded", (function()
+ local _base_0 = mainPage
+ local _fn_0 = _base_0.setupStartAndEndTimes
+ return function(...)
+ return _fn_0(_base_0, ...)
+ end
+end)())
diff --git a/.config/ncmpcpp/bindings b/.config/ncmpcpp/bindings
@@ -0,0 +1,12 @@
+def_key "j"
+ scroll_down
+
+def_key "k"
+ scroll_up
+
+def_key "h"
+ previous_column
+
+def_key "l"
+ next_column
+
diff --git a/.config/ncmpcpp/config b/.config/ncmpcpp/config
@@ -0,0 +1,48 @@
+ncmpcpp_directory = "~/.config/ncmpcpp"
+user_interface = alternative
+
+clock_display_seconds = no
+
+# Search
+empty_tag_color = 6
+default_find_mode = wrapped
+
+# Library
+media_library_primary_tag = album_artist
+current_item_prefix = "$(6)$r"
+current_item_suffix = "$/r$(end)"
+main_window_color = 13
+current_item_inactive_column_prefix = "$3"
+current_item_inactive_column_suffix = "$9"
+cyclic_scrolling = yes
+
+# Playlist
+song_columns_list_format = "(10)[5]{l} (20)[3]{a} (30)[6]{b} (40)[13]{t}"
+now_playing_prefix = "$b$(16)>>$9"
+now_playing_suffix = "$/b"
+playlist_display_mode = "columns"
+autocenter_mode = "yes"
+centered_cursor = "yes"
+playlist_disable_highlight_delay = "1"
+
+# Top Bar
+display_bitrate = "yes"
+titles_visibility = "no"
+header_visibility = "no"
+alternative_header_first_line_format = $b$1$aqqu$/a$9 $(13){%t}|{%f}$9 $1$atqq$/a$9$/b
+alternative_header_second_line_format = {$3$b%a$/b$9}|{$3$b%A$/b$9}|{%D}
+
+# Progress bar
+progressbar_look = "▄▄ "
+progressbar_elapsed_color = "5"
+
+# Lyrics
+lyrics_directory = "~/.config/ncmpcpp/lyrics"
+follow_now_playing_lyrics = no
+fetch_lyrics_for_current_song_in_background = no
+
+#mpd_host = "localhost"
+mpd_host = "~/.config/mpd/socket"
+#mpd_port = "6600"
+mpd_connection_timeout = "5"
+mpd_music_dir = "~/media/mus"
diff --git a/.kshrc b/.kshrc
@@ -3,31 +3,32 @@ set -o vi
UNAME="$(uname -s)"
-# Environment Variables
-if [ $UNAME == "OpenBSD" ]; then
- ulimit -c 0
- MPD_HOST="$HOME/.config/mpd/socket"
- export MPD_HOST
-fi
-
-if [ $UNAME == "Linux" ]; then
- if [ -z "$DISPLAY" ] && [ "$(tty)" = "/dev/tty1" ]; then
- startx
- fi
-fi
-
+#########################
+# Environment Variables #
+#########################
[ -f ~/.secrets ] && . ~/.secrets
PS1='$(tput bold)\W \$$(tput sgr0) '
if [ -n "$SSH_CONNECTION" ]; then
PS1="$(hostname) $PS1"
fi
+MPD_HOST="$HOME/.config/mpd/socket"
HISTFILE=$HOME/.history
HISTSIZE=10000
HISTCONTROL=ignoredups:ignorespace
EDITOR="vi"
VISUAL="vi"
-export HISTCONTROL HISTFILE HISTSIZE EDITOR VISUAL
+export MPD_HOST HISTCONTROL HISTFILE HISTSIZE EDITOR VISUAL
+
+if [ $UNAME == "OpenBSD" ]; then
+ ulimit -c 0
+fi
+
+if [ $UNAME == "Linux" ]; then
+ if [ -z "$DISPLAY" ] && [ "$(tty)" = "/dev/tty1" ]; then
+ startx
+ fi
+fi
###########
# Aliases #
@@ -84,8 +85,8 @@ if [ -f /etc/gentoo-release ]; then
alias c++='c++ -O3 -pipe -Wall -Werror'
alias emerge='doas /usr/bin/emerge'
alias ifconfig='doas /bin/ifconfig'
- alias -d tag=~/apv/mus/tag
- alias -d cu=/mnt/reah/cute-stuff
+ alias -d tag=~/media/mus/tag
+ alias -d cu=~/media/pic/cute-stuff
alias -d dn=~/downloads/normal
fi