Upload .lua file to Clip-Chime folder.

This commit is contained in:
2026-03-11 18:14:28 -04:00
parent e04fdeb803
commit 4007aeb8c2

189
Clip-Chime/clip-notify.lua Normal file
View File

@@ -0,0 +1,189 @@
--[[
clip-notify.lua v3
───────────────────────────────────────────────────────
"Clip Saved" chime for OBS Studio.
Plays a sound + logs to OBS when the replay buffer saves.
Install:
1. Place this file + your .wav in the same folder.
2. OBS → Tools → Scripts → "+" → select clip-notify.lua
3. Set your chime file & volume, hit Test.
───────────────────────────────────────────────────────
--]]
obs = obslua
local cfg = {
play_sound = true,
sound_path = "",
chime_volume = 80, -- 0100
notify_on_record = false,
}
-- ── Platform ──────────────────────────────────────────
local is_windows = package.config:sub(1,1) == "\\"
local is_mac, is_linux = false, false
if not is_windows then
local f = io.popen("uname -s 2>/dev/null")
if f then
local u = f:read("*l"); f:close()
is_mac = (u == "Darwin")
is_linux = not is_mac
end
end
-- ── FFI (Windows only) ────────────────────────────────
local ffi, winmm
local ffi_ok = false
if is_windows then
local ok; ok, ffi = pcall(require, "ffi")
if ok then
ffi.cdef[[
/* winmm */
int __stdcall PlaySoundA(const char* pszSound, void* hmod, unsigned long fdwSound);
unsigned long __stdcall waveOutSetVolume(void* hwo, unsigned long dwVolume);
unsigned long __stdcall waveOutGetVolume(void* hwo, unsigned long* pdwVolume);
]]
local ok2; ok2, winmm = pcall(ffi.load, "winmm")
ffi_ok = ok2
if ffi_ok then
obs.blog(obs.LOG_INFO, "[clip-notify] winmm FFI ready")
else
obs.blog(obs.LOG_WARNING, "[clip-notify] winmm failed to load — sound disabled")
end
end
end
-- ── Volume helpers ────────────────────────────────────
-- waveOutSetVolume: low word = left, high word = right (each 0x00000xFFFF)
local function pct_to_dword(pct)
local v = math.floor((math.max(0, math.min(100, pct)) / 100) * 0xFFFF)
return v + v * 0x10000
end
-- ── Sound playback ────────────────────────────────────
local SND_FILENAME = 0x00020000
local SND_ASYNC = 0x00000001
local SND_NODEFAULT = 0x00000002
local function play_chime(path)
if not cfg.play_sound or path == "" then return end
if is_windows then
if not ffi_ok then return end
-- Save current wave-out volume
local prev = ffi.new("unsigned long[1]", 0)
winmm.waveOutGetVolume(nil, prev)
local saved = prev[0]
-- Apply our volume
winmm.waveOutSetVolume(nil, pct_to_dword(cfg.chime_volume))
-- Fire async — returns immediately
winmm.PlaySoundA(path, nil, SND_FILENAME + SND_ASYNC + SND_NODEFAULT)
-- Restore original volume after the chime has finished (1.5s headroom)
obs.timer_add(function()
winmm.waveOutSetVolume(nil, saved)
obs.remove_current_callback()
end, 1500)
elseif is_mac then
-- afplay -v accepts 0.01.0
local vol = string.format("%.2f", cfg.chime_volume / 100)
os.execute(string.format('afplay -v %s "%s" &', vol, path))
elseif is_linux then
-- paplay --volume: 065536 (100% = 65536)
local vol = math.floor((cfg.chime_volume / 100) * 65536)
os.execute(string.format(
'paplay --volume=%d "%s" 2>/dev/null || aplay "%s" 2>/dev/null &',
vol, path, path))
end
end
-- ── Handlers ─────────────────────────────────────────
local function on_clip_saved()
play_chime(cfg.sound_path)
local filename = ""
if obs.obs_frontend_get_last_replay ~= nil then
local raw = obs.obs_frontend_get_last_replay()
if raw and raw ~= "" then
filename = raw:match("([^/\\]+)$") or raw
end
end
obs.blog(obs.LOG_INFO, "[clip-notify] Clip saved → " .. (filename ~= "" and filename or "done"))
end
local function on_recording_saved()
if not cfg.notify_on_record then return end
play_chime(cfg.sound_path)
obs.blog(obs.LOG_INFO, "[clip-notify] Recording saved.")
end
local function on_event(event)
if event == obs.OBS_FRONTEND_EVENT_REPLAY_BUFFER_SAVED then on_clip_saved()
elseif event == obs.OBS_FRONTEND_EVENT_RECORDING_STOPPED then on_recording_saved()
end
end
-- ── Script UI ─────────────────────────────────────────
function script_properties()
local p = obs.obs_properties_create()
obs.obs_properties_add_bool(p, "play_sound", "Enable chime sound")
obs.obs_properties_add_path(p, "sound_path",
"Chime sound file (.wav)",
obs.OBS_PATH_FILE,
"WAV Files (*.wav);;All Files (*.*)",
script_path())
obs.obs_properties_add_int_slider(p, "chime_volume", "Volume", 0, 100, 1)
obs.obs_properties_add_bool(p, "notify_on_record",
"Also chime when a recording is saved")
obs.obs_properties_add_button(p, "test_btn", "▶ Test Chime Now",
function() on_clip_saved(); return true end)
return p
end
function script_defaults(s)
obs.obs_data_set_default_bool (s, "play_sound", true)
obs.obs_data_set_default_string(s, "sound_path", "")
obs.obs_data_set_default_int (s, "chime_volume", 80)
obs.obs_data_set_default_bool (s, "notify_on_record", false)
end
function script_update(s)
cfg.play_sound = obs.obs_data_get_bool (s, "play_sound")
cfg.sound_path = obs.obs_data_get_string(s, "sound_path")
cfg.chime_volume = obs.obs_data_get_int (s, "chime_volume")
cfg.notify_on_record = obs.obs_data_get_bool (s, "notify_on_record")
if cfg.sound_path == "" then
cfg.sound_path = script_path() .. "clip-saved.wav"
end
end
function script_load(s)
script_update(s)
obs.obs_frontend_add_event_callback(on_event)
obs.blog(obs.LOG_INFO, "[clip-notify] v3 loaded.")
end
function script_unload()
obs.blog(obs.LOG_INFO, "[clip-notify] Unloaded.")
end
function script_description()
return [[<h3>🎬 Clip Notify &nbsp;<small>v3</small></h3>
<p>Plays a chime the moment your replay buffer clip is saved.</p>
<p><b>Windows:</b> native winmm — no subprocess, instant.<br>
<b>macOS:</b> afplay &nbsp;|&nbsp; <b>Linux:</b> paplay / aplay</p>
<br>
<b>Setup:</b> pick your .wav, set volume, hit <b>Test Chime Now</b>.]]
end