J2897 icon

TrayCallRecorder

J2897 | PRO | 04/12/25 02:13:03 PM UTC (Edited) | 0 ⭐ | 15272 👁️ | Never ⏰ | []
Autohotkey |

6.25 KB

|

None

|

0 👍

/

0 👎

#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

  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎

    
        
  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎

    
        
  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎