From 4cacc648a85341fab88164102835ca7e965254a0 Mon Sep 17 00:00:00 2001 From: sudacode Date: Thu, 28 Aug 2025 21:02:11 -0700 Subject: [PATCH] squash some bugs --- main.lua | 449 +++++++++++++++++++++++++++++++++---------------------- 1 file changed, 273 insertions(+), 176 deletions(-) diff --git a/main.lua b/main.lua index 0021a4a..64cb4a3 100644 --- a/main.lua +++ b/main.lua @@ -12,60 +12,33 @@ mp.options = require("mp.options") local options = { -- Keybinding settings start_tracking_key = "ctrl+t", - -- File paths (relative to script directory) data_dir = script_dir .. "/data", csv_file = script_dir .. "/data/immersion_log.csv", session_file = script_dir .. "/data/current_session.json", - -- Tracking settings - min_session_duration = 30, -- seconds - save_interval = 10, -- seconds - + video_info_refresh_delay = 1.0, -- seconds to wait before refreshing video info -- Advanced settings enable_debug_logging = false, backup_sessions = true, max_backup_files = 10, - -- Session naming preferences use_title = true, -- Use media title for session names use_filename = false, -- Use filename instead - custom_prefix = "[Immersion] ", -- Add custom prefix to session names max_title_length = 100, -- Maximum title length -- Export settings export_csv = true, -- Export to CSV - export_json = false, -- Export to JSON - export_html = false, -- Export to HTML report - backup_csv = true, -- Create backup CSV files - -- Notification settings show_session_start = true, -- Show OSD message when session starts show_session_end = true, -- Show OSD message when session ends - show_progress_milestones = false, -- Show progress milestone messages - milestone_percentages = "25,50,75,90", -- Comma-separated percentages } mp.options.read_options(options, "immersion-tracker") --- Parse milestone percentages from string to table -local function parse_milestone_percentages() - local percentages = {} - for percentage in options.milestone_percentages:gmatch("([^,]+)") do - local num = tonumber(percentage:match("^%s*(.-)%s*$")) - if num and num >= 0 and num <= 100 then - table.insert(percentages, num) - end - end - return percentages -end - -options.milestone_percentages = parse_milestone_percentages() - -- Global variables local current_session = nil local session_start_time = nil -local last_save_time = 0 local video_info = {} local is_tracking = false @@ -78,6 +51,20 @@ local function log(message) end end +-- Parse CSV line to extract session ID from first column +local function parse_csv_session_id(csv_line) + -- Handle quoted fields properly + if csv_line:match('^"') then + -- Find the closing quote for the first field + local end_quote = csv_line:find('"', 2) + if end_quote then + return csv_line:sub(2, end_quote - 1) + end + end + -- Fallback: split by comma and take first field + return csv_line:match("^([^,]+)") +end + local function ensure_data_directory() local dir = options.data_dir local result = utils.subprocess({ @@ -97,13 +84,6 @@ local function format_timestamp(timestamp) return os.date("%Y-%m-%d %H:%M:%S", timestamp) end -local function get_duration_string(seconds) - local hours = math.floor(seconds / 3600) - local minutes = math.floor((seconds % 3600) / 60) - local secs = seconds % 60 - return string.format("%02d:%02d:%02d", hours, minutes, secs) -end - -- File operations local function save_session_to_file() if not current_session then @@ -145,46 +125,106 @@ local function save_session_to_csv() local csv_path = options.csv_file log("Saving session to CSV: " .. csv_path) - -- Create CSV header if file doesn't exist - local file_exists = io.open(csv_path, "r") - local need_header = not file_exists - if file_exists then - file_exists:close() + -- Read existing CSV content to check for existing session + local existing_lines = {} + local session_exists = false + local existing_file = io.open(csv_path, "r") + + if existing_file then + for line in existing_file:lines() do + table.insert(existing_lines, line) + end + existing_file:close() end - local csv_file = io.open(csv_path, "a") + -- Check if session already exists in CSV + local header_line = nil + local updated_lines = {} + + if #existing_lines > 0 then + header_line = existing_lines[1] + table.insert(updated_lines, header_line) + + -- Check remaining lines for existing session + for i = 2, #existing_lines do + local line = existing_lines[i] + -- Parse CSV line to extract session ID (first column) + local session_id = parse_csv_session_id(line) + + if session_id == current_session.id then + -- Update existing session line + local updated_line = string.format( + '"%s","%s","%s","%s",%d,"%s","%s",%d,%.2f,"%s","%s","%s","%s","%s"', + current_session.id, + current_session.title:gsub('"', '""'), + current_session.filename:gsub('"', '""'), + current_session.path:gsub('"', '""'), + current_session.duration, + current_session.start_timestamp, + current_session.end_timestamp or current_session.start_timestamp, + current_session.total_watch_time or (get_current_timestamp() - session_start_time), + current_session.watch_progress, + current_session.video_format, + current_session.audio_format, + current_session.resolution, + current_session.fps, + current_session.bitrate + ) + table.insert(updated_lines, updated_line) + session_exists = true + log("Updated existing session in CSV: " .. current_session.id) + else + -- Keep other sessions unchanged + table.insert(updated_lines, line) + end + end + end + + -- If session doesn't exist, add header and new session + if not session_exists then + if not header_line then + header_line = "Session ID,Title,Filename,Path,Duration,Start Time,End Time," + .. "Watch Time,Progress,Video Format,Audio Format,Resolution,FPS,Bitrate" + table.insert(updated_lines, header_line) + end + + mp.msg.info("current_session: " .. utils.format_json(current_session)) + + -- Add new session line + local new_line = string.format( + '"%s","%s","%s","%s",%d,"%s","%s",%d,%.2f,"%s","%s","%s","%s","%s"', + current_session.id or "", + (current_session.title or ""):gsub('"', '""'), + (current_session.filename or ""):gsub('"', '""'), + (current_session.path or ""):gsub('"', '""'), + tonumber(current_session.duration) or 0, + current_session.start_timestamp or "", + current_session.end_timestamp or current_session.start_timestamp or "", + tonumber(current_session.total_watch_time) + or ( + get_current_timestamp and session_start_time and (get_current_timestamp() - session_start_time) or 0 + ), + tonumber(current_session.watch_progress) or 0, + current_session.video_format or "", + current_session.audio_format or "", + current_session.resolution or "", + current_session.fps or "", + current_session.bitrate or "" + ) + table.insert(updated_lines, new_line) + log("Added new session to CSV: " .. current_session.id) + end + + -- Write updated content back to file + local csv_file = io.open(csv_path, "w") if not csv_file then log("Failed to open CSV file for writing") return end - -- Write header if needed - if need_header then - csv_file:write( - "Session ID,Title,Filename,Path,Duration,Start Time,End Time,Watch Time,Progress,Video Format,Audio Format,Resolution,FPS,Bitrate\n" - ) + for _, line in ipairs(updated_lines) do + csv_file:write(line .. "\n") end - - -- Write session data - local csv_line = string.format( - '"%s","%s","%s","%s",%d,"%s","%s",%d,%.2f,"%s","%s","%s","%s","%s"\n', - current_session.id, - current_session.title:gsub('"', '""'), - current_session.filename:gsub('"', '""'), - current_session.path:gsub('"', '""'), - current_session.duration, - current_session.start_timestamp, - current_session.end_timestamp, - current_session.total_watch_time, - current_session.watch_progress, - current_session.video_format, - current_session.audio_format, - current_session.resolution, - current_session.fps, - current_session.bitrate - ) - - csv_file:write(csv_line) csv_file:close() log("Session saved to CSV: " .. current_session.title) @@ -192,112 +232,91 @@ end -- Video information gathering local function gather_video_info() + -- Get video dimensions - try multiple property names + local width = mp.get_property_number("video-params/w") or mp.get_property_number("video-params/width") + local height = mp.get_property_number("video-params/h") or mp.get_property_number("video-params/height") + local resolution = "unknown" + if width and height and width > 0 and height > 0 then + resolution = width .. "x" .. height + end + + -- Get FPS - try multiple property names + local fps = mp.get_property_number("video-params/fps") or mp.get_property_number("fps") + local fps_str = "unknown" + if fps and fps > 0 then + fps_str = string.format("%.2f", fps) + end + + -- Get bitrate - try multiple property names + local bitrate = mp.get_property_number("video-bitrate") or mp.get_property_number("bitrate") + local bitrate_str = "unknown" + if bitrate and bitrate > 0 then + bitrate_str = string.format("%.0f", bitrate) + end + + -- Get video codec - try multiple property names + local video_codec = mp.get_property("video-codec") or mp.get_property("video-params/codec") or "unknown" + + -- Get audio codec - try multiple property names + local audio_codec = mp.get_property("audio-codec") or mp.get_property("audio-params/codec") or "unknown" + video_info = { filename = mp.get_property("filename") or "unknown", path = mp.get_property("path") or "unknown", title = mp.get_property("media-title") or mp.get_property("filename") or "unknown", duration = mp.get_property_number("duration") or 0, - video_format = mp.get_property("video-codec") or "unknown", - audio_format = mp.get_property("audio-codec") or "unknown", - resolution = mp.get_property("video-params/width") - and mp.get_property("video-params/height") - and mp.get_property("video-params/width") .. "x" .. mp.get_property("video-params/height") - or "unknown", - fps = mp.get_property("video-params/fps") or "unknown", - bitrate = mp.get_property("video-bitrate") or "unknown", + video_format = video_codec, + audio_format = audio_codec, + resolution = resolution, + fps = fps_str, + bitrate = bitrate_str, } log("Video info gathered: " .. video_info.title) -end + log("Resolution: " .. resolution .. ", FPS: " .. fps_str .. ", Bitrate: " .. bitrate_str) --- Session management -local function start_new_session() - if current_session then - log("Session already in progress, ending previous session first") - end_current_session() - end - - -- Gather current video info - gather_video_info() - - -- Determine session title based on options - local session_title = video_info.title - if options.use_filename then - session_title = video_info.filename - end - - -- Apply custom prefix and length limit - if options.custom_prefix then - session_title = options.custom_prefix .. session_title - end - - if #session_title > options.max_title_length then - session_title = session_title:sub(1, options.max_title_length) .. "..." - end - - current_session = { - id = os.time() .. "_" .. math.random(1000, 9999), - filename = video_info.filename, - path = video_info.path, - title = session_title, - duration = video_info.duration, - start_time = get_current_timestamp(), - start_timestamp = format_timestamp(get_current_timestamp()), - video_format = video_info.video_format, - audio_format = video_info.audio_format, - resolution = video_info.resolution, - fps = video_info.fps, - bitrate = video_info.bitrate, - watch_progress = 0, - last_position = 0, - } - - session_start_time = get_current_timestamp() - is_tracking = true - save_session_to_file() - log("New immersion session started: " .. current_session.title) - - -- Show on-screen message if enabled - if options.show_session_start then - mp.osd_message("Immersion tracking started: " .. current_session.title, 3) + -- Debug: log all available video properties + if options.enable_debug_logging then + log("Debug - Available properties:") + log(" video-params/w: " .. tostring(mp.get_property("video-params/w"))) + log(" video-params/h: " .. tostring(mp.get_property("video-params/h"))) + log(" video-params/width: " .. tostring(mp.get_property("video-params/width"))) + log(" video-params/height: " .. tostring(mp.get_property("video-params/height"))) + log(" video-params/fps: " .. tostring(mp.get_property("video-params/fps"))) + log(" fps: " .. tostring(mp.get_property("fps"))) + log(" video-bitrate: " .. tostring(mp.get_property("video-bitrate"))) + log(" bitrate: " .. tostring(mp.get_property("bitrate"))) + log(" video-codec: " .. tostring(mp.get_property("video-codec"))) + log(" video-params/codec: " .. tostring(mp.get_property("video-params/codec"))) + log(" audio-codec: " .. tostring(mp.get_property("audio-codec"))) + log(" audio-params/codec: " .. tostring(mp.get_property("audio-params/codec"))) end end -local function update_session_progress() - if not current_session or not is_tracking then - return - end +-- Manual refresh function for video info +local function refresh_video_info() + if current_session and is_tracking then + log("Manually refreshing video info...") + gather_video_info() - local current_pos = mp.get_property_number("time-pos") or 0 - local duration = mp.get_property_number("duration") or 0 - - if duration > 0 then - current_session.watch_progress = (current_pos / duration) * 100 - current_session.last_position = current_pos - - -- Check for milestone notifications - if options.show_progress_milestones then - for _, milestone in ipairs(options.milestone_percentages) do - if - current_session.watch_progress >= milestone - and (not current_session.milestones_reached or not current_session.milestones_reached[milestone]) - then - if not current_session.milestones_reached then - current_session.milestones_reached = {} - end - current_session.milestones_reached[milestone] = true - mp.osd_message(string.format("Progress milestone: %d%%", milestone), 2) - log("Progress milestone reached: " .. milestone .. "%") - end - end + -- Update session with new info + if video_info.resolution ~= "unknown" then + current_session.resolution = video_info.resolution + end + if video_info.fps ~= "unknown" then + current_session.fps = video_info.fps + end + if video_info.bitrate ~= "unknown" then + current_session.bitrate = video_info.bitrate end - end - -- Auto-save if enough time has passed - local current_time = get_current_timestamp() - if current_time - last_save_time >= options.save_interval then + -- Save updated session save_session_to_file() - last_save_time = current_time + if options.export_csv then + save_session_to_csv() + end + + log("Video info refreshed and session updated") end end @@ -345,14 +364,79 @@ local function end_current_session() end end --- Keybinding handler -local function toggle_tracking() - if is_tracking then - log("Stopping immersion tracking...") +-- Session management +local function start_new_session() + if current_session then + log("Session already in progress, ending previous session first") end_current_session() - else - log("Starting immersion tracking...") - start_new_session() + end + + -- Gather current video info + gather_video_info() + + -- Also gather again after a delay to ensure properties are loaded + mp.add_timeout(options.video_info_refresh_delay, function() + if current_session and is_tracking then + gather_video_info() + log("Video info updated after delay") + end + end) + + -- Determine session title based on options + local session_title = video_info.title + if options.use_filename then + session_title = video_info.filename + end + + if #session_title > options.max_title_length then + session_title = session_title:sub(1, options.max_title_length) .. "..." + end + + current_session = { + id = os.time() .. "_" .. math.random(1000, 9999), + filename = video_info.filename, + path = video_info.path, + title = session_title, + duration = video_info.duration, + start_time = get_current_timestamp(), + start_timestamp = format_timestamp(get_current_timestamp()), + video_format = video_info.video_format, + audio_format = video_info.audio_format, + resolution = video_info.resolution, + fps = video_info.fps, + bitrate = video_info.bitrate, + watch_progress = 0, + last_position = 0, + } + + session_start_time = get_current_timestamp() + is_tracking = true + save_session_to_file() + + -- Save to CSV when starting session + if options.export_csv then + save_session_to_csv() + end + + log("New immersion session started: " .. current_session.title) + + -- Show on-screen message if enabled + if options.show_session_start then + mp.osd_message("Immersion tracking started: " .. current_session.title, 3) + end +end + +local function update_session_progress() + if not current_session or not is_tracking then + return + end + + local current_pos = mp.get_property_number("time-pos") or 0 + local duration = mp.get_property_number("duration") or 0 + + if duration > 0 then + current_session.watch_progress = (current_pos / duration) * 100 + current_session.last_position = current_pos end end @@ -362,6 +446,26 @@ local function on_file_loaded() -- Try to load existing session if available load_existing_session() + + -- If we have an active session, refresh video info + if current_session and is_tracking then + mp.add_timeout(options.video_info_refresh_delay, function() + if current_session and is_tracking then + gather_video_info() + log("Video info refreshed after file load") + end + end) + end +end + +local function toggle_tracking() + if is_tracking then + log("Stopping immersion tracking...") + end_current_session() + else + log("Starting immersion tracking...") + start_new_session() + end end local function on_file_end() @@ -384,40 +488,33 @@ local function on_seek() end end -local function on_time_update() - if current_session and is_tracking then - update_session_progress() - end -end - -- Initialize script local function init() log("Immersion Tracker initialized") log("Configuration loaded:") log(" Keybinding: " .. options.start_tracking_key) log(" Data directory: " .. options.data_dir) - log(" Save interval: " .. options.save_interval .. " seconds") + log(" Video info refresh delay: " .. options.video_info_refresh_delay .. " seconds") log(" Debug logging: " .. (options.enable_debug_logging and "enabled" or "disabled")) ensure_data_directory() - -- Register keybinding + -- Register keybindings mp.remove_key_binding("toggle-clipboard-insertion") mp.add_key_binding(options.start_tracking_key, "immersion_tracking", toggle_tracking) + mp.add_key_binding("ctrl+r", "refresh_video_info", refresh_video_info) -- Register event handlers mp.register_event("file-loaded", on_file_loaded) mp.register_event("end-file", on_file_end) mp.register_event("shutdown", on_shutdown) - -- Register property change handlers - mp.observe_property("time-pos", "number", on_time_update) - - -- Register seek event + -- Register seek event for milestone notifications mp.register_event("seek", on_seek) log("Event handlers registered successfully") log("Press " .. options.start_tracking_key .. " to start/stop immersion tracking") + log("Press Ctrl+R to manually refresh video info") end -- Start the script