summaryrefslogtreecommitdiff
path: root/modules
diff options
context:
space:
mode:
Diffstat (limited to 'modules')
-rw-r--r--modules/auth-config.el61
-rw-r--r--modules/config-utilities.el12
-rw-r--r--modules/dirvish-config.el17
-rw-r--r--modules/transcription-config.el96
-rw-r--r--modules/video-audio-recording.el12
5 files changed, 157 insertions, 41 deletions
diff --git a/modules/auth-config.el b/modules/auth-config.el
index 8376a2c0..2b52087e 100644
--- a/modules/auth-config.el
+++ b/modules/auth-config.el
@@ -55,5 +55,66 @@
;; Allow gpg-agent to cache the passphrase (400 days per gpg-agent.conf)
(setq plstore-encrypt-to nil)) ;; Use symmetric encryption, not key-based
+;; ------------------------ Authentication Reset Utility -----------------------
+
+(defun cj/reset-auth-cache (&optional include-gpg-agent)
+ "Reset authentication caches when wrong password was entered.
+
+By default, only clears Emacs-side caches (auth-source, EPA file
+handler) and leaves gpg-agent's long-term cache intact. This preserves
+your 400-day cache for GPG and SSH passphrases.
+
+With prefix argument INCLUDE-GPG-AGENT (\\[universal-argument]), also
+clears gpg-agent's password cache. Use this when gpg-agent itself has
+cached an incorrect password.
+
+Clears:
+1. auth-source cache (Emacs-level credential cache)
+2. EPA file handler cache (encrypted file cache)
+3. gpg-agent cache (only if INCLUDE-GPG-AGENT is non-nil)
+
+Use this when you see errors like:
+ - \"Bad session key\"
+ - \"Decryption failed\"
+ - GPG repeatedly using wrong cached password"
+ (interactive "P")
+ (message "Resetting authentication caches...")
+
+ ;; Clear auth-source cache (Emacs credential cache)
+ (auth-source-forget-all-cached)
+
+ ;; Clear EPA file handler cache
+ (when (fboundp 'epa-file-clear-cache)
+ (epa-file-clear-cache))
+
+ ;; Only clear gpg-agent cache if explicitly requested
+ (if include-gpg-agent
+ (let ((result (shell-command "echo RELOADAGENT | gpg-connect-agent")))
+ (if (zerop result)
+ (message "✓ Emacs and gpg-agent caches cleared. Next access will prompt for password.")
+ (message "⚠ Warning: Failed to clear gpg-agent cache")))
+ (message "✓ Emacs caches cleared. GPG/SSH passphrases preserved for session.")))
+
+(defun cj/kill-gpg-agent ()
+ "Force kill gpg-agent (it will restart automatically on next use).
+
+This is a more aggressive reset than `cj/reset-auth-cache'. Use this
+when gpg-agent is stuck or behaving incorrectly.
+
+The gpg-agent will automatically restart on the next GPG operation."
+ (interactive)
+ (let ((result (shell-command "gpgconf --kill gpg-agent")))
+ (if (zerop result)
+ (message "✓ gpg-agent killed. It will restart automatically on next use.")
+ (message "⚠ Warning: Failed to kill gpg-agent"))))
+
+;; Keybindings
+(with-eval-after-load 'keybindings
+ (keymap-set cj/custom-keymap "A" #'cj/reset-auth-cache))
+
+(with-eval-after-load 'which-key
+ (which-key-add-key-based-replacements
+ "C-; A" "reset auth cache"))
+
(provide 'auth-config)
;;; auth-config.el ends here.
diff --git a/modules/config-utilities.el b/modules/config-utilities.el
index 32018371..2af3effa 100644
--- a/modules/config-utilities.el
+++ b/modules/config-utilities.el
@@ -33,8 +33,7 @@
"C-c d i b" "info build"
"C-c d i p" "info packages"
"C-c d i f" "info features"
- "C-c d r" "reload init"
- "C-c d a" "reset auth cache"))
+ "C-c d r" "reload init"))
;;; --------------------------------- Profiling ---------------------------------
@@ -283,15 +282,6 @@ Recompile natively when supported, otherwise fall back to byte compilation."
(load-file user-init-file))
(keymap-set cj/debug-config-keymap "r" 'cj/reload-init-file)
-;; ----------------------------- Reset-Auth-Sources ----------------------------
-
-(defun cj/reset-auth-cache ()
- "Clear Emacs auth-source cache."
- (interactive)
- (auth-source-forget-all-cached)
- (message "Emacs auth-source cache cleared."))
-(keymap-set cj/debug-config-keymap "a" 'cj/reset-auth-cache)
-
;; ------------------------ Validate Org Agenda Entries ------------------------
(defun cj/validate-org-agenda-timestamps ()
diff --git a/modules/dirvish-config.el b/modules/dirvish-config.el
index 441ff61b..b10c97f0 100644
--- a/modules/dirvish-config.el
+++ b/modules/dirvish-config.el
@@ -8,15 +8,15 @@
;; ediff, playlist creation, path copying, and external file manager integration.
;;
;; Key Bindings:
-;; - d: Duplicate file at point (adds "-copy" before extension)
-;; - D: Delete marked files immediately (dired-do-delete)
+;; - d: Delete marked files (dired-do-delete)
+;; - D: Duplicate file at point (adds "-copy" before extension)
;; - g: Quick access menu (jump to predefined directories)
;; - G: Search with deadgrep in current directory
;; - f: Open system file manager in current directory
;; - o/O: Open file with xdg-open/custom command
;; - l: Copy file path (project-relative or home-relative)
;; - L: Copy absolute file path
-;; - P: Create M3U playlist from marked audio files
+;; - P: Copy file path (same as 'l', replaces dired-do-print)
;; - M-D: DWIM menu (context actions for files)
;; - TAB: Toggle subtree expansion
;; - F11: Toggle sidebar view
@@ -120,9 +120,9 @@ Filters for audio files, prompts for the playlist name, and saves the resulting
(setq dired-listing-switches "-l --almost-all --human-readable --group-directories-first")
(setq dired-dwim-target t)
(setq dired-clean-up-buffers-too t) ;; offer to kill buffers associated deleted files and dirs
- (setq dired-clean-confirm-killing-deleted-buffers t) ;; don't ask; just kill buffers associated with deleted files
- (setq dired-recursive-copies (quote always)) ;; “always” means no asking
- (setq dired-recursive-deletes (quote top))) ;; “top” means ask once
+ (setq dired-clean-confirm-killing-deleted-buffers nil) ;; don't ask; just kill buffers associated with deleted files
+ (setq dired-recursive-copies (quote always)) ;; "always" means no asking
+ (setq dired-recursive-deletes (quote top))) ;; "top" means ask once
;; note: disabled as it prevents marking and moving files to another directory
;; (setq dired-kill-when-opening-new-dired-buffer t) ;; don't litter by leaving buffers when navigating directories
@@ -322,13 +322,14 @@ regardless of what file or subdirectory the point is on."
("M-p" . dirvish-peek-toggle)
("M-s" . dirvish-setup-menu)
("TAB" . dirvish-subtree-toggle)
- ("d" . cj/dirvish-duplicate-file)
+ ("d" . dired-do-delete)
+ ("D" . cj/dirvish-duplicate-file)
("f" . cj/dirvish-open-file-manager-here)
("g" . dirvish-quick-access)
("o" . cj/xdg-open)
("O" . cj/open-file-with-command) ; Prompts for command to run
("r" . dirvish-rsync)
- ("P" . cj/dired-create-playlist-from-marked)
+ ("P" . cj/dired-copy-path-as-kill)
("s" . dirvish-quicksort)
("v" . dirvish-vc-menu)
("y" . dirvish-yank-menu)))
diff --git a/modules/transcription-config.el b/modules/transcription-config.el
index 13b9bbce..fd2f4aaa 100644
--- a/modules/transcription-config.el
+++ b/modules/transcription-config.el
@@ -5,19 +5,23 @@
;;; Commentary:
;;
-;; Audio transcription workflow using OpenAI Whisper (API or local).
+;; Audio transcription workflow with multiple backend options.
;;
;; USAGE:
;; In dired: Press `T` on an audio file to transcribe
;; Anywhere: M-x cj/transcribe-audio
;; View active: M-x cj/transcriptions-buffer
+;; Switch backend: C-; T b (or M-x cj/transcription-switch-backend)
;;
;; OUTPUT FILES:
;; audio.m4a → audio.txt (transcript)
;; → audio.log (process logs, conditionally kept)
;;
;; BACKENDS:
-;; - 'openai-api: Fast cloud transcription (requires OPENAI_API_KEY)
+;; - 'openai-api: Fast cloud transcription
+;; API key retrieved from authinfo.gpg (machine api.openai.com)
+;; - 'assemblyai: Cloud transcription with speaker diarization
+;; API key retrieved from authinfo.gpg (machine api.assemblyai.com)
;; - 'local-whisper: Local transcription (requires whisper installed)
;;
;; NOTIFICATIONS:
@@ -33,12 +37,14 @@
(require 'dired)
(require 'notifications)
+(require 'auth-source)
;; ----------------------------- Configuration ---------------------------------
-(defvar cj/transcribe-backend 'local-whisper
+(defvar cj/transcribe-backend 'assemblyai
"Transcription backend to use.
- `openai-api': Fast cloud transcription via OpenAI API
+- `assemblyai': Cloud transcription with speaker diarization via AssemblyAI
- `local-whisper': Local transcription using installed Whisper")
(defvar cj/transcription-keep-log-when-done nil
@@ -83,9 +89,36 @@ SUCCESS-P indicates whether transcription succeeded."
"Return absolute path to transcription script based on backend."
(let ((script-name (pcase cj/transcribe-backend
('openai-api "oai-transcribe")
+ ('assemblyai "assemblyai-transcribe")
('local-whisper "local-whisper"))))
(expand-file-name (concat "scripts/" script-name) user-emacs-directory)))
+(defun cj/--get-openai-api-key ()
+ "Retrieve OpenAI API key from authinfo.gpg.
+Expects entry in authinfo.gpg:
+ machine api.openai.com login api password sk-...
+Returns the API key string, or nil if not found."
+ (when-let* ((auth-info (car (auth-source-search
+ :host "api.openai.com"
+ :require '(:secret))))
+ (secret (plist-get auth-info :secret)))
+ (if (functionp secret)
+ (funcall secret)
+ secret)))
+
+(defun cj/--get-assemblyai-api-key ()
+ "Retrieve AssemblyAI API key from authinfo.gpg.
+Expects entry in authinfo.gpg:
+ machine api.assemblyai.com login api password <key>
+Returns the API key string, or nil if not found."
+ (when-let* ((auth-info (car (auth-source-search
+ :host "api.assemblyai.com"
+ :require '(:secret))))
+ (secret (plist-get auth-info :secret)))
+ (if (functionp secret)
+ (funcall secret)
+ secret)))
+
;; ---------------------------- Process Management -----------------------------
(defun cj/--notify (title message &optional urgency)
@@ -125,14 +158,28 @@ Returns the process object."
(format "Audio file: %s\n" audio-file)
(format "Script: %s\n\n" script)))
- ;; Start process
- (let ((process (make-process
- :name process-name
- :buffer (get-buffer-create buffer-name)
- :command (list script audio-file)
- :sentinel (lambda (proc event)
- (cj/--transcription-sentinel proc event audio-file txt-file log-file))
- :stderr log-file)))
+ ;; Start process with environment
+ (let* ((process-environment
+ ;; Add API key to environment based on backend
+ (pcase cj/transcribe-backend
+ ('openai-api
+ (if-let ((api-key (cj/--get-openai-api-key)))
+ (cons (format "OPENAI_API_KEY=%s" api-key)
+ process-environment)
+ (user-error "OpenAI API key not found in authinfo.gpg for host api.openai.com")))
+ ('assemblyai
+ (if-let ((api-key (cj/--get-assemblyai-api-key)))
+ (cons (format "ASSEMBLYAI_API_KEY=%s" api-key)
+ process-environment)
+ (user-error "AssemblyAI API key not found in authinfo.gpg for host api.assemblyai.com")))
+ (_ process-environment)))
+ (process (make-process
+ :name process-name
+ :buffer (get-buffer-create buffer-name)
+ :command (list script audio-file)
+ :sentinel (lambda (proc event)
+ (cj/--transcription-sentinel proc event audio-file txt-file log-file))
+ :stderr log-file)))
;; Track transcription
(push (list process audio-file (current-time) 'running) cj/transcriptions-list)
@@ -298,6 +345,21 @@ Uses backend specified by `cj/transcribe-backend'."
(kill-process process)
(message "Killed transcription process")))
+;;;###autoload
+(defun cj/transcription-switch-backend ()
+ "Switch transcription backend.
+Prompts with completing-read to select from available backends."
+ (interactive)
+ (let* ((backends '(("assemblyai" . assemblyai)
+ ("openai-api" . openai-api)
+ ("local-whisper" . local-whisper)))
+ (current (symbol-name cj/transcribe-backend))
+ (prompt (format "Transcription backend (current: %s): " current))
+ (choice (completing-read prompt backends nil t))
+ (new-backend (alist-get choice backends nil nil #'string=)))
+ (setq cj/transcribe-backend new-backend)
+ (message "Transcription backend: %s" choice)))
+
;; ------------------------------- Dired Integration ---------------------------
(with-eval-after-load 'dired
@@ -311,16 +373,18 @@ Uses backend specified by `cj/transcribe-backend'."
(defvar-keymap cj/transcribe-map
:doc "Keymap for transcription operations"
"a" #'cj/transcribe-audio
+ "b" #'cj/transcription-switch-backend
"v" #'cj/transcriptions-buffer
"k" #'cj/transcription-kill)
-(keymap-set cj/custom-keymap "t" cj/transcribe-map)
+(keymap-set cj/custom-keymap "T" cj/transcribe-map)
(with-eval-after-load 'which-key
(which-key-add-key-based-replacements
- "C-; t" "transcription menu"
- "C-; t a" "transcribe audio"
- "C-; t v" "view transcriptions"
- "C-; t k" "kill transcription"))
+ "C-; T" "transcription menu"
+ "C-; T a" "transcribe audio"
+ "C-; T b" "switch backend"
+ "C-; T v" "view transcriptions"
+ "C-; T k" "kill transcription"))
(provide 'transcription-config)
;;; transcription-config.el ends here
diff --git a/modules/video-audio-recording.el b/modules/video-audio-recording.el
index c714a0a6..45bab267 100644
--- a/modules/video-audio-recording.el
+++ b/modules/video-audio-recording.el
@@ -4,7 +4,7 @@
;;; Commentary:
;; Use ffmpeg to record desktop video or just audio.
;; with audio from mic and audio from default audio sink
-;; Also supports audio-only recording in Opus format.
+;; Audio recordings use M4A/AAC format for best compatibility.
;;
;; Note: video-recordings-dir and audio-recordings-dir are defined
;; (and directory created) in user-constants.el
@@ -311,16 +311,16 @@ Otherwise use the default location in `audio-recordings-dir'."
(system-device (cdr devices))
(location (expand-file-name directory))
(name (format-time-string "%Y-%m-%d-%H-%M-%S"))
- (filename (expand-file-name (concat name ".opus") location))
+ (filename (expand-file-name (concat name ".m4a") location))
(ffmpeg-command
(format (concat "ffmpeg "
"-f pulse -i %s "
"-ac 1 "
"-f pulse -i %s "
- "-ac 2 "
- "-filter_complex \"[0:a]volume=%.1f[mic];[1:a]volume=%.1f[sys];[mic][sys]amerge=inputs=2\" "
- "-c:a libopus "
- "-b:a 96k "
+ "-ac 1 "
+ "-filter_complex \"[0:a]volume=%.1f[mic];[1:a]volume=%.1f[sys];[mic][sys]amerge=inputs=2[out];[out]pan=mono|c0=0.5*c0+0.5*c1\" "
+ "-c:a aac "
+ "-b:a 64k "
"%s")
mic-device
system-device