Upload files to "/"
This commit is contained in:
189
clip-notify.lua
Normal file
189
clip-notify.lua
Normal 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, -- 0–100
|
||||||
|
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 0x0000–0xFFFF)
|
||||||
|
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.0–1.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: 0–65536 (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 <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 | <b>Linux:</b> paplay / aplay</p>
|
||||||
|
<br>
|
||||||
|
<b>Setup:</b> pick your .wav, set volume, hit <b>Test Chime Now</b>.]]
|
||||||
|
end
|
||||||
Reference in New Issue
Block a user