Last active
January 6, 2025 01:00
-
-
Save rtldg/04057fea36f8b4c9333fcb8107f3944a to your computer and use it in GitHub Desktop.
better screenshot filenames for mpv and also helps with long screenshot filenames
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| -- SPDX-License-Identifier: WTFPL | |
| -- Copyright 2025 rtldg <rtldg@protonmail.com> | |
| --[[ | |
| basically this: | |
| screenshot-template="%F %P %n %{sub-text}" | |
| Only includes sub-text in filename if the picture includes subs. | |
| Things this basically does: | |
| - Trim screenshot filenames to remove trailing '.', '_', and (c <= ' '). | |
| - #define ILLEGAL_FILENAME_CHARS "?\"/\\<>*|:" | |
| - ~~prepend \\?\ to filenames...~~ (NOT DONE) | |
| - trim basenames to be less than 251. (trim invalid utf-8 too) | |
| ]] | |
| mp.msg = require("mp.msg") | |
| mp.utils = require("mp.utils") | |
| --------------------------- | |
| ----- STATE VARIABLES ----- | |
| local screenshot_counter = 1 | |
| local current_filename = "" | |
| --------------------------- | |
| local function clean_string(s) | |
| local ILLEGAL_FILENAME_CHARS = "?\"/\\<>*|:" | |
| local clean = "" | |
| for c in s:gmatch(".") do | |
| if string.find(ILLEGAL_FILENAME_CHARS, c, 1, true) or string.byte(c) < 32 then | |
| clean = clean .. "_" | |
| else | |
| clean = clean .. c | |
| end | |
| end | |
| clean = clean:reverse() | |
| while clean ~= "" do | |
| local c = clean:sub(1,1) | |
| if c == "." or c == "_" or string.byte(c) <= 32 then | |
| clean = clean:sub(2) | |
| else | |
| break | |
| end | |
| end | |
| return clean:reverse() | |
| end | |
| local function split_filename_and_extension(filename) | |
| local reversed = filename:reverse() | |
| local dotidx = reversed:find("%.") | |
| if dotidx and dotidx ~= 1 then | |
| return reversed:sub(dotidx+1):reverse(), reversed:sub(1, dotidx-1):reverse() | |
| else | |
| return filename, "" | |
| end | |
| end | |
| local function truncate_utf8_string(str, maxlen) | |
| str = str:sub(1, maxlen) | |
| -- not thoroughly test but should work 😇 | |
| if string.byte(str:sub(-3)) >= 240 then return str:sub(1, str:len()-3) end | |
| if string.byte(str:sub(-2)) >= 224 then return str:sub(1, str:len()-2) end | |
| if string.byte(str:sub(-1)) >= 192 then return str:sub(1, str:len()-1) end | |
| return str | |
| end | |
| local function time_to_HH_MM_SS_mmm(time) | |
| local ms = math.fmod(time * 1000, 1000) | |
| local integer = math.floor(time) | |
| local h = math.floor(integer / (60 * 60)) | |
| local m = math.floor(integer / 60) - (h * 60) | |
| local s = math.fmod(integer, 60) | |
| --print(h, m, s, ms) | |
| return string.format("%02d:%02d:%02d.%03d", h, m, s, ms) | |
| end | |
| local function construct_screenshot_path(with_subs) | |
| local screenshot_extension = "." .. mp.get_property("screenshot-format", "jpg") | |
| local output_directory = mp.get_property("screenshot-directory", "") | |
| if output_directory ~= "" then | |
| local last_byte = output_directory:sub(-1) | |
| if last_byte ~= "/" and last_byte ~= "\\" then | |
| output_directory = output_directory .. "/" | |
| end | |
| end | |
| local playback_time = mp.get_property_number("playback-time/full", 0) | |
| local timestamp = time_to_HH_MM_SS_mmm(playback_time) | |
| local sub_text = "" | |
| if with_subs then | |
| sub_text = clean_string(mp.get_property("sub-text", "")) | |
| end | |
| for i=1, 12345 do | |
| local basename = string.format("%s %s %04d %s", current_filename, timestamp, screenshot_counter, sub_text) | |
| basename = clean_string(basename) | |
| basename = truncate_utf8_string(basename, 255 - screenshot_extension:len()) | |
| local path = string.format("%s%s%s", output_directory, basename, screenshot_extension) | |
| local info, err = mp.utils.file_info(path) | |
| screenshot_counter = screenshot_counter + 1 | |
| -- file doesn't exist so we can screenshot safely | |
| -- (if the file does exist then we'd loop and try again with a higher screenshot_counter) | |
| if not info then | |
| return path | |
| end | |
| end | |
| return "" | |
| end | |
| local function do_screenshot(with_subs) | |
| local path = construct_screenshot_path(with_subs) | |
| if path == "" then | |
| mp.osd_message("failed to build a screenshot path...") | |
| mp.msg.error("failed to build a screenshot path...") | |
| return | |
| end | |
| local cmd = {"screenshot-to-file", path, with_subs and "subtitles" or "video"} | |
| local ret = mp.command_native_async(cmd, function(success, result, err) | |
| if success then | |
| mp.osd_message(string.format("Screenshot: '%s'", path)) | |
| else | |
| if err ~= nil then | |
| mp.osd_message(string.format("Failed to save '%s'\nReason: %s", path, err)) | |
| mp.msg.error(err) | |
| else | |
| mp.osd_message(string.format("Failed to save '%s'\nReason: unknown", path)) | |
| mp.msg.error(err) | |
| end | |
| end | |
| end) | |
| end | |
| mp.register_event("file-loaded", function(event) | |
| current_filename, _ = split_filename_and_extension(mp.get_property("filename", "")) | |
| current_filename = clean_string(current_filename) | |
| end) | |
| -- screenshot with subtitles | |
| mp.add_forced_key_binding("s", function() | |
| do_screenshot(true) | |
| end) | |
| -- screenshot without subtitles | |
| mp.add_forced_key_binding("S", function() | |
| do_screenshot(false) | |
| end) |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Basically these four commits ported to lua mpv-player/mpv@master...rtldg:mpv:stuff-again
A PR for the 'long filename handling improvements' commit was never merged into the mpv repo so I gave up and starting building mpv locally with my patches.
I don't enjoy writing lua code (and I was hopeful a PR would one day be accepted) so I put off porting to lua for a long time.