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