Advertisement
J2897

TrayCallRecorder

Apr 11th, 2025 (edited)
652
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. #Requires AutoHotkey v2.0+
  2. #SingleInstance Force
  3. #Warn All, Off
  4.  
  5. ; ==============================================================================
  6. ; Script Name: Call Recorder for FFmpeg
  7. ; Synopsis:
  8. ;   This script manages audio recordings using FFmpeg. It captures audio from a
  9. ;   specified microphone and saves recordings with timestamped filenames.
  10. ;   The user can control recording via a system tray menu or hotkeys:
  11. ;     - Ctrl+Alt+R: Start recording
  12. ;     - Ctrl+Alt+S: Stop recording
  13. ;     - Ctrl+Alt+X: Exit the application gracefully
  14. ;  
  15. ;   Additional Features:
  16. ;     - Automatically creates a log directory if it doesn’t exist.
  17. ;     - Verifies the FFmpeg executable location.
  18. ;     - Ensures only one instance is running.
  19. ;     - Provides clear tooltips for user feedback.
  20. ;
  21. ;   Prerequisites:
  22. ;     - AutoHotkey v2.0+
  23. ;     - FFmpeg installed at the specified location.
  24. ;     - Correct microphone name matching the system device.
  25. ; ==============================================================================
  26.  
  27. ; --------------------------------------------------------------
  28. ;         CONFIGURATION
  29. ; --------------------------------------------------------------
  30. class Config {
  31.     static FFmpegPath := "C:\Users\J2897\Programs\ffmpeg\bin\ffmpeg.exe"
  32.     static MicName    := "Microphone (Scarlett 2i2 USB)"
  33.     static CallsDir   := A_ScriptDir "\Calls"  ; New directory
  34.     static OutputDir  := this.CallsDir         ; Alias for clarity
  35.     static LogDir     := A_ScriptDir "\Logs"
  36.     static StartDelay := 2000
  37. }
  38.  
  39. ; --------------------------------------------------------------
  40. ;          APPLICATION STATE
  41. ; --------------------------------------------------------------
  42. class State {
  43.     static ffmpegPID := 0
  44.     static IsRecording => this.ffmpegPID != 0 && ProcessExist(this.ffmpegPID)
  45.    
  46.     static Clear() {
  47.         this.ffmpegPID := 0
  48.     }
  49. }
  50.  
  51. ; --------------------------------------------------------------
  52. ;            TRAY MENU
  53. ; --------------------------------------------------------------
  54. CreateTrayMenu() {
  55.     A_TrayMenu.Delete()
  56.     A_TrayMenu.Add("&Start Recording", (*) => StartRecording())
  57.     A_TrayMenu.Add("&Stop Recording", (*) => StopRecording())
  58.     A_TrayMenu.Add()
  59.     A_TrayMenu.Add("E&xit", (*) => ExitAppGracefully())
  60.     UpdateTrayMenu()
  61. }
  62.  
  63. UpdateTrayMenu() {
  64.     ; Use separate Enable/Disable methods
  65.     if State.IsRecording {
  66.         A_TrayMenu.Disable("&Start Recording")
  67.         A_TrayMenu.Enable("&Stop Recording")
  68.     } else {
  69.         A_TrayMenu.Enable("&Start Recording")
  70.         A_TrayMenu.Disable("&Stop Recording")
  71.     }
  72. }
  73.  
  74. ; --------------------------------------------------------------
  75. ;            HOTKEYS
  76. ; --------------------------------------------------------------
  77. ^!R:: StartRecording()
  78. ^!S:: StopRecording()
  79. ^!X:: ExitAppGracefully()
  80.  
  81. ; --------------------------------------------------------------
  82. ;          INITIALIZATION
  83. ; --------------------------------------------------------------
  84. CreateTrayMenu()
  85. ShowToolTip("Call Recorder Initialized."
  86.     . "`nRight-click the tray icon for options."
  87.     . "`nHotkeys available:"
  88.     . "`n   Ctrl+Alt+R: Start Recording"
  89.     . "`n   Ctrl+Alt+S: Stop Recording"
  90.     . "`n   Ctrl+Alt+X: Exit", 1, 5000)
  91. return
  92.  
  93. ; --------------------------------------------------------------
  94. ;           FUNCTIONS
  95. ; --------------------------------------------------------------
  96. StartRecording() {
  97.     if State.IsRecording {
  98.         ShowToolTip("Recording already in progress!", 2, 2000)
  99.         return
  100.     }
  101.  
  102.     if !FileExist(Config.FFmpegPath) {
  103.         MsgBox("FFmpeg not found at:`n" Config.FFmpegPath, "Error", 0x30)
  104.         return
  105.     }
  106.  
  107.     ; Ensure both Logs and Calls directories exist
  108.     if !DirExist(Config.LogDir)
  109.         DirCreate(Config.LogDir)
  110.     if !DirExist(Config.CallsDir)  ; <-- Added this check
  111.         DirCreate(Config.CallsDir)
  112.    
  113.     timestamp := FormatTime(, "yyyy-MM-dd_HH-mm-ss")
  114.     outputFile := Config.OutputDir "\Call_" timestamp ".mp3"
  115.  
  116.     cmd := Format(
  117.         'cmd /c ""{1}" -hide_banner -f dshow -i audio="{2}" '
  118.        '-acodec libmp3lame -b:a 192k -ac 2 -y "{3}" & pause"',
  119.         Config.FFmpegPath,
  120.         Config.MicName,
  121.         outputFile
  122.     )
  123.  
  124.     try {
  125.         State.ffmpegPID := Run(cmd, Config.OutputDir,, &pid)
  126.        
  127.         ; Wait up to 5 seconds for FFmpeg to initialize
  128.         startTime := A_TickCount
  129.         while (A_TickCount - startTime < 5000) {
  130.             if ProcessExist(pid) && FileExist(outputFile)
  131.                 break
  132.             Sleep 500
  133.         }
  134.        
  135.         if !ProcessExist(pid) || !FileExist(outputFile)
  136.             throw Error("FFmpeg failed to initialize")
  137.        
  138.         State.ffmpegPID := pid
  139.         UpdateTrayMenu()
  140.     }
  141.     catch Error as e {
  142.         State.Clear()
  143.         MsgBox("Startup Error: " e.Message, "Error", 0x30)
  144.         return
  145.     }
  146.  
  147.     ShowToolTip("RECORDING STARTED (PID: " State.ffmpegPID ")", 2, 2000)
  148.     UpdateTrayMenu()
  149. }
  150.  
  151. StopRecording() {
  152.     if !State.IsRecording {
  153.         ShowToolTip("No active recording to stop", 2, 2000)
  154.         return
  155.     }
  156.  
  157.     ShowToolTip("Stopping recording...", 2, 2000)
  158.    
  159.     try {
  160.         ; Force focus to FFmpeg's console
  161.         WinActivate("ahk_pid " State.ffmpegPID)
  162.         WinWaitActive("ahk_pid " State.ffmpegPID,, 1)  ; Wait 1 sec for activation
  163.         Send "q"  ; Send 'q' directly to the active window
  164.         Sleep 500  ; Give FFmpeg time to process the command
  165.        
  166.         if ProcessWaitClose(State.ffmpegPID, 2) {
  167.             ShowToolTip("Stopped gracefully", 2, 2000)
  168.         } else {
  169.             ProcessClose(State.ffmpegPID)
  170.             ShowToolTip("Force-closed recording", 2, 2000)
  171.         }
  172.     }
  173.     catch Error as e {
  174.         MsgBox("Stop Error: " e.Message, "Error", 0x30)
  175.     }
  176.    
  177.     State.Clear()
  178.     UpdateTrayMenu()
  179. }
  180.  
  181. ExitAppGracefully() {
  182.     if State.IsRecording {
  183.         result := MsgBox("Recording in progress. Stop and exit?", "Confirm Exit", 0x34)
  184.         if result = "Yes"
  185.             StopRecording()
  186.         else
  187.             return
  188.     }
  189.     ExitApp()
  190. }
  191.  
  192. ShowToolTip(text, id := 1, timeout := 0) {
  193.     ToolTip(text,,, id)
  194.     if timeout > 0
  195.         SetTimer(() => ToolTip(,,, id), -timeout)
  196. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement