From bd7d69d5204e39a09682b0128757f9f4631175fe Mon Sep 17 00:00:00 2001 From: sloganking Date: Tue, 24 Oct 2023 23:13:59 -0400 Subject: [PATCH 1/5] instant typing on windows systems. This will get merged when a new enigo version is released on crates.io. See https://github.com/enigo-rs/enigo/issues/238 --- Cargo.toml | 3 ++- src/main.rs | 8 ++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 77aa9c1..f2b0490 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,8 @@ async-openai = "0.14.3" clap = { version = "4.4.6", features = ["derive"] } cpal = "0.15.2" dotenvy = "0.15.7" -enigo = "0.1.3" +# enigo = "0.1.3" +enigo = { git = "https://github.com/enigo-rs/enigo.git", rev = "96d5fd2b461fbe61e44473437356b2e2905dbc50" } hound = "3.5.1" rdev = "0.5.3" tempfile = "3.8.0" diff --git a/src/main.rs b/src/main.rs index 1d08aa5..4347d99 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,7 @@ use anyhow::Context; use async_openai::Client; use dotenvy::dotenv; -use enigo::{Enigo, KeyboardControllable}; +use enigo::{Enigo, EnigoSettings, KeyboardControllable}; use std::env; use tempfile::tempdir; mod transcribe; @@ -347,7 +347,11 @@ fn main() -> Result<(), Box> { let client = Client::new(); let runtime = tokio::runtime::Runtime::new().context("Failed to create tokio runtime")?; - let mut enigo = Enigo::new(); + + let settings = EnigoSettings { + ..Default::default() + }; + let mut enigo = Enigo::new(&settings).context("Failed to create enigo")?; let tmp_dir = tempdir()?; // println!("{:?}", tmp_dir.path()); From cdb1ce0847d0903cf148a3e0059a5c518254ce1d Mon Sep 17 00:00:00 2001 From: sloganking Date: Tue, 24 Oct 2023 23:20:36 -0400 Subject: [PATCH 2/5] make less verbose. --- src/main.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/main.rs b/src/main.rs index 4347d99..72e3bce 100644 --- a/src/main.rs +++ b/src/main.rs @@ -347,11 +347,8 @@ fn main() -> Result<(), Box> { let client = Client::new(); let runtime = tokio::runtime::Runtime::new().context("Failed to create tokio runtime")?; - - let settings = EnigoSettings { - ..Default::default() - }; - let mut enigo = Enigo::new(&settings).context("Failed to create enigo")?; + let mut enigo = + Enigo::new(&EnigoSettings::default()).context("Failed to create enigo")?; let tmp_dir = tempdir()?; // println!("{:?}", tmp_dir.path()); From c8050c22ec3d2fe8f23ace2962d60bc6df8a8668 Mon Sep 17 00:00:00 2001 From: sloganking Date: Sun, 19 Nov 2023 15:36:30 -0500 Subject: [PATCH 3/5] =?UTF-8?q?=E2=9A=A1=EF=B8=8Ffix=20hitching?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fixes hitching happening whenever doing the transcription API call. --- Cargo.toml | 1 + src/main.rs | 169 ++++++++++++++++++++++++++++++---------------------- 2 files changed, 99 insertions(+), 71 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1efb786..7022bb6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ cpal = "0.15.2" dotenvy = "0.15.7" # enigo = "0.1.3" enigo = { git = "https://github.com/enigo-rs/enigo.git", rev = "96d5fd2b461fbe61e44473437356b2e2905dbc50" } +flume = "0.11.0" hound = "3.5.1" rdev = "0.5.3" tempfile = "3.8.0" diff --git a/src/main.rs b/src/main.rs index 3c2e82e..a64a9ff 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,6 +5,7 @@ use enigo::{Enigo, EnigoSettings, KeyboardControllable}; use std::env; use tempfile::tempdir; mod transcribe; +use std::thread; use transcribe::trans; mod record; use async_std::future; @@ -344,98 +345,124 @@ fn main() -> Result<(), Box> { return Ok(()); } - let mut recorder = rec::Recorder::new(); - let client = Client::new(); - let runtime = - tokio::runtime::Runtime::new().context("Failed to create tokio runtime")?; - let mut enigo = - Enigo::new(&EnigoSettings::default()).context("Failed to create enigo")?; + let (tx, rx): (flume::Sender, flume::Receiver) = flume::unbounded(); - let tmp_dir = tempdir()?; - // println!("{:?}", tmp_dir.path()); - let voice_tmp_path = tmp_dir.path().join("voice_tmp.wav"); + // create key handler thread + thread::spawn(move || { + let mut recorder = rec::Recorder::new(); + let client = Client::new(); + let runtime = tokio::runtime::Runtime::new() + .context("Failed to create tokio runtime") + .unwrap(); + let mut enigo = Enigo::new(&EnigoSettings::default()) + .context("Failed to create enigo") + .unwrap(); - let mut recording_start = std::time::SystemTime::now(); - let mut key_pressed = false; + let tmp_dir = tempdir().unwrap(); + // println!("{:?}", tmp_dir.path()); + let voice_tmp_path = tmp_dir.path().join("voice_tmp.wav"); - let callback = move |event: Event| { + let mut recording_start = std::time::SystemTime::now(); + let mut key_pressed = false; let key_to_check = ptt_key; - match event.event_type { - rdev::EventType::KeyPress(key) => { - if key == key_to_check && !key_pressed { - key_pressed = true; - // handle key press - recording_start = std::time::SystemTime::now(); - match recorder.start_recording(&voice_tmp_path, Some(&opt.device)) { - Ok(_) => (), - Err(err) => println!("Error: Failed to start recording: {:?}", err), - } - } - } - rdev::EventType::KeyRelease(key) => { - if key == key_to_check && key_pressed { - key_pressed = false; - // handle key release - // get elapsed time since recording started - let elapsed = match recording_start.elapsed() { - Ok(elapsed) => elapsed, - Err(err) => { - println!( - "Error: Failed to get elapsed recording time. Skipping transcription: \n\n{}",err - ); - return; - } - }; - match recorder.stop_recording() { - Ok(_) => (), - Err(err) => { - println!("Error: Failed to stop recording: {:?}", err); - return; + for event in rx.iter() { + // println!("Received: {:?}", event); + match event.event_type { + rdev::EventType::KeyPress(key) => { + if key == key_to_check && !key_pressed { + key_pressed = true; + // handle key press + recording_start = std::time::SystemTime::now(); + match recorder.start_recording(&voice_tmp_path, Some(&opt.device)) { + Ok(_) => (), + Err(err) => { + println!("Error: Failed to start recording: {:?}", err) + } } } + } + rdev::EventType::KeyRelease(key) => { + if key == key_to_check && key_pressed { + key_pressed = false; + // handle key release - // Whisper API can't handle less than 0.1 seconds of audio. - // So we'll only transcribe if the recording is longer than 0.2 seconds. - if elapsed.as_secs_f32() > 0.2 { - let transcription_result = match runtime.block_on(future::timeout( - Duration::from_secs(10), - trans::transcribe(&client, &voice_tmp_path), - )) { - Ok(transcription_result) => transcription_result, + // get elapsed time since recording started + let elapsed = match recording_start.elapsed() { + Ok(elapsed) => elapsed, Err(err) => { - println!("Error: Failed to transcribe audio due to timeout: {:?}", err); - return; + println!("Error: Failed to get elapsed recording time. Skipping transcription: \n\n{}",err); + continue; } }; - - let mut transcription = match transcription_result { - Ok(transcription) => transcription, + match recorder.stop_recording() { + Ok(_) => (), Err(err) => { - println!("Error: Failed to transcribe audio: {:?}", err); - return; + println!("Error: Failed to stop recording: {:?}", err); + continue; } - }; + } + + // future::timeout( + // Duration::from_secs(10), + // trans::transcribe(&client, &voice_tmp_path), + // ) + // .await; + + // Whisper API can't handle less than 0.1 seconds of audio. + // So we'll only transcribe if the recording is longer than 0.2 seconds. + if elapsed.as_secs_f32() > 0.2 { + let transcription_result = match runtime.block_on( + future::timeout( + Duration::from_secs(10), + trans::transcribe(&client, &voice_tmp_path), + ), + ) { + Ok(transcription_result) => transcription_result, + Err(err) => { + println!("Error: Failed to transcribe audio due to timeout: {:?}", err); + continue; + } + }; - if let Some(last_char) = transcription.chars().last() { - if ['.', '?', '!', ','].contains(&last_char) { - transcription.push(' '); + let mut transcription = match transcription_result { + Ok(transcription) => transcription, + Err(err) => { + println!( + "Error: Failed to transcribe audio: {:?}", + err + ); + continue; + } + }; + + if let Some(last_char) = transcription.chars().last() { + if ['.', '?', '!', ','].contains(&last_char) { + transcription.push(' '); + } } - } - enigo.key_sequence(&transcription); - } else { - println!("Recording too short"); + enigo.key_sequence(&transcription); + } else { + println!("Recording too short"); + } } } + _ => (), } - _ => (), } - }; + }); - // This will block. - if let Err(error) = listen(callback) { - println!("Error: {:?}", error) + // Have this main thread recieve events and send them to the key handler thread + { + let callback = move |event: Event| { + tx.send(event).unwrap(); + }; + + // This will block. + if let Err(error) = listen(callback) { + println!("Error: {:?}", error) + } } Ok(()) From 1e634a8c71ab9c5216166da35865885c51b3aaa5 Mon Sep 17 00:00:00 2001 From: sloganking Date: Thu, 4 Jan 2024 21:25:32 -0500 Subject: [PATCH 4/5] =?UTF-8?q?=F0=9F=93=9Dprintln=20if=20no=20transcripti?= =?UTF-8?q?on?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main.rs b/src/main.rs index a64a9ff..38b0e7d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -442,6 +442,10 @@ fn main() -> Result<(), Box> { } } + if transcription.is_empty() { + println!("No transcription"); + } + enigo.key_sequence(&transcription); } else { println!("Recording too short"); From cc4c95e97cfe26e23c18bd1ecd3acfc9a43cbdf2 Mon Sep 17 00:00:00 2001 From: sloganking Date: Thu, 4 Jan 2024 21:44:48 -0500 Subject: [PATCH 5/5] =?UTF-8?q?=E2=AC=87=EF=B8=8FUse=20released=20enigo=20?= =?UTF-8?q?version?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I want to use a released version so I can publish a new version of this crate to crates.io --- Cargo.toml | 3 +-- src/main.rs | 6 ++---- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 7022bb6..ba7aacd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,8 +15,7 @@ async-std = "1.12.0" clap = { version = "4.4.6", features = ["derive"] } cpal = "0.15.2" dotenvy = "0.15.7" -# enigo = "0.1.3" -enigo = { git = "https://github.com/enigo-rs/enigo.git", rev = "96d5fd2b461fbe61e44473437356b2e2905dbc50" } +enigo = "0.1.3" flume = "0.11.0" hound = "3.5.1" rdev = "0.5.3" diff --git a/src/main.rs b/src/main.rs index 38b0e7d..1a51b4e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,7 @@ use anyhow::Context; use async_openai::Client; use dotenvy::dotenv; -use enigo::{Enigo, EnigoSettings, KeyboardControllable}; +use enigo::{Enigo, KeyboardControllable}; use std::env; use tempfile::tempdir; mod transcribe; @@ -354,9 +354,7 @@ fn main() -> Result<(), Box> { let runtime = tokio::runtime::Runtime::new() .context("Failed to create tokio runtime") .unwrap(); - let mut enigo = Enigo::new(&EnigoSettings::default()) - .context("Failed to create enigo") - .unwrap(); + let mut enigo = Enigo::new(); let tmp_dir = tempdir().unwrap(); // println!("{:?}", tmp_dir.path());