basic image HTML rendering. Current tests pass!

This commit is contained in:
Nick Pegg 2025-05-06 18:09:31 -07:00
parent bc33331dae
commit 9434eb835d
6 changed files with 156 additions and 113 deletions

View file

@ -102,13 +102,14 @@ impl TryFrom<&AlbumDir> for AlbumContext {
}
/// A Tera context for slide (individual image) pages
#[derive(Serialize)]
#[derive(Serialize, Debug)]
struct SlideContext {
// TODO: Path or String?
// Path required to get back to the root album
root_path: PathBuf,
image: Image,
prev_image: Image,
next_image: Image,
prev_image: Option<Image>,
next_image: Option<Image>,
}
pub fn generate(root_path: &PathBuf) -> anyhow::Result<PathBuf> {
@ -120,6 +121,7 @@ pub fn generate(root_path: &PathBuf) -> anyhow::Result<PathBuf> {
env::set_current_dir(root_path)?;
let album = AlbumDir::try_from(root_path)?;
fs::create_dir(&config.output_dir)?;
copy_static(&config)?;
generate_images(&config, &album)?;
generate_html(&config, &album)?;
@ -129,6 +131,13 @@ pub fn generate(root_path: &PathBuf) -> anyhow::Result<PathBuf> {
}
fn copy_static(config: &Config) -> anyhow::Result<()> {
let dst = &config.output_dir.join("static");
log::info!("Copying static files from _static to {}", dst.display());
fs_extra::dir::copy(
"_static",
dst,
&fs_extra::dir::CopyOptions::new().content_only(true),
)?;
Ok(())
}
@ -139,6 +148,15 @@ fn generate_images(config: &Config, album: &AlbumDir) -> anyhow::Result<()> {
for img in album.iter() {
let orig_image = image::open(&img.path)?;
let orig_path = output_path.join(&img.path);
log::info!(
"Copying original {} -> {}",
img.path.display(),
orig_path.display()
);
fs::create_dir_all(orig_path.parent().unwrap_or(Path::new("")))?;
orig_image.save(&orig_path)?;
let thumb_path = output_path.join(&img.thumb_path);
log::info!(
"Resizing {} -> {}",
@ -180,7 +198,6 @@ fn generate_html(config: &Config, album: &AlbumDir) -> anyhow::Result<()> {
.ok_or(anyhow!("Missing _templates dir in album dir"))?,
)?;
// Queue of album dir and depth (distance from root AlbumDir)
let mut dir_queue: VecDeque<&AlbumDir> = VecDeque::from([album]);
while let Some(album) = dir_queue.pop_front() {
let html_path = output_path.join(&album.path).join("index.html");
@ -197,5 +214,37 @@ fn generate_html(config: &Config, album: &AlbumDir) -> anyhow::Result<()> {
}
}
let all_images: Vec<&Image> = album.iter().collect();
for (pos, img) in all_images.iter().enumerate() {
let img: &Image = *img;
let prev_image: Option<&Image> = match pos {
0 => None,
n => Some(&all_images[n - 1]),
};
let next_image: Option<&Image> = all_images.get(pos + 1).map(|i| *i);
// Find the path to the root by counting the parts of the path
let mut path_to_root = PathBuf::new();
if let Some(parent) = img.path.parent() {
let mut parent = parent.to_path_buf();
while parent.pop() {
path_to_root = path_to_root.join("..");
}
}
log::info!("Rendering image {}", img.html_path.display());
let ctx = SlideContext {
root_path: path_to_root,
image: img.clone(),
prev_image: prev_image.cloned(),
next_image: next_image.cloned(),
};
log::debug!("Image context: {ctx:?}");
fs::write(
output_path.join(&img.html_path),
tera.render("photo.html", &tera::Context::from_serialize(&ctx)?)?,
)?;
}
Ok(())
}

View file

@ -1,7 +1,7 @@
use anyhow::anyhow;
use image::ImageReader;
use serde::Serialize;
use std::ffi::OsString;
use std::ffi::{OsStr, OsString};
use std::fs;
use std::path::{Path, PathBuf};
use std::slice::Iter;
@ -21,6 +21,7 @@ pub struct AlbumDir {
impl AlbumDir {
/// Returns an iterator over all images in the album and subalbums
// TODO: Rename to iter_images() and make separate one for dirs?
pub fn iter(&self) -> AlbumIter {
AlbumIter::new(self)
}
@ -145,10 +146,11 @@ impl<'a> Iterator for AlbumIter<'a> {
}
}
#[derive(Clone, Hash, PartialEq, Eq, Serialize)]
#[derive(Clone, Debug, Hash, PartialEq, Eq, Serialize)]
pub struct Image {
/// Path to the image, relative to the root album
pub path: PathBuf,
pub filename: OsString,
/// Text description of the image which is displayed below it on the HTML page
pub description: String,
@ -163,17 +165,25 @@ pub struct Image {
impl Image {
pub fn new(path: PathBuf, description: String) -> anyhow::Result<Self> {
let filename = path
.file_name()
.ok_or(anyhow!(
"Image path {} is missing a filename",
path.display()
))?
.into();
let thumb_filename = Self::slide_filename(&path, "thumb", true)?;
let thumb_path = Self::slide_path(&path, "thumb")?;
let thumb_path = Self::slide_path(&path, &thumb_filename);
let screen_filename = Self::slide_filename(&path, "screen", true)?;
let screen_path = Self::slide_path(&path, "screen")?;
let screen_path = Self::slide_path(&path, &screen_filename);
// TODO: add "slides" in html path?
let html_filename = Self::slide_filename(&path, "html", false)?;
let html_path = path.with_extension("html");
let html_path = Self::slide_path(&path, &html_filename);
Ok(Image {
path,
description,
filename,
thumb_filename,
thumb_path,
screen_filename,
@ -209,30 +219,13 @@ impl Image {
Ok(new_name.into())
}
/// Returns the path to the file in the slides dir with the given extention insert, e.g.
/// "thumb" or "display"
fn slide_path(path: &PathBuf, ext: &str) -> anyhow::Result<PathBuf> {
let new_ext = match path.extension() {
Some(e) => {
ext.to_string()
+ "."
+ e.to_str().ok_or(anyhow!(
"Image {} extension is not valid UTF-8",
path.display()
))?
}
None => ext.to_string(),
};
let new_path = path.with_extension(new_ext);
let new_name = new_path
.file_name()
.ok_or(anyhow!("Image {} missing a file name", path.display()))?;
let parent = path
.parent()
.ok_or(anyhow!("Image {} has no parent dir", path.display()))?;
Ok(parent.join("slides").join(new_name))
/// Returns the path to the file in the slides dir given the path to the original image
fn slide_path(path: &PathBuf, file_name: &OsStr) -> PathBuf {
let mut new_path = path.clone();
new_path.pop();
new_path.push("slides");
new_path.push(&file_name);
new_path
}
}