diff --git a/.config/mpv/scripts/SimpleHistory.lua b/.config/mpv/scripts/SimpleHistory.lua new file mode 100644 index 0000000..b17d5a3 --- /dev/null +++ b/.config/mpv/scripts/SimpleHistory.lua @@ -0,0 +1,2275 @@ +-- Copyright (c) 2022, Eisa AlAwadhi +-- License: BSD 2-Clause License +-- Creator: Eisa AlAwadhi +-- Project: SimpleHistory +-- Version: 1.1.6 + +local o = { +---------------------------USER CUSTOMIZATION SETTINGS--------------------------- +--These settings are for users to manually change some options. +--Changes are recommended to be made in the script-opts directory. + + -----Script Settings---- + auto_run_list_idle = 'recents', --Auto run the list when opening mpv and there is no video / file loaded. 'none' for disabled. Or choose between: 'all', 'recents', 'distinct', 'protocols', 'fileonly', 'titleonly', 'timeonly', 'keywords'. + startup_idle_behavior = 'none', --The behavior when mpv launches and nothing is loaded. 'none' for disabled. 'resume' to automatically resume your last played item. 'resume-notime' to resume your last played item but starts from the beginning. + toggle_idlescreen = false, --hides OSC idle screen message when opening and closing menu (could cause unexpected behavior if multiple scripts are triggering osc-idlescreen off) + resume_offset = -0.65, --change to 0 so item resumes from the exact position, or decrease the value so that it gives you a little preview before loading the resume point + osd_messages = true, --true is for displaying osd messages when actions occur. Change to false will disable all osd messages generated from this script + resume_option = 'notification', --'none': for disabled. 'notification': a message to resume the previous reached time will be triggered. 'force': to forcefully resume last playback based on threshold + resume_option_threshold = 2, --0 to always trigger the resume option when the same video has been played previously, a value such as 5 will only trigger the resume option if the last played time starts after 5% of the video and ends before completion by 5% + mark_history_as_chapter = false, --true is for marking the time as a chapter. false disables mark as chapter behavior. + invert_history_blacklist = false, --true so that blacklist becomes a whitelist, resulting in stuff such as paths / websites that are added to history_blacklist to be saved into history + history_blacklist=[[ + [""] + ]], --Paths / URLs / Websites / Files / Protocols / Extensions, that wont be added to history automatically, e.g.: ["c:\\users\\eisa01\\desktop", "c:\\users\\eisa01\\desktop\\*", "c:\\temp\\naruto-01.mp4", "youtube.com", "https://dailymotion.com/", "avi", "https://www.youtube.com/watch?v=e8YBesRKq_U", ".jpeg", "magnet:", "https://", "ftp"] + history_resume_keybind=[[ + ["ctrl+r", "ctrl+R"] + ]], --Keybind that will be used to immediately load and resume last item when no video is playing. If video is playing it will resume to the last found position + history_load_last_keybind=[[ + ["alt+r", "alt+R"] + ]], --Keybind that will be used to immediately load the last item without resuming when no video is playing. If video is playing then it will add into playlist + open_list_keybind=[[ + [ ["h", "all"], ["H", "all"], ["r", "recents"], ["R", "recents"] ] + ]], --Keybind that will be used to open the list along with the specified filter. Available filters: 'all', 'recents', 'distinct', 'protocols', 'fileonly', 'titleonly', 'timeonly', 'keywords'. + list_filter_jump_keybind=[[ + [ ["h", "all"], ["H", "all"], ["r", "recents"], ["R", "recents"], ["d", "distinct"], ["D", "distinct"], ["f", "fileonly"], ["F", "fileonly"] ] + ]], --Keybind that is used while the list is open to jump to the specific filter (it also enables pressing a filter keybind twice to close list). Available fitlers: 'all', 'recents', 'distinct', 'protocols', 'fileonly', 'titleonly', 'timeonly', 'keywords'. + + -----Incognito Settings---- + auto_run_incognito_mode = false, --true to automatically start incognito mode when mpv launches, false disables this behavior + delete_incognito_entry = true, --true so that the file that had incognito mode triggered on gets removed from history automatically, false keeps the file in history that incognito mode triggered on + restore_incognito_entry = 'always', --'none' for disabled, 'deleted-restore' so that the the file that was removed when entering incognito automtically gets restored, 'always' so that exiting incognito_mode always immediately updates entry into history + history_incognito_mode_keybind=[[ + ["ctrl+H"] + ]], --Triggers incognito mode. When enabled files played wont be added to history until this mode is disabled. + + -----Logging Settings----- + log_path = '/:dir%mpvconf%', --Change to '/:dir%script%' for placing it in the same directory of script, OR change to '/:dir%mpvconf%' for mpv portable_config directory. OR write any variable using '/:var' then the variable '/:var%APPDATA%' you can use path also, such as: '/:var%APPDATA%\\mpv' OR '/:var%HOME%/mpv' OR specify the absolute path , e.g.: 'C:\\Users\\Eisa01\\Desktop\\' + log_file = 'mpvHistory.log', --name+extension of the file that will be used to store the log data + date_format = '%A/%B %d/%m/%Y %X', --Date format in the log (see lua date formatting), e.g.:'%d/%m/%y %X' or '%d/%b/%y %X' + file_title_logging = 'protocols', --Change between 'all', 'protocols', 'none'. This option will store the media title in log file, it is useful for websites / protocols because title cannot be parsed from links alone + logging_protocols=[[ + ["https?://", "magnet:", "rtmp:"] + ]], --add above (after a comma) any protocol you want its title to be stored in the log file. This is valid only for (file_title_logging = 'protocols' or file_title_logging = 'all') + prefer_filename_over_title = 'local', --Prefers to log filename over filetitle. Select between 'local', 'protocols', 'all', and 'none'. 'local' prefer filenames for videos that are not protocols. 'protocols' will prefer filenames for protocols only. 'all' will prefer filename over filetitle for both protocols and not protocols videos. 'none' will always use filetitle instead of filename + same_entry_limit = 2, --Limit saving entries with same path: -1 for unlimited, 0 will always update entries of same path, e.g. value of 3 will have the limit of 3 then it will start updating old values on the 4th entry. + + -----List Settings----- + loop_through_list = false, --true is for going up on the first item loops towards the last item and vise-versa. false disables this behavior. + list_middle_loader = true, --false is for more items to show, then u must reach the end. true is for new items to show after reaching the middle of list. + show_paths = false, --Show file paths instead of media-title + show_item_number = true, --Show the number of each item before displaying its name and values. + slice_longfilenames = false, --Change to true or false. Slices long filenames per the amount specified below + slice_longfilenames_amount = 55, --Amount for slicing long filenames + list_show_amount = 10, --Change maximum number to show items at once + quickselect_0to9_keybind = true, --Keybind entries from 0 to 9 for quick selection when list is open (list_show_amount = 10 is maximum for this feature to work) + main_list_keybind_twice_exits = true, --Will exit the list when double tapping the main list, even if the list was accessed through a different filter. + search_not_typing_smartly = true, --To smartly set the search as not typing (when search box is open) without needing to press ctrl+enter. + search_behavior = 'any', --'specific' to find a match of either a date, title, path / url, time. 'any' to find any typed search based on combination of date, title, path / url, and time. 'any-notime' to find any typed search based on combination of date, title, and path / url, but without looking for time (this is to reduce unwanted results). + + -----Filter Settings------ + --available filters: "all" to display all the items. Or "recents" to display recently added items to log without duplicate. Or "distinct" to show recent saved entries for files in different paths. Or "fileonly" to display files saved without time. Or "timeonly" to display files that have time only. Or "keywords" to display files with matching keywords specified in the configuration. Or "playing" to show list of current playing file. + filters_and_sequence=[[ + ["all", "recents", "distinct", "protocols", "playing", "fileonly", "titleonly", "keywords"] + ]], --Jump to the following filters and in the shown sequence when navigating via left and right keys. You can change the sequence and delete filters that are not needed. + next_filter_sequence_keybind=[[ + ["RIGHT", "MBTN_FORWARD"] + ]], --Keybind that will be used to go to the next available filter based on the filters_and_sequence + previous_filter_sequence_keybind=[[ + ["LEFT", "MBTN_BACK"] + ]], --Keybind that will be used to go to the previous available filter based on the filters_and_sequence + loop_through_filters = true, --true is for bypassing the last filter to go to first filter when navigating through filters using arrow keys, and vice-versa. false disables this behavior. + keywords_filter_list=[[ + [""] + ]], --Create a filter out of your desired 'keywords', e.g.: youtube.com will filter out the videos from youtube. You can also insert a portion of filename or title, or extension or a full path / portion of a path. e.g.: ["youtube.com", "mp4", "naruto", "c:\\users\\eisa01\\desktop"] + + -----Sort Settings------ + --available sort: 'added-asc' is for the newest added item to show first. Or 'added-desc' for the newest added to show last. Or 'alphanum-asc' is for A to Z approach with filename and episode number lower first. Or 'alphanum-desc' is for its Z to A approach. Or 'time-asc', 'time-desc' to sort the list based on time. + list_default_sort = 'added-asc', --the default sorting method for all the different filters in the list. select between 'added-asc', 'added-desc', 'time-asc', 'time-desc', 'alphanum-asc', 'alphanum-desc' + list_filters_sort=[[ + [ ] + ]], --Default sort for specific filters, e.g.: [ ["all", "alphanum-asc"], ["playing", "added-desc"] ] + list_cycle_sort_keybind=[[ + ["alt+s", "alt+S"] + ]], --Keybind to cycle through the different available sorts when list is open + + -----List Design Settings----- + list_alignment = 7, --The alignment for the list, uses numpad positions choose from 1-9 or 0 to disable. e,g.:7 top left alignment, 8 top middle alignment, 9 top right alignment. + text_time_type = 'duration', --The time type for items on the list. Select between 'duration', 'length', 'remaining'. + time_seperator = ' 🕒 ', --Time seperator that will be used before the time + list_sliced_prefix = '...\\h\\N\\N', --The text that indicates there are more items above. \\N is for new line. \\h is for hard space. + list_sliced_suffix = '...', --The text that indicates there are more items below. + quickselect_0to9_pre_text = false, --true enables pre text for showing quickselect keybinds before the list. false to disable + text_color = 'ffffff', --Text color for list in BGR hexadecimal + text_scale = 50, --Font size for the text of list + text_border = 0.7, --Black border size for the text of list + text_cursor_color = 'ffbf7f', --Text color of current cursor position in BGR hexadecimal + text_cursor_scale = 50, --Font size for text of current cursor position in list + text_cursor_border = 0.7, --Black border size for text of current cursor position in list + text_highlight_pre_text = '✅ ', --Pre text for highlighted multi-select item + search_color_typing = 'ffffaa', --Search color when in typing mode + search_color_not_typing = '00bfff', --Search color when not in typing mode and it is active + header_color = '00bfff', --Header color in BGR hexadecimal + header_scale = 55, --Header text size for the list + header_border = 0.8, --Black border size for the Header of list + header_text = '⌛ History [%cursor%/%total%]%prehighlight%%highlight%%afterhighlight%%prelistduration%%listduration%%afterlistduration%%prefilter%%filter%%afterfilter%%presort%%sort%%aftersort%%presearch%%search%%aftersearch%', --Text to be shown as header for the list + --Available header variables: %cursor%, %total%, %highlight%, %filter%, %search%, %listduration%, %listlength%, %listremaining% + --User defined text that only displays if a variable is triggered: %prefilter%, %afterfilter%, %prehighlight%, %afterhighlight% %presearch%, %aftersearch%, %prelistduration%, %afterlistduration%, %prelistlength%, %afterlistlength%, %prelistremaining%, %afterlistremaining% + --Variables explanation: %cursor: displays the number of cursor position in list. %total: total amount of items in current list. %highlight%: total number of highlighted items. %filter: shows the filter name, %search: shows the typed search. Example of user defined text that only displays if a variable is triggered of user: %prefilter: user defined text before showing filter, %afterfilter: user defined text after showing filter. + header_sort_hide_text = 'added-asc',--Sort method that is hidden from header when using %sort% variable + header_sort_pre_text = ' \\{',--Text to be shown before sort in the header, when using %presort% + header_sort_after_text = '}',--Text to be shown after sort in the header, when using %aftersort% + header_filter_pre_text = ' [Filter: ', --Text to be shown before filter in the header, when using %prefilter% + header_filter_after_text = ']', --Text to be shown after filter in the header, when using %afterfilter% + header_search_pre_text = '\\h\\N\\N[Search=', --Text to be shown before search in the header, when using %presearch% + header_search_after_text = '..]', --Text to be shown after search in the header, when using %aftersearch% + header_highlight_pre_text = '✅', --Text to be shown before total highlighted items of displayed list in the header + header_highlight_after_text = '', --Text to be shown after total highlighted items of displayed list in the header + header_list_duration_pre_text = ' 🕒 ', --Text to be shown before playback total duration of displayed list in the header + header_list_duration_after_text = '', --Text to be shown after playback total duration of displayed list in the header + header_list_length_pre_text = ' 🕒 ', --Text to be shown before playback total duration of displayed list in the header + header_list_length_after_text = '', --Text to be shown after playback total duration of displayed list in the header + header_list_remaining_pre_text = ' 🕒 ', --Text to be shown before playback total duration of displayed list in the header + header_list_remaining_after_text = '', --Text to be shown after playback total duration of displayed list in the header + + -----Time Format Settings----- + --in the first parameter, you can define from the available styles: default, hms, hms-full, timestamp, timestamp-concise "default" to show in HH:MM:SS.sss format. "hms" to show in 1h 2m 3.4s format. "hms-full" is the same as hms but keeps the hours and minutes persistent when they are 0. "timestamp" to show the total time as timestamp 123456.700 format. "timestamp-concise" shows the total time in 123456.7 format (shows and hides decimals depending on availability). + --in the second parameter, you can define whether to show milliseconds, round them or truncate them. Available options: 'truncate' to remove the milliseconds and keep the seconds. 0 to remove the milliseconds and round the seconds. 1 or above is the amount of milliseconds to display. The default value is 3 milliseconds. + --in the third parameter you can define the seperator between hour:minute:second. "default" style is automatically set to ":", "hms", "hms-full" are automatically set to " ". You can define your own. Some examples: ["default", 3, "-"],["hms-full", 5, "."],["hms", "truncate", ":"],["timestamp-concise"],["timestamp", 0],["timestamp", "truncate"],["timestamp", 5] + osd_time_format=[[ + ["default", "truncate"] + ]], + list_time_format=[[ + ["default", "truncate"] + ]], + header_duration_time_format=[[ + ["hms", "truncate", ":"] + ]], + header_length_time_format=[[ + ["hms", "truncate", ":"] + ]], + header_remaining_time_format=[[ + ["hms", "truncate", ":"] + ]], + + -----List Keybind Settings----- + --Add below (after a comma) any additional keybind you want to bind. Or change the letter inside the quotes to change the keybind + --Example of changing and adding keybinds: --From ["b", "B"] To ["b"]. --From [""] to ["alt+b"]. --From [""] to ["a" "ctrl+a", "alt+a"] + list_move_up_keybind=[[ + ["UP", "WHEEL_UP"] + ]], --Keybind that will be used to navigate up on the list + list_move_down_keybind=[[ + ["DOWN", "WHEEL_DOWN"] + ]], --Keybind that will be used to navigate down on the list + list_page_up_keybind=[[ + ["PGUP"] + ]], --Keybind that will be used to go to the first item for the page shown on the list + list_page_down_keybind=[[ + ["PGDWN"] + ]], --Keybind that will be used to go to the last item for the page shown on the list + list_move_first_keybind=[[ + ["HOME"] + ]], --Keybind that will be used to navigate to the first item on the list + list_move_last_keybind=[[ + ["END"] + ]], --Keybind that will be used to navigate to the last item on the list + list_highlight_move_keybind=[[ + ["SHIFT"] + ]], --Keybind that will be used to highlight while pressing a navigational keybind, keep holding shift and then press any navigation keybind, such as: up, down, home, pgdwn, etc.. + list_highlight_all_keybind=[[ + ["ctrl+a", "ctrl+A"] + ]], --Keybind that will be used to highlight all displayed items on the list + list_unhighlight_all_keybind=[[ + ["ctrl+d", "ctrl+D"] + ]], --Keybind that will be used to remove all currently highlighted items from the list + list_select_keybind=[[ + ["ENTER", "MBTN_MID"] + ]], --Keybind that will be used to load entry based on cursor position + list_add_playlist_keybind=[[ + ["CTRL+ENTER"] + ]], --Keybind that will be used to add entry to playlist based on cursor position + list_add_playlist_highlighted_keybind=[[ + ["SHIFT+ENTER"] + ]], --Keybind that will be used to add all highlighted entries to playlist + list_close_keybind=[[ + ["ESC", "MBTN_RIGHT"] + ]], --Keybind that will be used to close the list (closes search first if it is open) + list_delete_keybind=[[ + ["DEL"] + ]], --Keybind that will be used to delete the entry based on cursor position + list_delete_highlighted_keybind=[[ + ["SHIFT+DEL"] + ]], --Keybind that will be used to delete all highlighted entries from the list + list_search_activate_keybind=[[ + ["ctrl+f", "ctrl+F"] + ]], --Keybind that will be used to trigger search + list_search_not_typing_mode_keybind=[[ + ["ALT+ENTER"] + ]], --Keybind that will be used to exit typing mode of search while keeping search open + list_ignored_keybind=[[ + ["B", "b", "k", "K", "c", "C"] + ]], --Keybind thats are ignored when list is open + +---------------------------END OF USER CUSTOMIZATION SETTINGS--------------------------- +} + +(require 'mp.options').read_options(o) +local utils = require 'mp.utils' +local msg = require 'mp.msg' + +o.history_blacklist = utils.parse_json(o.history_blacklist) +o.history_incognito_mode_keybind = utils.parse_json(o.history_incognito_mode_keybind) +o.filters_and_sequence = utils.parse_json(o.filters_and_sequence) +o.keywords_filter_list = utils.parse_json(o.keywords_filter_list) +o.list_filters_sort = utils.parse_json(o.list_filters_sort) +o.logging_protocols = utils.parse_json(o.logging_protocols) +o.history_resume_keybind = utils.parse_json(o.history_resume_keybind) +o.history_load_last_keybind = utils.parse_json(o.history_load_last_keybind) +o.osd_time_format = utils.parse_json(o.osd_time_format) +o.list_time_format = utils.parse_json(o.list_time_format) +o.header_duration_time_format = utils.parse_json(o.header_duration_time_format) +o.header_length_time_format = utils.parse_json(o.header_length_time_format) +o.header_remaining_time_format = utils.parse_json(o.header_remaining_time_format) +o.list_move_up_keybind = utils.parse_json(o.list_move_up_keybind) +o.list_move_down_keybind = utils.parse_json(o.list_move_down_keybind) +o.list_page_up_keybind = utils.parse_json(o.list_page_up_keybind) +o.list_page_down_keybind = utils.parse_json(o.list_page_down_keybind) +o.list_move_first_keybind = utils.parse_json(o.list_move_first_keybind) +o.list_move_last_keybind = utils.parse_json(o.list_move_last_keybind) +o.list_highlight_move_keybind = utils.parse_json(o.list_highlight_move_keybind) +o.list_highlight_all_keybind = utils.parse_json(o.list_highlight_all_keybind) +o.list_unhighlight_all_keybind = utils.parse_json(o.list_unhighlight_all_keybind) +o.list_cycle_sort_keybind = utils.parse_json(o.list_cycle_sort_keybind) +o.list_select_keybind = utils.parse_json(o.list_select_keybind) +o.list_add_playlist_keybind = utils.parse_json(o.list_add_playlist_keybind) +o.list_add_playlist_highlighted_keybind = utils.parse_json(o.list_add_playlist_highlighted_keybind) +o.list_close_keybind = utils.parse_json(o.list_close_keybind) +o.list_delete_keybind = utils.parse_json(o.list_delete_keybind) +o.list_delete_highlighted_keybind = utils.parse_json(o.list_delete_highlighted_keybind) +o.list_search_activate_keybind = utils.parse_json(o.list_search_activate_keybind) +o.list_search_not_typing_mode_keybind = utils.parse_json(o.list_search_not_typing_mode_keybind) +o.next_filter_sequence_keybind = utils.parse_json(o.next_filter_sequence_keybind) +o.previous_filter_sequence_keybind = utils.parse_json(o.previous_filter_sequence_keybind) +o.open_list_keybind = utils.parse_json(o.open_list_keybind) +o.list_filter_jump_keybind = utils.parse_json(o.list_filter_jump_keybind) +o.list_ignored_keybind = utils.parse_json(o.list_ignored_keybind) + +if utils.shared_script_property_set then + utils.shared_script_property_set('simplehistory-menu-open', 'no') +end +mp.set_property('user-data/simplehistory/menu-open', 'no') + +if string.lower(o.log_path) == '/:dir%mpvconf%' then + o.log_path = mp.find_config_file('.') +elseif string.lower(o.log_path) == '/:dir%script%' then + o.log_path = debug.getinfo(1).source:match('@?(.*/)') +elseif o.log_path:match('/:var%%(.*)%%') then + local os_variable = o.log_path:match('/:var%%(.*)%%') + o.log_path = o.log_path:gsub('/:var%%(.*)%%', os.getenv(os_variable)) +end +local log_fullpath = utils.join_path(o.log_path, o.log_file) + +local log_length_text = 'length=' +local log_time_text = 'time=' +local protocols = {'https?:', 'magnet:', 'rtmps?:', 'smb:', 'ftps?:', 'sftp:'} +local available_filters = {'all', 'recents', 'distinct', 'playing', 'protocols', 'fileonly', 'titleonly', 'timeonly', 'keywords'} +local available_sorts = {'added-asc', 'added-desc', 'time-asc', 'time-desc', 'alphanum-asc', 'alphanum-desc'} +local search_string = '' +local search_active = false + +local incognito_mode = false +local autosaved_entry = false +local incognito_auto_run_triggered = false + +local loadTriggered = false --1.1.5# to identify if load is triggered atleast once for idle option +local resume_selected = false +local list_contents = {} +local list_start = 0 +local list_cursor = 1 +local list_highlight_cursor = {} +local list_drawn = false +local list_pages = {} +local filePath, fileTitle, fileLength +local seekTime = 0 +local logTime = 0 --1.3# use logTime since seekTime is used in multiple places +local filterName = 'all' +local sortName + +function starts_protocol(tab, val) + for index, value in ipairs(tab) do + if (val:find(value) == 1) then + return true + end + end + return false +end + +function contain_value(tab, val) + if not tab then return msg.error('check value passed') end + if not val then return msg.error('check value passed') end + + for index, value in ipairs(tab) do + if value.match(string.lower(val), string.lower(value)) then + return true + end + end + + return false +end + +function has_value(tab, val, array2d) + if not tab then return msg.error('check value passed') end + if not val then return msg.error('check value passed') end + if not array2d then + for index, value in ipairs(tab) do + if string.lower(value) == string.lower(val) then + return true + end + end + end + if array2d then + for i=1, #tab do + if tab[i] and string.lower(tab[i][array2d]) == string.lower(val) then + return true + end + end + end + + return false +end + +function file_exists(name) + local f = io.open(name, "r") + if f ~= nil then io.close(f) return true else return false end +end + +function format_time(seconds, sep, decimals, style) + local function divmod (a, b) + return math.floor(a / b), a % b + end + decimals = decimals == nil and 3 or decimals + + local s = seconds + local h, s = divmod(s, 60*60) + local m, s = divmod(s, 60) + + if decimals == 'truncate' then + s = math.floor(s) + decimals = 0 + if style == 'timestamp' then + seconds = math.floor(seconds) + end + end + + if not style or style == '' or style == 'default' then + local second_format = string.format("%%0%d.%df", 2+(decimals > 0 and decimals+1 or 0), decimals) + sep = sep and sep or ":" + return string.format("%02d"..sep.."%02d"..sep..second_format, h, m, s) + elseif style == 'hms' or style == 'hms-full' then + sep = sep ~= nil and sep or " " + if style == 'hms-full' or h > 0 then + return string.format("%dh"..sep.."%dm"..sep.."%." .. tostring(decimals) .. "fs", h, m, s) + elseif m > 0 then + return string.format("%dm"..sep.."%." .. tostring(decimals) .. "fs", m, s) + else + return string.format("%." .. tostring(decimals) .. "fs", s) + end + elseif style == 'timestamp' then + return string.format("%." .. tostring(decimals) .. "f", seconds) + elseif style == 'timestamp-concise' then + return seconds + end +end + +function get_file() + local path = mp.get_property('path') + if not path then return end + + local length = (mp.get_property_number('duration') or 0) + + local title = mp.get_property('media-title'):gsub("\"", "") + + + if starts_protocol(o.logging_protocols, path) and o.prefer_filename_over_title == 'protocols' then + title = mp.get_property('filename'):gsub("\"", "") + elseif not starts_protocol(o.logging_protocols, path) and o.prefer_filename_over_title == 'local' then + title = mp.get_property('filename'):gsub("\"", "") + elseif o.prefer_filename_over_title == 'all' then + title = mp.get_property('filename'):gsub("\"", "") + end + + return path, title, length +end + +function bind_keys(keys, name, func, opts) + if not keys then + mp.add_forced_key_binding(keys, name, func, opts) + return + end + + for i = 1, #keys do + if i == 1 then + mp.add_forced_key_binding(keys[i], name, func, opts) + else + mp.add_forced_key_binding(keys[i], name .. i, func, opts) + end + end +end + +function unbind_keys(keys, name) + if not keys then + mp.remove_key_binding(name) + return + end + + for i = 1, #keys do + if i == 1 then + mp.remove_key_binding(name) + else + mp.remove_key_binding(name .. i) + end + end +end + +function esc_string(str) + return str:gsub("([%p])", "%%%1") +end + +---------Start of LogManager--------- +--LogManager (Read and Format the List from Log)-- +function read_log(func) + local f = io.open(log_fullpath, "r") + if not f then return end + local contents = {} + for line in f:lines() do + table.insert(contents, (func(line))) + end + f:close() + return contents +end + +function read_log_table() + local line_pos = 0 + return read_log(function(line) + local tt, p, t, s, d, n, e, l, dt, ln, r + if line:match('^.-\"(.-)\"') then + tt = line:match('^.-\"(.-)\"') + n, p = line:match('^.-\"(.-)\" | (.*) | ' .. esc_string(log_length_text) .. '(.*)') + else + p = line:match('[(.*)%]]%s(.*) | ' .. esc_string(log_length_text) .. '(.*)') + d, n, e = p:match('^(.-)([^\\/]-)%.([^\\/%.]-)%.?$') + end + dt = line:match('%[(.-)%]') + t = line:match(' | ' .. esc_string(log_time_text) .. '(%d*%.?%d*)(.*)$') + ln = line:match(' | ' .. esc_string(log_length_text) .. '(%d*%.?%d*)(.*)$') + r = tonumber(ln) - tonumber(t) + l = line + line_pos = line_pos + 1 + return {found_path = p, found_time = t, found_name = n, found_title = tt, found_line = l, found_sequence = line_pos, found_directory = d, found_datetime = dt, found_length = ln, found_remaining = r} + end) +end + +function list_sort(tab, sort) + if sort == 'added-asc' then + table.sort(tab, function(a, b) return a['found_sequence'] < b['found_sequence'] end) + elseif sort == 'added-desc' then + table.sort(tab, function(a, b) return a['found_sequence'] > b['found_sequence'] end) + elseif sort == 'time-asc' then + table.sort(tab, function(a, b) return tonumber(a['found_time']) > tonumber(b['found_time']) end) + elseif sort == 'time-desc' then + table.sort(tab, function(a, b) return tonumber(a['found_time']) < tonumber(b['found_time']) end) + elseif sort == 'alphanum-asc' or sort == 'alphanum-desc' then + local function padnum(d) local dec, n = string.match(d, "(%.?)0*(.+)") + return #dec > 0 and ("%.12f"):format(d) or ("%s%03d%s"):format(dec, #n, n) end + if sort == 'alphanum-asc' then + table.sort(tab, function(a, b) return tostring(a['found_path']):gsub("%.?%d+", padnum) .. ("%3d"):format(#b) > tostring(b['found_path']):gsub("%.?%d+", padnum) .. ("%3d"):format(#a) end) + elseif sort == 'alphanum-desc' then + table.sort(tab, function(a, b) return tostring(a['found_path']):gsub("%.?%d+", padnum) .. ("%3d"):format(#b) < tostring(b['found_path']):gsub("%.?%d+", padnum) .. ("%3d"):format(#a) end) + end + end + + return tab +end + +function parse_header(string) + local osd_header_color = string.format("{\\1c&H%s}", o.header_color) + local osd_search_color = osd_header_color + if search_active == 'typing' then + osd_search_color = string.format("{\\1c&H%s}", o.search_color_typing) + elseif search_active == 'not_typing' then + osd_search_color = string.format("{\\1c&H%s}", o.search_color_not_typing) + end + local osd_msg_end = "{\\1c&HFFFFFF}" + + string = string:gsub("%%total%%", #list_contents) + :gsub("%%cursor%%", list_cursor) + + if filterName ~= 'all' then + string = string:gsub("%%filter%%", filterName) + :gsub("%%prefilter%%", o.header_filter_pre_text) + :gsub("%%afterfilter%%", o.header_filter_after_text) + else + string = string:gsub("%%filter%%", '') + :gsub("%%prefilter%%", '') + :gsub("%%afterfilter%%", '') + end + + local list_total_duration = 0 + if string:match('%listduration%%') then + list_total_duration = get_total_duration('found_time') + if list_total_duration > 0 then + string = string:gsub("%%listduration%%", format_time(list_total_duration, o.header_duration_time_format[3], o.header_duration_time_format[2], o.header_duration_time_format[1])) + else + string = string:gsub("%%listduration%%", '') + end + end + if list_total_duration > 0 then + string = string:gsub("%%prelistduration%%", o.header_list_duration_pre_text) + :gsub("%%afterlistduration%%", o.header_list_duration_after_text) + else + string = string:gsub("%%prelistduration%%", '') + :gsub("%%afterlistduration%%", '') + end + + local list_total_length = 0 + if string:match('%listlength%%') then + list_total_length = get_total_duration('found_length') + if list_total_length > 0 then + string = string:gsub("%%listlength%%", format_time(list_total_length, o.header_length_time_format[3], o.header_length_time_format[2], o.header_length_time_format[1])) + else + string = string:gsub("%%listlength%%", '') + end + end + if list_total_length > 0 then + string = string:gsub("%%prelistlength%%", o.header_list_length_pre_text) + :gsub("%%afterlistlength%%", o.header_list_length_after_text) + else + string = string:gsub("%%prelistlength%%", '') + :gsub("%%afterlistlength%%", '') + end + + local list_total_remaining = 0 + if string:match('%listremaining%%') then + list_total_remaining = get_total_duration('found_remaining') + if list_total_remaining > 0 then + string = string:gsub("%%listremaining%%", format_time(list_total_remaining, o.header_remaining_time_format[3], o.header_remaining_time_format[2], o.header_remaining_time_format[1])) + else + string = string:gsub("%%listremaining%%", '') + end + end + if list_total_remaining > 0 then + string = string:gsub("%%prelistremaining%%", o.header_list_remaining_pre_text) + :gsub("%%afterlistremaining%%", o.header_list_remaining_after_text) + else + string = string:gsub("%%prelistremaining%%", '') + :gsub("%%afterlistremaining%%", '') + end + + if #list_highlight_cursor > 0 then + string = string:gsub("%%highlight%%", #list_highlight_cursor) + :gsub("%%prehighlight%%", o.header_highlight_pre_text) + :gsub("%%afterhighlight%%", o.header_highlight_after_text) + else + string = string:gsub("%%highlight%%", '') + :gsub("%%prehighlight%%", '') + :gsub("%%afterhighlight%%", '') + end + + if sortName and sortName ~= o.header_sort_hide_text then + string = string:gsub("%%sort%%", sortName) + :gsub("%%presort%%", o.header_sort_pre_text) + :gsub("%%aftersort%%", o.header_sort_after_text) + else + string = string:gsub("%%sort%%", '') + :gsub("%%presort%%", '') + :gsub("%%aftersort%%", '') + end + + if search_active then + local search_string_osd = search_string + if search_string_osd ~= '' then + search_string_osd = search_string:gsub('%%', '%%%%%%%%'):gsub('\\', '\\​'):gsub('{', '\\{') + end + + string = string:gsub("%%search%%", osd_search_color..search_string_osd..osd_header_color) + :gsub("%%presearch%%", o.header_search_pre_text) + :gsub("%%aftersearch%%", o.header_search_after_text) + else + string = string:gsub("%%search%%", '') + :gsub("%%presearch%%", '') + :gsub("%%aftersearch%%", '') + end + string = string:gsub("%%%%", "%%") + return string +end + +function get_list_contents(filter, sort) + if not filter then filter = filterName end + if not sort then sort = get_list_sort(filter) end + + local current_sort + + local filtered_table = {} + + local prev_list_contents + if list_contents ~= nil and list_contents[1] then + prev_list_contents = list_contents + else + prev_list_contents = read_log_table() + end + + list_contents = read_log_table() + if not list_contents and not search_active or not list_contents[1] and not search_active then return end + current_sort = 'added-asc' + + if filter == 'recents' then + table.sort(list_contents, function(a, b) return a['found_sequence'] < b['found_sequence'] end) + local unique_values = {} + local list_total = #list_contents + + if filePath == list_contents[#list_contents].found_path and tonumber(list_contents[#list_contents].found_time) == 0 then + list_total = list_total -1 + end + + for i = list_total, 1, -1 do + if not has_value(unique_values, list_contents[i].found_path) then + table.insert(unique_values, list_contents[i].found_path) + table.insert(filtered_table, list_contents[i]) + end + end + table.sort(filtered_table, function(a, b) return a['found_sequence'] < b['found_sequence'] end) + + list_contents = filtered_table + + end + + if filter == 'distinct' then + table.sort(list_contents, function(a, b) return a['found_sequence'] < b['found_sequence'] end) + local unique_values = {} + local list_total = #list_contents + + if filePath == list_contents[#list_contents].found_path and tonumber(list_contents[#list_contents].found_time) == 0 then + list_total = list_total -1 + end + + for i = list_total, 1, -1 do + if list_contents[i].found_directory and not has_value(unique_values, list_contents[i].found_directory) and not starts_protocol(protocols, list_contents[i].found_path) then + table.insert(unique_values, list_contents[i].found_directory) + table.insert(filtered_table, list_contents[i]) + end + end + table.sort(filtered_table, function(a, b) return a['found_sequence'] < b['found_sequence'] end) + + list_contents = filtered_table + end + + if filter == 'fileonly' then + for i = 1, #list_contents do + if tonumber(list_contents[i].found_time) == 0 then + table.insert(filtered_table, list_contents[i]) + end + end + + list_contents = filtered_table + end + + if filter == 'timeonly' then + for i = 1, #list_contents do + if tonumber(list_contents[i].found_time) > 0 then + table.insert(filtered_table, list_contents[i]) + end + end + + list_contents = filtered_table + end + + if filter == 'titleonly' then + for i = 1, #list_contents do + if list_contents[i].found_title then + table.insert(filtered_table, list_contents[i]) + end + end + + list_contents = filtered_table + end + + if filter == 'protocols' then + for i = 1, #list_contents do + if starts_protocol(o.logging_protocols, list_contents[i].found_path) then + table.insert(filtered_table, list_contents[i]) + end + end + + list_contents = filtered_table + end + + if filter == 'keywords' then + for i = 1, #list_contents do + if contain_value(o.keywords_filter_list, list_contents[i].found_line) then + table.insert(filtered_table, list_contents[i]) + end + end + + list_contents = filtered_table + end + + if filter == 'playing' then + for i = 1, #list_contents do + if list_contents[i].found_path == filePath then + table.insert(filtered_table, list_contents[i]) + end + end + + list_contents = filtered_table + end + + if search_active and search_string ~= '' then + local filtered_table = {} + + local search_query = '' + for search in search_string:gmatch("[^%s]+") do + search_query = search_query..'.-'..esc_string(search) + end + + local contents_string = '' + for i = 1, #list_contents do + + if o.search_behavior == 'specific' then + if string.lower(list_contents[i].found_path):match(string.lower(search_query)) then + table.insert(filtered_table, list_contents[i]) + elseif list_contents[i].found_title and string.lower(list_contents[i].found_title):match(string.lower(search_query)) then + table.insert(filtered_table, list_contents[i]) + elseif tonumber(list_contents[i].found_time) > 0 and format_time(list_contents[i].found_time, o.osd_time_format[3], o.osd_time_format[2], o.osd_time_format[1]):match(search_query) then + table.insert(filtered_table, list_contents[i]) + elseif string.lower(list_contents[i].found_datetime):match(string.lower(search_query)) then + table.insert(filtered_table, list_contents[i]) + end + elseif o.search_behavior == 'any' then + contents_string = list_contents[i].found_datetime..(list_contents[i].found_title or '')..list_contents[i].found_path + if tonumber(list_contents[i].found_time) > 0 then + contents_string = contents_string..format_time(list_contents[i].found_time, o.osd_time_format[3], o.osd_time_format[2], o.osd_time_format[1]) + end + elseif o.search_behavior == 'any-notime' then + contents_string = list_contents[i].found_datetime..(list_contents[i].found_title or '')..list_contents[i].found_path + end + + if string.lower(contents_string):match(string.lower(search_query)) then + table.insert(filtered_table, list_contents[i]) + end + end + + list_contents = filtered_table + + end + + if sort ~= current_sort then + list_sort(list_contents, sort) + end + + if not list_contents and not search_active or not list_contents[1] and not search_active then return end +end + +function get_list_sort(filter) + if not filter then filter = filterName end + + local sort + for i=1, #o.list_filters_sort do + if o.list_filters_sort[i][1] == filter then + if has_value(available_sorts, o.list_filters_sort[i][2]) then sort = o.list_filters_sort[i][2] end + break + end + end + + if not sort and has_value(available_sorts, o.list_default_sort) then sort = o.list_default_sort end + + if not sort then sort = 'added-asc' end + + return sort +end + +function draw_list() + local osd_msg = '' + local osd_index = '' + local osd_key = '' + local osd_color = '' + local key = 0 + local osd_text = string.format("{\\an%f{\\fscx%f}{\\fscy%f}{\\bord%f}{\\1c&H%s}", o.list_alignment, o.text_scale, o.text_scale, o.text_border, o.text_color) + local osd_cursor = string.format("{\\an%f}{\\fscx%f}{\\fscy%f}{\\bord%f}{\\1c&H%s}", o.list_alignment, o.text_cursor_scale, o.text_cursor_scale, o.text_cursor_border, o.text_cursor_color) + local osd_header = string.format("{\\an%f}{\\fscx%f}{\\fscy%f}{\\bord%f}{\\1c&H%s}", o.list_alignment, o.header_scale, o.header_scale, o.header_border, o.header_color) + local osd_msg_end = "{\\1c&HFFFFFF}" + local osd_time_type = 'found_time' + + if o.text_time_type == 'length' then + osd_time_type = 'found_length' + elseif o.text_time_type == 'remaining' then + osd_time_type = 'found_remaining' + end + + if o.header_text ~= '' then + osd_msg = osd_msg .. osd_header .. parse_header(o.header_text) + osd_msg = osd_msg .. "\\h\\N\\N" .. osd_msg_end + end + + if search_active and not list_contents[1] then + osd_msg = osd_msg .. 'No search results found' .. osd_msg_end + end + + if o.list_middle_loader then + list_start = list_cursor - math.floor(o.list_show_amount / 2) + else + list_start = list_cursor - o.list_show_amount + end + + local showall = false + local showrest = false + if list_start < 0 then list_start = 0 end + if #list_contents <= o.list_show_amount then + list_start = 0 + showall = true + end + if list_start > math.max(#list_contents - o.list_show_amount - 1, 0) then + list_start = #list_contents - o.list_show_amount + showrest = true + end + if list_start > 0 and not showall then + osd_msg = osd_msg .. o.list_sliced_prefix .. osd_msg_end + end + for i = list_start, list_start + o.list_show_amount - 1, 1 do + if i == #list_contents then break end + + if o.show_paths then + p = list_contents[#list_contents - i].found_path or list_contents[#list_contents - i].found_name or "" + else + p = list_contents[#list_contents - i].found_name or list_contents[#list_contents - i].found_path or "" + end + + if o.slice_longfilenames and p:len() > o.slice_longfilenames_amount then + p = p:sub(1, o.slice_longfilenames_amount) .. "..." + end + + if o.quickselect_0to9_keybind and o.list_show_amount <= 10 and o.quickselect_0to9_pre_text then + key = 1 + key + if key == 10 then key = 0 end + osd_key = '(' .. key .. ') ' + end + + if o.show_item_number then + osd_index = (i + 1) .. '. ' + end + + if i + 1 == list_cursor then + osd_color = osd_cursor + else + osd_color = osd_text + end + + for j = 1, #list_highlight_cursor do + if list_highlight_cursor[j] and list_highlight_cursor[j][1] == i+1 then + osd_msg = osd_msg..osd_color..esc_string(o.text_highlight_pre_text) + end + end + + osd_msg = osd_msg .. osd_color .. osd_key .. osd_index .. p + + if list_contents[#list_contents - i][osd_time_type] and tonumber(list_contents[#list_contents - i][osd_time_type]) > 0 then + osd_msg = osd_msg .. o.time_seperator .. format_time(list_contents[#list_contents - i][osd_time_type], o.list_time_format[3], o.list_time_format[2], o.list_time_format[1]) + end + + osd_msg = osd_msg .. '\\h\\N\\N' .. osd_msg_end + + if i == list_start + o.list_show_amount - 1 and not showall and not showrest then + osd_msg = osd_msg .. o.list_sliced_suffix + end + + end + mp.set_osd_ass(0, 0, osd_msg) +end + +function list_empty_error_msg() + if list_contents ~= nil and list_contents[1] then return end + local msg_text + if filterName ~= 'all' then + msg_text = filterName .. " filter in History Empty" + else + msg_text = "History Empty" + end + msg.info(msg_text) + if o.osd_messages == true and not list_drawn then + mp.osd_message(msg_text) + end +end + +function display_list(filter, sort, action) + if not filter or not has_value(available_filters, filter) then filter = 'all' end + if not sortName then sortName = get_list_sort(filter) end + + local prev_sort = sortName + if not has_value(available_sorts, prev_sort) then prev_sort = get_list_sort() end + + if not sort then sort = get_list_sort(filter) end + sortName = sort + + local prev_filter = filterName + filterName = filter + + get_list_contents(filter, sort) + + if action ~= 'hide-osd' then + if not list_contents or not list_contents[1] then + list_empty_error_msg() + filterName = prev_filter + get_list_contents(filterName) + return + end + end + if not list_contents and not search_active or not list_contents[1] and not search_active then return end + + if not has_value(o.filters_and_sequence, filter) then + table.insert(o.filters_and_sequence, filter) + end + + local insert_new = false + + local trigger_close_list = false + local trigger_initial_list = false + + + if not list_pages or not list_pages[1] then + table.insert(list_pages, {filter, 1, 1, {}, sort}) + else + for i = 1, #list_pages do + if list_pages[i][1] == filter then + list_pages[i][3] = list_pages[i][3]+1 + insert_new = false + break + else + insert_new = true + end + end + end + + if insert_new then table.insert(list_pages, {filter, 1, 1, {}, sort}) end + + for i = 1, #list_pages do + if not search_active and list_pages[i][1] == prev_filter then + list_pages[i][2] = list_cursor + list_pages[i][4] = list_highlight_cursor + list_pages[i][5] = prev_sort + end + if list_pages[i][1] ~= filter then + list_pages[i][3] = 0 + end + if list_pages[i][3] == 2 and filter == 'all' and o.main_list_keybind_twice_exits then + trigger_close_list = true + elseif list_pages[i][3] == 2 and list_pages[1][1] == filter then + trigger_close_list = true + elseif list_pages[i][3] == 2 then + trigger_initial_list = true + end + end + + if trigger_initial_list then + display_list(list_pages[1][1], nil, 'hide-osd') + return + end + + if trigger_close_list then + list_close_and_trash_collection() + return + end + + if not search_active then get_page_properties(filter) else update_search_results('','') end + draw_list() + if utils.shared_script_property_set then + utils.shared_script_property_set('simplehistory-menu-open', 'yes') + end + mp.set_property('user-data/simplehistory/menu-open', 'yes') + if o.toggle_idlescreen then mp.commandv('script-message', 'osc-idlescreen', 'no', 'no_osd') end --1.1.6# fix osc-idlescreen (value was yes for some reason) + list_drawn = true + if not search_active then get_list_keybinds() end +end + +--End of LogManager (Read and Format the List from Log)-- + +--LogManager Navigation-- +function select(pos, action) + if not search_active then + if not list_contents or not list_contents[1] then + list_close_and_trash_collection() + return + end + end + + local list_cursor_temp = list_cursor + pos + if list_cursor_temp > 0 and list_cursor_temp <= #list_contents then + list_cursor = list_cursor_temp + + if action == 'highlight' then + if not has_value(list_highlight_cursor, list_cursor, 1) then + if pos > -1 then + for i = pos, 1, -1 do + if not has_value(list_highlight_cursor, list_cursor-i, 1) then + table.insert(list_highlight_cursor, {list_cursor-i, list_contents[#list_contents+1+i - list_cursor]}) + end + end + else + for i = pos, -1, 1 do + if not has_value(list_highlight_cursor, list_cursor-i, 1) then + table.insert(list_highlight_cursor, {list_cursor-i, list_contents[#list_contents+1+i - list_cursor]}) + end + end + end + table.insert(list_highlight_cursor, {list_cursor, list_contents[#list_contents+1 - list_cursor]}) + else + for i=1, #list_highlight_cursor do + if list_highlight_cursor[i] and list_highlight_cursor[i][1] == list_cursor then + table.remove(list_highlight_cursor, i) + end + end + if pos > -1 then + for i=1, #list_highlight_cursor do + for j = pos, 1, -1 do + if list_highlight_cursor[i] and list_highlight_cursor[i][1] == list_cursor-j then + table.remove(list_highlight_cursor, i) + end + end + end + else + for i=#list_highlight_cursor, 1, -1 do + for j = pos, -1, 1 do + if list_highlight_cursor[i] and list_highlight_cursor[i][1] == list_cursor-j then + table.remove(list_highlight_cursor, i) + end + end + end + end + end + end + end + + if o.loop_through_list then + if list_cursor_temp > #list_contents then + list_cursor = 1 + elseif list_cursor_temp < 1 then + list_cursor = #list_contents + end + end + + draw_list() +end + +function list_move_up(action) + select(-1, action) + + if search_active and o.search_not_typing_smartly then + list_search_not_typing_mode(true) + end +end + +function list_move_down(action) + select(1, action) + + if search_active and o.search_not_typing_smartly then + list_search_not_typing_mode(true) + end +end + +function list_move_first(action) + select(1 - list_cursor, action) + + if search_active and o.search_not_typing_smartly then + list_search_not_typing_mode(true) + end +end + +function list_move_last(action) + select(#list_contents - list_cursor, action) + + if search_active and o.search_not_typing_smartly then + list_search_not_typing_mode(true) + end +end + +function list_page_up(action) + select(list_start + 1 - list_cursor, action) + + if search_active and o.search_not_typing_smartly then + list_search_not_typing_mode(true) + end +end + +function list_page_down(action) + if o.list_middle_loader then + if #list_contents < o.list_show_amount then + select(#list_contents - list_cursor, action) + else + select(o.list_show_amount + list_start - list_cursor, action) + end + else + if o.list_show_amount > list_cursor then + select(o.list_show_amount - list_cursor, action) + elseif #list_contents - list_cursor >= o.list_show_amount then + select(o.list_show_amount, action) + else + select(#list_contents - list_cursor, action) + end + end + + if search_active and o.search_not_typing_smartly then + list_search_not_typing_mode(true) + end +end + +function list_highlight_all() + get_list_contents(filterName) + if not list_contents or not list_contents[1] then return end + + if #list_highlight_cursor < #list_contents then + for i=1, #list_contents do + if not has_value(list_highlight_cursor, i, 1) then + table.insert(list_highlight_cursor, {i, list_contents[#list_contents+1-i]}) + end + end + select(0) + else + list_unhighlight_all() + end +end + +function list_unhighlight_all() + if not list_highlight_cursor or not list_highlight_cursor[1] then return end + list_highlight_cursor = {} + select(0) +end +--End of LogManager Navigation-- + +--LogManager Actions-- +function load(list_cursor, add_playlist, target_time) + if not list_contents or not list_contents[1] then return end + if not target_time then + seekTime = tonumber(list_contents[#list_contents - list_cursor + 1].found_time) + o.resume_offset + if (seekTime < 0) then + seekTime = 0 + end + else + seekTime = target_time + end + if file_exists(list_contents[#list_contents - list_cursor + 1].found_path) or starts_protocol(protocols, list_contents[#list_contents - list_cursor + 1].found_path) then + if not add_playlist then + if filePath ~= list_contents[#list_contents - list_cursor + 1].found_path then + mp.commandv('loadfile', list_contents[#list_contents - list_cursor + 1].found_path) + resume_selected = true + else + mp.commandv('seek', seekTime, 'absolute', 'exact') + list_close_and_trash_collection() + end + if o.osd_messages == true then + mp.osd_message('Loaded:\n' .. list_contents[#list_contents - list_cursor + 1].found_name.. o.time_seperator .. format_time(seekTime, o.osd_time_format[3], o.osd_time_format[2], o.osd_time_format[1])) + end + msg.info('Loaded the below file:\n' .. list_contents[#list_contents - list_cursor + 1].found_name .. ' | '.. format_time(seekTime)) + else + mp.commandv('loadfile', list_contents[#list_contents - list_cursor + 1].found_path, 'append-play') + if o.osd_messages == true then + mp.osd_message('Added into Playlist:\n'..list_contents[#list_contents - list_cursor + 1].found_name..' ') + end + msg.info('Added the below file into playlist:\n' .. list_contents[#list_contents - list_cursor + 1].found_path) + end + else + if o.osd_messages == true then + mp.osd_message('File Doesn\'t Exist:\n' .. list_contents[#list_contents - list_cursor + 1].found_path) + end + msg.info('The file below doesn\'t seem to exist:\n' .. list_contents[#list_contents - list_cursor + 1].found_path) + return + end +end + +function list_select() + load(list_cursor) +end + +function list_add_playlist(action) + if not action then + load(list_cursor, true) + elseif action == 'highlight' then + if not list_highlight_cursor or not list_highlight_cursor[1] then return end + local file_ignored_total = 0 + + for i=1, #list_highlight_cursor do + if file_exists(list_highlight_cursor[i][2].found_path) or starts_protocol(protocols, list_highlight_cursor[i][2].found_path) then + mp.commandv("loadfile", list_highlight_cursor[i][2].found_path, "append-play") + else + msg.warn('The below file was not added into playlist as it does not seem to exist:\n' .. list_highlight_cursor[i][2].found_path) + file_ignored_total = file_ignored_total + 1 + end + end + if o.osd_messages == true then + if file_ignored_total > 0 then + mp.osd_message('Added into Playlist '..#list_highlight_cursor - file_ignored_total..' Item/s\nIgnored '..file_ignored_total.. " Item/s That Do Not Exist") + else + mp.osd_message('Added into Playlist '..#list_highlight_cursor - file_ignored_total..' Item/s') + end + end + if file_ignored_total > 0 then + msg.warn('Ignored a total of '..file_ignored_total.. " Item/s that does not seem to exist") + end + msg.info('Added into playlist a total of '..#list_highlight_cursor - file_ignored_total..' item/s') + end +end + +function delete_log_entry_specific(target_index, target_path, target_time) + local trigger_delete = false + list_contents = read_log_table() + if not list_contents or not list_contents[1] then return end + if target_index == 'last' then target_index = #list_contents end + if not target_index then return end + + if target_index and target_path and target_time then + if list_contents[target_index].found_path == target_path and tonumber(list_contents[target_index].found_time) == target_time then + table.remove(list_contents, target_index) + trigger_delete = true + end + elseif target_index and target_path and not target_time then + if list_contents[target_index].found_path == target_path then + table.remove(list_contents, target_index) + trigger_delete = true + end + elseif target_index and target_time and not target_path then + if tonumber(list_contents[target_index].found_time) == target_time then + table.remove(list_contents, target_index) + trigger_delete = true + end + elseif target_index and not target_path and not target_time then + table.remove(list_contents, target_index) + trigger_delete = true + end + + if not trigger_delete then return end + local f = io.open(log_fullpath, "w+") + if list_contents ~= nil and list_contents[1] then + for i = 1, #list_contents do + f:write(("%s\n"):format(list_contents[i].found_line)) + end + end + f:close() +end + +function delete_log_entry(multiple, round, target_path, target_time, entry_limit) + if not target_path then target_path = filePath end + if not target_time then target_time = seekTime end + list_contents = read_log_table() + if not list_contents or not list_contents[1] then return end + local trigger_delete = false + + if not multiple then + for i = #list_contents, 1, -1 do + if not round then + if list_contents[i].found_path == target_path and tonumber(list_contents[i].found_time) == target_time then + table.remove(list_contents, i) + trigger_delete = true + break + end + else + if list_contents[i].found_path == target_path and math.floor(tonumber(list_contents[i].found_time)) == target_time then + table.remove(list_contents, i) + trigger_delete = true + break + end + end + end + else + for i = #list_contents, 1, -1 do + if not round then + if list_contents[i].found_path == target_path and tonumber(list_contents[i].found_time) == target_time then + table.remove(list_contents, i) + trigger_delete = true + end + else + if list_contents[i].found_path == target_path and math.floor(tonumber(list_contents[i].found_time)) == target_time then + table.remove(list_contents, i) + trigger_delete = true + end + end + end + end + + if entry_limit and entry_limit > -1 then + local entries_found = 0 + for i = #list_contents, 1, -1 do + if list_contents[i].found_path == target_path and entries_found < entry_limit then + entries_found = entries_found + 1 + elseif list_contents[i].found_path == target_path and entries_found >= entry_limit then + table.remove(list_contents,i) + trigger_delete = true + end + end + end + + if not trigger_delete then return end + local f = io.open(log_fullpath, "w+") + if list_contents ~= nil and list_contents[1] then + for i = 1, #list_contents do + f:write(("%s\n"):format(list_contents[i].found_line)) + end + end + f:close() +end + +function delete_log_entry_highlighted() + if not list_highlight_cursor or not list_highlight_cursor[1] then return end + list_contents = read_log_table() + if not list_contents or not list_contents[1] then return end + + local list_contents_length = #list_contents + + for i = 1, list_contents_length do + for j=1, #list_highlight_cursor do + if list_contents[list_contents_length+1-i] then + if list_contents[list_contents_length+1-i].found_sequence == list_highlight_cursor[j][2].found_sequence then + table.remove(list_contents, list_contents_length+1-i) + end + end + end + end + + msg.info("Deleted "..#list_highlight_cursor.." Item/s") + + list_unhighlight_all() + + local f = io.open(log_fullpath, "w+") + if list_contents ~= nil and list_contents[1] then + for i = 1, #list_contents do + f:write(("%s\n"):format(list_contents[i].found_line)) + end + end + f:close() + +end + +function delete_selected() + filePath = list_contents[#list_contents - list_cursor + 1].found_path + fileTitle = list_contents[#list_contents - list_cursor + 1].found_name + seekTime = tonumber(list_contents[#list_contents - list_cursor + 1].found_time) + if not filePath and not seekTime then + msg.info("Failed to delete") + return + end + delete_log_entry() + msg.info("Deleted \"" .. filePath .. "\" | " .. format_time(seekTime)) + filePath, fileTitle, fileLength = get_file() +end + +function list_delete(action) + if not action then + delete_selected() + elseif action == 'highlight' then + delete_log_entry_highlighted() + end + get_list_contents() + if not list_contents or not list_contents[1] then + list_close_and_trash_collection() + return + end + if list_cursor < #list_contents + 1 then + select(0) + else + list_move_last() + end +end + +function get_total_duration(action) + if not list_contents or not list_contents[1] then return 0 end + local list_total_duration = 0 + if action == 'found_time' or action == 'found_length' or action == 'found_remaining' then + for i = #list_contents, 1, -1 do + if tonumber(list_contents[i][action]) > 0 then + list_total_duration = list_total_duration + list_contents[i][action] + end + end + end + return list_total_duration +end + +function list_cycle_sort() + local next_sort + for i = 1, #available_sorts do + if sortName == available_sorts[i] then + if i == #available_sorts then + next_sort = available_sorts[1] + break + else + next_sort = available_sorts[i+1] + break + end + end + end + if not next_sort then return end + get_list_contents(filterName, next_sort) + sortName = next_sort + update_list_highlist_cursor() + select(0) +end + +function update_list_highlist_cursor() + if not list_highlight_cursor or not list_highlight_cursor[1] then return end + + local temp_list_highlight_cursor = {} + for i = 1, #list_contents do + for j=1, #list_highlight_cursor do + if list_contents[#list_contents+1-i].found_sequence == list_highlight_cursor[j][2].found_sequence then + table.insert(temp_list_highlight_cursor, {i, list_highlight_cursor[j][2]}) + end + end + end + + list_highlight_cursor = temp_list_highlight_cursor +end + +--End of LogManager Actions-- + +--LogManager Filter Functions-- +function get_page_properties(filter) + if not filter then return end + for i=1, #list_pages do + if list_pages[i][1] == filter then + list_cursor = list_pages[i][2] + list_highlight_cursor = list_pages[i][4] + sortName = list_pages[i][5] + end + end + if list_cursor > #list_contents then + list_move_last() + end +end + +function select_filter_sequence(pos) + if not list_drawn then return end + local curr_pos + local target_pos + + for i = 1, #o.filters_and_sequence do + if filterName == o.filters_and_sequence[i] then + curr_pos = i + end + end + + if curr_pos and pos > -1 then + for i = curr_pos, #o.filters_and_sequence do + if o.filters_and_sequence[i + pos] then + get_list_contents(o.filters_and_sequence[i + pos]) + if list_contents ~= nil and list_contents[1] then + target_pos = i + pos + break + end + end + end + elseif curr_pos and pos < 0 then + for i = curr_pos, 0, -1 do + if o.filters_and_sequence[i + pos] then + get_list_contents(o.filters_and_sequence[i + pos]) + if list_contents ~= nil and list_contents[1] then + target_pos = i + pos + break + end + end + end + end + + if o.loop_through_filters then + if not target_pos and pos > -1 or target_pos and target_pos > #o.filters_and_sequence then + for i = 1, #o.filters_and_sequence do + get_list_contents(o.filters_and_sequence[i]) + if list_contents ~= nil and list_contents[1] then + target_pos = i + break + end + end + end + if not target_pos and pos < 0 or target_pos and target_pos < 1 then + for i = #o.filters_and_sequence, 1, -1 do + get_list_contents(o.filters_and_sequence[i]) + if list_contents ~= nil and list_contents[1] then + target_pos = i + break + end + end + end + end + + if o.filters_and_sequence[target_pos] then + display_list(o.filters_and_sequence[target_pos], nil, 'hide-osd') + end +end + +function list_filter_next() + select_filter_sequence(1) +end +function list_filter_previous() + select_filter_sequence(-1) +end +--End of LogManager Filter Functions-- + +--LogManager (List Bind and Unbind)-- +function get_list_keybinds() + bind_keys(o.list_ignored_keybind, 'ignore') + bind_keys(o.list_move_up_keybind, 'move-up', list_move_up, 'repeatable') + bind_keys(o.list_move_down_keybind, 'move-down', list_move_down, 'repeatable') + bind_keys(o.list_move_first_keybind, 'move-first', list_move_first, 'repeatable') + bind_keys(o.list_move_last_keybind, 'move-last', list_move_last, 'repeatable') + bind_keys(o.list_page_up_keybind, 'page-up', list_page_up, 'repeatable') + bind_keys(o.list_page_down_keybind, 'page-down', list_page_down, 'repeatable') + bind_keys(o.list_select_keybind, 'list-select', list_select) + bind_keys(o.list_add_playlist_keybind, 'list-add-playlist', list_add_playlist) + bind_keys(o.list_add_playlist_highlighted_keybind, 'list-add-playlist-highlight', function()list_add_playlist('highlight')end) + bind_keys(o.list_delete_keybind, 'list-delete', list_delete) + bind_keys(o.list_delete_highlighted_keybind, 'list-delete-highlight', function()list_delete('highlight')end) + bind_keys(o.next_filter_sequence_keybind, 'list-filter-next', list_filter_next) + bind_keys(o.previous_filter_sequence_keybind, 'list-filter-previous', list_filter_previous) + bind_keys(o.list_search_activate_keybind, 'list-search-activate', list_search_activate) + bind_keys(o.list_highlight_all_keybind, 'list-highlight-all', list_highlight_all) + bind_keys(o.list_unhighlight_all_keybind, 'list-unhighlight-all', list_unhighlight_all) + bind_keys(o.list_cycle_sort_keybind, 'list-cycle-sort', list_cycle_sort) + + for i = 1, #o.list_highlight_move_keybind do + for j = 1, #o.list_move_up_keybind do + mp.add_forced_key_binding(o.list_highlight_move_keybind[i]..'+'..o.list_move_up_keybind[j], 'highlight-move-up'..j, function()list_move_up('highlight') end, 'repeatable') + end + for j = 1, #o.list_move_down_keybind do + mp.add_forced_key_binding(o.list_highlight_move_keybind[i]..'+'..o.list_move_down_keybind[j], 'highlight-move-down'..j, function()list_move_down('highlight') end, 'repeatable') + end + for j = 1, #o.list_move_first_keybind do + mp.add_forced_key_binding(o.list_highlight_move_keybind[i]..'+'..o.list_move_first_keybind[j], 'highlight-move-first'..j, function()list_move_first('highlight') end, 'repeatable') + end + for j = 1, #o.list_move_last_keybind do + mp.add_forced_key_binding(o.list_highlight_move_keybind[i]..'+'..o.list_move_last_keybind[j], 'highlight-move-last'..j, function()list_move_last('highlight') end, 'repeatable') + end + for j = 1, #o.list_page_up_keybind do + mp.add_forced_key_binding(o.list_highlight_move_keybind[i]..'+'..o.list_page_up_keybind[j], 'highlight-page-up'..j, function()list_page_up('highlight') end, 'repeatable') + end + for j = 1, #o.list_page_down_keybind do + mp.add_forced_key_binding(o.list_highlight_move_keybind[i]..'+'..o.list_page_down_keybind[j], 'highlight-page-down'..j, function()list_page_down('highlight') end, 'repeatable') + end + end + + if not search_active then + bind_keys(o.list_close_keybind, 'list-close', list_close_and_trash_collection) + end + + for i = 1, #o.list_filter_jump_keybind do + mp.add_forced_key_binding(o.list_filter_jump_keybind[i][1], 'list-filter-jump'..i, function()display_list(o.list_filter_jump_keybind[i][2]) end) + end + + for i = 1, #o.open_list_keybind do + if i == 1 then + mp.remove_key_binding('open-list') + else + mp.remove_key_binding('open-list'..i) + end + end + + if o.quickselect_0to9_keybind and o.list_show_amount <= 10 then + mp.add_forced_key_binding("1", "recent-1", function()load(list_start + 1) end) + mp.add_forced_key_binding("2", "recent-2", function()load(list_start + 2) end) + mp.add_forced_key_binding("3", "recent-3", function()load(list_start + 3) end) + mp.add_forced_key_binding("4", "recent-4", function()load(list_start + 4) end) + mp.add_forced_key_binding("5", "recent-5", function()load(list_start + 5) end) + mp.add_forced_key_binding("6", "recent-6", function()load(list_start + 6) end) + mp.add_forced_key_binding("7", "recent-7", function()load(list_start + 7) end) + mp.add_forced_key_binding("8", "recent-8", function()load(list_start + 8) end) + mp.add_forced_key_binding("9", "recent-9", function()load(list_start + 9) end) + mp.add_forced_key_binding("0", "recent-0", function()load(list_start + 10) end) + end +end + +function unbind_list_keys() + unbind_keys(o.list_ignored_keybind, 'ignore') + unbind_keys(o.list_move_up_keybind, 'move-up') + unbind_keys(o.list_move_down_keybind, 'move-down') + unbind_keys(o.list_move_first_keybind, 'move-first') + unbind_keys(o.list_move_last_keybind, 'move-last') + unbind_keys(o.list_page_up_keybind, 'page-up') + unbind_keys(o.list_page_down_keybind, 'page-down') + unbind_keys(o.list_select_keybind, 'list-select') + unbind_keys(o.list_add_playlist_keybind, 'list-add-playlist') + unbind_keys(o.list_add_playlist_highlighted_keybind, 'list-add-playlist-highlight') + unbind_keys(o.list_delete_keybind, 'list-delete') + unbind_keys(o.list_delete_highlighted_keybind, 'list-delete-highlight') + unbind_keys(o.list_close_keybind, 'list-close') + unbind_keys(o.next_filter_sequence_keybind, 'list-filter-next') + unbind_keys(o.previous_filter_sequence_keybind, 'list-filter-previous') + unbind_keys(o.list_highlight_all_keybind, 'list-highlight-all') + unbind_keys(o.list_highlight_all_keybind, 'list-unhighlight-all') + unbind_keys(o.list_cycle_sort_keybind, 'list-cycle-sort') + + for i = 1, #o.list_move_up_keybind do + mp.remove_key_binding('highlight-move-up'..i) + end + for i = 1, #o.list_move_down_keybind do + mp.remove_key_binding('highlight-move-down'..i) + end + for i = 1, #o.list_move_first_keybind do + mp.remove_key_binding('highlight-move-first'..i) + end + for i = 1, #o.list_move_last_keybind do + mp.remove_key_binding('highlight-move-last'..i) + end + for i = 1, #o.list_page_up_keybind do + mp.remove_key_binding('highlight-page-up'..i) + end + for i = 1, #o.list_page_down_keybind do + mp.remove_key_binding('highlight-page-down'..i) + end + + for i = 1, #o.list_filter_jump_keybind do + mp.remove_key_binding('list-filter-jump'..i) + end + + for i = 1, #o.open_list_keybind do + if i == 1 then + mp.add_forced_key_binding(o.open_list_keybind[i][1], 'open-list', function()display_list(o.open_list_keybind[i][2]) end) + else + mp.add_forced_key_binding(o.open_list_keybind[i][1], 'open-list'..i, function()display_list(o.open_list_keybind[i][2]) end) + end + end + + if o.quickselect_0to9_keybind and o.list_show_amount <= 10 then + 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") + end +end + +function list_close_and_trash_collection() + if utils.shared_script_property_set then + utils.shared_script_property_set('simplehistory-menu-open', 'no') + end + mp.set_property('user-data/simplehistory/menu-open', 'no') + if o.toggle_idlescreen then mp.commandv('script-message', 'osc-idlescreen', 'yes', 'no_osd') end + unbind_list_keys() + unbind_search_keys() + mp.set_osd_ass(0, 0, "") + list_drawn = false + list_cursor = 1 + list_start = 0 + filterName = 'all' + list_pages = {} + search_string = '' + search_active = false + list_highlight_cursor = {} + sortName = nil +end +--End of LogManager (List Bind and Unbind)-- + +--LogManager Search Feature-- +function list_search_exit() + search_active = false + get_list_contents(filterName) + get_page_properties(filterName) + select(0) + unbind_search_keys() + get_list_keybinds() +end + +function list_search_not_typing_mode(auto_triggered) + if auto_triggered then + if search_string ~= '' and list_contents[1] then + search_active = 'not_typing' + elseif not list_contents[1] then + return + else + search_active = false + end + else + if search_string ~= '' then + search_active = 'not_typing' + else + search_active = false + end + end + select(0) + unbind_search_keys() + get_list_keybinds() +end + +function list_search_activate() + if not list_drawn then return end + if search_active == 'typing' then list_search_exit() return end + search_active = 'typing' + + for i = 1, #list_pages do + if list_pages[i][1] == filterName then + list_pages[i][2] = list_cursor + list_pages[i][4] = list_highlight_cursor + list_pages[i][5] = sortName + end + end + + update_search_results('','') + bind_search_keys() +end + +function update_search_results(character, action) + if not character then character = '' end + if action == 'string_del' then + search_string = search_string:sub(1, -2) + end + search_string = search_string..character + local prev_contents_length = #list_contents + get_list_contents(filterName) + + if prev_contents_length ~= #list_contents then + list_highlight_cursor = {} + end + + if character ~= '' and #list_contents > 0 or action ~= nil and #list_contents > 0 then + select(1-list_cursor) + elseif #list_contents == 0 then + list_cursor = 0 + select(list_cursor) + else + select(0) + end +end + +function bind_search_keys() + mp.add_forced_key_binding('a', 'search_string_a', function() update_search_results('a') end, 'repeatable') + mp.add_forced_key_binding('b', 'search_string_b', function() update_search_results('b') end, 'repeatable') + mp.add_forced_key_binding('c', 'search_string_c', function() update_search_results('c') end, 'repeatable') + mp.add_forced_key_binding('d', 'search_string_d', function() update_search_results('d') end, 'repeatable') + mp.add_forced_key_binding('e', 'search_string_e', function() update_search_results('e') end, 'repeatable') + mp.add_forced_key_binding('f', 'search_string_f', function() update_search_results('f') end, 'repeatable') + mp.add_forced_key_binding('g', 'search_string_g', function() update_search_results('g') end, 'repeatable') + mp.add_forced_key_binding('h', 'search_string_h', function() update_search_results('h') end, 'repeatable') + mp.add_forced_key_binding('i', 'search_string_i', function() update_search_results('i') end, 'repeatable') + mp.add_forced_key_binding('j', 'search_string_j', function() update_search_results('j') end, 'repeatable') + mp.add_forced_key_binding('k', 'search_string_k', function() update_search_results('k') end, 'repeatable') + mp.add_forced_key_binding('l', 'search_string_l', function() update_search_results('l') end, 'repeatable') + mp.add_forced_key_binding('m', 'search_string_m', function() update_search_results('m') end, 'repeatable') + mp.add_forced_key_binding('n', 'search_string_n', function() update_search_results('n') end, 'repeatable') + mp.add_forced_key_binding('o', 'search_string_o', function() update_search_results('o') end, 'repeatable') + mp.add_forced_key_binding('p', 'search_string_p', function() update_search_results('p') end, 'repeatable') + mp.add_forced_key_binding('q', 'search_string_q', function() update_search_results('q') end, 'repeatable') + mp.add_forced_key_binding('r', 'search_string_r', function() update_search_results('r') end, 'repeatable') + mp.add_forced_key_binding('s', 'search_string_s', function() update_search_results('s') end, 'repeatable') + mp.add_forced_key_binding('t', 'search_string_t', function() update_search_results('t') end, 'repeatable') + mp.add_forced_key_binding('u', 'search_string_u', function() update_search_results('u') end, 'repeatable') + mp.add_forced_key_binding('v', 'search_string_v', function() update_search_results('v') end, 'repeatable') + mp.add_forced_key_binding('w', 'search_string_w', function() update_search_results('w') end, 'repeatable') + mp.add_forced_key_binding('x', 'search_string_x', function() update_search_results('x') end, 'repeatable') + mp.add_forced_key_binding('y', 'search_string_y', function() update_search_results('y') end, 'repeatable') + mp.add_forced_key_binding('z', 'search_string_z', function() update_search_results('z') end, 'repeatable') + + mp.add_forced_key_binding('A', 'search_string_A', function() update_search_results('A') end, 'repeatable') + mp.add_forced_key_binding('B', 'search_string_B', function() update_search_results('B') end, 'repeatable') + mp.add_forced_key_binding('C', 'search_string_C', function() update_search_results('C') end, 'repeatable') + mp.add_forced_key_binding('D', 'search_string_D', function() update_search_results('D') end, 'repeatable') + mp.add_forced_key_binding('E', 'search_string_E', function() update_search_results('E') end, 'repeatable') + mp.add_forced_key_binding('F', 'search_string_F', function() update_search_results('F') end, 'repeatable') + mp.add_forced_key_binding('G', 'search_string_G', function() update_search_results('G') end, 'repeatable') + mp.add_forced_key_binding('H', 'search_string_H', function() update_search_results('H') end, 'repeatable') + mp.add_forced_key_binding('I', 'search_string_I', function() update_search_results('I') end, 'repeatable') + mp.add_forced_key_binding('J', 'search_string_J', function() update_search_results('J') end, 'repeatable') + mp.add_forced_key_binding('K', 'search_string_K', function() update_search_results('K') end, 'repeatable') + mp.add_forced_key_binding('L', 'search_string_L', function() update_search_results('L') end, 'repeatable') + mp.add_forced_key_binding('M', 'search_string_M', function() update_search_results('M') end, 'repeatable') + mp.add_forced_key_binding('N', 'search_string_N', function() update_search_results('N') end, 'repeatable') + mp.add_forced_key_binding('O', 'search_string_O', function() update_search_results('O') end, 'repeatable') + mp.add_forced_key_binding('P', 'search_string_P', function() update_search_results('P') end, 'repeatable') + mp.add_forced_key_binding('Q', 'search_string_Q', function() update_search_results('Q') end, 'repeatable') + mp.add_forced_key_binding('R', 'search_string_R', function() update_search_results('R') end, 'repeatable') + mp.add_forced_key_binding('S', 'search_string_S', function() update_search_results('S') end, 'repeatable') + mp.add_forced_key_binding('T', 'search_string_T', function() update_search_results('T') end, 'repeatable') + mp.add_forced_key_binding('U', 'search_string_U', function() update_search_results('U') end, 'repeatable') + mp.add_forced_key_binding('V', 'search_string_V', function() update_search_results('V') end, 'repeatable') + mp.add_forced_key_binding('W', 'search_string_W', function() update_search_results('W') end, 'repeatable') + mp.add_forced_key_binding('X', 'search_string_X', function() update_search_results('X') end, 'repeatable') + mp.add_forced_key_binding('Y', 'search_string_Y', function() update_search_results('Y') end, 'repeatable') + mp.add_forced_key_binding('Z', 'search_string_Z', function() update_search_results('Z') end, 'repeatable') + + mp.add_forced_key_binding('1', 'search_string_1', function() update_search_results('1') end, 'repeatable') + mp.add_forced_key_binding('2', 'search_string_2', function() update_search_results('2') end, 'repeatable') + mp.add_forced_key_binding('3', 'search_string_3', function() update_search_results('3') end, 'repeatable') + mp.add_forced_key_binding('4', 'search_string_4', function() update_search_results('4') end, 'repeatable') + mp.add_forced_key_binding('5', 'search_string_5', function() update_search_results('5') end, 'repeatable') + mp.add_forced_key_binding('6', 'search_string_6', function() update_search_results('6') end, 'repeatable') + mp.add_forced_key_binding('7', 'search_string_7', function() update_search_results('7') end, 'repeatable') + mp.add_forced_key_binding('8', 'search_string_8', function() update_search_results('8') end, 'repeatable') + mp.add_forced_key_binding('9', 'search_string_9', function() update_search_results('9') end, 'repeatable') + mp.add_forced_key_binding('0', 'search_string_0', function() update_search_results('0') end, 'repeatable') + + mp.add_forced_key_binding('SPACE', 'search_string_space', function() update_search_results(' ') end, 'repeatable') + mp.add_forced_key_binding('`', 'search_string_`', function() update_search_results('`') end, 'repeatable') + mp.add_forced_key_binding('~', 'search_string_~', function() update_search_results('~') end, 'repeatable') + mp.add_forced_key_binding('!', 'search_string_!', function() update_search_results('!') end, 'repeatable') + mp.add_forced_key_binding('@', 'search_string_@', function() update_search_results('@') end, 'repeatable') + mp.add_forced_key_binding('SHARP', 'search_string_sharp', function() update_search_results('#') end, 'repeatable') + mp.add_forced_key_binding('$', 'search_string_$', function() update_search_results('$') end, 'repeatable') + mp.add_forced_key_binding('%', 'search_string_percentage', function() update_search_results('%') end, 'repeatable') + mp.add_forced_key_binding('^', 'search_string_^', function() update_search_results('^') end, 'repeatable') + mp.add_forced_key_binding('&', 'search_string_&', function() update_search_results('&') end, 'repeatable') + mp.add_forced_key_binding('*', 'search_string_*', function() update_search_results('*') end, 'repeatable') + mp.add_forced_key_binding('(', 'search_string_(', function() update_search_results('(') end, 'repeatable') + mp.add_forced_key_binding(')', 'search_string_)', function() update_search_results(')') end, 'repeatable') + mp.add_forced_key_binding('-', 'search_string_-', function() update_search_results('-') end, 'repeatable') + mp.add_forced_key_binding('_', 'search_string__', function() update_search_results('_') end, 'repeatable') + mp.add_forced_key_binding('=', 'search_string_=', function() update_search_results('=') end, 'repeatable') + mp.add_forced_key_binding('+', 'search_string_+', function() update_search_results('+') end, 'repeatable') + mp.add_forced_key_binding('\\', 'search_string_\\', function() update_search_results('\\') end, 'repeatable') + mp.add_forced_key_binding('|', 'search_string_|', function() update_search_results('|') end, 'repeatable') + mp.add_forced_key_binding(']', 'search_string_]', function() update_search_results(']') end, 'repeatable') + mp.add_forced_key_binding('}', 'search_string_rightcurly', function() update_search_results('}') end, 'repeatable') + mp.add_forced_key_binding('[', 'search_string_[', function() update_search_results('[') end, 'repeatable') + mp.add_forced_key_binding('{', 'search_string_leftcurly', function() update_search_results('{') end, 'repeatable') + mp.add_forced_key_binding('\'', 'search_string_\'', function() update_search_results('\'') end, 'repeatable') + mp.add_forced_key_binding('\"', 'search_string_\"', function() update_search_results('\"') end, 'repeatable') + mp.add_forced_key_binding(';', 'search_string_semicolon', function() update_search_results(';') end, 'repeatable') + mp.add_forced_key_binding(':', 'search_string_:', function() update_search_results(':') end, 'repeatable') + mp.add_forced_key_binding('/', 'search_string_/', function() update_search_results('/') end, 'repeatable') + mp.add_forced_key_binding('?', 'search_string_?', function() update_search_results('?') end, 'repeatable') + mp.add_forced_key_binding('.', 'search_string_.', function() update_search_results('.') end, 'repeatable') + mp.add_forced_key_binding('>', 'search_string_>', function() update_search_results('>') end, 'repeatable') + mp.add_forced_key_binding(',', 'search_string_,', function() update_search_results(',') end, 'repeatable') + mp.add_forced_key_binding('<', 'search_string_<', function() update_search_results('<') end, 'repeatable') + + mp.add_forced_key_binding('bs', 'search_string_del', function() update_search_results('', 'string_del') end, 'repeatable') + bind_keys(o.list_close_keybind, 'search_exit', function() list_search_exit() end) + bind_keys(o.list_search_not_typing_mode_keybind, 'search_string_not_typing', function()list_search_not_typing_mode(false) end) + + if o.search_not_typing_smartly then + bind_keys(o.next_filter_sequence_keybind, 'list-filter-next', function() list_filter_next() list_search_not_typing_mode(true) end) + bind_keys(o.previous_filter_sequence_keybind, 'list-filter-previous', function() list_filter_previous() list_search_not_typing_mode(true) end) + bind_keys(o.list_delete_keybind, 'list-delete', function() list_delete() list_search_not_typing_mode(true) end) + bind_keys(o.list_delete_highlighted_keybind, 'list-delete-highlight', function() list_delete('highlight') list_search_not_typing_mode(true) end) + end +end + +function unbind_search_keys() + mp.remove_key_binding('search_string_a') + mp.remove_key_binding('search_string_b') + mp.remove_key_binding('search_string_c') + mp.remove_key_binding('search_string_d') + mp.remove_key_binding('search_string_e') + mp.remove_key_binding('search_string_f') + mp.remove_key_binding('search_string_g') + mp.remove_key_binding('search_string_h') + mp.remove_key_binding('search_string_i') + mp.remove_key_binding('search_string_j') + mp.remove_key_binding('search_string_k') + mp.remove_key_binding('search_string_l') + mp.remove_key_binding('search_string_m') + mp.remove_key_binding('search_string_n') + mp.remove_key_binding('search_string_o') + mp.remove_key_binding('search_string_p') + mp.remove_key_binding('search_string_q') + mp.remove_key_binding('search_string_r') + mp.remove_key_binding('search_string_s') + mp.remove_key_binding('search_string_t') + mp.remove_key_binding('search_string_u') + mp.remove_key_binding('search_string_v') + mp.remove_key_binding('search_string_w') + mp.remove_key_binding('search_string_x') + mp.remove_key_binding('search_string_y') + mp.remove_key_binding('search_string_z') + + mp.remove_key_binding('search_string_A') + mp.remove_key_binding('search_string_B') + mp.remove_key_binding('search_string_C') + mp.remove_key_binding('search_string_D') + mp.remove_key_binding('search_string_E') + mp.remove_key_binding('search_string_F') + mp.remove_key_binding('search_string_G') + mp.remove_key_binding('search_string_H') + mp.remove_key_binding('search_string_I') + mp.remove_key_binding('search_string_J') + mp.remove_key_binding('search_string_K') + mp.remove_key_binding('search_string_L') + mp.remove_key_binding('search_string_M') + mp.remove_key_binding('search_string_N') + mp.remove_key_binding('search_string_O') + mp.remove_key_binding('search_string_P') + mp.remove_key_binding('search_string_Q') + mp.remove_key_binding('search_string_R') + mp.remove_key_binding('search_string_S') + mp.remove_key_binding('search_string_T') + mp.remove_key_binding('search_string_U') + mp.remove_key_binding('search_string_V') + mp.remove_key_binding('search_string_W') + mp.remove_key_binding('search_string_X') + mp.remove_key_binding('search_string_Y') + mp.remove_key_binding('search_string_Z') + + mp.remove_key_binding('search_string_1') + mp.remove_key_binding('search_string_2') + mp.remove_key_binding('search_string_3') + mp.remove_key_binding('search_string_4') + mp.remove_key_binding('search_string_5') + mp.remove_key_binding('search_string_6') + mp.remove_key_binding('search_string_7') + mp.remove_key_binding('search_string_8') + mp.remove_key_binding('search_string_9') + mp.remove_key_binding('search_string_0') + + mp.remove_key_binding('search_string_space') + mp.remove_key_binding('search_string_`') + mp.remove_key_binding('search_string_~') + mp.remove_key_binding('search_string_!') + mp.remove_key_binding('search_string_@') + mp.remove_key_binding('search_string_sharp') + mp.remove_key_binding('search_string_$') + mp.remove_key_binding('search_string_percentage') + mp.remove_key_binding('search_string_^') + mp.remove_key_binding('search_string_&') + mp.remove_key_binding('search_string_*') + mp.remove_key_binding('search_string_(') + mp.remove_key_binding('search_string_)') + mp.remove_key_binding('search_string_-') + mp.remove_key_binding('search_string__') + mp.remove_key_binding('search_string_=') + mp.remove_key_binding('search_string_+') + mp.remove_key_binding('search_string_\\') + mp.remove_key_binding('search_string_|') + mp.remove_key_binding('search_string_]') + mp.remove_key_binding('search_string_rightcurly') + mp.remove_key_binding('search_string_[') + mp.remove_key_binding('search_string_leftcurly') + mp.remove_key_binding('search_string_\'') + mp.remove_key_binding('search_string_\"') + mp.remove_key_binding('search_string_semicolon') + mp.remove_key_binding('search_string_:') + mp.remove_key_binding('search_string_/') + mp.remove_key_binding('search_string_?') + mp.remove_key_binding('search_string_.') + mp.remove_key_binding('search_string_>') + mp.remove_key_binding('search_string_,') + mp.remove_key_binding('search_string_<') + + mp.remove_key_binding('search_string_del') + if not search_active then + unbind_keys(o.list_close_keybind, 'search_exit') + end +end +--End of LogManager Search Feature-- +---------End of LogManager--------- + +function history_blacklist_check() + if not o.history_blacklist[1] or #o.history_blacklist == 1 and o.history_blacklist[1] == "" then return false end + local invertable_return = {true, false} + local blacklist_msg = 'File was not added to history because of blacklist' + if o.invert_history_blacklist then + invertable_return = {false, true} + blacklist_msg = 'File was added to history because of whitelist' + end + + if has_value(o.history_blacklist, filePath, nil) then + msg.info(blacklist_msg) + return invertable_return[1] + elseif not starts_protocol(protocols, filePath) then + if has_value(o.history_blacklist, filePath:match('^(.-)([^\\/]-)%.([^\\/%.]-)%.?$'), nil) or + has_value(o.history_blacklist, filePath:match('^(.-)([^\\/]-)%.([^\\/%.]-)%.?$'):gsub('\\$', ''), nil) then + msg.info(blacklist_msg) + return invertable_return[1] + elseif has_value(o.history_blacklist, filePath:match('%.([^%.]+)$'), nil) or + has_value(o.history_blacklist, "."..filePath:match('%.([^%.]+)$'), nil) then + msg.info(blacklist_msg) + return invertable_return[1] + else --1.1.2# check to add any subfolder after /* to blacklist. issue #70 + for i=1, #o.history_blacklist do --1.1.2# loop through blacklisted items, if the blacklist ends with * and it is a match after subbing of the current filePath then log it. #and additionally if it is the exact same path then ignore it. + if string.lower(filePath):match(string.lower(o.history_blacklist[i])) and o.history_blacklist[i]:sub(-1,#o.history_blacklist[i]) == '*' and string.lower(o.history_blacklist[i]:sub(1,-2)) ~= string.lower(filePath):match("(.*[\\/])") then + msg.info(blacklist_msg) + return invertable_return[1] + end + end + end + elseif starts_protocol(protocols, filePath) then + if has_value(o.history_blacklist, filePath:match('(.-)(:)'), nil) or + has_value(o.history_blacklist, filePath:match('(.-:)'), nil) or + has_value(o.history_blacklist, filePath:match('(.-:/?/?)'), nil) then + msg.info(blacklist_msg) + return invertable_return[1] + elseif filePath:find('https?://') == 1 then + local difchk_1, difchk_2 = filePath:match("(https?://)w?w?w?%.?([%w%.%:]*)") + local different_check_temp = difchk_1..difchk_2 + local different_checks = {different_check_temp, filePath:match("https?://w?w?w?%.?([%w%.%:]*)"), filePath:match("https?://([%w%.%:]*)"), filePath:match("(https?://[%w%.%:]*)") } + for i = 1, #different_checks do + if different_checks[i] and has_value(o.history_blacklist, different_checks[i], nil) + or different_checks[i]..'/' and has_value(o.history_blacklist, different_checks[i]..'/', nil) then + msg.info(blacklist_msg) + return invertable_return[1] + end + end + end + end + + return invertable_return[2] +end + +function mark_chapter() + if not o.mark_history_as_chapter then return end + + local all_chapters = mp.get_property_native("chapter-list") + local chapter_index = 0 + local chapters_time = {} + + get_list_contents() + if not list_contents or not list_contents[1] then return end + for i = 1, #list_contents do + if list_contents[i].found_path == filePath and tonumber(list_contents[i].found_time) > 0 then + table.insert(chapters_time, tonumber(list_contents[i].found_time)) + end + end + if not chapters_time[1] then return end + + table.sort(chapters_time, function(a, b) return a < b end) + + for i = 1, #chapters_time do + chapter_index = chapter_index + 1 + + all_chapters[chapter_index] = { + title = 'SimpleHistory ' .. chapter_index, + time = chapters_time[i] + } + end + + table.sort(all_chapters, function(a, b) return a['time'] < b['time'] end) + + mp.set_property_native("chapter-list", all_chapters) +end + +function write_log(target_time, update_seekTime, entry_limit) + if not filePath then return end + local prev_seekTime = seekTime + seekTime = (mp.get_property_number('time-pos') or 0) + if target_time then + seekTime = target_time + end + if seekTime < 0 then seekTime = 0 end + + delete_log_entry(false, true, filePath, math.floor(seekTime), entry_limit) + + local f = io.open(log_fullpath, "a+") + if o.file_title_logging == 'all' then + f:write(("[%s] \"%s\" | %s | %s | %s"):format(os.date(o.date_format), fileTitle, filePath, log_length_text .. tostring(fileLength), log_time_text .. tostring(seekTime))) + elseif o.file_title_logging == 'protocols' and (starts_protocol(o.logging_protocols, filePath)) then + f:write(("[%s] \"%s\" | %s | %s | %s"):format(os.date(o.date_format), fileTitle, filePath, log_length_text .. tostring(fileLength), log_time_text .. tostring(seekTime))) + elseif o.file_title_logging == 'protocols' and not (starts_protocol(o.logging_protocols, filePath)) then + f:write(("[%s] %s | %s | %s"):format(os.date(o.date_format), filePath, log_length_text .. tostring(fileLength), log_time_text .. tostring(seekTime))) + else + f:write(("[%s] %s | %s | %s"):format(os.date(o.date_format), filePath, log_length_text .. tostring(fileLength), log_time_text .. tostring(seekTime))) + end + + f:write('\n') + f:close() + + if not update_seekTime then + seekTime = prev_seekTime + end +end + +function history_incognito_mode() + if not incognito_mode then + incognito_mode = true + if o.osd_messages == true then + mp.osd_message('🕵 Incognito Mode Enabled') + end + msg.info('Incognito Mode Enabled') + + if o.delete_incognito_entry and autosaved_entry == true then + delete_log_entry_specific('last', filePath, 0) + autosaved_entry = 'autosaved-restore' + if list_drawn then + get_list_contents() + select(0) + end + end + else + incognito_mode = false + if o.osd_messages == true then + mp.osd_message('Incognito Mode Disabled') + end + msg.info('Incognito Mode Disabled') + + if o.restore_incognito_entry == 'always' then + history_fileonly_save() + autosaved_entry = true + elseif o.restore_incognito_entry == 'deleted-restore' and autosaved_entry == 'autosaved-restore' then + history_fileonly_save() + autosaved_entry = true + if list_drawn then + get_list_contents() + select(0) + end + end + end +end + +function history_resume_option() + if o.resume_option == 'notification' or o.resume_option == 'force' then + local video_time = mp.get_property_number('time-pos') + local video_path = mp.get_property('path') --1.1.4# local variable instead of filePath + if video_time > 0 then return end + local logged_time = 0 + local percentage = 0 + local video_duration = (mp.get_property_number('duration') or 0) + list_contents = read_log_table() + if not list_contents or not list_contents[1] then return end + for i = #list_contents, 1, -1 do + if list_contents[i].found_path == video_path and tonumber(list_contents[i].found_time) > 0 then --1.1.4# instead of filePath in case it is causing issue + logged_time = tonumber(list_contents[i].found_time) + o.resume_offset + break + end + end + if logged_time > 0 then + percentage = math.floor((logged_time / video_duration) * 100 + 0.5) + if o.resume_option == 'notification' then + if percentage > o.resume_option_threshold and percentage < (100-o.resume_option_threshold) or o.resume_option_threshold == 0 then + mp.osd_message('⌨ [' .. string.upper(o.history_resume_keybind[1]) .. '] Resumes To' .. o.time_seperator .. format_time(logged_time, o.osd_time_format[3], o.osd_time_format[2], o.osd_time_format[1]),3) + end + elseif o.resume_option == 'force' then + if percentage > o.resume_option_threshold and percentage < (100-o.resume_option_threshold) or o.resume_option_threshold == 0 then + mp.commandv('seek', logged_time, 'absolute', 'exact') + if (o.osd_messages == true) then + mp.osd_message('Resumed To Last Played Position\n' .. o.time_seperator .. format_time(logged_time, o.osd_time_format[3], o.osd_time_format[2], o.osd_time_format[1])) + end + msg.info('Resumed to the last played position') + end + end + end + end +end + +function history_save(target_time) + if filePath ~= nil then + if history_blacklist_check() then + return + end + write_log(target_time, false, o.same_entry_limit) + if list_drawn then + get_list_contents() + select(0) + end + msg.info('Added the below into history\n' .. fileTitle .. o.time_seperator .. format_time(seekTime)) + else + msg.info("Failed to add into history") + end +end + +function history_fileonly_save() + if filePath ~= nil then + if history_blacklist_check() then + return + end + write_log(0, false) + if list_drawn then + get_list_contents() + select(0) + end + msg.info('Added the below into history\n' .. fileTitle .. o.time_seperator .. format_time(seekTime)) + else + msg.info("Failed to add into history, no file found") + end +end + +function history_resume() + if filePath == nil then + list_contents = read_log_table() + load(1) + elseif filePath ~= nil then + list_contents = read_log_table() + if list_contents ~= nil and list_contents[1] then + for i = #list_contents, 1, -1 do + if list_contents[i].found_path == filePath and tonumber(list_contents[i].found_time) > 0 then + seekTime = tonumber(list_contents[i].found_time) + o.resume_offset + break + end + end + end + if seekTime > 0 then + mp.commandv('seek', seekTime, 'absolute', 'exact') + if (o.osd_messages == true) then + mp.osd_message('Resumed To Last Played Position\n' .. o.time_seperator .. format_time(seekTime, o.osd_time_format[3], o.osd_time_format[2], o.osd_time_format[1])) + end + msg.info('Resumed to the last played position') + else + if (o.osd_messages == true) then + mp.osd_message('No Resume Position Found For This Video') + end + msg.info('No resume position found for this video') + end + end +end + +function history_load_last() + if filePath == nil then + list_contents = read_log_table() + load(1, false, 0) + elseif filePath ~= nil then + list_contents = read_log_table() + load(2, true) + end +end + +mp.register_event('file-loaded', function() + list_close_and_trash_collection() + filePath, fileTitle, fileLength = get_file() + loadTriggered = true --1.1.5# for resume and resume-notime startup behavior (so that it only triggers if started as idle and only once) + if (resume_selected == true and seekTime > 0) then + mp.commandv('seek', seekTime, 'absolute', 'exact') + resume_selected = false + end + history_resume_option() --1.1.4# remove timeout, cant remember why I put it in first place + mark_chapter() + if not incognito_mode then + history_fileonly_save() + autosaved_entry = true + end +end) + +mp.add_hook('on_unload', 9, function()--1.1.3# get the LogTime only when using on_unload because big functions do not run fully in here + logTime = (mp.get_property_number('time-pos') or 0) +end) +mp.register_event('end-file', function()--1.1.3# use end-file instead so that it doesn't cause crash while seeking ( i am able to run big functions here) + if not incognito_mode then + if autosaved_entry == true then delete_log_entry_specific('last', filePath, 0) end + history_save(logTime) --1.1.3# get the updated time from on_unload since it will still be preserved + end + autosaved_entry = false + logTime = 0 --1.1.3# reset logTime to 0 +end) + +mp.observe_property("idle-active", "bool", function(_, v) + if v then --1.1.2# if idle is triggered + filePath, fileTitle, fileLength = nil --1.1.2# set it back to nil if idle is triggered for better trash collection. issue #69 + end + + if v and o.startup_idle_behavior == 'resume' and not loadTriggered then --1.1.5# option to resume on startup + history_resume() + elseif v and o.startup_idle_behavior == 'resume-notime' and not loadTriggered then --1.1.5# option to load last item on startup + history_load_last() + elseif v and has_value(available_filters, o.auto_run_list_idle) then + display_list(o.auto_run_list_idle, nil, 'hide-osd') + end + + if v and o.auto_run_incognito_mode and not incognito_auto_run_triggered or + not v and o.auto_run_incognito_mode and not incognito_auto_run_triggered then + history_incognito_mode() + incognito_auto_run_triggered = true + end +end) + +bind_keys(o.history_resume_keybind, 'history-resume', history_resume) +bind_keys(o.history_load_last_keybind, 'history-load-last', history_load_last) +bind_keys(o.history_incognito_mode_keybind, 'history-incognito-mode', history_incognito_mode) + +for i = 1, #o.open_list_keybind do + if i == 1 then + mp.add_forced_key_binding(o.open_list_keybind[i][1], 'open-list', function()display_list(o.open_list_keybind[i][2]) end) + else + mp.add_forced_key_binding(o.open_list_keybind[i][1], 'open-list'..i, function()display_list(o.open_list_keybind[i][2]) end) + end +end diff --git a/.config/mpv/scripts/force-fps.lua b/.config/mpv/scripts/force-fps.lua new file mode 100644 index 0000000..5fae7ed --- /dev/null +++ b/.config/mpv/scripts/force-fps.lua @@ -0,0 +1,10 @@ +local target_fps = 23 + +mp.add_periodic_timer(1, function() + local fps = mp.get_property_native("estimated-vf-fps") + if fps and math.abs(fps - target_fps) > 0.1 then + mp.set_property_native("speed", fps / target_fps) + else + mp.set_property_native("speed", 1) + end +end) diff --git a/.config/mpv/scripts/next-episode.lua b/.config/mpv/scripts/next-episode.lua new file mode 100644 index 0000000..3c18b9e --- /dev/null +++ b/.config/mpv/scripts/next-episode.lua @@ -0,0 +1,154 @@ +local utils = require 'mp.utils' +local msg = require 'mp.msg' +local settings = { + + filetypes = { + 'jpg', 'jpeg', 'png', 'tif', 'tiff', 'gif', 'webp', 'svg', 'bmp', + 'mp3', 'wav', 'ogm', 'flac', 'm4a', 'wma', 'ogg', 'opus', + 'mkv', 'avi', 'mp4', 'ogv', 'webm', 'rmvb', 'flv', 'wmv', 'mpeg', 'mpg', 'm4v', '3gp' + }, + + --linux(true)/windows(false)/auto(nil) + linux_over_windows = nil, + + --at end of directory jump to start and vice versa + allow_looping = true, + + --order by natural (version) numbers, thus behaving case-insensitively and treating multi-digit numbers atomically + --e.x.: true will result in the following order: 09A 9A 09a 9a 10A 10a + -- while false will result in: 09a 09A 10a 10A 9a 9A + version_flag = true, +} + +local filetype_lookup = {} +for _, ext in ipairs(settings.filetypes) do + filetype_lookup[ext] = true +end + +--check os +if settings.linux_over_windows==nil then + local o = {} + if mp.get_property_native('options/vo-mmcss-profile', o) ~= o then + settings.linux_over_windows = false + else + settings.linux_over_windows = true + end +end + +function show_osd_message(file) + mp.osd_message("Now playing: " .. file, 3) -- Adjust OSD display time as needed +end + +function nexthandler() + movetofile(true) +end + +function prevhandler() + movetofile(false) +end + +function get_files_windows(dir) + local args = { + 'powershell', '-NoProfile', '-Command', [[& { + Trap { + Write-Error -ErrorRecord $_ + Exit 1 + } + $path = "]]..dir..[[" + $escapedPath = [WildcardPattern]::Escape($path) + cd $escapedPath + + $list = (Get-ChildItem -File | Sort-Object { [regex]::Replace($_.Name, '\d+', { $args[0].Value.PadLeft(20) }) }).Name + $string = ($list -join "/") + $u8list = [System.Text.Encoding]::UTF8.GetBytes($string) + [Console]::OpenStandardOutput().Write($u8list, 0, $u8list.Length) + }]] + } + local process = utils.subprocess({ args = args, cancellable = false }) + return parse_files(process, '%/') +end + +function get_files_linux(dir) + local flags = ('-1p' .. (settings.version_flag and 'v' or '')) + local args = { 'ls', flags, dir } + local process = utils.subprocess({ args = args, cancellable = false }) + return parse_files(process, '\n') +end + +function parse_files(res, delimiter) + if not res.error and res.status == 0 then + local valid_files = {} + for line in res.stdout:gmatch("[^"..delimiter.."]+") do + local ext = line:match("^.+%.(.+)$") + if ext and filetype_lookup[ext:lower()] then + table.insert(valid_files, line) + end + end + return valid_files, nil + else + return nil, res.error + end +end + +function movetofile(forward) + if mp.get_property('filename'):match("^%a%a+:%/%/") then return end + local pwd = mp.get_property('working-directory') + local relpath = mp.get_property('path') + if not pwd or not relpath then return end + + local path = utils.join_path(pwd, relpath) + local filename = mp.get_property("filename") + local dir = utils.split_path(path) + + local files, error + if settings.linux_over_windows then + files, error = get_files_linux(dir) + else + files, error = get_files_windows(dir) + end + + if not files then + msg.error("Subprocess failed: "..(error or '')) + return + end + + local found = false + local memory = nil + local lastfile = true + local firstfile = nil + for _, file in ipairs(files) do + if found == true then + mp.commandv("loadfile", utils.join_path(dir, file), "replace") + lastfile = false + show_osd_message(file) + break + end + if file == filename then + found = true + if not forward then + lastfile = false + if settings.allow_looping and firstfile == nil then + found = false + else + if firstfile == nil then break end + mp.commandv("loadfile", utils.join_path(dir, memory), "replace") + show_osd_message(memory) + break + end + end + end + memory = file + if firstfile == nil then firstfile = file end + end + if lastfile and firstfile and settings.allow_looping then + mp.commandv("loadfile", utils.join_path(dir, firstfile), "replace") + show_osd_message(firstfile) + end + if not found and memory then + mp.commandv("loadfile", utils.join_path(dir, memory), "replace") + show_osd_message(memory) + end +end + +mp.add_key_binding('Shift+RIGHT', 'nextfile', nexthandler) +mp.add_key_binding('Shift+LEFT', 'previousfile', prevhandler)