WIP create a video type inference method and a mapping into a content type.

This commit is contained in:
Florian THIERRY
2026-07-13 23:26:51 +02:00
parent 03dbbb9ac9
commit 850dbca6cf
12 changed files with 354 additions and 28 deletions
+3
View File
@@ -14,6 +14,9 @@ path = "tests/user_service_test.rs"
[dependencies] [dependencies]
lumiere-domain = { path = "../domain" } lumiere-domain = { path = "../domain" }
axum = "0.8.9"
tokio = "1.52.3"
mime = "0.3.17"
bcrypt = "0.19.0" bcrypt = "0.19.0"
chrono = { version = "0.4.44", features = ["serde"] } chrono = { version = "0.4.44", features = ["serde"] }
log = "0.4.29" log = "0.4.29"
+42 -4
View File
@@ -67,12 +67,50 @@ fn sanitize_and_resolve_path(video_path: &str) -> Result<std::path::PathBuf, Sta
Ok(full_path) Ok(full_path)
} }
/// Determine Content-Type based on file extension /// Determine Content-Type by reading file headers (magic numbers)
fn get_content_type(path: &Path) -> &'static str { fn get_content_type(path: &Path) -> &'static str {
use std::io::Read;
// Read first few bytes to detect magic numbers
let mut file = match File::open(path) {
Ok(f) => f,
Err(_) => return "application/octet-stream",
};
let mut buffer = [0u8; 16]; // Read enough bytes to detect common formats
let bytes_read = match file.read(&mut buffer) {
Ok(n) if n > 0 => n,
_ => return "application/octet-stream",
};
// Detect AVI format (RIFF header with AVI)
if bytes_read >= 12 && buffer[0..4] == [82, 73, 70, 70] && buffer[8..12] == [65, 86, 73, 32] {
return "video/x-msvideo";
}
// Detect MKV format (EB ML header)
if bytes_read >= 4 && buffer[0..4] == [0x1A, 0x45, 0xDF, 0xA3] {
return "video/x-matroska"; // Proper MIME type for MKV
}
// Detect MP4/MOV format (ftyp box)
if bytes_read >= 8 && buffer[4..8] == [102, 116, 121, 112] {
return "video/mp4";
}
// Detect WebM format (EB ML header like MKV but with different signature)
if bytes_read >= 8 && buffer[0..4] == [0x1A, 0x45, 0xDF, 0xA3] {
// Check for WebM signature at offset 4
if bytes_read >= 12 && buffer[4..8] == [119, 101, 98, 109] {
return "video/webm";
}
}
// Fallback to extension-based detection for unknown formats
match path.extension().and_then(|ext| ext.to_str()) { match path.extension().and_then(|ext| ext.to_str()) {
Some("avi") => "video/x-msvideo", Some("mp4") | Some("mov") => "video/mp4",
Some("mkv") | Some("mp4") | Some("mov") | Some("webm") => "video/mp4", // Generic video type Some("webm") => "video/webm",
_ => "application/octet-stream", // Fallback for unknown types _ => "application/octet-stream",
} }
} }
+2 -1
View File
@@ -1 +1,2 @@
pub mod service; pub mod service;
//pub mod api;
+9 -1
View File
@@ -1,4 +1,12 @@
pub enum VideoFormat {
AVI,
MKV,
MP4,
WEBM,
UNKNOWN
}
pub struct Video { pub struct Video {
filename: String, filename: String,
format: VideoFormat
} }
+1
View File
@@ -7,6 +7,7 @@ use actix_web::{get, web, HttpResponse, Responder};
use actix_web_httpauth::middleware::HttpAuthentication; use actix_web_httpauth::middleware::HttpAuthentication;
pub mod user; pub mod user;
pub mod video;
#[get("/health")] #[get("/health")]
async fn health() -> impl Responder { async fn health() -> impl Responder {
+1
View File
@@ -0,0 +1 @@
pub mod video_format_content_type_mapper;
@@ -0,0 +1,11 @@
use surveillant_systeme_domain::video::model::VideoFormat;
pub fn map_to_content_type(video_format: &VideoFormat) -> &'static str {
match video_format {
VideoFormat::AVI => "video/x-msvideo",
VideoFormat::MKV => "video/x-matroska",
VideoFormat::MP4 => "video/mp4",
VideoFormat::WEBM => "video/webm",
VideoFormat::UNKNOWN => "application/octet-stream"
}
}
+1
View File
@@ -2,3 +2,4 @@ pub mod common;
pub mod configuration; pub mod configuration;
pub mod schema; pub mod schema;
pub mod user; pub mod user;
pub mod video;
+1
View File
@@ -0,0 +1 @@
pub mod video_format_detector;
@@ -0,0 +1,51 @@
use std::fs::File;
use std::path::Path;
use surveillant_systeme_domain::video::model::VideoFormat;
use surveillant_systeme_domain::video::model::VideoFormat::{AVI, MKV, MP4, UNKNOWN, WEBM};
/// Determine Content-Type by reading file headers (magic numbers)
pub fn get_content_type(path: &Path) -> VideoFormat {
use std::io::Read;
// Read first few bytes to detect magic numbers
let mut file = match File::open(path) {
Ok(f) => f,
Err(_) => return UNKNOWN,
};
let mut buffer = [0u8; 16]; // Read enough bytes to detect common formats
let bytes_read = match file.read(&mut buffer) {
Ok(n) if n > 0 => n,
_ => return UNKNOWN,
};
// Detect AVI format (RIFF header with AVI)
if bytes_read >= 12 && buffer[0..4] == [82, 73, 70, 70] && buffer[8..12] == [65, 86, 73, 32] {
return AVI;
}
// Detect MKV format (EB ML header)
if bytes_read >= 4 && buffer[0..4] == [0x1A, 0x45, 0xDF, 0xA3] {
return MKV; // Proper MIME type for MKV
}
// Detect MP4/MOV format (ftyp box)
if bytes_read >= 8 && buffer[4..8] == [102, 116, 121, 112] {
return MP4;
}
// Detect WebM format (EB ML header like MKV but with different signature)
if bytes_read >= 8 && buffer[0..4] == [0x1A, 0x45, 0xDF, 0xA3] {
// Check for WebM signature at offset 4
if bytes_read >= 12 && buffer[4..8] == [119, 101, 98, 109] {
return WEBM;
}
}
// Fallback to extension-based detection for unknown formats
match path.extension().and_then(|ext| ext.to_str()) {
Some("mp4") | Some("mov") => MP4,
Some("webm") => WEBM,
_ => UNKNOWN,
}
}
+204 -3
View File
@@ -41,7 +41,7 @@ dependencies = [
"foldhash 0.1.5", "foldhash 0.1.5",
"futures-core", "futures-core",
"h2", "h2",
"http", "http 0.2.12",
"httparse", "httparse",
"httpdate", "httpdate",
"itoa", "itoa",
@@ -77,7 +77,7 @@ checksum = "14f8c75c51892f18d9c46150c5ac7beb81c95f78c8b83a634d49f4ca32551fe7"
dependencies = [ dependencies = [
"bytestring", "bytestring",
"cfg-if", "cfg-if",
"http", "http 0.2.12",
"regex", "regex",
"regex-lite", "regex-lite",
"serde", "serde",
@@ -316,6 +316,12 @@ version = "1.0.102"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
[[package]]
name = "atomic-waker"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
[[package]] [[package]]
name = "autocfg" name = "autocfg"
version = "1.5.0" version = "1.5.0"
@@ -344,6 +350,58 @@ dependencies = [
"fs_extra", "fs_extra",
] ]
[[package]]
name = "axum"
version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "31b698c5f9a010f6573133b09e0de5408834d0c82f8d7475a89fc1867a71cd90"
dependencies = [
"axum-core",
"bytes",
"form_urlencoded",
"futures-util",
"http 1.4.2",
"http-body",
"http-body-util",
"hyper",
"hyper-util",
"itoa",
"matchit",
"memchr",
"mime",
"percent-encoding",
"pin-project-lite",
"serde_core",
"serde_json",
"serde_path_to_error",
"serde_urlencoded",
"sync_wrapper",
"tokio",
"tower",
"tower-layer",
"tower-service",
"tracing",
]
[[package]]
name = "axum-core"
version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1"
dependencies = [
"bytes",
"futures-core",
"http 1.4.2",
"http-body",
"http-body-util",
"mime",
"pin-project-lite",
"sync_wrapper",
"tower-layer",
"tower-service",
"tracing",
]
[[package]] [[package]]
name = "base16ct" name = "base16ct"
version = "0.2.0" version = "0.2.0"
@@ -1125,6 +1183,15 @@ version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
[[package]]
name = "futures-channel"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d"
dependencies = [
"futures-core",
]
[[package]] [[package]]
name = "futures-core" name = "futures-core"
version = "0.3.32" version = "0.3.32"
@@ -1225,7 +1292,7 @@ dependencies = [
"futures-core", "futures-core",
"futures-sink", "futures-sink",
"futures-util", "futures-util",
"http", "http 0.2.12",
"indexmap", "indexmap",
"slab", "slab",
"tokio", "tokio",
@@ -1301,6 +1368,39 @@ dependencies = [
"itoa", "itoa",
] ]
[[package]]
name = "http"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425"
dependencies = [
"bytes",
"itoa",
]
[[package]]
name = "http-body"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c"
dependencies = [
"bytes",
"http 1.4.2",
]
[[package]]
name = "http-body-util"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e9f41fd6a08e4d4ec69df65976da761afd5ad5e58a9d4acb46bd1c953a9e3ff2"
dependencies = [
"bytes",
"futures-core",
"http 1.4.2",
"http-body",
"pin-project-lite",
]
[[package]] [[package]]
name = "httparse" name = "httparse"
version = "1.10.1" version = "1.10.1"
@@ -1322,6 +1422,41 @@ dependencies = [
"typenum", "typenum",
] ]
[[package]]
name = "hyper"
version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55281c53a1894c864990125767da440a4e630446785086f52523b20033b74498"
dependencies = [
"atomic-waker",
"bytes",
"futures-channel",
"futures-core",
"http 1.4.2",
"http-body",
"httparse",
"httpdate",
"itoa",
"pin-project-lite",
"smallvec",
"tokio",
]
[[package]]
name = "hyper-util"
version = "0.1.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0"
dependencies = [
"bytes",
"http 1.4.2",
"http-body",
"hyper",
"pin-project-lite",
"tokio",
"tower-service",
]
[[package]] [[package]]
name = "iana-time-zone" name = "iana-time-zone"
version = "0.1.65" version = "0.1.65"
@@ -1674,12 +1809,15 @@ dependencies = [
name = "lumiere-application" name = "lumiere-application"
version = "1.0.0" version = "1.0.0"
dependencies = [ dependencies = [
"axum",
"bcrypt", "bcrypt",
"chrono", "chrono",
"jsonwebtoken", "jsonwebtoken",
"log", "log",
"lumiere-domain", "lumiere-domain",
"mime",
"rand 0.10.1", "rand 0.10.1",
"tokio",
"uuid", "uuid",
] ]
@@ -1721,6 +1859,12 @@ dependencies = [
"uuid", "uuid",
] ]
[[package]]
name = "matchit"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3"
[[package]] [[package]]
name = "memchr" name = "memchr"
version = "2.8.0" version = "2.8.0"
@@ -2433,6 +2577,17 @@ dependencies = [
"zmij", "zmij",
] ]
[[package]]
name = "serde_path_to_error"
version = "0.1.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457"
dependencies = [
"itoa",
"serde",
"serde_core",
]
[[package]] [[package]]
name = "serde_spanned" name = "serde_spanned"
version = "1.1.1" version = "1.1.1"
@@ -2636,6 +2791,12 @@ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]]
name = "sync_wrapper"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
[[package]] [[package]]
name = "synstructure" name = "synstructure"
version = "0.13.2" version = "0.13.2"
@@ -2736,6 +2897,7 @@ dependencies = [
"pin-project-lite", "pin-project-lite",
"signal-hook-registry", "signal-hook-registry",
"socket2 0.6.3", "socket2 0.6.3",
"tokio-macros",
"windows-sys 0.61.2", "windows-sys 0.61.2",
] ]
@@ -2755,6 +2917,17 @@ dependencies = [
"uuid", "uuid",
] ]
[[package]]
name = "tokio-macros"
version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "tokio-rustls" name = "tokio-rustls"
version = "0.26.4" version = "0.26.4"
@@ -2809,6 +2982,34 @@ dependencies = [
"winnow 1.0.3", "winnow 1.0.3",
] ]
[[package]]
name = "tower"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4"
dependencies = [
"futures-core",
"futures-util",
"pin-project-lite",
"sync_wrapper",
"tokio",
"tower-layer",
"tower-service",
"tracing",
]
[[package]]
name = "tower-layer"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e"
[[package]]
name = "tower-service"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
[[package]] [[package]]
name = "tracing" name = "tracing"
version = "0.1.44" version = "0.1.44"
+28 -19
View File
@@ -9,28 +9,37 @@ use rustls::ServerConfig;
use std::env; use std::env;
use std::fs::File; use std::fs::File;
use std::io::{BufReader, Error}; use std::io::{BufReader, Error};
use std::path::Path;
use std::sync::Arc; use std::sync::Arc;
use surveillant_systeme_exposition::init; use surveillant_systeme_exposition::init;
use surveillant_systeme_exposition::video::video_format_content_type_mapper::map_to_content_type;
use surveillant_systeme_infrastructure::video::video_format_detector::get_content_type;
use tokio_cron_scheduler::{Job, JobScheduler}; use tokio_cron_scheduler::{Job, JobScheduler};
#[actix_web::main] // #[actix_web::main]
async fn main() -> std::io::Result<()>{ fn main() {
dotenv().ok(); let path_str = "/home/florian/Videos/AirFly/GH010015.MP4";
unsafe { env::set_var("RUST_LOG", "debug"); } let path = Path::new(path_str);
check_all_variables_are_set(); let file_type = get_content_type(&path);
env_logger::init(); let file_content_type = map_to_content_type(&file_type);
println!("file_type={file_content_type}");
let application_context = ApplicationContext::init()?; ()
// dotenv().ok();
let tls_config = init_tls_configuration()?; // unsafe { env::set_var("RUST_LOG", "debug"); }
let server = HttpServer::new( // check_all_variables_are_set();
move || App::new().configure(init) // env_logger::init();
.app_data(web::Data::new(Arc::new(application_context.user_service.clone()))) //
) // let application_context = ApplicationContext::init()?;
.bind_rustls_0_23(("0.0.0.0", 19000), tls_config) //
.expect("Unable to start server"); // let tls_config = init_tls_configuration()?;
// let server = HttpServer::new(
server.run().await // move || App::new().configure(init)
// .app_data(web::Data::new(Arc::new(application_context.user_service.clone())))
// )
// .bind_rustls_0_23(("0.0.0.0", 19000), tls_config)
// .expect("Unable to start server");
//
// server.run().await
} }
fn init_tls_configuration() -> Result<ServerConfig, Error> { fn init_tls_configuration() -> Result<ServerConfig, Error> {
@@ -60,4 +69,4 @@ fn check_all_variables_are_set() {
env::var(variable).expect(format!("{} should be defined!", variable).as_str()); env::var(variable).expect(format!("{} should be defined!", variable).as_str());
}); });
info!("All variables are set correctly."); info!("All variables are set correctly.");
} }