diff --git a/README.md b/README.md index 13fa436..4e84277 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,9 @@

Website • - Author + Author + • + Discord Channel

@@ -229,4 +231,4 @@ Last updated: December 26, 2024 ## Star History -[![Star History Chart](https://api.star-history.com/svg?repos=Zackriya-Solutions/meeting-minutes&type=Date)](https://star-history.com/#Zackriya-Solutions/meeting-minutes&Date) \ No newline at end of file +[![Star History Chart](https://api.star-history.com/svg?repos=Zackriya-Solutions/meeting-minutes&type=Date)](https://star-history.com/#Zackriya-Solutions/meeting-minutes&Date) diff --git a/frontend/BUG.txt b/frontend/BUG.txt deleted file mode 100644 index 17c9f5f..0000000 --- a/frontend/BUG.txt +++ /dev/null @@ -1,101 +0,0 @@ -Rust log -""" -○ Compiling / ... - ✓ Compiled / in 4.1s (1264 modules) - GET / 200 in 4434ms - ✓ Compiled in 259ms (621 modules) -[2025-02-06T12:44:09Z INFO app_lib] Attempting to start recording... -[2025-02-06T12:44:09Z INFO app_lib] Recording flag set to true -[2025-02-06T12:44:09Z INFO app_lib] Initialized audio buffers -[2025-02-06T12:44:09Z INFO app_lib] System temp directory: "/var/folders/8w/b5k5fvp94rn8g23lf136nmtc0000gn/T/" -[2025-02-06T12:44:09Z INFO app_lib] Full debug directory path: "/var/folders/8w/b5k5fvp94rn8g23lf136nmtc0000gn/T/meeting_minutes_debug" -[2025-02-06T12:44:09Z INFO app_lib] Debug directory successfully created and exists -[2025-02-06T12:44:09Z INFO app_lib] Mic config: 48000 Hz, 1 channels -[2025-02-06T12:44:09Z INFO app_lib] Attempting to stop recording... -[2025-02-06T12:44:09Z INFO app_lib] Recording flag set to false -[2025-02-06T12:44:09Z INFO app_lib] Audio stream running flag set to false -[2025-02-06T12:44:09Z INFO app_lib] Transcription task ended -[2025-02-06T12:44:10Z INFO app_lib::audio::core] stopped recording audio stream -[2025-02-06T12:44:10Z INFO app_lib] Successfully stopped mic stream -[2025-02-06T12:44:10Z INFO app_lib::audio::core] stopped recording audio stream -[2025-02-06T12:44:11Z INFO app_lib] Successfully stopped system stream -[2025-02-06T12:44:11Z ERROR app_lib] No audio data captured -[2025-02-06T12:44:11Z INFO app_lib] Attempting to start recording... -[2025-02-06T12:44:11Z INFO app_lib] Recording flag set to true -[2025-02-06T12:44:11Z INFO app_lib] Initialized audio buffers -[2025-02-06T12:44:11Z INFO app_lib] System temp directory: "/var/folders/8w/b5k5fvp94rn8g23lf136nmtc0000gn/T/" -[2025-02-06T12:44:11Z INFO app_lib] Full debug directory path: "/var/folders/8w/b5k5fvp94rn8g23lf136nmtc0000gn/T/meeting_minutes_debug" -[2025-02-06T12:44:11Z INFO app_lib] Debug directory successfully created and exists -[2025-02-06T12:44:11Z INFO app_lib] Mic config: 48000 Hz, 1 channels -[2025-02-06T12:44:18Z INFO app_lib] Should send chunk with 480192 samples -[2025-02-06T12:44:18Z INFO app_lib] Processing chunk 0 -[2025-02-06T12:44:18Z INFO app_lib] Saving mic chunk to "/var/folders/8w/b5k5fvp94rn8g23lf136nmtc0000gn/T/meeting_minutes_debug/chunk_0_mic.wav" -[2025-02-06T12:44:18Z INFO app_lib] Successfully saved mic chunk 0 with 512 samples -[2025-02-06T12:44:18Z INFO app_lib] Saving system chunk to "/var/folders/8w/b5k5fvp94rn8g23lf136nmtc0000gn/T/meeting_minutes_debug/chunk_0_system.wav" -[2025-02-06T12:44:18Z INFO app_lib] Successfully saved system chunk 0 with 960 samples -[2025-02-06T12:44:18Z INFO app_lib] Saving mixed chunk to "/var/folders/8w/b5k5fvp94rn8g23lf136nmtc0000gn/T/meeting_minutes_debug/chunk_0_mixed.wav" -RemoteLayerTreeDrawingAreaProxyMac::scheduleDisplayLink(): page has no displayID -[2025-02-06T12:44:28Z INFO app_lib] Received 1 transcript segments -[2025-02-06T12:44:28Z INFO app_lib] Processing segment: Let's look at our interest slides. (0.0s - 1020.0s) -[2025-02-06T12:44:28Z INFO app_lib] Processing new transcript segment: TranscriptSegment { text: " Let's look at our interest slides.", t0: 0.0, t1: 1020.0 } -[2025-02-06T12:44:28Z INFO app_lib] Clean transcript text: Let's look at our interest slides. -[2025-02-06T12:44:28Z INFO app_lib] Generated transcript update: TranscriptUpdate { text: "Let's look at our interest slides.", timestamp: "0.0 - 1020.0", source: "Mixed Audio" } -[2025-02-06T12:44:28Z INFO app_lib] Should send chunk with 480128 samples -[2025-02-06T12:44:28Z INFO app_lib] Processing chunk 1 -[2025-02-06T12:44:28Z INFO app_lib] Saving mic chunk to "/var/folders/8w/b5k5fvp94rn8g23lf136nmtc0000gn/T/meeting_minutes_debug/chunk_1_mic.wav" -[2025-02-06T12:44:28Z INFO app_lib] Successfully saved mic chunk 1 with 512 samples -[2025-02-06T12:44:28Z INFO app_lib] No system samples to save for chunk 1 -[2025-02-06T12:44:28Z INFO app_lib] Saving mixed chunk to "/var/folders/8w/b5k5fvp94rn8g23lf136nmtc0000gn/T/meeting_minutes_debug/chunk_1_mixed.wav" -[2025-02-06T12:44:37Z INFO app_lib] Received 1 transcript segments -[2025-02-06T12:44:37Z INFO app_lib] Processing segment: Yeah, so my name is Moya. I'm the product manager for this role, this project, and I'm an engineering psychology major in economics, and I have. (0.0s - 976.0s) -[2025-02-06T12:44:37Z INFO app_lib] Processing new transcript segment: TranscriptSegment { text: " Yeah, so my name is Moya. I'm the product manager for this role, this project, and I'm an engineering psychology major in economics, and I have.", t0: 0.0, t1: 976.0 } -[2025-02-06T12:44:37Z INFO app_lib] Clean transcript text: Yeah, so my name is Moya. I'm the product manager for this role, this project, and I'm an engineering psychology major in economics, and I have. -[2025-02-06T12:44:37Z INFO app_lib] Generated transcript update: TranscriptUpdate { text: "Yeah, so my name is Moya. I'm the product manager for this role, this project, and I'm an engineering psychology major in economics, and I have.", timestamp: "0.0 - 976.0", source: "Mixed Audio" } -[2025-02-06T12:44:38Z INFO app_lib] Should send chunk with 480576 samples -[2025-02-06T12:44:38Z INFO app_lib] Processing chunk 2 -[2025-02-06T12:44:38Z INFO app_lib] Saving mic chunk to "/var/folders/8w/b5k5fvp94rn8g23lf136nmtc0000gn/T/meeting_minutes_debug/chunk_2_mic.wav" -[2025-02-06T12:44:38Z INFO app_lib] Successfully saved mic chunk 2 with 512 samples -[2025-02-06T12:44:38Z INFO app_lib] Saving system chunk to "/var/folders/8w/b5k5fvp94rn8g23lf136nmtc0000gn/T/meeting_minutes_debug/chunk_2_system.wav" -[2025-02-06T12:44:38Z INFO app_lib] Successfully saved system chunk 2 with 960 samples -[2025-02-06T12:44:38Z INFO app_lib] Saving mixed chunk to "/var/folders/8w/b5k5fvp94rn8g23lf136nmtc0000gn/T/meeting_minutes_debug/chunk_2_mixed.wav" -[2025-02-06T12:44:40Z INFO app_lib] Attempting to stop recording... -[2025-02-06T12:44:40Z INFO app_lib] Recording flag set to false -[2025-02-06T12:44:40Z INFO app_lib] Audio stream running flag set to false -[2025-02-06T12:44:41Z ERROR app_lib] Could not get exclusive ownership of mic stream -[2025-02-06T12:44:41Z ERROR app_lib] Could not get exclusive ownership of system stream -[2025-02-06T12:44:42Z INFO app_lib] Mixed 1269760 audio samples -[2025-02-06T12:44:42Z INFO app_lib] Converted to 2539520 bytes of PCM data -[2025-02-06T12:44:42Z INFO app_lib] Created WAV file with 2539564 bytes total -[2025-02-06T12:44:42Z INFO app_lib] Saving transcript to: /Users/sujith/Library/Application Support/com.meetily.aitranscript-2025-02-06T12-44-40-484Z.txt -[2025-02-06T12:44:42Z INFO app_lib] Transcript saved successfully -[2025-02-06T12:44:46Z INFO app_lib] Received 2 transcript segments -[2025-02-06T12:44:46Z INFO app_lib] Processing segment: prior experience as a product designer for Jumbo Code and a UX/UI designer at (0.0s - 510.0s) -[2025-02-06T12:44:46Z INFO app_lib] Processing new transcript segment: TranscriptSegment { text: " prior experience as a product designer for Jumbo Code and a UX/UI designer at", t0: 0.0, t1: 510.0 } -""" - -Tauri web log -""" -[Error] Failed to stop recording: – "No audio data captured" - (anonymous function) (app-index.js:33) - (anonymous function) (hydration-error-info.js:63) - (anonymous function) (page.tsx:246) -> Selected Element -<

-[Error] Failed to stop recording: – "invalid args `args` for command `stop_recording`: command stop_recording missing required key args" - (anonymous function) (app-index.js:33) - (anonymous function) (hydration-error-info.js:63) - (anonymous function) (RecordingControls.tsx:126) -""" - -Something is wrong with record start stop. Observation - -1. The recording button is not going to inactive when click detected. -2. When record button is clicked, for 1 second, the record starts, the suffenly stops raising error in frontend and backend as - " ERROR app_lib] No audio data captured", then record restarts and captures everything and the button state is recording. -3. When recording stops, the record button works correctly but backend and frontend error occures "[2025-02-06T12:44:41Z ERROR app_lib] Could not get exclusive ownership of mic stream -[2025-02-06T12:44:41Z ERROR app_lib] Could not get exclusive ownership of system stream" and in frontend "ailed to stop recording:"invalid args `args` for command `stop_recording`: command stop_recording missing required key args" - -These errors makes -1. The screen capture stll going on and all. What is the issue? -2. Please do not fuck up any existing stuff - -1. When record button initially clicked, start recording and start transcription parallel, When clicked again, stop recording and start the summarization. Please don't fuckup the design and stuff while fixing. Please make sure existing code is not affected and use the stuff that's already there to fix the bug. Do not remove functionalities since all are essential. just fix bug, evaluate if anythig might break \ No newline at end of file diff --git a/frontend/src-tauri/Info.plist b/frontend/src-tauri/Info.plist new file mode 100644 index 0000000..9308f40 --- /dev/null +++ b/frontend/src-tauri/Info.plist @@ -0,0 +1,8 @@ + + + + + NSMicrophoneUsageDescription + This application needs access to your microphone to record meeting audio. + + diff --git a/frontend/src-tauri/entitlements.plist b/frontend/src-tauri/entitlements.plist new file mode 100644 index 0000000..5658ffc --- /dev/null +++ b/frontend/src-tauri/entitlements.plist @@ -0,0 +1,10 @@ + + + + + com.apple.security.device.audio-input + + com.apple.security.device.microphone + + + \ No newline at end of file diff --git a/frontend/src-tauri/src/audio/audio_processing.rs b/frontend/src-tauri/src/audio/audio_processing.rs index 1617fd6..bd71994 100644 --- a/frontend/src-tauri/src/audio/audio_processing.rs +++ b/frontend/src-tauri/src/audio/audio_processing.rs @@ -22,7 +22,7 @@ pub fn normalize_v2(audio: &[f32]) -> Vec { } // Increase target RMS for better voice volume while keeping peak in check - let target_rms = 0.5; // Increased from 0.6 + let target_rms = 0.9; // Increased from 0.6 let target_peak = 0.95; // Slightly reduced to prevent clipping let rms_scaling = target_rms / rms; diff --git a/frontend/src-tauri/src/audio/core.rs b/frontend/src-tauri/src/audio/core.rs index 867b910..e91a1a2 100644 --- a/frontend/src-tauri/src/audio/core.rs +++ b/frontend/src-tauri/src/audio/core.rs @@ -3,7 +3,7 @@ use anyhow::{anyhow, Result}; use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; use cpal::StreamError; use lazy_static::lazy_static; -use log::{ error, info, warn}; +use log::{ error, info, warn, debug}; use serde::{Deserialize, Serialize}; use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; use std::sync::mpsc; @@ -273,18 +273,24 @@ pub fn trigger_audio_permission() -> Result<()> { let config = device.default_input_config()?; - // Attempt to build an input stream, which should trigger the permission request - let _stream = device.build_input_stream( + // Build and start an input stream to trigger the permission request + let stream = device.build_input_stream( &config.into(), |_data: &[f32], _: &cpal::InputCallbackInfo| { // Do nothing, we just want to trigger the permission request }, - |err| eprintln!("Error in audio stream: {}", err), + |err| error!("Error in audio stream: {}", err), None, )?; - // We don't actually need to start the stream - // The mere attempt to build it should trigger the permission request + // Start the stream to actually trigger the permission dialog + stream.play()?; + + // Sleep briefly to allow the permission dialog to appear + std::thread::sleep(std::time::Duration::from_millis(100)); + + // Stop the stream + drop(stream); Ok(()) } @@ -308,10 +314,23 @@ impl AudioStream { device: Arc, is_running: Arc, ) -> Result { + info!("Initializing audio stream for device: {}", device.to_string()); let (tx, _) = broadcast::channel::>(1000); let tx_clone = tx.clone(); let (cpal_audio_device, config) = get_device_and_config(&device).await?; + + // Verify we can actually get input config + match cpal_audio_device.default_input_config() { + Ok(conf) => info!("Default input config: {:?}", conf), + Err(e) => { + error!("Failed to get default input config: {}", e); + return Err(anyhow!("Failed to get default input config: {}", e)); + } + } + let channels = config.channels(); + info!("Audio config - Sample rate: {}, Channels: {}, Format: {:?}", + config.sample_rate().0, channels, config.sample_format()); let is_running_weak_2 = Arc::downgrade(&is_running); let is_disconnected = Arc::new(AtomicBool::new(false)); @@ -324,7 +343,9 @@ impl AudioStream { let stream_thread = Arc::new(tokio::sync::Mutex::new(Some(thread::spawn(move || { let device = device_clone; let device_name = device.to_string(); + let device_name_clone = device_name.clone(); // Clone for the closure let config = config_clone; + info!("Starting audio stream thread for device: {}", device_name); let error_callback = move |err: StreamError| { if err .to_string() @@ -332,13 +353,19 @@ impl AudioStream { { warn!( "audio device {} disconnected. stopping recording.", - device_name + device_name_clone ); stream_control_tx_clone .send(StreamControl::Stop(oneshot::channel().0)) .unwrap(); is_disconnected_clone.store(true, Ordering::Relaxed); + } else if err.to_string().to_lowercase().contains("permission denied") || + err.to_string().to_lowercase().contains("access denied") { + error!("Permission denied for audio device {}. Please check microphone permissions.", device_name_clone); + if let Some(arc) = is_running_weak_2.upgrade() { + arc.store(false, Ordering::Relaxed); + } } else { error!("an error occurred on the audio stream: {}", err); if err.to_string().contains("device is no longer valid") { @@ -351,50 +378,86 @@ impl AudioStream { }; let stream = match config.sample_format() { - cpal::SampleFormat::F32 => cpal_audio_device - .build_input_stream( + cpal::SampleFormat::F32 => { + match cpal_audio_device.build_input_stream( &config.into(), move |data: &[f32], _: &_| { let mono = audio_to_mono(data, channels); - let _ = tx.send(mono); + debug!("Received audio chunk: {} samples", mono.len()); + if let Err(e) = tx.send(mono) { + error!("Failed to send audio data: {}", e); + } }, - error_callback, + error_callback.clone(), None, - ) - .expect("Failed to build input stream"), - cpal::SampleFormat::I16 => cpal_audio_device - .build_input_stream( + ) { + Ok(stream) => stream, + Err(e) => { + error!("Failed to build input stream: {}", e); + return; + } + } + } + cpal::SampleFormat::I16 => { + match cpal_audio_device.build_input_stream( &config.into(), move |data: &[i16], _: &_| { let mono = audio_to_mono(bytemuck::cast_slice(data), channels); - let _ = tx.send(mono); + debug!("Received audio chunk: {} samples", mono.len()); + if let Err(e) = tx.send(mono) { + error!("Failed to send audio data: {}", e); + } }, - error_callback, + error_callback.clone(), None, - ) - .expect("Failed to build input stream"), - cpal::SampleFormat::I32 => cpal_audio_device - .build_input_stream( + ) { + Ok(stream) => stream, + Err(e) => { + error!("Failed to build input stream: {}", e); + return; + } + } + } + cpal::SampleFormat::I32 => { + match cpal_audio_device.build_input_stream( &config.into(), move |data: &[i32], _: &_| { let mono = audio_to_mono(bytemuck::cast_slice(data), channels); - let _ = tx.send(mono); + debug!("Received audio chunk: {} samples", mono.len()); + if let Err(e) = tx.send(mono) { + error!("Failed to send audio data: {}", e); + } }, - error_callback, + error_callback.clone(), None, - ) - .expect("Failed to build input stream"), - cpal::SampleFormat::I8 => cpal_audio_device - .build_input_stream( + ) { + Ok(stream) => stream, + Err(e) => { + error!("Failed to build input stream: {}", e); + return; + } + } + } + cpal::SampleFormat::I8 => { + match cpal_audio_device.build_input_stream( &config.into(), move |data: &[i8], _: &_| { let mono = audio_to_mono(bytemuck::cast_slice(data), channels); - let _ = tx.send(mono); + debug!("Received audio chunk: {} samples", mono.len()); + if let Err(e) = tx.send(mono) { + error!("Failed to send audio data: {}", e); + } }, - error_callback, + error_callback.clone(), None, - ) - .expect("Failed to build input stream"), + ) { + Ok(stream) => stream, + Err(e) => { + error!("Failed to build input stream: {}", e); + return; + } + } + } _ => { error!("unsupported sample format: {}", config.sample_format()); return; @@ -403,8 +466,15 @@ impl AudioStream { if let Err(e) = stream.play() { error!("failed to play stream for {}: {}", device.to_string(), e); + let err_str = e.to_string().to_lowercase(); + if err_str.contains("permission") { + error!("Permission error detected. Please check microphone permissions in System Preferences"); + } else if err_str.contains("busy") { + error!("Device is busy. Another application might be using it"); + } + return; } - + info!("Audio stream started successfully for device: {}", device_name); if let Ok(StreamControl::Stop(response)) = stream_control_rx.recv() { info!("stopping audio stream..."); // First stop the stream @@ -438,9 +508,8 @@ impl AudioStream { self.is_disconnected.store(true, Ordering::Release); // Send stop signal and wait for confirmation - let (tx, rx) = oneshot::channel(); + let (tx, _rx) = oneshot::channel(); self.stream_control.send(StreamControl::Stop(tx))?; - rx.await?; // Wait for thread to finish if let Some(thread_arc) = &self.stream_thread { diff --git a/frontend/src-tauri/src/lib.rs b/frontend/src-tauri/src/lib.rs index 0845c81..a3136b1 100644 --- a/frontend/src-tauri/src/lib.rs +++ b/frontend/src-tauri/src/lib.rs @@ -798,6 +798,12 @@ pub fn run() { tauri::Builder::default() .setup(|_app| { log::info!("Application setup complete"); + + // Trigger microphone permission request on startup + if let Err(e) = audio::core::trigger_audio_permission() { + log::error!("Failed to trigger audio permission: {}", e); + } + Ok(()) }) .invoke_handler(tauri::generate_handler![