Skip to content

Commit 2efdedd

Browse files
authored
Add phrase list support for speech recognizer (#31)
* Add phrase list support. * Address Copilot code review feedbacks. * Refactor GrammarPhrase into its own module Moved the GrammarPhrase struct and its implementation from phrase_list_grammar.rs to a new grammar_phrase.rs module for better separation of concerns and code organization. Updated imports to use the new module.
1 parent e10a214 commit 2efdedd

File tree

5 files changed

+133
-1
lines changed

5 files changed

+133
-1
lines changed

src/speech.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ mod audio_data_stream;
33
mod auto_detect_source_language_config;
44
mod cancellation_details;
55
mod embedded_speech_config;
6+
mod grammar_phrase;
67
mod keyword_recognition_model;
8+
mod phrase_list_grammar;
79
mod recognition_event;
810
mod session_event;
911
mod source_language_config;
@@ -28,6 +30,7 @@ pub use self::auto_detect_source_language_config::AutoDetectSourceLanguageConfig
2830
pub use self::cancellation_details::CancellationDetails;
2931
pub use self::embedded_speech_config::EmbeddedSpeechConfig;
3032
pub use self::keyword_recognition_model::KeywordRecognitionModel;
33+
pub use self::phrase_list_grammar::PhraseListGrammar;
3134
pub use self::recognition_event::RecognitionEvent;
3235
pub use self::session_event::SessionEvent;
3336
pub use self::source_language_config::SourceLanguageConfig;

src/speech/grammar_phrase.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
use std::{ffi::CString, mem::MaybeUninit};
2+
3+
use crate::{
4+
error::{convert_err, Result},
5+
ffi::{
6+
grammar_phrase_create_from_text, grammar_phrase_handle_release, SmartHandle,
7+
SPXPHRASEHANDLE,
8+
},
9+
};
10+
11+
/// Represents base class grammar for customizing speech recognition. \
12+
/// Added in version 1.5.0.
13+
#[derive(Debug)]
14+
pub(crate) struct GrammarPhrase {
15+
pub handle: SmartHandle<SPXPHRASEHANDLE>,
16+
}
17+
18+
impl GrammarPhrase {
19+
/// Creates a grammar phrase using the specified phrase text.
20+
/// # Arguments
21+
/// * `text` - The text representing a phrase that may be spoken by the user.
22+
pub(crate) fn from_text(text: impl AsRef<str>) -> Result<GrammarPhrase> {
23+
unsafe {
24+
let mut handle: MaybeUninit<SPXPHRASEHANDLE> = MaybeUninit::uninit();
25+
let c_text = CString::new(text.as_ref())?;
26+
let ret = grammar_phrase_create_from_text(handle.as_mut_ptr(), c_text.as_ptr());
27+
convert_err(ret, "GrammarPhrase::grammar_phrase_from_text error")?;
28+
Ok(GrammarPhrase {
29+
handle: SmartHandle::create(
30+
"GrammarPhrase",
31+
handle.assume_init(),
32+
grammar_phrase_handle_release,
33+
),
34+
})
35+
}
36+
}
37+
}

src/speech/phrase_list_grammar.rs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
use std::ffi::CString;
2+
use std::mem::MaybeUninit;
3+
4+
use crate::error::{convert_err, Result};
5+
use crate::ffi::{
6+
grammar_handle_release, phrase_list_grammar_add_phrase, phrase_list_grammar_clear,
7+
phrase_list_grammar_from_recognizer_by_name, SmartHandle, SPXGRAMMARHANDLE,
8+
};
9+
use crate::speech::grammar_phrase::GrammarPhrase;
10+
use crate::speech::SpeechRecognizer;
11+
12+
/// Represents a phrase list grammar for dynamic grammar scenarios. \
13+
/// Added in version 1.5.0.
14+
#[derive(Debug)]
15+
pub struct PhraseListGrammar {
16+
handle: SmartHandle<SPXGRAMMARHANDLE>,
17+
}
18+
19+
impl PhraseListGrammar {
20+
/// Creates a phrase list grammar for the specified recognizer.
21+
pub fn from_recognizer(recognizer: &SpeechRecognizer) -> Result<PhraseListGrammar> {
22+
unsafe {
23+
let c_name = CString::new("")?;
24+
let mut handle: MaybeUninit<SPXGRAMMARHANDLE> = MaybeUninit::uninit();
25+
let ret = phrase_list_grammar_from_recognizer_by_name(
26+
handle.as_mut_ptr(),
27+
recognizer.handle.inner(),
28+
c_name.as_ptr(),
29+
);
30+
convert_err(ret, "PhraseListGrammar::from_recognizer error")?;
31+
Ok(PhraseListGrammar {
32+
handle: SmartHandle::create(
33+
"PhraseListGrammar",
34+
handle.assume_init(),
35+
grammar_handle_release,
36+
),
37+
})
38+
}
39+
}
40+
41+
/// AddPhrase adds a simple phrase that may be spoken by the user.
42+
pub fn add_phrase(&self, text: impl AsRef<str>) -> Result<()> {
43+
let grammar_phrase = GrammarPhrase::from_text(text)?;
44+
let ret: usize = unsafe {
45+
phrase_list_grammar_add_phrase(self.handle.inner(), grammar_phrase.handle.inner())
46+
};
47+
convert_err(ret, "PhraseListGrammar::add_phrase error")?;
48+
Ok(())
49+
}
50+
51+
/// Clears all phrases from the phrase list grammar.
52+
pub fn clear(&self) -> Result<()> {
53+
let ret = unsafe { phrase_list_grammar_clear(self.handle.inner()) };
54+
convert_err(ret, "PhraseListGrammar::clear error")?;
55+
Ok(())
56+
}
57+
}

src/speech/speech_recognizer.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ use std::os::raw::c_void;
3232

3333
/// SpeechRecognizer struct holds functionality for speech-to-text recognition.
3434
pub struct SpeechRecognizer {
35-
handle: SmartHandle<SPXRECOHANDLE>,
35+
pub(crate) handle: SmartHandle<SPXRECOHANDLE>,
3636
properties: PropertyCollection,
3737
handle_async_start_continuous: Option<SmartHandle<SPXASYNCHANDLE>>,
3838
handle_async_stop_continuous: Option<SmartHandle<SPXASYNCHANDLE>>,

tests/integration_test.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use cognitive_services_speech_sdk_rs as msspeech;
2+
use cognitive_services_speech_sdk_rs::speech::PhraseListGrammar;
23
use log::*;
34
use rust_embed::Embed;
45
use std::env;
@@ -111,3 +112,37 @@ async fn text_to_speech() {
111112
}
112113
}
113114
}
115+
116+
#[tokio::test]
117+
async fn phrase_list_test() {
118+
let current_dir = std::env::current_dir().expect("Failed to get current directory");
119+
let mut file_path = PathBuf::from(&current_dir);
120+
file_path.push("examples");
121+
file_path.push("sample_files");
122+
file_path.push("peloozoid.wav");
123+
let file_path_str = &file_path.into_os_string().into_string().unwrap();
124+
let audio_config = msspeech::audio::AudioConfig::from_wav_file_input(file_path_str).unwrap();
125+
126+
// before running this text export the below listed variables. Example:
127+
// export MSSubscriptionKey=32...
128+
// export MSServiceRegion=westeurope
129+
let speech_config = msspeech::speech::SpeechConfig::from_subscription(
130+
env::var("MSSubscriptionKey").unwrap(),
131+
env::var("MSServiceRegion").unwrap_or("westeurope".to_string()),
132+
)
133+
.unwrap();
134+
135+
let mut speech_recognizer =
136+
msspeech::speech::SpeechRecognizer::from_config(speech_config, audio_config).unwrap();
137+
138+
let grammar = PhraseListGrammar::from_recognizer(&speech_recognizer).unwrap();
139+
grammar.add_phrase("peloozoid").unwrap();
140+
141+
let result = speech_recognizer.recognize_once_async().await.unwrap();
142+
// "That shape is a Peloozoid."
143+
println!(
144+
"[phrase_list_test] got recognition result: {:?}",
145+
result.text
146+
);
147+
assert!(result.text.to_lowercase().contains("peloozoid"));
148+
}

0 commit comments

Comments
 (0)