-- Pomodoro Timer Script with cross-platform sound support (Windows + macOS) obs = obslua -- Variables source_name = "" total_sessions = 4 work_duration = 25 break_duration = 5 long_break_duration = 15 start_message = "Time to focus!" break_message = "Take a break!" long_break_message = "Time for a long break!" complete_message = "Pomodoro complete!" sound_focus_path = "" sound_break_path = "" sound_complete_path = "" current_session = 1 is_break = false is_long_break = false is_running = false is_paused = false time_left = 0 display_timer = 0 function script_description() return "Pomodoro Timer with sound alerts and cross-platform support for OBS." end function script_load(settings) obs.obs_hotkey_register_frontend("pomodoro_start", "Pomodoro: Start Timer", start_timer_hotkey) obs.obs_hotkey_register_frontend("pomodoro_pause", "Pomodoro: Pause Timer", pause_timer_hotkey) obs.obs_hotkey_register_frontend("pomodoro_resume", "Pomodoro: Resume Timer", resume_timer_hotkey) obs.obs_hotkey_register_frontend("pomodoro_stop", "Pomodoro: Stop Timer", stop_timer_hotkey) obs.obs_hotkey_register_frontend("pomodoro_reset_current", "Pomodoro: Reset Current", reset_current_hotkey) obs.obs_hotkey_register_frontend("pomodoro_reset_all", "Pomodoro: Reset All Sessions", reset_sessions_hotkey) obs.obs_hotkey_register_frontend("pomodoro_skip", "Pomodoro: Skip Phase", skip_phase_hotkey) end function script_properties() local props = obs.obs_properties_create() obs.obs_properties_add_int(props, "total_sessions", "Number of Sessions", 1, 100, 1) obs.obs_properties_add_int(props, "work_duration", "Work Duration (minutes)", 1, 180, 1) obs.obs_properties_add_int(props, "break_duration", "Short Break Duration (minutes)", 1, 60, 1) obs.obs_properties_add_int(props, "long_break_duration", "Long Break Duration (minutes)", 1, 120, 1) obs.obs_properties_add_text(props, "start_message", "Start Message", obs.OBS_TEXT_DEFAULT) obs.obs_properties_add_text(props, "break_message", "Short Break Message", obs.OBS_TEXT_DEFAULT) obs.obs_properties_add_text(props, "long_break_message", "Long Break Message", obs.OBS_TEXT_DEFAULT) obs.obs_properties_add_text(props, "complete_message", "Completion Message", obs.OBS_TEXT_DEFAULT) obs.obs_properties_add_text(props, "source_name", "Text Source (GDI+)", obs.OBS_TEXT_DEFAULT) obs.obs_properties_add_path(props, "sound_focus_path", "Focus Sound (.wav)", obs.OBS_PATH_FILE, "WAV files (*.wav);;All files (*.*)", nil) obs.obs_properties_add_path(props, "sound_break_path", "Break Sound (.wav)", obs.OBS_PATH_FILE, "WAV files (*.wav);;All files (*.*)", nil) obs.obs_properties_add_path(props, "sound_complete_path", "Complete Sound (.wav)", obs.OBS_PATH_FILE, "WAV files (*.wav);;All files (*.*)", nil) obs.obs_properties_add_button(props, "start_button", "Start Timer", start_timer) obs.obs_properties_add_button(props, "pause_button", "Pause Timer", pause_timer) obs.obs_properties_add_button(props, "resume_button", "Resume Timer", resume_timer) obs.obs_properties_add_button(props, "stop_button", "Stop Timer", stop_timer) obs.obs_properties_add_button(props, "reset_current_button", "Reset Current", reset_current) obs.obs_properties_add_button(props, "reset_button", "Reset All Sessions", reset_sessions) obs.obs_properties_add_button(props, "skip_button", "Skip Phase", skip_phase) return props end function script_update(settings) total_sessions = obs.obs_data_get_int(settings, "total_sessions") work_duration = obs.obs_data_get_int(settings, "work_duration") break_duration = obs.obs_data_get_int(settings, "break_duration") long_break_duration = obs.obs_data_get_int(settings, "long_break_duration") start_message = obs.obs_data_get_string(settings, "start_message") break_message = obs.obs_data_get_string(settings, "break_message") long_break_message = obs.obs_data_get_string(settings, "long_break_message") complete_message = obs.obs_data_get_string(settings, "complete_message") source_name = obs.obs_data_get_string(settings, "source_name") sound_focus_path = obs.obs_data_get_string(settings, "sound_focus_path") sound_break_path = obs.obs_data_get_string(settings, "sound_break_path") sound_complete_path = obs.obs_data_get_string(settings, "sound_complete_path") end function start_timer_hotkey(pressed) if pressed then start_timer() end end function pause_timer_hotkey(pressed) if pressed then pause_timer() end end function resume_timer_hotkey(pressed) if pressed then resume_timer() end end function stop_timer_hotkey(pressed) if pressed then stop_timer() end end function reset_current_hotkey(pressed) if pressed then reset_current() end end function reset_sessions_hotkey(pressed) if pressed then reset_sessions() end end function skip_phase_hotkey(pressed) if pressed then skip_phase() end end function start_timer() if not is_running then is_running = true is_paused = false is_break = false is_long_break = false current_session = 1 time_left = work_duration * 60 show_temp_message(start_message) play_sound("focus") obs.timer_add(update_timer, 1000) end end function pause_timer() if is_running and not is_paused then is_paused = true update_display() end end function resume_timer() if is_running and is_paused then is_paused = false update_display() end end function stop_timer() is_running = false is_paused = false obs.timer_remove(update_timer) update_text_source("Timer Stopped", "") end function reset_current() if is_running then if is_long_break then time_left = long_break_duration * 60 elseif is_break then time_left = break_duration * 60 else time_left = work_duration * 60 end is_paused = false update_display() end end function reset_sessions() current_session = 1 is_break = false is_long_break = false if is_running then time_left = work_duration * 60 update_display() else update_text_source("Ready to Start", "") end end function skip_phase() if is_running and not is_paused then time_left = 1 end end function update_timer() if is_paused then update_display() return end if time_left > 0 then time_left = time_left - 1 update_display() else if is_long_break or is_break then is_long_break = false is_break = false current_session = current_session + 1 if current_session > total_sessions then stop_timer() show_temp_message(complete_message) play_sound("complete") return else time_left = work_duration * 60 show_temp_message(start_message) play_sound("focus") end else if current_session % 4 == 0 and current_session < total_sessions then is_long_break = true time_left = long_break_duration * 60 show_temp_message(long_break_message) play_sound("break") else is_break = true time_left = break_duration * 60 show_temp_message(break_message) play_sound("break") end end end end function show_temp_message(message) display_timer = 3 update_text_source(message) obs.timer_add(display_message_timer, 1000) end function display_message_timer() display_timer = display_timer - 1 if display_timer <= 0 then obs.timer_remove(display_message_timer) update_display() end end function update_display() local minutes = math.floor(time_left / 60) local seconds = time_left % 60 local timer_text = string.format("%02d:%02d", minutes, seconds) update_text_source(timer_text) end function update_text_source(timer_text) local source = obs.obs_get_source_by_name(source_name) if source ~= nil then local full_text = "" if is_running then local mode = is_long_break and "Long Break" or (is_break and "Break" or "Focus") local session_text = string.format("%d/%d", current_session, total_sessions) full_text = string.format("%s | %s | %s", mode, timer_text, session_text) if is_paused then full_text = full_text .. "\nPAUSED" end else full_text = timer_text end local settings = obs.obs_data_create() obs.obs_data_set_string(settings, "text", full_text) obs.obs_source_update(source, settings) obs.obs_data_release(settings) obs.obs_source_release(source) end end function play_sound(phase) local path = "" if phase == "focus" then path = sound_focus_path elseif phase == "break" then path = sound_break_path elseif phase == "complete" then path = sound_complete_path end if path ~= nil and path ~= "" then local is_windows = package.config:sub(1,1) == "\\" if is_windows then os.execute('start "" "' .. path .. '"') else local afplay_test = os.execute("command -v afplay >/dev/null 2>&1") if afplay_test == 0 then os.execute("afplay '" .. path .. "' &") else os.execute("open '" .. path .. "' &") end end end end