From a2402cbf16b1b636556e95fefda25f99cb12da2d Mon Sep 17 00:00:00 2001 From: Bernd Date: Fri, 3 Jan 2025 22:55:07 +0500 Subject: [PATCH] first commit --- README.md | 23 +++ recent.lua | 318 ++++++++++++++++++++++++++++++++++++++++ script-opts/recent.conf | 18 +++ 3 files changed, 359 insertions(+) create mode 100644 README.md create mode 100644 recent.lua create mode 100644 script-opts/recent.conf diff --git a/README.md b/README.md new file mode 100644 index 0000000..314e2f2 --- /dev/null +++ b/README.md @@ -0,0 +1,23 @@ +# recent.lua +#### Recently played menu +Logs played files to a history log file with an interactive 'recently played' menu that reads from the log. Allows for automatic or manual logging if you want a file bookmark menu instead. + + +![](../assets/recent.png) +### Menu controls: +* Default display hotkey is **`` ` ``** +* Keyboard: + * `UP`/`DOWN` to move selection + * `ENTER` to load highlighted entry + * `DEL` to delete highlighted entry + * `0`-`9` for quick selection + * `ESC` to exit +* Mouse (if turned on): + * `WHEEL_UP`/`WHEEL_DOWN` to move selection + * `MBTN_MID` to load highlighted entry + * `MBTN_RIGHT` to exit +#### Modify settings through `script-opts/recent.conf` +* Log path is `history.log` inside whatever config directory mpv reads +* Disabling `auto_save` makes it only save with a keybind + * No save key is bound by default, see `script-opts` +* See comments in the script for more info diff --git a/recent.lua b/recent.lua new file mode 100644 index 0000000..8aa2151 --- /dev/null +++ b/recent.lua @@ -0,0 +1,318 @@ +local o = { + -- Automatically save to log, otherwise only saves when requested + -- you need to bind a save key if you disable it + auto_save = true, + save_bind = "", + -- When automatically saving, skip entries with playback positions + -- past this value, in percent. 100 saves all, around 95 is + -- good for skipping videos that have reached final credits. + auto_save_skip_past = 100, + -- Runs automatically when --idle + auto_run_idle = true, + -- Write watch later for current file when switching + write_watch_later = true, + -- Display menu bind + display_bind = "`", + -- Middle click: Select; Right click: Exit; + -- Scroll wheel: Up/Down + mouse_controls = true, + -- Reads from config directory or an absolute path + log_path = "history.log", + -- Date format in the log (see lua date formatting) + date_format = "%d/%m/%y %X", + -- Show file paths instead of media-title + show_paths = false, + -- Split paths to only show the file or show the full path + split_paths = true, + -- Font settings + font_scale = 50, + border_size = 0.7, + -- Highlight color in BGR hexadecimal + hi_color = "H46CFFF", + -- Draw ellipsis at start/end denoting ommited entries + ellipsis = false, + -- Don't log certain media types + ignore_audio = true, + ignore_picture = true, + ignore_video = false +} +(require "mp.options").read_options(o) +local utils = require("mp.utils") +o.log_path = utils.join_path(mp.find_config_file("."), o.log_path) + +local cur_title, cur_path +local list_drawn = false + +function get_content_type() + local track_list = mp.get_property_native("track-list") + local file_type = "" + for _, track in ipairs(track_list) do + if track.type == "audio" then file_type = "audio" + elseif not track.albumart and (track["demux-fps"] or 2) > 1 then + file_type = "video" + elseif track.type == "video" and not track.albumart and track["demux-fps"] == 1 then + file_type = "picture" + end + end + return file_type +end + +function esc_string(str) + return str:gsub("([%p])", "%%%1") +end + +function get_path() + local path = mp.get_property("path") + local title = mp.get_property("media-title"):gsub("\"", "") + if not path then return end + if path:find("http.?://") or path:find("magnet:%?") or path:find("rtmp://") then + return title, path + else + return title, utils.join_path(mp.get_property("working-directory"), path) + end +end + +function unbind() + if o.mouse_controls then + mp.remove_key_binding("recent-WUP") + mp.remove_key_binding("recent-WDOWN") + mp.remove_key_binding("recent-MMID") + mp.remove_key_binding("recent-MRIGHT") + end + mp.remove_key_binding("recent-UP") + mp.remove_key_binding("recent-DOWN") + mp.remove_key_binding("recent-ENTER") + mp.remove_key_binding("recent-1") + mp.remove_key_binding("recent-2") + mp.remove_key_binding("recent-3") + mp.remove_key_binding("recent-4") + mp.remove_key_binding("recent-5") + mp.remove_key_binding("recent-6") + mp.remove_key_binding("recent-7") + mp.remove_key_binding("recent-8") + mp.remove_key_binding("recent-9") + mp.remove_key_binding("recent-0") + mp.remove_key_binding("recent-ESC") + mp.remove_key_binding("recent-DEL") + mp.set_osd_ass(0, 0, "") + list_drawn = false +end + +function read_log(func) + local f = io.open(o.log_path, "r") + if not f then return end + local list = {} + for line in f:lines() do + table.insert(list, (func(line))) + end + f:close() + return list +end + +function read_log_table() + return read_log(function(line) + local t, p + t, p = line:match("^.-\"(.-)\" | (.*)$") + return {title = t, path = p} + end) +end + +-- Write path to log on file end +-- removing duplicates along the way +function write_log(delete) + if not cur_path then return end + if o.ignore_audio and get_content_type() == "audio" then return + elseif o.ignore_picture and get_content_type() == "picture" then return + elseif o.ignore_video and get_content_type() == "video" then return end + local content = read_log(function(line) + if line:find(esc_string(cur_path)) then + return nil + else + return line + end + end) + f = io.open(o.log_path, "w+") + if content then + for i=1, #content do + f:write(("%s\n"):format(content[i])) + end + end + if not delete then + f:write(("[%s] \"%s\" | %s\n"):format(os.date(o.date_format), cur_title, cur_path)) + end + f:close() +end + +-- Display list on OSD and terminal +function draw_list(list, start, choice) + local msg = string.format("{\\fscx%f}{\\fscy%f}{\\bord%f}", + o.font_scale, o.font_scale, o.border_size) + local hi_start = string.format("{\\1c&H%s}", o.hi_color) + local hi_end = "{\\1c&HFFFFFF}" + if o.ellipsis then + if start ~= 0 then + msg = msg.."..." + end + msg = msg.."\\h\\N\\N" + end + local size = #list + for i=1, math.min(10, size-start), 1 do + local key = i % 10 + local p + if o.show_paths then + if o.split_paths and not list[size-start-i+1].path:find("^http.?://") then + _, p = utils.split_path(list[size-start-i+1].path) + else + p = list[size-start-i+1].title or "" + end + else + p = list[size-start-i+1].title or list[size-start-i+1].path or "" + end + if i == choice+1 then + msg = msg..hi_start.."("..key..") "..p.."\\N\\N"..hi_end + else + msg = msg.."("..key..") "..p.."\\N\\N" + end + if not list_drawn then + print("("..key..") "..p) + end + end + if o.ellipsis and start+10 < size then + msg = msg .."..." + end + mp.set_osd_ass(0, 0, msg) +end + +-- Handle up/down keys +function select(list, start, choice, inc) + choice = choice + inc + if choice < 0 then + choice = choice + 1 + start = start + inc + elseif choice >= math.min(#list, 10) then + choice = choice - 1 + start = start + inc + end + if start > math.max(#list-10, 0) then + start = start - 1 + elseif start < 0 then + start = start + 1 + end + draw_list(list, start, choice) + return start, choice +end + +-- Delete selected entry from the log +function delete(list, start, choice) + local playing_path = cur_path + cur_path = list[#list-start-choice].path + if not cur_path then + print("Failed to delete") + return + end + write_log(true) + print("Deleted \""..cur_path.."\"") + cur_path = playing_path +end + +-- Load file and remove binds +function load(list, start, choice) + unbind() + if start+choice >= #list then return end + if o.write_watch_later then + mp.command("write-watch-later-config") + end + mp.commandv("loadfile", list[#list-start-choice].path, "replace") +end + +-- Display list and add keybinds +function display_list() + if list_drawn then + unbind() + return + end + local list = read_log_table() + if not list or not list[1] then + mp.osd_message("Log empty") + return + end + local choice = 0 + local start = 0 + draw_list(list, start, choice) + list_drawn = true + + mp.add_forced_key_binding("UP", "recent-UP", function() + start, choice = select(list, start, choice, -1) + end, {repeatable=true}) + mp.add_forced_key_binding("DOWN", "recent-DOWN", function() + start, choice = select(list, start, choice, 1) + end, {repeatable=true}) + mp.add_forced_key_binding("ENTER", "recent-ENTER", function() + load(list, start, choice) + end) + mp.add_forced_key_binding("DEL", "recent-DEL", function() + delete(list, start, choice) + list = read_log_table() + if not list or not list[1] then + unbind() + return + end + start, choice = select(list, start, choice, 0) + end) + if o.mouse_controls then + mp.add_forced_key_binding("WHEEL_UP", "recent-WUP", function() + start, choice = select(list, start, choice, -1) + end) + mp.add_forced_key_binding("WHEEL_DOWN", "recent-WDOWN", function() + start, choice = select(list, start, choice, 1) + end) + mp.add_forced_key_binding("MBTN_MID", "recent-MMID", function() + load(list, start, choice) + end) + mp.add_forced_key_binding("MBTN_RIGHT", "recent-MRIGHT", function() + unbind() + end) + end + mp.add_forced_key_binding("1", "recent-1", function() load(list, start, 0) end) + mp.add_forced_key_binding("2", "recent-2", function() load(list, start, 1) end) + mp.add_forced_key_binding("3", "recent-3", function() load(list, start, 2) end) + mp.add_forced_key_binding("4", "recent-4", function() load(list, start, 3) end) + mp.add_forced_key_binding("5", "recent-5", function() load(list, start, 4) end) + mp.add_forced_key_binding("6", "recent-6", function() load(list, start, 5) end) + mp.add_forced_key_binding("7", "recent-7", function() load(list, start, 6) end) + mp.add_forced_key_binding("8", "recent-8", function() load(list, start, 7) end) + mp.add_forced_key_binding("9", "recent-9", function() load(list, start, 8) end) + mp.add_forced_key_binding("0", "recent-0", function() load(list, start, 9) end) + mp.add_forced_key_binding("ESC", "recent-ESC", function() unbind() end) +end + +if o.auto_save then + -- Using hook, as at the "end-file" event the playback position info is already unset. + mp.add_hook("on_unload", 9, function () + local pos = mp.get_property("percent-pos") + if not pos then return end + if tonumber(pos) <= o.auto_save_skip_past then + write_log(false) + else + write_log(true) + end + end) +else + mp.add_key_binding(o.save_bind, "recent-save", function() + write_log(false) + mp.osd_message("Saved entry to log") + end) +end + +if o.auto_run_idle then + mp.observe_property("idle-active", "bool", function(_, v) + if v then display_list() end + end) +end + +mp.register_event("file-loaded", function() + unbind() + cur_title, cur_path = get_path() +end) + +mp.add_key_binding(o.display_bind, "display-recent", display_list) diff --git a/script-opts/recent.conf b/script-opts/recent.conf new file mode 100644 index 0000000..b0f07c7 --- /dev/null +++ b/script-opts/recent.conf @@ -0,0 +1,18 @@ +auto_save=yes +save_bind= +auto_save_skip_past= +auto_run_idle=yes +write_watch_later=yes +display_bind=` +mouse_controls=yes +log_path=history.log +date_format=%d/%m/%y %X +show_paths=no +split_paths=yes +font_scale=50 +border_size=0.7 +hi_color=H46CFFF +ellipsis=no +ignore_audio=no +ignore_picture=no +ignore_video=no