diff --git a/Cargo.lock b/Cargo.lock index 7ad6f7e..f51588e 100755 --- a/Cargo.lock +++ b/Cargo.lock @@ -1122,7 +1122,7 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "picturium" -version = "0.1.3" +version = "0.1.4" dependencies = [ "actix-cors", "actix-files", diff --git a/Cargo.toml b/Cargo.toml index a152f40..cacb89c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "picturium" -version = "0.1.3" +version = "0.1.4" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/src/pipeline/finalize.rs b/src/pipeline/finalize.rs index 59e873e..a984d5d 100755 --- a/src/pipeline/finalize.rs +++ b/src/pipeline/finalize.rs @@ -1,7 +1,7 @@ use std::path::PathBuf; -use libvips::{ops, VipsImage}; use libvips::ops::{ForeignHeifCompression, ForeignHeifEncoder, ForeignKeep, ForeignSubsample, ForeignWebpPreset, HeifsaveOptions, JpegsaveOptions, PngsaveOptions, WebpsaveOptions}; +use libvips::{ops, VipsImage}; use log::{debug, error}; use crate::cache; diff --git a/src/pipeline/icc.rs b/src/pipeline/icc.rs new file mode 100644 index 0000000..badf31c --- /dev/null +++ b/src/pipeline/icc.rs @@ -0,0 +1,10 @@ +use crate::pipeline::{PipelineError, PipelineResult}; +use crate::services::vips::get_error_message; +use libvips::{ops, VipsImage}; + +pub(crate) async fn transform(image: VipsImage) -> PipelineResult { + match ops::icc_transform(&image, "sRGB") { + Ok(image) => Ok(image), + Err(_) => Err(PipelineError(format!("Failed to transform image to sRGB: {}", get_error_message()))) + } +} \ No newline at end of file diff --git a/src/pipeline/mod.rs b/src/pipeline/mod.rs index 8160c0e..d8caa91 100755 --- a/src/pipeline/mod.rs +++ b/src/pipeline/mod.rs @@ -13,50 +13,63 @@ mod crop; mod finalize; mod rasterize; mod background; +mod icc; pub type PipelineResult = Result; #[derive(Debug)] pub struct PipelineError(pub String); -pub async fn run(url_parameters: &UrlParameters<'_>, output_format: OutputFormat) -> PipelineResult { - - debug!("Running pipeline for {}", url_parameters.path.to_string_lossy()); +pub enum PipelineOutput { + Image(PathBuf), + OutputFormat(OutputFormat) +} +pub async fn run(url_parameters: &UrlParameters<'_>, output_format: OutputFormat) -> PipelineResult { let mut image = thumbnail::run(url_parameters.path, url_parameters).await?; if output_format == OutputFormat::Pdf { - return Ok(cache::get_document_path_from_url_parameters(url_parameters).into()); + return Ok(PipelineOutput::Image(cache::get_document_path_from_url_parameters(url_parameters).into())); } if is_svg(url_parameters.path) { image = rasterize::run(image, url_parameters).await?; } - + + debug!("Performing autorotate"); image = rotate::autorotate(image).await?; + let valid_output_format = validate_output_format(&image, url_parameters, &output_format)?; + + if valid_output_format != output_format { + return Ok(PipelineOutput::OutputFormat(valid_output_format)); + } + + let output_format = valid_output_format; + + debug!("Performing ICC transform"); + image = icc::transform(image).await?; + // if url_parameters.crop.is_some() { // crop::run(&image, &url_parameters, &output_format).await?; // } - - let output_format = validate_output_format(&image, url_parameters, &output_format)?; if url_parameters.width.is_some() || url_parameters.height.is_some() { image = resize::run(image, url_parameters).await?; } - debug!("Before rotate"); - if url_parameters.rotate != Rotate::No { + debug!("Rotating image"); image = rotate::run(image, url_parameters).await?; } - debug!("Checking if background is required"); - if supports_transparency(url_parameters.path) && output_format != OutputFormat::Jpg { + debug!("Applying background"); image = background::run(image, url_parameters).await?; } - finalize::run(image, url_parameters, &output_format).await - + match finalize::run(image, url_parameters, &output_format).await { + Ok(result) => Ok(PipelineOutput::Image(result)), + Err(e) => Err(e) + } } \ No newline at end of file diff --git a/src/services/mod.rs b/src/services/mod.rs index af29aa5..433d06f 100755 --- a/src/services/mod.rs +++ b/src/services/mod.rs @@ -12,6 +12,7 @@ use log::{debug, error}; use crate::parameters::{RawUrlParameters, UrlParameters}; use crate::pipeline; use crate::cache; +use crate::pipeline::PipelineOutput; use crate::services::formats::is_generated; pub mod formats; @@ -62,21 +63,11 @@ pub async fn serve(req: HttpRequest, path: Path, parameters: Query, parameters: Query output, + PipelineOutput::OutputFormat(output_format) => { + let cache_path = cache::get_path_from_url_parameters(&url_parameters, &output_format); + + // Return from cache + if let Some(response) = cache_response(cache_enable, &cache_path, &url_parameters, &req) { + return response; + } + + debug!("Running pipeline for {} @ {cache_path}", url_parameters.path.to_string_lossy()); + + // Process image + let output = match pipeline::run(&url_parameters, output_format).await { + Ok(output) => output, + Err(e) => { + error!("Failed to process image: {}", e.0); + return HttpResponse::InternalServerError().into(); + } + }; + + match output { + PipelineOutput::Image(output) => output, + _ => { + error!("Failed to process image: detected output format resolution recursion"); + return HttpResponse::InternalServerError().into(); + } + } + } + }; + match NamedFile::open(output) { Ok(named_file) => { @@ -109,4 +131,22 @@ pub async fn serve(req: HttpRequest, path: Path, parameters: Query HttpResponse::InternalServerError().into() } +} + +fn cache_response(enabled: bool, cache_path: &str, url_parameters: &UrlParameters, req: &HttpRequest) -> Option { + if !enabled || !cache::is_cached(&cache_path, &url_parameters) { + return None; + } + + debug!("Using cache @{cache_path}"); + + let mut response = NamedFile::open(cache_path).unwrap().set_content_disposition( + ContentDisposition { + disposition: DispositionType::Inline, + parameters: vec![DispositionParam::Filename(url_parameters.path.file_name().unwrap().to_string_lossy().into())] + } + ).into_response(req); + + response.headers_mut().insert(header::CACHE_CONTROL, header::HeaderValue::from_static("public, max-age=604800, must-revalidate")); + Some(response) } \ No newline at end of file