Improve image generation perf
This commit is contained in:
		| @@ -3,21 +3,15 @@ pub struct Link { | ||||
|     pub label: String, | ||||
| } | ||||
|  | ||||
| #[derive(Default)] | ||||
| pub struct HeaderProps { | ||||
|     pub back_link: Option<Link>, | ||||
| } | ||||
|  | ||||
| impl Default for HeaderProps { | ||||
|     fn default() -> Self { | ||||
|         Self { back_link: None } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl HeaderProps { | ||||
|     pub fn with_back_link(link: Link) -> Self { | ||||
|         Self { | ||||
|             back_link: Some(link), | ||||
|             ..Default::default() | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -48,7 +48,6 @@ async fn main() { | ||||
|     axum::serve(listener, app).await.unwrap(); | ||||
| } | ||||
|  | ||||
| // TODO footer | ||||
| // TODO Display blog posts | ||||
| // TODO responsive design | ||||
| // TODO go live pipeline | ||||
|   | ||||
| @@ -21,7 +21,7 @@ pub struct BlogPostTemplate { | ||||
|  | ||||
| pub async fn render_blog_post(Path(post_id): Path<String>) -> Result<BlogPostTemplate, StatusCode> { | ||||
|     let path = format!("../_posts/blog/{}.md", post_id); | ||||
|     let parse_post = parse_post::<BlogPostMetadata>(&path); | ||||
|     let parse_post = parse_post::<BlogPostMetadata>(&path, true); | ||||
|     let parsed = parse_post.await?; | ||||
|  | ||||
|     Ok(BlogPostTemplate { | ||||
|   | ||||
| @@ -24,6 +24,7 @@ pub fn generate_images( | ||||
|                 .with_extension(format.get_extension()); | ||||
|  | ||||
|             if save_path.exists() { | ||||
|                 debug!("Skip generating {save_path:?} - Already exists"); | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|   | ||||
| @@ -1,12 +1,11 @@ | ||||
| use std::{ | ||||
|     cmp::Ordering, | ||||
|     path::{Path, PathBuf}, | ||||
|     str::FromStr as _, | ||||
|     sync::Arc, | ||||
| }; | ||||
|  | ||||
| use anyhow::Context; | ||||
| use image::{GenericImageView, ImageReader}; | ||||
| use image::{image_dimensions, ImageReader}; | ||||
|  | ||||
| use super::{export_format::ExportFormat, image_generator::generate_images}; | ||||
|  | ||||
| @@ -17,23 +16,27 @@ pub fn generate_picture_markup( | ||||
|     width: u32, | ||||
|     height: u32, | ||||
|     alt_text: &str, | ||||
|     generate_image: bool, | ||||
| ) -> Result<String, anyhow::Error> { | ||||
|     if !orig_img_path.starts_with("/") { | ||||
|         return Ok(format!( | ||||
|             r#"<img | ||||
|               alt="{alt_text}" | ||||
|               src="{orig_img_path}" | ||||
|             />"# | ||||
|         )); | ||||
|     } | ||||
|  | ||||
|     let exported_formats = get_export_formats(orig_img_path); | ||||
|     let path_to_generated = get_generated_file_name(orig_img_path); | ||||
|  | ||||
|     // TODO This should get removed when we move the project structure #move | ||||
|     let dev_only_img_path = Path::new("../static/").join(orig_img_path.strip_prefix("/").unwrap()); | ||||
|     let dev_only_img_path = | ||||
|         Path::new("../static/").join(orig_img_path.strip_prefix("/").unwrap_or(orig_img_path)); | ||||
|  | ||||
|     let orig_img = ImageReader::open(&dev_only_img_path) | ||||
|         .with_context(|| format!("Failed to read instrs from {:?}", &dev_only_img_path))? | ||||
|         .decode()?; | ||||
|     let orig_img_dimensions = orig_img.dimensions(); | ||||
|     let orig_img_dimensions = image_dimensions(&dev_only_img_path).unwrap(); | ||||
|     let resolutions = get_resolutions(orig_img_dimensions, width, height); | ||||
|  | ||||
|     // TODO lets generate images | ||||
|  | ||||
|     let orig_img_arc = Arc::new(orig_img); | ||||
|     let orig_img_clone = Arc::clone(&orig_img_arc); | ||||
|     let path_to_generated_arc = Arc::new(path_to_generated); | ||||
|     let path_to_generated_clone = Arc::clone(&path_to_generated_arc); | ||||
|     let resolutions_arc = Arc::new(resolutions); | ||||
| @@ -41,18 +44,25 @@ pub fn generate_picture_markup( | ||||
|     let exported_formats_arc = Arc::new(exported_formats); | ||||
|     let exported_formats_clone = Arc::clone(&exported_formats_arc); | ||||
|  | ||||
|     tokio::spawn(async move { | ||||
|         let orig_img = orig_img_clone.as_ref(); | ||||
|         let path_to_generated = path_to_generated_clone.as_ref(); | ||||
|         let resolutions = resolutions_clone.as_ref(); | ||||
|         let exported_formats = exported_formats_clone.as_ref(); | ||||
|     if generate_image { | ||||
|         rayon::spawn(move || { | ||||
|             let orig_img = ImageReader::open(&dev_only_img_path) | ||||
|                 .with_context(|| format!("Failed to read instrs from {:?}", &dev_only_img_path)) | ||||
|                 .unwrap() | ||||
|                 .decode() | ||||
|                 .unwrap(); | ||||
|             let path_to_generated = path_to_generated_clone.as_ref(); | ||||
|             let resolutions = resolutions_clone.as_ref(); | ||||
|             let exported_formats = exported_formats_clone.as_ref(); | ||||
|  | ||||
|         let result = generate_images(orig_img, path_to_generated, resolutions, exported_formats) | ||||
|             .with_context(|| "Failed to generate images".to_string()); | ||||
|         if let Err(e) = result { | ||||
|             tracing::error!("Error: {}", e); | ||||
|         } | ||||
|     }); | ||||
|             let result = | ||||
|                 generate_images(&orig_img, path_to_generated, resolutions, exported_formats) | ||||
|                     .with_context(|| "Failed to generate images".to_string()); | ||||
|             if let Err(e) = result { | ||||
|                 tracing::error!("Error: {}", e); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     let exported_formats = Arc::clone(&exported_formats_arc); | ||||
|     let path_to_generated = Arc::clone(&path_to_generated_arc); | ||||
| @@ -232,7 +242,11 @@ fn get_generated_file_name(orig_img_path: &str) -> PathBuf { | ||||
|         .file_stem() | ||||
|         .expect("There should be a name for every img"); | ||||
|     let result = Path::new("/generated_images/") | ||||
|         .join(parent.strip_prefix("/").unwrap()) | ||||
|         .join( | ||||
|             parent | ||||
|                 .strip_prefix("/") | ||||
|                 .unwrap_or(Path::new("/generated_images/")), | ||||
|         ) | ||||
|         .join(file_name); | ||||
|     result | ||||
| } | ||||
| @@ -287,7 +301,9 @@ fn test_get_export_formats() { | ||||
| } | ||||
| #[test] | ||||
| fn test_generate_srcset() { | ||||
|     let orig_img_path = PathBuf::from_str("/generated_images/images/uploads/img_name").unwrap(); | ||||
|     let orig_img_path = | ||||
|         <PathBuf as std::str::FromStr>::from_str("/generated_images/images/uploads/img_name") | ||||
|             .unwrap(); | ||||
|     let export_format = ExportFormat::Avif; | ||||
|     let resolutions = vec![ | ||||
|         (320, 200, 1.), | ||||
| @@ -325,7 +341,7 @@ fn test_generate_picture_markup() { | ||||
|         > | ||||
|         </picture>"#; | ||||
|     assert_eq!( | ||||
|         generate_picture_markup(orig_img_path, width, height, "Testing image alt") | ||||
|         generate_picture_markup(orig_img_path, width, height, "Testing image alt", false) | ||||
|             .expect("picture markup has to be generated"), | ||||
|         result | ||||
|     ); | ||||
|   | ||||
| @@ -22,7 +22,7 @@ pub async fn get_post_list<'de, Metadata: DeserializeOwned>( | ||||
|         let file_path = file.path(); | ||||
|         let file_path_str = file_path.to_str().unwrap(); | ||||
|         info!(":{}", file_path_str); | ||||
|         let post = parse_post::<Metadata>(file_path_str).await?; | ||||
|         let post = parse_post::<Metadata>(file_path_str, false).await?; | ||||
|         posts.push(post); | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -6,6 +6,9 @@ use gray_matter::{engine::YAML, Matter}; | ||||
| use pulldown_cmark::{Event, Options, Parser, Tag, TagEnd}; | ||||
| use serde::{de::DeserializeOwned, Deserialize, Deserializer}; | ||||
| use tokio::fs; | ||||
| use tracing::debug; | ||||
|  | ||||
| use crate::picture_generator::picture_markup_generator::generate_picture_markup; | ||||
|  | ||||
| pub fn deserialize_date<'de, D>(deserializer: D) -> Result<DateTime<Utc>, D::Error> | ||||
| where | ||||
| @@ -29,6 +32,7 @@ pub struct ParseResult<Metadata> { | ||||
|  | ||||
| pub async fn parse_post<'de, Metadata: DeserializeOwned>( | ||||
|     path: &str, | ||||
|     generate_images: bool, | ||||
| ) -> Result<ParseResult<Metadata>, StatusCode> { | ||||
|     let file_contents = fs::read_to_string(path) | ||||
|         .await | ||||
| @@ -43,7 +47,7 @@ pub async fn parse_post<'de, Metadata: DeserializeOwned>( | ||||
|             StatusCode::INTERNAL_SERVER_ERROR | ||||
|         })?; | ||||
|  | ||||
|     let body = parse_html(&metadata.content); | ||||
|     let body = parse_html(&metadata.content, generate_images); | ||||
|  | ||||
|     let filename = Path::new(path) | ||||
|         .file_stem() | ||||
| @@ -59,7 +63,7 @@ pub async fn parse_post<'de, Metadata: DeserializeOwned>( | ||||
|     }) | ||||
| } | ||||
|  | ||||
| pub fn parse_html(markdown: &str) -> String { | ||||
| pub fn parse_html(markdown: &str, generate_images: bool) -> String { | ||||
|     let mut options = Options::empty(); | ||||
|     options.insert(Options::ENABLE_TABLES); | ||||
|     options.insert(Options::ENABLE_FOOTNOTES); | ||||
| @@ -80,22 +84,41 @@ pub fn parse_html(markdown: &str) -> String { | ||||
|             title, | ||||
|             id, | ||||
|         }) => { | ||||
|             println!( | ||||
|             // TODO Get image resolution | ||||
|  | ||||
|             // Place image into the content with scaled reso to a boundary | ||||
|             let picture_markup = | ||||
|                 generate_picture_markup(&dest_url, 500, 500, &title, generate_images).unwrap_or( | ||||
|                     format!( | ||||
|                         r#" | ||||
|                         <img | ||||
|                           alt="{alt}" | ||||
|                           src="{src}" | ||||
|                         />"#, | ||||
|                         alt = title, | ||||
|                         src = dest_url, | ||||
|                     ), | ||||
|                 ); | ||||
|             // let picture_markup = format!( | ||||
|             //     r#" | ||||
|             //             <img | ||||
|             //               alt="{alt}" | ||||
|             //               src="{src}" | ||||
|             //             />"#, | ||||
|             //     alt = title, | ||||
|             //     src = dest_url, | ||||
|             // ); | ||||
|  | ||||
|             debug!( | ||||
|                 "Image link_type: {:?} url: {} title: {} id: {}", | ||||
|                 link_type, dest_url, title, id | ||||
|             ); | ||||
|             // TODO src set | ||||
|             Event::Html( | ||||
|                 format!( | ||||
|                     r#"<figure> | ||||
|                             <img | ||||
|                               alt="{alt}" | ||||
|                               src="{src}" | ||||
|                             /> | ||||
|                             <figcaption> | ||||
|                         "#, | ||||
|                     alt = title, | ||||
|                     src = dest_url, | ||||
|                         {picture_markup} | ||||
|                         <figcaption> | ||||
|                     "#, | ||||
|                 ) | ||||
|                 .into(), | ||||
|             ) | ||||
|   | ||||
| @@ -14,7 +14,7 @@ pub async fn get_featured_projects() -> Result<Vec<ParseResult<ProjectMetadata>> | ||||
|         .into_iter() | ||||
|         .filter(|post| post.metadata.featured) | ||||
|         .map(|mut post| { | ||||
|             post.metadata.description = parse_html(&post.metadata.description); | ||||
|             post.metadata.description = parse_html(&post.metadata.description, false); | ||||
|             post | ||||
|         }) | ||||
|         .collect(); | ||||
|   | ||||
| @@ -2,7 +2,7 @@ | ||||
| 	<aside class="row-span-3"> | ||||
| 		{% match post.metadata.thumbnail %} | ||||
| 		{% when Some with (orig_path) %} | ||||
| 		{{ crate::picture_generator::picture_markup_generator::generate_picture_markup(orig_path, 180, 240, "Article thumbnail").unwrap()|safe }} | ||||
| 		{{ crate::picture_generator::picture_markup_generator::generate_picture_markup(orig_path, 180, 240, "Article thumbnail", true).unwrap()|safe }} | ||||
| 		{% when None %} | ||||
| 		<div> TODO default obrazok </div> | ||||
| 		{% endmatch %} | ||||
|   | ||||
		Reference in New Issue
	
	Block a user