Skip to content

Commit

Permalink
select.lua: select from the watch history with g-h
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
guidocella committed Jan 5, 2025
1 parent 7a59a12 commit fd03b7c
Show file tree
Hide file tree
Showing 3 changed files with 100 additions and 0 deletions.
6 changes: 6 additions & 0 deletions DOCS/man/mpv.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions etc/input.conf
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
93 changes: 93 additions & 0 deletions player/lua/select.lua
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@ License along with mpv. If not, see <http://www.gnu.org/licenses/>.
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
Expand Down Expand Up @@ -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")

Expand Down

0 comments on commit fd03b7c

Please sign in to comment.