use std::collections::HashMap;
use log::{error, warn};
use reqwest::header::HeaderMap;
use crate::azure::{
endpoint::{
request_get_endpoint, request_post_endpoint, request_post_endpoint_ssml,
SpeechServiceEndpoint,
},
types::{
common::{MicrosoftOutputFormat, ResponseExpectation, ResponseType},
speech::{
entity::EntityReference,
file::{File, FileKind, FileType, PaginatedFiles},
filter::FilterOperator,
health::ServiceHealth,
transcription::{
Status, Transcription, TranscriptionProperties, TranscriptionReport,
TranscriptionResult,
},
ErrorResponse,
},
tts::Voice,
SSML,
},
Locale,
};
pub struct Speech {
ssml: SSML,
output_format: MicrosoftOutputFormat,
}
impl Default for Speech {
fn default() -> Self {
Self {
ssml: SSML::default(),
output_format: MicrosoftOutputFormat::Audio_24khz_48kbitrate_Mono_Mp3,
}
}
}
impl From<SSML> for Speech {
fn from(value: SSML) -> Self {
Self {
ssml: value,
output_format: MicrosoftOutputFormat::Audio_24khz_48kbitrate_Mono_Mp3,
}
}
}
impl Speech {
pub fn new_transcription(display_name: String) -> Transcription {
Transcription::default().display_name(display_name)
}
pub fn format(self, f: MicrosoftOutputFormat) -> Self {
Self {
output_format: f,
..self
}
}
pub fn ssml(self, ssml: SSML) -> Self {
Self { ssml, ..self }
}
pub async fn voice_list() -> Result<Vec<Voice>, Box<dyn std::error::Error>> {
let text =
request_get_endpoint(&SpeechServiceEndpoint::Get_List_of_Voices, None, None).await?;
match serde_json::from_str::<Vec<Voice>>(&text) {
Ok(voices) => Ok(voices),
Err(e) => {
warn!(target: "azure", "Error parsing response: {:?}", e);
Err("Unable to parse voice list, check log for details".into())
}
}
}
pub async fn health_check() -> Result<ServiceHealth, Box<dyn std::error::Error>> {
let text = request_get_endpoint(
&SpeechServiceEndpoint::Get_Speech_to_Text_Health_Status_v3_1,
None,
None,
)
.await?;
match serde_json::from_str::<ServiceHealth>(&text) {
Ok(status) => Ok(status),
Err(e) => {
warn!(target: "azure", "Error parsing response: {:?}", e);
Err("Unable to parse health status of speech cognitive services, check log for details".into())
}
}
}
pub async fn text_to_speech(self) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
let mut headers = HeaderMap::new();
headers.insert("X-Microsoft-OutputFormat", self.output_format.into());
match request_post_endpoint_ssml(
&SpeechServiceEndpoint::Post_Text_to_Speech_v1,
self.ssml,
ResponseExpectation::Bytes,
Some(headers),
)
.await
{
Ok(ResponseType::Bytes(bytes)) => Ok(bytes),
Err(e) => Err(e),
_ => Err("Incorrect response type".into()),
}
}
pub async fn tts(self) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
Ok(self.text_to_speech().await?)
}
}
impl Transcription {
pub fn skip(self, skip: usize) -> Self {
Self {
skip: Some(skip),
..self
}
}
pub fn top(self, top: usize) -> Self {
Self {
top: Some(top),
..self
}
}
pub fn filter(self, filter: FilterOperator) -> Self {
Self {
filter: Some(filter),
..self
}
}
pub fn sas_validity_in_seconds(self, sec: u32) -> Self {
Self {
sas_validity_in_seconds: Some(sec),
..self
}
}
pub fn model(self, model: String) -> Self {
Self {
model: Some(EntityReference::from(model)),
..self
}
}
pub fn content_container_url(self, url: String) -> Self {
Self {
content_container_url: Some(url),
..self
}
}
pub fn content_urls(self, urls: Vec<String>) -> Self {
Self {
content_urls: Some(urls),
..self
}
}
pub fn project(self, project: String) -> Self {
Self {
project: Some(EntityReference::from(project)),
..self
}
}
pub fn set_self(self, _self: String) -> Self {
Self {
_self: Some(_self),
..self
}
}
pub fn display_name(self, display_name: String) -> Self {
Self {
display_name,
..self
}
}
pub fn locale(self, locale: Locale) -> Self {
Self { locale, ..self }
}
pub fn properties<F>(self, mut f: F) -> Self
where
F: FnMut(Option<TranscriptionProperties>) -> TranscriptionProperties,
{
Self {
properties: Some(f(self.properties)),
..self
}
}
pub async fn models(self) -> Result<(), Box<dyn std::error::Error>> {
todo!("Test with custom models");
}
pub async fn create(&self) -> Result<Transcription, Box<dyn std::error::Error>> {
return if let ResponseType::Text(text) = request_post_endpoint(
&SpeechServiceEndpoint::Post_Create_Transcription_v3_1,
self.clone(),
ResponseExpectation::Text,
None,
)
.await?
{
return match serde_json::from_str::<Transcription>(&text) {
Ok(trans) => Ok(trans),
Err(e) => {
warn!(target: "azure", "Unable to parse transcription creation result: `{:#?}`", e);
match serde_json::from_str::<ErrorResponse>(&text) {
Ok(e) => {
error!(target: "azure", "Error from Azure: `{:?}`", e);
Err(Box::new(e))
}
Err(e) => {
error!(target: "azure", "Unable to parse error response: `{:?}`", e);
Err(Box::new(e))
}
}
}
};
} else {
Err("Unable to load output from Azure speech service endpoint".into())
};
}
pub async fn status(&mut self) -> Result<Transcription, Box<dyn std::error::Error>> {
let text = request_get_endpoint(
&SpeechServiceEndpoint::Get_Transcription_v3_1,
None,
Some(self.transcription_id().unwrap()),
)
.await?;
return match serde_json::from_str::<Transcription>(&text) {
Ok(trans) => {
self.last_action_date_time = trans.last_action_date_time.clone();
self.status = trans.status.clone();
Ok(trans)
}
Err(e) => {
warn!(target: "azure", "Unable to parse transcription status result: `{:#?}`", e);
match serde_json::from_str::<ErrorResponse>(&text) {
Ok(e) => {
error!(target: "azure", "Error from Azure: `{:?}`", e);
Err(Box::new(e))
}
Err(e) => {
error!(target: "azure", "Unable to parse error response: `{:?}`", e);
Err(Box::new(e))
}
}
}
};
}
pub async fn files(&self) -> Result<PaginatedFiles, Box<dyn std::error::Error>> {
if let None = self.status.clone() {
return Err("You should submit the create request first.".into());
} else {
match self.status.clone().unwrap() {
Status::Succeeded => (),
Status::Failed => {
return Err("The transcription failed, thus no results available.".into())
}
Status::NotStarted => return Err("Please wait for transcription to start.".into()),
Status::Running => return Err("Please wait until results available.".into()),
}
}
let mut params = HashMap::<String, String>::new();
if let Some(sas) = self.sas_validity_in_seconds.clone() {
params.insert("sasValidityInSeconds".into(), sas.to_string());
}
if let Some(skip) = self.skip.clone() {
params.insert("skip".into(), skip.to_string());
}
if let Some(top) = self.top.clone() {
params.insert("top".into(), top.to_string());
}
if let Some(filter) = self.filter.clone() {
params.insert("filter".into(), filter.to_string());
}
let text = request_get_endpoint(
&SpeechServiceEndpoint::Get_Transcription_Files_v3_1,
Some(params),
Some(format!("{}/files", self.transcription_id().unwrap())),
)
.await?;
return match serde_json::from_str::<PaginatedFiles>(&text) {
Ok(files) => Ok(files),
Err(e) => {
warn!(target: "azure", "Unable to parse transcription files list result: `{:#?}`", e);
match serde_json::from_str::<ErrorResponse>(&text) {
Ok(e) => {
error!(target: "azure", "Error from Azure: `{:?}`", e);
Err(Box::new(e))
}
Err(e) => {
error!(target: "azure", "Unable to parse error response: `{:?}`", e);
Err(Box::new(e))
}
}
}
};
}
}
impl PaginatedFiles {
pub async fn more(self) -> Result<Option<PaginatedFiles>, Box<dyn std::error::Error>> {
if let Some(_next_page_url) = self.next_link {
todo!("Unimplemented method");
} else {
Ok(None)
}
}
pub async fn report(&self) -> Result<Option<TranscriptionReport>, Box<dyn std::error::Error>> {
if self.values.len() == 0 {
return Ok(None);
}
let mut report = self
.values
.iter()
.filter(|file| file.kind == FileKind::TranscriptionReport);
if let Some(report) = report.next().clone() {
let text = request_get_endpoint(
&SpeechServiceEndpoint::None,
None,
Some(report.links.content_url.clone()),
)
.await?;
return match serde_json::from_str::<TranscriptionReport>(&text) {
Ok(report) => Ok(Some(report)),
Err(e) => {
warn!(target: "azure", "Unable to parse transcription result file: `{:#?}`", e);
match serde_json::from_str::<ErrorResponse>(&text) {
Ok(e) => {
error!(target: "azure", "Error from Azure: `{:?}`", e);
Err(Box::new(e))
}
Err(e) => {
error!(target: "azure", "Unable to parse error response: `{:?}`", e);
Err(Box::new(e))
}
}
}
};
} else {
Ok(None)
}
}
}
impl File {
pub async fn file(&self) -> Result<File, Box<dyn std::error::Error>> {
let (trans_id, file_id) = self.file_id()?;
let mut params = HashMap::<String, String>::new();
if let Some(sas) = self.sas_validity_in_seconds.clone() {
params.insert("sasValidityInSeconds".into(), sas.to_string());
}
let text = request_get_endpoint(
&SpeechServiceEndpoint::Get_Transcription_File_v3_1,
Some(params),
Some(format!("{}/files/{}", trans_id, file_id)),
)
.await?;
return match serde_json::from_str::<File>(&text) {
Ok(file) => Ok(file),
Err(e) => {
warn!(target: "azure", "Unable to parse transcription result file: `{:#?}`", e);
match serde_json::from_str::<ErrorResponse>(&text) {
Ok(e) => {
error!(target: "azure", "Error from Azure: `{:?}`", e);
Err(Box::new(e))
}
Err(e) => {
error!(target: "azure", "Unable to parse error response: `{:?}`", e);
Err(Box::new(e))
}
}
}
};
}
pub async fn details(&self) -> Result<FileType, Box<dyn std::error::Error>> {
let text = request_get_endpoint(
&SpeechServiceEndpoint::None,
None,
Some(self.links.content_url.clone()),
)
.await?;
return match self.kind {
FileKind::TranscriptionReport => {
match serde_json::from_str::<TranscriptionReport>(&text) {
Ok(report) => Ok(FileType::TranscriptionReport(report)),
Err(e) => {
warn!(target: "azure", "Unable to parse transcription result file: `{:#?}`", e);
match serde_json::from_str::<ErrorResponse>(&text) {
Ok(e) => {
error!(target: "azure", "Error from Azure: `{:?}`", e);
Err(Box::new(e))
}
Err(e) => {
error!(target: "azure", "Unable to parse error response: `{:?}`", e);
Err(Box::new(e))
}
}
}
}
}
FileKind::Transcription => match serde_json::from_str::<TranscriptionResult>(&text) {
Ok(report) => Ok(FileType::Transcription(report)),
Err(e) => {
warn!(target: "azure", "Unable to parse transcription result file: `{:#?}`", e);
match serde_json::from_str::<ErrorResponse>(&text) {
Ok(e) => {
error!(target: "azure", "Error from Azure: `{:?}`", e);
Err(Box::new(e))
}
Err(e) => {
error!(target: "azure", "Unable to parse error response: `{:?}`", e);
Err(Box::new(e))
}
}
}
},
_ => {
todo!(
"Not yet supported file type `{}`",
Into::<String>::into(self.kind.clone())
);
}
};
}
}