diff options
author | Santo Cariotti <santo@dcariotti.me> | 2025-01-17 20:41:20 +0100 |
---|---|---|
committer | Santo Cariotti <santo@dcariotti.me> | 2025-01-17 20:41:20 +0100 |
commit | a56ecb83c70e57144a4b01153aab21656c97e366 (patch) | |
tree | 6951efbfa4e89969452b097acb7531d6162de6a6 | |
parent | c6dcfcfe833c28fec288918965fc31cd86199fb1 (diff) |
Sound as binary string
-rw-r--r-- | Dockerfile | 3 | ||||
-rw-r--r-- | README.md | 2 | ||||
-rw-r--r-- | assets/sounds/readme.md | 1 | ||||
-rw-r--r-- | docker-compose.yml | 1 | ||||
-rw-r--r-- | schema/init.sql | 3 | ||||
-rw-r--r-- | src/audio.rs | 59 | ||||
-rw-r--r-- | src/config.rs | 3 | ||||
-rw-r--r-- | src/graphql/query.rs | 2 | ||||
-rw-r--r-- | src/graphql/types/alert.rs | 98 | ||||
-rw-r--r-- | src/graphql/types/notification.rs | 12 | ||||
-rw-r--r-- | src/main.rs | 3 |
11 files changed, 75 insertions, 112 deletions
@@ -17,9 +17,6 @@ RUN apt-get update && apt-get install -y libssl-dev ca-certificates RUN groupadd -g 999 appuser && \ useradd -r -u 999 -g appuser appuser -RUN mkdir -p /app/assets -RUN chown -R appuser:appuser /app/assets - USER appuser COPY --from=builder /app/target/release/cas /app @@ -32,8 +32,6 @@ Now you set up some env variables: - `UNREALSPEECH_TOKEN`: used by [Unrealspeech](https://unrealspeech.com) for text-to-speach API. -- `AUDIO_PATH`: folder path where to store audios - After that you must copy the `schema/init.sql` file into the database. Now just run the app diff --git a/assets/sounds/readme.md b/assets/sounds/readme.md deleted file mode 100644 index e4ffb4a..0000000 --- a/assets/sounds/readme.md +++ /dev/null @@ -1 +0,0 @@ -Here is the sounds list diff --git a/docker-compose.yml b/docker-compose.yml index 211f18e..9291d91 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -31,7 +31,6 @@ services: - ALLOWED_HOST=${ALLOWED_HOST} - EXPO_ACCESS_TOKEN=${EXPO_ACCESS_TOKEN} - UNREALSPEECH_TOKEN=${UNREALSPEECH_TOKEN} - - AUDIO_PATH=${AUDIO_PATH} depends_on: - postgres diff --git a/schema/init.sql b/schema/init.sql index 68f1f92..ebd428b 100644 --- a/schema/init.sql +++ b/schema/init.sql @@ -34,6 +34,9 @@ CREATE TABLE alerts( text1 text NOT NULL, text2 text NOT NULL, text3 text NOT NULL, + audio1 bytea NOT NULL, + audio2 bytea NOT NULL, + audio3 bytea NOT NULL, reached_users INTEGER DEFAULT 0 NOT NULL, PRIMARY KEY(id), CONSTRAINT fk_users_id diff --git a/src/audio.rs b/src/audio.rs index 07ecdd8..925ccd4 100644 --- a/src/audio.rs +++ b/src/audio.rs @@ -1,17 +1,8 @@ use crate::config::CONFIG; -use axum::{ - extract::Path, - http::{HeaderMap, HeaderName, HeaderValue, StatusCode}, -}; use reqwest::header::AUTHORIZATION; -use std::{ - fs::{self, File}, - io::Write, - path::Path as StdPath, -}; /// Create a new sound from a text -pub async fn tts(text: String, filename: String) -> Result<(), String> { +pub async fn tts(text: &String) -> Result<bytes::Bytes, String> { let url = "https://api.v7.unrealspeech.com/stream"; let api_key = format!("Bearer {}", CONFIG.unrealspeech_token); @@ -34,59 +25,13 @@ pub async fn tts(text: String, filename: String) -> Result<(), String> { .map_err(|e| format!("Error creating new audio: {}", e))?; if response.status().is_success() { - let filepath = format!("{}/{}", CONFIG.audio_path, filename); - if let Some(parent) = StdPath::new(&filepath).parent() { - fs::create_dir_all(parent) - .map_err(|e| format!("Failed to create directories: {}", e))?; - } - - let mut file = - File::create(&filepath).map_err(|e| format!("Failed to create file: {}", e))?; let content = response .bytes() .await .map_err(|e| format!("Failed to get response bytes: {}", e))?; - file.write_all(&content) - .map_err(|e| format!("Failed to write file: {}", e))?; - Ok(()) + Ok(content) } else { Err(format!("Failed to fetch the audio: {}", response.status())) } } - -/// Axum endpoint which shows files -pub async fn show_file( - Path(id): Path<String>, -) -> Result<(HeaderMap, Vec<u8>), (StatusCode, String)> { - let index = id.find('.').unwrap_or(usize::MAX); - let ext_name = if index != usize::MAX { - &id[index + 1..] - } else { - "xxx" - }; - - let mut headers = HeaderMap::new(); - if ["mp3"].contains(&ext_name) { - headers.insert( - HeaderName::from_static("content-type"), - HeaderValue::from_str("audio/mpeg").unwrap(), - ); - } - - let file_name = format!("{}/{}", CONFIG.audio_path, id); - let file_path = StdPath::new(&file_name); - - if !file_path.exists() { - return Err((StatusCode::NOT_FOUND, "File not found".to_string())); - } - - fs::read(file_path) - .map(|file_content| (headers, file_content)) - .map_err(|_| { - ( - StatusCode::INTERNAL_SERVER_ERROR, - "Failed to read file".to_string(), - ) - }) -} diff --git a/src/config.rs b/src/config.rs index 11c46e8..763b852 100644 --- a/src/config.rs +++ b/src/config.rs @@ -22,9 +22,6 @@ pub struct Configuration { /// Token used for text-to-speach API pub unrealspeech_token: String, - - /// Audio folder path - pub audio_path: String, } impl Configuration { diff --git a/src/graphql/query.rs b/src/graphql/query.rs index 63ca49c..ccade65 100644 --- a/src/graphql/query.rs +++ b/src/graphql/query.rs @@ -95,7 +95,7 @@ impl Query { /// -H 'content-type: application/json' /// -d '{"query":"{notifications { /// id, - /// alert { id, userId, createdAt, area, areaLevel2, areaLevel3, text1, text2, text3, reachedUsers }, + /// alert { id, userId, createdAt, area, areaLevel2, areaLevel3, text1, text2, text3, audio1, audio2, audio3, reachedUsers }, /// userId, latitude, longitude, movingActivity, level, seen, createdAt /// }}"}' /// ``` diff --git a/src/graphql/types/alert.rs b/src/graphql/types/alert.rs index 9b71188..1156f3d 100644 --- a/src/graphql/types/alert.rs +++ b/src/graphql/types/alert.rs @@ -29,6 +29,9 @@ pub struct Alert { pub text1: String, pub text2: String, pub text3: String, + pub audio1: Vec<u8>, + pub audio2: Vec<u8>, + pub audio3: Vec<u8>, pub reached_users: i32, } @@ -82,6 +85,9 @@ pub mod query { text1, text2, text3, + audio1, + audio2, + audio3, reached_users FROM alerts WHERE id = $1", @@ -101,6 +107,9 @@ pub mod query { text1, text2, text3, + audio1, + audio2, + audio3, reached_users FROM alerts ORDER BY id DESC @@ -124,6 +133,9 @@ pub mod query { text1: row.get("text1"), text2: row.get("text2"), text3: row.get("text3"), + audio1: row.get("audio1"), + audio2: row.get("audio2"), + audio3: row.get("audio3"), reached_users: row.get("reached_users"), }) .collect(); @@ -190,6 +202,7 @@ pub mod mutations { ST_AsText(ST_Buffer(area::geography, 1000)) as area_level2, ST_AsText(ST_Buffer(area::geography, 2000)) as area_level3, text1, text2, text3, + audio1, audio2, audio3, reached_users FROM alerts WHERE area = {} AND created_at >= NOW() - INTERVAL '10 MINUTE'", polygon @@ -207,6 +220,9 @@ pub mod mutations { text1: row.get("text1"), text2: row.get("text2"), text3: row.get("text3"), + audio1: row.get("audio1"), + audio2: row.get("audio2"), + audio3: row.get("audio3"), reached_users: row.get("reached_users"), }) .collect::<Vec<Alert>>() @@ -215,15 +231,40 @@ pub mod mutations { return Ok(previous_alert); } + let audio1 = match audio::tts(&input.text1).await { + Ok(content) => content, + Err(e) => { + tracing::error!("Error for `{}`: {}", &input.text1, e); + bytes::Bytes::new() + } + }; + + let audio2 = match audio::tts(&input.text2).await { + Ok(content) => content, + Err(e) => { + tracing::error!("Error for `{}`: {}", &input.text2, e); + bytes::Bytes::new() + } + }; + + let audio3 = match audio::tts(&input.text3).await { + Ok(content) => content, + Err(e) => { + tracing::error!("Error for `{}`: {}", &input.text3, e); + bytes::Bytes::new() + } + }; + let insert_query = format!( - "INSERT INTO alerts (user_id, area, text1, text2, text3) - VALUES($1, {}, $2, $3, $4) + "INSERT INTO alerts (user_id, area, text1, text2, text3, audio1, audio2, audio3) + VALUES($1, {}, $2, $3, $4, $5, $6, $7) RETURNING id, user_id, extract(epoch from created_at)::double precision as created_at, ST_AsText(area) as area, ST_AsText(ST_Buffer(area::geography, 1000)) as area_level2, ST_AsText(ST_Buffer(area::geography, 2000)) as area_level3, text1, text2, text3, + audio1, audio2, audio3, reached_users", polygon ); @@ -231,7 +272,15 @@ pub mod mutations { let rows = client .query( &insert_query, - &[&claims.user_id, &input.text1, &input.text2, &input.text3], + &[ + &claims.user_id, + &input.text1, + &input.text2, + &input.text3, + &audio1.to_vec(), + &audio2.to_vec(), + &audio3.to_vec(), + ], ) .await?; let mut alert = rows @@ -246,6 +295,9 @@ pub mod mutations { text1: row.get("text1"), text2: row.get("text2"), text3: row.get("text3"), + audio1: row.get("audio1"), + audio2: row.get("audio2"), + audio3: row.get("audio3"), reached_users: row.get("reached_users"), }) .collect::<Vec<Alert>>() @@ -311,7 +363,7 @@ pub mod mutations { let notification = Notification::insert_db( client, alert.id, - &p, + p, LevelAlert::from_str(level.text).unwrap(), ) .await?; @@ -363,44 +415,6 @@ pub mod mutations { ) .await?; - if let Err(e) = audio::tts( - alert.text1.clone(), - format!("alert-{}-text-1.mp3", alert.id), - ) - .await - { - tracing::error!( - "Error for `{}`: {}", - format!("alert-{}-text-1.mp3", alert.id), - e - ); - } - - if let Err(e) = audio::tts( - alert.text2.clone(), - format!("alert-{}-text-2.mp3", alert.id), - ) - .await - { - tracing::error!( - "Error for `{}`: {}", - format!("alert-{}-text-2.mp3", alert.id), - e - ); - } - if let Err(e) = audio::tts( - alert.text3.clone(), - format!("alert-{}-text-3.mp3", alert.id), - ) - .await - { - tracing::error!( - "Error for `{}`: {}", - format!("alert-{}-text-3.mp3", alert.id), - e - ); - } - Ok(alert) } } diff --git a/src/graphql/types/notification.rs b/src/graphql/types/notification.rs index 21fc1aa..87d478a 100644 --- a/src/graphql/types/notification.rs +++ b/src/graphql/types/notification.rs @@ -186,6 +186,9 @@ pub mod query { a.text1 as alert_text1, a.text2 as alert_text2, a.text3 as alert_text3, + a.audio1 as alert_audio1, + a.audio2 as alert_audio2, + a.audio3 as alert_audio3, a.reached_users as alert_reached_users FROM notifications n JOIN alerts a ON n.alert_id = a.id".to_string(); @@ -242,6 +245,9 @@ pub mod query { text1: row.get("alert_text1"), text2: row.get("alert_text2"), text3: row.get("alert_text3"), + audio1: row.get("alert_audio1"), + audio2: row.get("alert_audio2"), + audio3: row.get("alert_audio3"), reached_users: row.get("alert_reached_users"), }, seen: row.get("seen"), @@ -296,6 +302,9 @@ pub mod mutations { a.text1 as alert_text1, a.text2 as alert_text2, a.text3 as alert_text3, + a.audio1 as alert_audio1, + a.audio2 as alert_audio2, + a.audio3 as alert_audio3, a.reached_users as alert_reached_users FROM notifications n JOIN alerts a ON n.alert_id = a.id @@ -316,6 +325,9 @@ pub mod mutations { text1: row.get("alert_text1"), text2: row.get("alert_text2"), text3: row.get("alert_text3"), + audio1: row.get("alert_audio1"), + audio2: row.get("alert_audio2"), + audio3: row.get("alert_audio3"), reached_users: row.get("alert_reached_users"), }, seen: row.get("seen"), diff --git a/src/main.rs b/src/main.rs index bf8afda..e27f887 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,7 +14,7 @@ use crate::config::CONFIG; use async_graphql::{EmptySubscription, Schema}; use axum::{ http::{header, Method, Request}, - routing::{get, post}, + routing::post, Extension, Router, }; use errors::AppError; @@ -47,7 +47,6 @@ async fn create_app() -> Result<Router, AppError> { .finish(); Ok(Router::new() - .route("/assets/sounds/:id", get(audio::show_file)) .route( "/graphql", post(graphql::routes::graphql_handler).layer(Extension(schema.clone())), |