Add basic JPEG implementation
This commit is contained in:
parent
7acc03218d
commit
9fe02b4e6e
3 changed files with 1256 additions and 27 deletions
172
src/main.rs
172
src/main.rs
|
@ -1,28 +1,168 @@
|
|||
use actix_web::{get, post, web, App, HttpResponse, HttpServer, Responder};
|
||||
use std::{
|
||||
io::Read,
|
||||
os::unix::fs::MetadataExt,
|
||||
process::Command,
|
||||
};
|
||||
|
||||
use actix_multipart::form::{MultipartForm, tempfile::TempFile};
|
||||
use actix_web::{
|
||||
App, Either, Error, HttpResponse, HttpServer, Responder, get, post,
|
||||
};
|
||||
use anyhow::format_err;
|
||||
use tempfile::NamedTempFile;
|
||||
use tracing::{Level, event, instrument};
|
||||
// use serde::Deserialize;
|
||||
|
||||
// #[derive(Debug, Deserialize)]
|
||||
// struct Metadata {
|
||||
// name: String,
|
||||
// }
|
||||
|
||||
type _HandledResult = Either<HttpResponse, anyhow::Result<(), Error>>;
|
||||
|
||||
#[derive(Debug, MultipartForm)]
|
||||
struct UploadImageForm {
|
||||
#[multipart(limit = "100MB")]
|
||||
file: TempFile,
|
||||
// json: MpJson<Metadata>,
|
||||
}
|
||||
|
||||
#[instrument]
|
||||
#[get("/")]
|
||||
async fn hello() -> impl Responder {
|
||||
HttpResponse::Ok().body("Hello world!")
|
||||
}
|
||||
|
||||
#[post("/echo")]
|
||||
async fn echo(req_body: String) -> impl Responder {
|
||||
HttpResponse::Ok().body(req_body)
|
||||
#[instrument]
|
||||
#[post("/echo-image")]
|
||||
async fn echo_image(MultipartForm(form): MultipartForm<UploadImageForm>) -> impl Responder {
|
||||
match is_file_image(&form.file.content_type) {
|
||||
Some(x) => return x,
|
||||
None => {}
|
||||
}
|
||||
|
||||
let mut reader = std::io::BufReader::new(form.file.file.as_file());
|
||||
let file_contents = &mut vec![];
|
||||
|
||||
let _ = reader.read_to_end(file_contents);
|
||||
|
||||
return HttpResponse::Ok()
|
||||
.content_type(form.file.content_type.unwrap())
|
||||
.body(file_contents.clone());
|
||||
}
|
||||
|
||||
async fn manual_hello() -> impl Responder {
|
||||
HttpResponse::Ok().body("Hey there!")
|
||||
#[instrument]
|
||||
#[post("/jpeg")]
|
||||
async fn jpeg(MultipartForm(form): MultipartForm<UploadImageForm>) -> impl Responder {
|
||||
|
||||
match is_file_image(&form.file.content_type) {
|
||||
Some(x) => return x,
|
||||
None => {}
|
||||
}
|
||||
|
||||
let mut reader = std::io::BufReader::new(form.file.file.as_file());
|
||||
let input_file_contents = &mut vec![];
|
||||
let _ = reader.read_to_end(input_file_contents);
|
||||
drop(reader);
|
||||
|
||||
// let img = ImageReader::new(Cursor::new(input_file_contents)).with_guessed_format().unwrap().decode().unwrap();
|
||||
let jpeg = tempfile::NamedTempFile::new().unwrap();
|
||||
|
||||
let status = encode_jpeg(&form.file.file, &jpeg, Some(5)).await;
|
||||
if status.is_err() {
|
||||
event!(Level::ERROR, "jpeg compression failed:\n{:?}", status.unwrap());
|
||||
return HttpResponse::InternalServerError().body("Compression failed");
|
||||
}
|
||||
|
||||
let mut reader = std::io::BufReader::new(&jpeg);
|
||||
let output_file_contents = &mut vec![];
|
||||
let _ = reader.read_to_end(output_file_contents);
|
||||
|
||||
let size_percentage = jpeg.as_file().metadata().unwrap().size() * 100 / form.file.size as u64;
|
||||
|
||||
event!(
|
||||
Level::INFO,
|
||||
"{} is now {}% of origional size as an {}!",
|
||||
&form.file.content_type.unwrap(),
|
||||
size_percentage,
|
||||
"image/jpeg"
|
||||
);
|
||||
|
||||
return HttpResponse::Ok()
|
||||
.content_type(mime::IMAGE_JPEG)
|
||||
.append_header((
|
||||
"content-disposition",
|
||||
format!("filename=\"{}.jpeg\"", form.file.file_name.unwrap()),
|
||||
))
|
||||
.body(output_file_contents.clone());
|
||||
}
|
||||
|
||||
#[instrument]
|
||||
async fn encode_jpeg(input: &NamedTempFile, output: &NamedTempFile, quality: Option<u8>) -> Result<(), anyhow::Error> {
|
||||
let jpeg_quality = match quality {
|
||||
Some(x) => {
|
||||
if x > 100 {
|
||||
return Err(format_err!("Invalid quality value"));
|
||||
} else {
|
||||
x
|
||||
}
|
||||
}
|
||||
None => 75,
|
||||
};
|
||||
|
||||
let jpeg_command = format!(
|
||||
"magick {} ppm:- | cjpeg -outfile {} -quality {}",
|
||||
input.path().display(),
|
||||
output.path().display(),
|
||||
jpeg_quality
|
||||
);
|
||||
|
||||
let optimise_command = format!(
|
||||
"jpegoptim {}",
|
||||
output.path().display()
|
||||
);
|
||||
|
||||
let _output = Command::new("sh")
|
||||
.arg("-c")
|
||||
.arg(jpeg_command)
|
||||
.output()
|
||||
.expect("failed to execute process");
|
||||
|
||||
// event!(Level::INFO, "{:?}", _output);
|
||||
|
||||
let _output = Command::new("sh")
|
||||
.arg("-c")
|
||||
.arg(optimise_command)
|
||||
.output()
|
||||
.expect("failed to execute process");
|
||||
|
||||
event!(Level::INFO, "{:?}", _output);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns `None` if file content_type starts with 'image' otherwise returns with an error response to serve to client
|
||||
#[instrument]
|
||||
fn is_file_image(input_mime: &Option<mime::Mime>) -> Option<HttpResponse> {
|
||||
if input_mime.is_none() {
|
||||
return Some(HttpResponse::BadRequest().body("Bad file content type?"));
|
||||
}
|
||||
|
||||
// This long ass line is just to get the first half of the content type. with 'image/jpeg' it would produce 'image' and then get cast it as a str for comparison
|
||||
if input_mime.clone().unwrap().type_().as_str() != "image" {
|
||||
return Some(HttpResponse::BadRequest().body("File uploaded in not a supported format!"));
|
||||
}
|
||||
|
||||
return None;
|
||||
}
|
||||
|
||||
#[actix_web::main]
|
||||
#[instrument]
|
||||
async fn main() -> std::io::Result<()> {
|
||||
HttpServer::new(|| {
|
||||
App::new()
|
||||
.service(hello)
|
||||
.service(echo)
|
||||
.route("/hey", web::get().to(manual_hello))
|
||||
})
|
||||
.bind(("127.0.0.1", 3970))?
|
||||
.run()
|
||||
.await
|
||||
}
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
HttpServer::new(|| App::new().service(hello).service(echo_image).service(jpeg))
|
||||
.bind(("127.0.0.1", 3970))?
|
||||
.run()
|
||||
.await
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue