commit 8bbb487c4bbd66802493c1d8b203fa06362fae64 Author: Neur0toxine Date: Fri May 24 23:44:26 2024 +0300 initial commit diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..e40fd92 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,15 @@ +[*.rs] +indent_size = 4 +indent_style = space +end_of_file_newline = lf +trim_trailing_whitespace = true +insert_spaces = true +match_opening_closing_characters = true + +[*.toml] +indent_size = 2 +indent_style = space +end_of_file_newline = lf +trim_trailing_whitespace = true +insert_spaces = false +match_opening_closing_characters = false diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9d25fac --- /dev/null +++ b/.gitignore @@ -0,0 +1,28 @@ +# Editor generated files +*.rs~ +*.toml~ +Cargo.lock +target/ + +# Compiler output files +out/ + +# Build artifacts +*.exe +*.so +*.dylib + +# IDE project files +.idea/ +.cproject/ +.project/ + +# Visual Studio Code +.vscode/ + +# Other editor settings +.idea/private/ +.idea/workspace.xml + +# macOS specific files +.DS_Store diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..8adfcac --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "atranscoder-rpc" +version = "0.1.0" +edition = "2021" + +[dependencies] +axum = { version = "0.7.5", features = ["macros", "multipart"] } +axum_typed_multipart = "0.11.1" +serde = { version = "1.0.202", features = ["derive"] } +tokio = { version = "1.37.0", features = ["rt-multi-thread"] } +tempfile = "3.10.1" +uuid = { version = "1.8.0", features = ["v4"] } +tracing = "0.1.37" +tracing-subscriber = { version = "0.3.16", features = ["env-filter"] } +tower-http = { version = "0.5.2", features = ["trace"] } diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..86bc535 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,25 @@ +The MIT License (MIT) +===================== + +Copyright © `2024` `Pavel Kovalenko` + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the “Software”), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..bacf750 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# atranscoder-rpc + +Audio transcoder with simple HTTP API. Work in progress, nothing is implemented yet... \ No newline at end of file diff --git a/src/dto.rs b/src/dto.rs new file mode 100644 index 0000000..963c2bf --- /dev/null +++ b/src/dto.rs @@ -0,0 +1,26 @@ +use axum_typed_multipart::{FieldData, TryFromMultipart}; +use serde::{Deserialize, Serialize}; +use tempfile::NamedTempFile; + +#[derive(Serialize, Deserialize)] +pub struct Error { + pub error: String, +} + +#[derive(Serialize, Deserialize)] +pub struct TaskResponse { + pub id: Option, + pub error: Option, +} + +#[derive(TryFromMultipart)] +#[try_from_multipart(rename_all = "camelCase")] +pub struct Task { + pub codec: String, + pub bit_rate: usize, + pub max_bit_rate: usize, + pub channel_layout: String, + + #[form_data(limit = "25MiB")] + pub file: FieldData, +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..bd4e847 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,17 @@ +use crate::server::serve; +use std::env; +use tracing_subscriber::EnvFilter; + +mod dto; +mod server; + +#[tokio::main] +async fn main() { + tracing_subscriber::fmt() + .with_env_filter(EnvFilter::from_env("LOG_LEVEL")) + .init(); + + let addr = env::var("LISTEN").unwrap_or_else(|_| "0.0.0.0:8090".to_string()); + + serve(&addr).await.expect("Cannot bind the addr") +} diff --git a/src/server.rs b/src/server.rs new file mode 100644 index 0000000..168c529 --- /dev/null +++ b/src/server.rs @@ -0,0 +1,67 @@ +use axum::extract::DefaultBodyLimit; +use axum::http::StatusCode; +use axum::routing::{get, post}; +use axum::{Json, Router}; +use axum_typed_multipart::TypedMultipart; +use std::path::Path; +use tokio::net::TcpListener; +use tower_http::trace::TraceLayer; +use uuid::Uuid; + +use crate::dto; +use crate::dto::{Task, TaskResponse}; + +const CONTENT_LENGTH_LIMIT: usize = 30 * 1024 * 1024; + +pub async fn serve(addr: &str) -> std::io::Result<()> { + let app = Router::new() + .route( + "/enqueue", + post(enqueue_file).layer(DefaultBodyLimit::max(CONTENT_LENGTH_LIMIT)), + ) + .route("/download", get(download_file)) + .layer(TraceLayer::new_for_http()); + + tracing::info!("listening on {addr}"); + let listener = match TcpListener::bind(addr).await { + Ok(listen) => listen, + Err(err) => return Err(err), + }; + axum::serve(listener, app).await +} + +async fn enqueue_file( + TypedMultipart(Task { file, .. }): TypedMultipart, +) -> (StatusCode, Json) { + let task_id = Uuid::new_v4().to_string(); + let path = Path::new( + std::env::temp_dir() + .to_str() + .expect("Cannot get temporary directory"), + ) + .join(format!("{}.bin", task_id)); + + match file.contents.persist(path) { + Ok(_) => ( + StatusCode::CREATED, + Json::from(TaskResponse { + id: Option::from(task_id), + error: None, + }), + ), + Err(_) => ( + StatusCode::CREATED, + Json::from(TaskResponse { + id: Option::from(task_id), + error: Some(String::from("Cannot save the file")), + }), + ), + } +} + +async fn download_file() -> (StatusCode, Json) { + let resp = dto::Error { + error: String::from("Not implemented yet."), + }; + (StatusCode::INTERNAL_SERVER_ERROR, Json(resp)) +}