#Requires AutoHotkey v2.0+
#SingleInstance Force
#Warn All, Off
; ==============================================================================
; Script Name: Call Recorder for FFmpeg
; Synopsis:
; This script manages audio recordings using FFmpeg. It captures audio from a
; specified microphone and saves recordings with timestamped filenames.
; The user can control recording via a system tray menu or hotkeys:
; - Ctrl+Alt+R: Start recording
; - Ctrl+Alt+S: Stop recording
; - Ctrl+Alt+X: Exit the application gracefully
;
; Additional Features:
; - Automatically creates a log directory if it doesn’t exist.
; - Verifies the FFmpeg executable location.
; - Ensures only one instance is running.
; - Provides clear tooltips for user feedback.
;
; Prerequisites:
; - AutoHotkey v2.0+
; - FFmpeg installed at the specified location.
; - Correct microphone name matching the system device.
; ==============================================================================
; --------------------------------------------------------------
; CONFIGURATION
; --------------------------------------------------------------
class Config {
static FFmpegPath := "C:\Users\J2897\Programs\ffmpeg\bin\ffmpeg.exe"
static MicName := "Microphone (Scarlett 2i2 USB)"
static CallsDir := A_ScriptDir "\Calls" ; New directory
static OutputDir := this.CallsDir ; Alias for clarity
static LogDir := A_ScriptDir "\Logs"
static StartDelay := 2000
}
; --------------------------------------------------------------
; APPLICATION STATE
; --------------------------------------------------------------
class State {
static ffmpegPID := 0
static IsRecording => this.ffmpegPID != 0 && ProcessExist(this.ffmpegPID)
static Clear() {
this.ffmpegPID := 0
}
}
; --------------------------------------------------------------
; TRAY MENU
; --------------------------------------------------------------
CreateTrayMenu() {
A_TrayMenu.Delete()
A_TrayMenu.Add("&Start Recording", (*) => StartRecording())
A_TrayMenu.Add("&Stop Recording", (*) => StopRecording())
A_TrayMenu.Add()
A_TrayMenu.Add("E&xit", (*) => ExitAppGracefully())
UpdateTrayMenu()
}
UpdateTrayMenu() {
; Use separate Enable/Disable methods
if State.IsRecording {
A_TrayMenu.Disable("&Start Recording")
A_TrayMenu.Enable("&Stop Recording")
} else {
A_TrayMenu.Enable("&Start Recording")
A_TrayMenu.Disable("&Stop Recording")
}
}
; --------------------------------------------------------------
; HOTKEYS
; --------------------------------------------------------------
^!R:: StartRecording()
^!S:: StopRecording()
^!X:: ExitAppGracefully()
; --------------------------------------------------------------
; INITIALIZATION
; --------------------------------------------------------------
CreateTrayMenu()
ShowToolTip("Call Recorder Initialized."
. "`nRight-click the tray icon for options."
. "`nHotkeys available:"
. "`n Ctrl+Alt+R: Start Recording"
. "`n Ctrl+Alt+S: Stop Recording"
. "`n Ctrl+Alt+X: Exit", 1, 5000)
return
; --------------------------------------------------------------
; FUNCTIONS
; --------------------------------------------------------------
StartRecording() {
if State.IsRecording {
ShowToolTip("Recording already in progress!", 2, 2000)
return
}
if !FileExist(Config.FFmpegPath) {
MsgBox("FFmpeg not found at:`n" Config.FFmpegPath, "Error", 0x30)
return
}
; Ensure both Logs and Calls directories exist
if !DirExist(Config.LogDir)
DirCreate(Config.LogDir)
if !DirExist(Config.CallsDir) ; <-- Added this check
DirCreate(Config.CallsDir)
timestamp := FormatTime(, "yyyy-MM-dd_HH-mm-ss")
outputFile := Config.OutputDir "\Call_" timestamp ".mp3"
cmd := Format(
'cmd /c ""{1}" -hide_banner -f dshow -i audio="{2}" '
'-acodec libmp3lame -b:a 192k -ac 2 -y "{3}" & pause"',
Config.FFmpegPath,
Config.MicName,
outputFile
)
try {
State.ffmpegPID := Run(cmd, Config.OutputDir,, &pid)
; Wait up to 5 seconds for FFmpeg to initialize
startTime := A_TickCount
while (A_TickCount - startTime < 5000) {
if ProcessExist(pid) && FileExist(outputFile)
break
Sleep 500
}
if !ProcessExist(pid) || !FileExist(outputFile)
throw Error("FFmpeg failed to initialize")
State.ffmpegPID := pid
UpdateTrayMenu()
}
catch Error as e {
State.Clear()
MsgBox("Startup Error: " e.Message, "Error", 0x30)
return
}
ShowToolTip("RECORDING STARTED (PID: " State.ffmpegPID ")", 2, 2000)
UpdateTrayMenu()
}
StopRecording() {
if !State.IsRecording {
ShowToolTip("No active recording to stop", 2, 2000)
return
}
ShowToolTip("Stopping recording...", 2, 2000)
try {
; Force focus to FFmpeg's console
WinActivate("ahk_pid " State.ffmpegPID)
WinWaitActive("ahk_pid " State.ffmpegPID,, 1) ; Wait 1 sec for activation
Send "q" ; Send 'q' directly to the active window
Sleep 500 ; Give FFmpeg time to process the command
if ProcessWaitClose(State.ffmpegPID, 2) {
ShowToolTip("Stopped gracefully", 2, 2000)
} else {
ProcessClose(State.ffmpegPID)
ShowToolTip("Force-closed recording", 2, 2000)
}
}
catch Error as e {
MsgBox("Stop Error: " e.Message, "Error", 0x30)
}
State.Clear()
UpdateTrayMenu()
}
ExitAppGracefully() {
if State.IsRecording {
result := MsgBox("Recording in progress. Stop and exit?", "Confirm Exit", 0x34)
if result = "Yes"
StopRecording()
else
return
}
ExitApp()
}
ShowToolTip(text, id := 1, timeout := 0) {
ToolTip(text,,, id)
if timeout > 0
SetTimer(() => ToolTip(,,, id), -timeout)
}
Comments
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎