From d1366d5a0886c4c50dbf70f8b3f6965480d69783 Mon Sep 17 00:00:00 2001 From: gaitas13 <91051922+gaitas13@users.noreply.github.com> Date: Sun, 15 Oct 2023 00:03:02 +0200 Subject: [PATCH] added mpv manga reader --- .config/mpv/scripts/manga-reader.lua | 770 +++++++++++++++++++++++++++ 1 file changed, 770 insertions(+) create mode 100644 .config/mpv/scripts/manga-reader.lua diff --git a/.config/mpv/scripts/manga-reader.lua b/.config/mpv/scripts/manga-reader.lua new file mode 100644 index 0000000..f5725de --- /dev/null +++ b/.config/mpv/scripts/manga-reader.lua @@ -0,0 +1,770 @@ +local ext = { + ".7z", + ".avif", + ".bmp", + ".cbr", + ".cbz", + ".gif", + ".jpg", + ".jpeg", + ".jxl", + ".png", + ".rar", + ".tar", + ".tif", + ".tiff", + ".webp", + ".zip" +} +local double_page_check = false +local first_start = true +local filedims = {} +local initiated = false +local input = "" +local jump = false +local upwards = false +local init_values = { + force_window = false, + image_display_duration = 1, +} +local opts = { + auto_start = false, + continuous = false, + continuous_size = 8, + double = false, + manga = true, + pan_size = 0.05, + similar_height_threshold = 50, + skip_size = 10, + trigger_zone = 0.05, + zoom_multiplier = 1, +} +local lavfi_scale = {} +local similar_height = {} +local valid_width = {} + +function add_tracks(start, finish) + for i=start + 1, finish do + local new_file = mp.get_property("playlist/"..tostring(i).."/filename") + mp.commandv("video-add", new_file, "auto") + end +end + +function calculate_zoom_level(dims, pages) + local display_width = mp.get_property_number("display-width") + local display_height = mp.get_property_number("display-height") + local display_dpi = mp.get_property_number("display-hidpi-scale") + + display_width = display_width / display_dpi + display_height = display_height / display_dpi + + dims[0] = tonumber(dims[0]) + dims[1] = tonumber(dims[1]) * opts.continuous_size + + local scaled_width = display_height/dims[1] * dims[0] + if display_width >= opts.continuous_size*scaled_width then + return pages + else + return display_width / scaled_width + end +end + +function check_aspect_ratio(index) + local a = filedims[index] + local b = filedims[index+1] + local m = a[0]+b[0] + local n + if a[1] > b[1] then + n = a[1] + else + n = b[1] + end + local aspect_ratio + local display_width = mp.get_property_number("display-width") + local display_height = mp.get_property_number("display-height") + if display_width ~= nil and display_height ~= nil then + local display_dpi = mp.get_property_number("display-hidpi-scale") + display_width = display_width / display_dpi + display_height = display_height / display_dpi + aspect_ratio = display_width / display_height + else + return true + end + if m/n <= aspect_ratio then + return true + else + return false + end +end + +function check_double_page_dims(index) + -- additional check if we don't know the correct index to go to + -- can happen when going backwards or skipping to the last page + if double_page_check and (not valid_width[index] or not similar_height[index]) then + mp.commandv("playlist-play-index", index + 1) + end + double_page_check = false +end + +function check_images() + local audio = mp.get_property("audio-params") + local image = mp.get_property_bool("current-tracks/video/image") + local length = mp.get_property_number("playlist-count") + if audio == nil and image and length > 1 then + return true + else + return false + end +end + +function set_custom_title(last_index) + local first_page = mp.get_property("filename") + local last_page = mp.get_property("track-list/"..last_index.."/title") + local ext = string.gsub(first_page, ".*%.", "") + first_page = string.gsub(first_page, "%..*", "") + last_page = string.gsub(last_page, "%..*", "") + local new_title = first_page.."-"..last_page.."."..ext + mp.set_property("force-media-title", new_title) +end + +function create_modes() + local index = mp.get_property_number("playlist-pos") + local len = mp.get_property_number("playlist-count") + local pages + if opts.double then + pages = 2 + elseif opts.continuous then + pages = opts.continuous_size + else + return + end + local finish = index + pages - 1 + if finish >= len then + finish = len - 1 + end + add_tracks(index, finish) + store_file_dims(index, finish) + if opts.double then + check_double_page_dims(index) + set_lavfi_complex_double() + if mp.get_property("lavfi-complex") ~= "" then + set_custom_title(1) + end + else + local arg = "[vid1]" + for i=1, finish - index do + arg = arg.." [vid"..tostring(i+1).."]" + end + set_lavfi_complex_continuous(arg, finish) + set_custom_title(finish - index) + end +end + +function store_file_dims(start, finish) + local len = mp.get_property_number("playlist-count") + local needs_dims = false + for i=start, finish do + if valid_width[i] == nil then + needs_dims = true + break + end + end + if not needs_dims then + return + end + for i=0, finish - start do + local dims = {} + local failures = 0 + local width = nil + local height = nil + -- Don't loop forever here if we can't get this from the container. + while (width == nil or height == nil) and failures < 20 do + width = mp.get_property_number("track-list/"..tostring(i).."/demux-w") + height = mp.get_property_number("track-list/"..tostring(i).."/demux-h") + failures = failures + 1 + end + if width == nil or height == nil then + -- Just make up stuff in this case so double page can work. + width = 300 + height = 500 + end + dims[0] = width + dims[1] = height + filedims[i+start] = dims + end + for i=start, finish - 1 do + valid_width[i] = check_aspect_ratio(i) + if filedims[i][1] ~= filedims[i+1][1] then + lavfi_scale[i] = true + end + if math.abs(filedims[i][1] - filedims[i+1][1]) < opts.similar_height_threshold then + similar_height[i] = true + else + similar_height[i] = false + end + end +end + +function log2(num) + return math.log(num)/math.log(2) +end + +function check_lavfi_complex(event) + if event.file_error then + mp.set_property("lavfi-complex", "") + local index = mp.get_property_number("playlist-pos") + if opts.continuous then + opts.continous = false + toggle_continuous_mode() + mp.osd_message("Error when trying to set continuous mode! Disabling!") + end + if opts.double then + opts.double = false + toggle_double_page() + mp.osd_message("Error when trying to set double page mode! Disabling!") + end + end +end + +function set_lavfi_complex_continuous(arg, finish) + local vstack = "" + local split = str_split(arg, " ") + local index = mp.get_property_number("playlist-pos") + local pages = finish - index + local max_width = find_max_width(pages) + for i=0, pages do + if filedims[index+i][0] ~= max_width then + local split_pad = string.gsub(split[i], "]", "_pad]") + vstack = vstack..split[i].." pad="..max_width..":"..filedims[index+i][1]..":"..tostring((max_width - filedims[index+i][0])/2)..":"..filedims[index+i][1].." "..split_pad.."; " + split[i] = split_pad + end + end + for i=0, pages do + vstack = vstack..split[i].." " + end + vstack = vstack.."vstack=inputs="..tostring(pages + 1).." [vo]" + mp.set_property("lavfi-complex", vstack) + local index = mp.get_property_number("playlist-pos") + local zoom_level = calculate_zoom_level(filedims[index], pages+1) + mp.set_property_number("video-zoom", opts.zoom_multiplier * log2(zoom_level)) + mp.set_property_number("video-pan-y", 0) + if upwards then + mp.set_property_number("video-align-y", 1) + upwards = false + else + mp.set_property_number("video-align-y", -1) + end +end + +function set_lavfi_complex_double() + local index = mp.get_property_number("playlist-pos") + if not valid_width[index] or not similar_height[index] then + if mp.get_property("lavfi-complex") ~= "" then + mp.set_property("lavfi-complex", "") + mp.set_property("force-media-title", "") + end + return + end + local hstack + local external_vid = "[vid2]" + if lavfi_scale[index] then + external_vid = string.sub(external_vid, 0, 5).."_scale]" + end + if opts.manga then + hstack = external_vid.." [vid1] hstack [vo]" + else + hstack = "[vid1] "..external_vid.." hstack [vo]" + end + if lavfi_scale[index] then + hstack = "[vid2] scale="..filedims[index][0].."x"..filedims[index][1]..":flags=lanczos [vid2_scale]; "..hstack + end + mp.set_property("lavfi-complex", hstack) +end + +function next_page() + local len = mp.get_property_number("playlist-count") + local index = mp.get_property_number("playlist-pos") + local new_index + if opts.double then + local double_displayed = mp.get_property("lavfi-complex") ~= "" + if double_displayed then + new_index = index + 2 + else + new_index = index + 1 + end + if new_index > len - 2 and double_displayed then + new_index = len - 2 + elseif new_index > len - 2 then + new_index = len - 1 + end + if new_index == index then + return + end + elseif opts.continuous then + new_index = math.min(index + opts.continuous_size, len - 1) + if index + opts.continuous_size > new_index then + return + end + else + new_index = math.min(len - 1, index + 1) + if new_index == index then + return + end + end + mp.commandv("playlist-play-index", new_index) +end + +function prev_page() + local len = mp.get_property_number("playlist-count") + local index = mp.get_property_number("playlist-pos") + local new_index + if opts.double then + new_index = math.max(0, index - 2) + if valid_width[new_index] == nil then + double_page_check = true + end + if valid_width[new_index] == false or similar_height[new_index] == false then + new_index = index - 1 + new_index = math.max(0, new_index) + end + if new_index == index then + return + end + elseif opts.continuous then + new_index = math.max(0, index - opts.continuous_size) + if new_index == index then + return + end + mp.set_property_number("video-align-y", 1) + else + new_index = math.max(0, index - 1) + if new_index == index then + return + end + end + mp.commandv("playlist-play-index", new_index) +end + +function next_single_page() + local len = mp.get_property_number("playlist-count") + local index = mp.get_property_number("playlist-pos") + local new_index = math.min(index + 1, len - 1) + mp.commandv("playlist-play-index", new_index) +end + +function prev_single_page() + local index = mp.get_property_number("playlist-pos") + local new_index = math.max(0, index - 1) + mp.commandv("playlist-play-index", new_index) +end + +function skip_forward() + local len = mp.get_property_number("playlist-count") + local index = mp.get_property_number("playlist-pos") + local new_index = math.min(index + opts.skip_size, len - 1) + mp.commandv("playlist-play-index", new_index) +end + +function skip_backward() + local index = mp.get_property_number("playlist-pos") + local new_index = math.max(0, index - opts.skip_size) + mp.commandv("playlist-play-index", new_index) +end + +function first_page() + mp.commandv("playlist-play-index", 0) +end + +function last_page() + local len = mp.get_property_number("playlist-count") + local index = 0; + if opts.continuous then + index = len - opts.continuous_size + upwards = true + elseif opts.double then + if valid_width[len - 2] == false and similar_height[len - 2] == false then + index = len - 1 + else + index = len - 2 + end + double_page_check = true + else + index = len - 1 + end + mp.commandv("playlist-play-index", index) +end + +function pan_up() + mp.commandv("add", "video-pan-y", opts.pan_size) +end + +function pan_down() + mp.commandv("add", "video-pan-y", -opts.pan_size) +end + +function one_handler() + input = input.."1" + mp.osd_message("Jump to page "..input, 100000) +end + +function two_handler() + input = input.."2" + mp.osd_message("Jump to page "..input, 100000) +end + +function three_handler() + input = input.."3" + mp.osd_message("Jump to page "..input, 100000) +end + +function four_handler() + input = input.."4" + mp.osd_message("Jump to page "..input, 100000) +end + +function five_handler() + input = input.."5" + mp.osd_message("Jump to page "..input, 100000) +end + +function six_handler() + input = input.."6" + mp.osd_message("Jump to page "..input, 100000) +end + +function seven_handler() + input = input.."7" + mp.osd_message("Jump to page "..input, 100000) +end + +function eight_handler() + input = input.."8" + mp.osd_message("Jump to page "..input, 100000) +end + +function nine_handler() + input = input.."9" + mp.osd_message("Jump to page "..input, 100000) +end + +function zero_handler() + input = input.."0" + mp.osd_message("Jump to page "..input, 100000) +end + +function bs_handler() + input = input:sub(1, -2) + mp.osd_message("Jump to page "..input, 100000) +end + +function jump_page_go() + local dest = tonumber(input) - 1 + local len = mp.get_property_number("playlist-count") + local index = mp.get_property_number("playlist-pos") + input = "" + mp.osd_message("") + if (dest > len - 1) or (dest < 0) then + mp.osd_message("Specified page does not exist") + else + mp.commandv("playlist-play-index", dest) + end + remove_jump_keys() + jump = false +end + +function remove_jump_keys() + mp.remove_key_binding("one-handler") + mp.remove_key_binding("two-handler") + mp.remove_key_binding("three-handler") + mp.remove_key_binding("four-handler") + mp.remove_key_binding("five-handler") + mp.remove_key_binding("six-handler") + mp.remove_key_binding("seven-handler") + mp.remove_key_binding("eight-handler") + mp.remove_key_binding("nine-handler") + mp.remove_key_binding("zero-handler") + mp.remove_key_binding("bs-handler") + mp.remove_key_binding("jump-page-go") + mp.remove_key_binding("jump-page-quit") +end + +function jump_page_quit() + jump = false + input = "" + remove_jump_keys() + mp.osd_message("") +end + +function set_jump_keys() + mp.add_forced_key_binding("1", "one-handler", one_handler) + mp.add_forced_key_binding("2", "two-handler", two_handler) + mp.add_forced_key_binding("3", "three-handler", three_handler) + mp.add_forced_key_binding("4", "four-handler", four_handler) + mp.add_forced_key_binding("5", "five-handler", five_handler) + mp.add_forced_key_binding("6", "six-handler", six_handler) + mp.add_forced_key_binding("7", "seven-handler", seven_handler) + mp.add_forced_key_binding("8", "eight-handler", eight_handler) + mp.add_forced_key_binding("9", "nine-handler", nine_handler) + mp.add_forced_key_binding("0", "zero-handler", zero_handler) + mp.add_forced_key_binding("BS", "bs-handler", bs_handler) + mp.add_forced_key_binding("ENTER", "jump-page-go", jump_page_go) + mp.add_forced_key_binding("ctrl+[", "jump-page-quit", jump_page_quit) +end + +function jump_page_mode() + if jump == false then + jump = true + set_jump_keys() + mp.osd_message("Jump to page ", 100000) + end +end + +function set_properties() + init_values.force_window = true + init_values.force_window = mp.get_property_bool("force-window") + init_values.image_display_duration = mp.get_property("image-display-duration") + mp.set_property_bool("force-window", true) + mp.set_property("image-display-duration", "inf") +end + +function restore_properties() + mp.set_property_bool("force-window", init_values.force_window) + mp.set_property("image-display-duration", init_values.image_display_duration) +end + +function set_keys() + if opts.manga then + mp.add_forced_key_binding("LEFT", "next-page", next_page) + mp.add_forced_key_binding("RIGHT", "prev-page", prev_page) + mp.add_forced_key_binding("Shift+LEFT", "next-single-page", next_single_page) + mp.add_forced_key_binding("Shift+RIGHT", "prev-single-page", prev_single_page) + mp.add_forced_key_binding("Ctrl+LEFT", "skip-forward", skip_forward) + mp.add_forced_key_binding("Ctrl+RIGHT", "skip-backward", skip_backward) + else + mp.add_forced_key_binding("RIGHT", "next-page", next_page) + mp.add_forced_key_binding("LEFT", "prev-page", prev_page) + mp.add_forced_key_binding("Shift+RIGHT", "next-single-page", next_single_page) + mp.add_forced_key_binding("Shift+LEFT", "prev-single-page", prev_single_page) + mp.add_forced_key_binding("Ctrl+RIGHT", "skip-forward", skip_forward) + mp.add_forced_key_binding("Ctrl+LEFT", "skip-backward", skip_backward) + end + mp.add_forced_key_binding("UP", "pan-up", pan_up, "repeatable") + mp.add_forced_key_binding("DOWN", "pan-down", pan_down, "repeatable") + mp.add_forced_key_binding("HOME", "first-page", first_page) + mp.add_forced_key_binding("END", "last-page", last_page) + mp.add_forced_key_binding("/", "jump-page-mode", jump_page_mode) +end + +function remove_keys() + mp.remove_key_binding("next-page") + mp.remove_key_binding("prev-page") + mp.remove_key_binding("next-single-page") + mp.remove_key_binding("prev-single-page") + mp.remove_key_binding("skip-forward") + mp.remove_key_binding("skip-backward") + mp.remove_key_binding("pan-up") + mp.remove_key_binding("pan-down") + mp.remove_key_binding("first-page") + mp.remove_key_binding("last-page") + mp.remove_key_binding("jump-page-mode") +end + +function remove_non_images() + local length = mp.get_property_number("playlist-count") + local i = 0 + local name = mp.get_property("playlist/"..tostring(i).."/filename") + while name ~= nil do + local name_ext = string.sub(name, -5) + local match = false + for j = 1, #ext do + if string.match(name_ext, ext[j]) then + match = true + break + end + end + if string.match(name_ext, "%.") == nil and not match then + match = true + end + if not match then + mp.commandv("playlist-remove", i) + else + i = i + 1 + end + name = mp.get_property("playlist/"..tostring(i).."/filename") + end +end + +function find_max_width(pages) + local index = mp.get_property_number("playlist-pos") + local len = mp.get_property_number("playlist-count") + local max_width = 0 + for i=index, pages do + if tonumber(filedims[i][0]) > tonumber(max_width) then + max_width = filedims[i][0] + end + end + return max_width +end + +function str_split(str, delim) + local split = {} + local i = 0 + for token in string.gmatch(str, "([^"..delim.."]+)") do + split[i] = token + i = i + 1 + end + return split +end + +function toggle_reader() + local image = check_images() + if image then + local index = mp.get_property_number("playlist-pos") + if opts.continuous then + opts.double = false + opts.continuous = true + mp.observe_property("video-pan-y", number, check_y_pos) + end + if not initiated then + initiated = true + set_keys() + set_properties() + mp.observe_property("playlist-count", number, remove_non_images) + mp.osd_message("Manga Reader Started") + mp.add_key_binding("c", "toggle-continuous-mode", toggle_continuous_mode) + mp.add_key_binding("d", "toggle-double-page", toggle_double_page) + mp.add_key_binding("m", "toggle-manga-mode", toggle_manga_mode) + mp.register_event("end-file", check_lavfi_complex) + mp.commandv("playlist-play-index", index) + else + initiated = false + remove_keys() + restore_properties() + mp.unobserve_property(check_y_pos) + mp.unobserve_property(remove_non_images) + mp.set_property_number("video-zoom", 0) + mp.set_property_number("video-align-y", 0) + mp.set_property_number("video-pan-y", 0) + mp.set_property("lavfi-complex", "") + mp.set_property_bool("force-window", false) + mp.remove_key_binding("toggle-continuous-mode") + mp.remove_key_binding("toggle-double-page") + mp.remove_key_binding("toggle-manga-mode") + mp.osd_message("Closing Reader") + mp.unregister_event(check_lavfi_complex) + mp.commandv("playlist-play-index", index) + end + else + if not first_start then + mp.osd_message("Not a playlist of images.") + end + end +end + +function init() + if opts.auto_start then + toggle_reader() + end + mp.unregister_event(init) + first_start = false +end + +function check_y_pos() + local index = mp.get_property_number("playlist-pos") + local len = mp.get_property_number("playlist-count") + local first_chunk = false + if index+opts.continuous_size < 0 then + first_chunk = true + elseif index == 0 then + first_chunk = true + end + local last_chunk = false + if index+opts.continuous_size >= len - 1 then + last_chunk = true + end + local middle_index + if index == len - 1 then + middle_index = index - 1 + else + middle_index = index + 1 + end + local total_height = mp.get_property_number("height") + if total_height == nil then + return + end + local y_pos = mp.get_property_number("video-pan-y") + local y_align = mp.get_property_number("video-align-y") + if y_align == -1 then + local height = filedims[middle_index][1] + local bottom_threshold = height / total_height - 1 - opts.trigger_zone + if y_pos < bottom_threshold and not last_chunk then + next_page() + end + if y_pos > 0 and not first_chunk then + upwards = true + prev_page() + end + elseif y_align == 1 then + local height = filedims[middle_index][1] + local top_threshold = 1 - height / total_height + opts.trigger_zone + if y_pos > top_threshold and not first_chunk then + upwards = true + prev_page() + end + if y_pos < 0 and not last_chunk then + next_page() + end + end +end + +function toggle_continuous_mode() + if opts.continuous then + mp.osd_message("Continuous Mode Off") + opts.continuous = false + mp.unobserve_property(check_y_pos) + mp.set_property("lavfi-complex", "") + mp.set_property_number("video-zoom", 0) + mp.set_property_number("video-align-y", 0) + mp.set_property_number("video-pan-y", 0) + else + mp.osd_message("Continuous Mode On") + opts.double = false + opts.continuous = true + mp.observe_property("video-pan-y", number, check_y_pos) + end + local index = mp.get_property_number("playlist-pos") + mp.commandv("playlist-play-index", index) +end + +function toggle_double_page() + if opts.double then + mp.osd_message("Double Page Mode Off") + opts.double = false + mp.set_property("lavfi-complex", "") + mp.set_property("force-media-title", "") + else + mp.osd_message("Double Page Mode On") + opts.continuous = false + opts.double = true + end + local index = mp.get_property_number("playlist-pos") + mp.commandv("playlist-play-index", index) +end + +function toggle_manga_mode() + if opts.manga then + mp.osd_message("Manga Mode Off") + opts.manga = false + else + mp.osd_message("Manga Mode On") + opts.manga = true + end + set_keys() + local index = mp.get_property_number("playlist-pos") + mp.commandv("playlist-play-index", index) +end + +mp.add_hook("on_preloaded", 50, create_modes) +mp.register_event("file-loaded", init) +mp.add_key_binding("y", "toggle-reader", toggle_reader) +require "mp.options".read_options(opts, "manga-reader")