From fd03b7c16a9bb9462fa5949fe5ee811d62765da8 Mon Sep 17 00:00:00 2001 From: Guido Cella Date: Mon, 6 Jan 2025 00:05:33 +0100 Subject: [PATCH] select.lua: select from the watch history with g-h Implement saving watched paths and selecting them. --osd-playlist-entry determines whether titles and/or filenames are shown. But unlike in show-text ${playlist} and select-playlist, "file" and "both" print full paths because history is much more likely to have files from completely different directories, so showing the directory conveys where files are located. This is particularly helpful for filenames like 1.jpg. The last entry in the selector deletes the history file, as requested by Samillion. The history could be formatted as CSV, but this requires escaping the separator in the fields and doesn't work with paths and titles with newlines, or as JSON, but it is inefficient to reread and rewrite the whole history on each new file, and doing so overwrites the history with an empty file when writing without disk space left. I went with an hybrid of one JSON array per line to get the best of both worlds. And I discovered afterwards that this was an existing thing called NDJSON or JSONL. Since there are these 2 competing standards it is not clear if the file extension should be ndjson or jsonl, so I just used txt. watch_history_path is awkwardly documented along with the key binding because I don't think it's worth adding a select.lua section to the manual just for this. I will add it and move it there if I add more script-opts in the future. --- DOCS/man/mpv.rst | 6 +++ etc/input.conf | 1 + player/lua/select.lua | 93 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 100 insertions(+) diff --git a/DOCS/man/mpv.rst b/DOCS/man/mpv.rst index 7c67cf0d96d18..b4abf4e4519af 100644 --- a/DOCS/man/mpv.rst +++ b/DOCS/man/mpv.rst @@ -328,6 +328,12 @@ g-l g-d Select an audio device. +g-h + Select a file from the watch history. Requires + ``--script-opt=select-save_watch_history=yes``. The history path is + configured with ``--script-opt=select-watch_history_path=...``, and defaults + to ``~~state/watch_history.txt`` (see `PATHS`_). + g-w Select a file from watch later config files (see `RESUMING PLAYBACK`_) to resume playing. Requires ``--write-filename-in-watch-later-config``. This diff --git a/etc/input.conf b/etc/input.conf index 2faa88c233b28..8dbd42d53555f 100644 --- a/etc/input.conf +++ b/etc/input.conf @@ -187,6 +187,7 @@ #g-e script-binding select/select-edition #g-l script-binding select/select-subtitle-line #g-d script-binding select/select-audio-device +#g-h script-binding select/select-history #g-w script-binding select/select-watch-later #g-b script-binding select/select-binding #g-r script-binding select/show-properties diff --git a/player/lua/select.lua b/player/lua/select.lua index 7f20602aff098..2db92f6fb9645 100644 --- a/player/lua/select.lua +++ b/player/lua/select.lua @@ -18,6 +18,13 @@ License along with mpv. If not, see . local utils = require "mp.utils" local input = require "mp.input" +local options = { + save_watch_history = false, + watch_history_path = "~~state/watch_history.txt", +} + +require "mp.options".read_options(options) + local function show_warning(message) mp.msg.warn(message) if mp.get_property_native("vo-configured") then @@ -353,6 +360,92 @@ mp.add_key_binding(nil, "select-audio-device", function () }) end) +local history_file_path = mp.command_native({"expand-path", options.watch_history_path}) + +local function save_to_watch_history() + local history_file, error_message = io.open(history_file_path, "a") + if not history_file then + show_error("Failed to write the watch history: " .. error_message) + return + end + + local path = mp.command_native({"normalize-path", mp.get_property("path")}) + local title = mp.get_property("playlist/" .. mp.get_property("playlist-pos") .. "/title") + + history_file:write(utils.format_json({os.time(), path, title}) .. "\n") + history_file:close() +end + +if options.save_watch_history then + mp.register_event("file-loaded", save_to_watch_history) +end + +local function add_history_entry(line, items, paths, osd_playlist_entry) + local entry = utils.parse_json(line) + + if not entry then + mp.msg.warn(line .. " in " .. history_file_path .. " is not valid JSON.") + return + end + + local time, path, title = unpack(entry) + + local status, date = pcall(os.date, "(%Y-%m-%d %H:%M) ", time) + + if not status or not path then + mp.msg.warn(line .. " in " .. history_file_path .. " has invalid data.") + return + end + + local item = path + if title and osd_playlist_entry == "title" then + item = title + elseif title and osd_playlist_entry == "both" then + item = title .. " (" .. path .. ")" + end + + table.insert(items, 1, date .. item) + table.insert(paths, 1, path) +end + +mp.add_key_binding(nil, "select-history", function () + local history_file, error_message = io.open(history_file_path) + if not history_file then + show_warning(options.save_watch_history + and error_message + or "Enable --script-opt=select-save_watch_history=yes") + return + end + + local items = {} + local paths = {} + local osd_playlist_entry = mp.get_property("osd-playlist-entry") + + for line in history_file:lines() do + add_history_entry(line, items, paths, osd_playlist_entry) + end + + items[#items+1] = "Clear the history" + + input.select({ + prompt = "Select a file:", + items = items, + submit = function (i) + if paths[i] then + mp.commandv("loadfile", paths[i]) + return + end + + error_message = select(2, os.remove(history_file_path)) + if error_message then + show_error(error_message) + else + mp.osd_message("History cleared.") + end + end, + }) +end) + mp.add_key_binding(nil, "select-watch-later", function () local watch_later_dir = mp.get_property("current-watch-later-dir")