reorganize
This commit is contained in:
parent
9945b9eb7f
commit
4ebaee95cc
3 changed files with 219 additions and 211 deletions
220
src/generate.rs
220
src/generate.rs
|
@ -1,9 +1,10 @@
|
|||
pub mod album_dir;
|
||||
mod image;
|
||||
|
||||
use crate::config::Config;
|
||||
use album_dir::{AlbumDir, Image};
|
||||
use anyhow::{Context, anyhow};
|
||||
use image::imageops::FilterType;
|
||||
use crate::generate::image::Image;
|
||||
use album_dir::AlbumDir;
|
||||
use anyhow::{anyhow, Context};
|
||||
use indicatif::ProgressBar;
|
||||
use rayon::prelude::*;
|
||||
use serde::Serialize;
|
||||
|
@ -16,112 +17,7 @@ use std::path::{Path, PathBuf};
|
|||
use std::sync::Mutex;
|
||||
use tera::Tera;
|
||||
|
||||
const IMG_RESIZE_FILTER: FilterType = FilterType::Lanczos3;
|
||||
|
||||
#[derive(Serialize, Debug)]
|
||||
struct Breadcrumb {
|
||||
path: PathBuf,
|
||||
name: String,
|
||||
}
|
||||
|
||||
/// A Tera context for album pages
|
||||
#[derive(Serialize, Debug)]
|
||||
struct AlbumContext {
|
||||
// Path required to get back to the root album
|
||||
root_path: PathBuf,
|
||||
|
||||
name: String,
|
||||
description: String,
|
||||
|
||||
images: Vec<Image>,
|
||||
|
||||
// Path to the cover image thumbnail within /slides/, relative to the album dir. Used when
|
||||
// linking to an album from a parent album
|
||||
cover_thumbnail_path: PathBuf,
|
||||
|
||||
// list of:
|
||||
// - relative dir to walk up to root, e.g. "../../.."
|
||||
// - dir name
|
||||
breadcrumbs: Vec<Breadcrumb>,
|
||||
|
||||
// Immediate children of this album
|
||||
children: Vec<AlbumContext>,
|
||||
}
|
||||
|
||||
impl TryFrom<&AlbumDir> for AlbumContext {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(album: &AlbumDir) -> anyhow::Result<Self> {
|
||||
let name: String = match album.path.file_name() {
|
||||
Some(n) => n.to_string_lossy().to_string(),
|
||||
None => String::new(),
|
||||
};
|
||||
|
||||
// Build breadcrumbs
|
||||
let mut breadcrumbs: Vec<Breadcrumb> = Vec::new();
|
||||
{
|
||||
let mut album_path = album.path.clone();
|
||||
let mut relpath: PathBuf = PathBuf::new();
|
||||
while album_path.pop() {
|
||||
let filename: &OsStr = album_path.file_name().unwrap_or(OsStr::new("Home"));
|
||||
relpath.push("..");
|
||||
breadcrumbs.push(Breadcrumb {
|
||||
path: relpath.clone(),
|
||||
name: filename.to_string_lossy().to_string(),
|
||||
});
|
||||
}
|
||||
}
|
||||
breadcrumbs.reverse();
|
||||
log::debug!("Crumbs for {}: {breadcrumbs:?}", album.path.display());
|
||||
|
||||
// The first breadcrumb path is the relative path to the root album
|
||||
let root_path = if !breadcrumbs.is_empty() {
|
||||
breadcrumbs[0].path.clone()
|
||||
} else {
|
||||
PathBuf::new()
|
||||
};
|
||||
|
||||
// Make the path to the thumbnail relative to the album that we're in
|
||||
let cover_thumbnail_path: PathBuf;
|
||||
if let Some(parent) = album.path.parent() {
|
||||
cover_thumbnail_path = album.cover.thumb_path.strip_prefix(parent)?.to_path_buf()
|
||||
} else {
|
||||
cover_thumbnail_path = album
|
||||
.cover
|
||||
.thumb_path
|
||||
.strip_prefix(&album.path)?
|
||||
.to_path_buf()
|
||||
};
|
||||
|
||||
let children: Vec<AlbumContext> = album
|
||||
.children
|
||||
.iter()
|
||||
.map(AlbumContext::try_from)
|
||||
.collect::<anyhow::Result<Vec<AlbumContext>>>()?;
|
||||
let images: Vec<Image> = album.images.clone();
|
||||
|
||||
Ok(AlbumContext {
|
||||
root_path,
|
||||
name,
|
||||
description: album.description.clone(),
|
||||
breadcrumbs,
|
||||
images,
|
||||
children,
|
||||
cover_thumbnail_path,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// A Tera context for slide (individual image) pages
|
||||
#[derive(Serialize, Debug)]
|
||||
struct SlideContext {
|
||||
// Path required to get back to the root album
|
||||
root_path: PathBuf,
|
||||
image: Image,
|
||||
prev_image: Option<Image>,
|
||||
next_image: Option<Image>,
|
||||
}
|
||||
|
||||
const IMG_RESIZE_FILTER: ::image::imageops::FilterType = ::image::imageops::FilterType::Lanczos3;
|
||||
/// Generate an album
|
||||
///
|
||||
/// `root_path` is a path to the root directory of the album. `full` if true will regenerate
|
||||
|
@ -202,7 +98,7 @@ fn generate_images(config: &Config, album: &AlbumDir, full: bool) -> anyhow::Res
|
|||
fs::hard_link(&img.path, &full_size_path)
|
||||
.with_context(|| format!("Error creating hard link at {}", full_size_path.display()))?;
|
||||
|
||||
let orig_image = image::open(&img.path)?;
|
||||
let orig_image = ::image::open(&img.path)?;
|
||||
let thumb_path = output_path.join(&img.thumb_path);
|
||||
log::info!(
|
||||
"Resizing {} -> {}",
|
||||
|
@ -301,6 +197,110 @@ fn generate_html(config: &Config, album: &AlbumDir) -> anyhow::Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug)]
|
||||
struct Breadcrumb {
|
||||
path: PathBuf,
|
||||
name: String,
|
||||
}
|
||||
|
||||
/// A Tera context for album pages
|
||||
#[derive(Serialize, Debug)]
|
||||
struct AlbumContext {
|
||||
/// Path required to get back to the root album
|
||||
root_path: PathBuf,
|
||||
|
||||
name: String,
|
||||
description: String,
|
||||
|
||||
images: Vec<Image>,
|
||||
|
||||
/// Path to the cover image thumbnail within /slides/, relative to the album dir. Used when
|
||||
/// linking to an album from a parent album
|
||||
cover_thumbnail_path: PathBuf,
|
||||
|
||||
/// list of:
|
||||
/// - relative dir to walk up to root, e.g. "../../.."
|
||||
/// - dir name
|
||||
breadcrumbs: Vec<Breadcrumb>,
|
||||
|
||||
/// Immediate children of this album
|
||||
children: Vec<AlbumContext>,
|
||||
}
|
||||
|
||||
impl TryFrom<&AlbumDir> for AlbumContext {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(album: &AlbumDir) -> anyhow::Result<Self> {
|
||||
let name: String = match album.path.file_name() {
|
||||
Some(n) => n.to_string_lossy().to_string(),
|
||||
None => String::new(),
|
||||
};
|
||||
|
||||
// Build breadcrumbs
|
||||
let mut breadcrumbs: Vec<Breadcrumb> = Vec::new();
|
||||
{
|
||||
let mut album_path = album.path.clone();
|
||||
let mut relpath: PathBuf = PathBuf::new();
|
||||
while album_path.pop() {
|
||||
let filename: &OsStr = album_path.file_name().unwrap_or(OsStr::new("Home"));
|
||||
relpath.push("..");
|
||||
breadcrumbs.push(Breadcrumb {
|
||||
path: relpath.clone(),
|
||||
name: filename.to_string_lossy().to_string(),
|
||||
});
|
||||
}
|
||||
}
|
||||
breadcrumbs.reverse();
|
||||
log::debug!("Crumbs for {}: {breadcrumbs:?}", album.path.display());
|
||||
|
||||
// The first breadcrumb path is the relative path to the root album
|
||||
let root_path = if !breadcrumbs.is_empty() {
|
||||
breadcrumbs[0].path.clone()
|
||||
} else {
|
||||
PathBuf::new()
|
||||
};
|
||||
|
||||
// Make the path to the thumbnail relative to the album that we're in
|
||||
let cover_thumbnail_path: PathBuf;
|
||||
if let Some(parent) = album.path.parent() {
|
||||
cover_thumbnail_path = album.cover.thumb_path.strip_prefix(parent)?.to_path_buf()
|
||||
} else {
|
||||
cover_thumbnail_path = album
|
||||
.cover
|
||||
.thumb_path
|
||||
.strip_prefix(&album.path)?
|
||||
.to_path_buf()
|
||||
};
|
||||
|
||||
let children: Vec<AlbumContext> = album
|
||||
.children
|
||||
.iter()
|
||||
.map(AlbumContext::try_from)
|
||||
.collect::<anyhow::Result<Vec<AlbumContext>>>()?;
|
||||
let images: Vec<Image> = album.images.clone();
|
||||
|
||||
Ok(AlbumContext {
|
||||
root_path,
|
||||
name,
|
||||
description: album.description.clone(),
|
||||
breadcrumbs,
|
||||
images,
|
||||
children,
|
||||
cover_thumbnail_path,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// A Tera context for slide (individual image) pages
|
||||
#[derive(Serialize, Debug)]
|
||||
struct SlideContext {
|
||||
// Path required to get back to the root album
|
||||
root_path: PathBuf,
|
||||
image: Image,
|
||||
prev_image: Option<Image>,
|
||||
next_image: Option<Image>,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::generate;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use crate::generate::image::Image;
|
||||
use anyhow::anyhow;
|
||||
use image::ImageReader;
|
||||
use serde::Serialize;
|
||||
use std::ffi::OsString;
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::slice::Iter;
|
||||
|
@ -169,93 +169,6 @@ impl<'a> Iterator for AlbumImageIter<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Hash, PartialEq, Eq, Serialize)]
|
||||
pub struct Image {
|
||||
/// Path to the image, relative to the root album
|
||||
pub path: PathBuf,
|
||||
pub filename: String,
|
||||
|
||||
/// Text description of the image which is displayed below it on the HTML page
|
||||
pub description: String,
|
||||
|
||||
pub thumb_filename: String,
|
||||
pub thumb_path: PathBuf,
|
||||
pub screen_filename: String,
|
||||
pub screen_path: PathBuf,
|
||||
pub html_filename: String,
|
||||
pub html_path: PathBuf,
|
||||
}
|
||||
|
||||
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()
|
||||
))?
|
||||
.to_str()
|
||||
.ok_or(anyhow!("Cannot convert {} to a string", path.display()))?
|
||||
.to_string();
|
||||
let thumb_filename = Self::slide_filename(&path, "thumb", true)?;
|
||||
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_filename);
|
||||
let html_filename = Self::slide_filename(&path, "html", false)?;
|
||||
let html_path = Self::slide_path(&path, &html_filename);
|
||||
|
||||
Ok(Image {
|
||||
path,
|
||||
description,
|
||||
filename,
|
||||
thumb_filename,
|
||||
thumb_path,
|
||||
screen_filename,
|
||||
screen_path,
|
||||
html_filename,
|
||||
html_path,
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the filename for a given slide type. For example if ext = "thumb" and the current
|
||||
/// filename is "blah.jpg" this will return "blah.thumb.jpg". If keep_ext if false, it would
|
||||
/// return "blah.thumb"
|
||||
fn slide_filename(path: &Path, ext: &str, keep_ext: bool) -> anyhow::Result<String> {
|
||||
let mut new_ext: OsString = ext.into();
|
||||
if keep_ext {
|
||||
if let Some(e) = path.extension() {
|
||||
new_ext = OsString::from(
|
||||
ext.to_string()
|
||||
+ "."
|
||||
+ e.to_str().ok_or(anyhow!(
|
||||
"Image {} extension is not valid UTF-8",
|
||||
path.display()
|
||||
))?,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
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()))?
|
||||
.to_str()
|
||||
.ok_or(anyhow!("Unable to convert {} to a string", path.display()))?
|
||||
.to_string();
|
||||
|
||||
Ok(new_name)
|
||||
}
|
||||
|
||||
/// Returns the path to the file in the slides dir given the path to the original image
|
||||
fn slide_path(path: &Path, file_name: &str) -> PathBuf {
|
||||
let mut new_path = path.to_path_buf();
|
||||
new_path.pop();
|
||||
new_path.push("slides");
|
||||
new_path.push(file_name);
|
||||
new_path
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
@ -314,17 +227,4 @@ mod tests {
|
|||
]);
|
||||
assert_eq!(imgs, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn image_paths() {
|
||||
let img = Image::new(PathBuf::from("foo/bar/image.jpg"), String::new()).unwrap();
|
||||
assert_eq!(
|
||||
img.thumb_path,
|
||||
PathBuf::from("foo/bar/slides/image.thumb.jpg")
|
||||
);
|
||||
assert_eq!(
|
||||
img.screen_path,
|
||||
PathBuf::from("foo/bar/slides/image.screen.jpg")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
108
src/generate/image.rs
Normal file
108
src/generate/image.rs
Normal file
|
@ -0,0 +1,108 @@
|
|||
use anyhow::anyhow;
|
||||
use serde::Serialize;
|
||||
use std::ffi::OsString;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
#[derive(Clone, Debug, Hash, PartialEq, Eq, Serialize)]
|
||||
pub struct Image {
|
||||
/// Path to the image, relative to the root album
|
||||
pub path: PathBuf,
|
||||
pub filename: String,
|
||||
|
||||
/// Text description of the image which is displayed below it on the HTML page
|
||||
pub description: String,
|
||||
|
||||
pub thumb_filename: String,
|
||||
pub thumb_path: PathBuf,
|
||||
pub screen_filename: String,
|
||||
pub screen_path: PathBuf,
|
||||
pub html_filename: String,
|
||||
pub html_path: PathBuf,
|
||||
}
|
||||
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()
|
||||
))?
|
||||
.to_str()
|
||||
.ok_or(anyhow!("Cannot convert {} to a string", path.display()))?
|
||||
.to_string();
|
||||
let thumb_filename = Self::slide_filename(&path, "thumb", true)?;
|
||||
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_filename);
|
||||
let html_filename = Self::slide_filename(&path, "html", false)?;
|
||||
let html_path = Self::slide_path(&path, &html_filename);
|
||||
|
||||
Ok(Image {
|
||||
path,
|
||||
description,
|
||||
filename,
|
||||
thumb_filename,
|
||||
thumb_path,
|
||||
screen_filename,
|
||||
screen_path,
|
||||
html_filename,
|
||||
html_path,
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the filename for a given slide type. For example if ext = "thumb" and the current
|
||||
/// filename is "blah.jpg" this will return "blah.thumb.jpg". If keep_ext if false, it would
|
||||
/// return "blah.thumb"
|
||||
fn slide_filename(path: &Path, ext: &str, keep_ext: bool) -> anyhow::Result<String> {
|
||||
let mut new_ext: OsString = ext.into();
|
||||
if keep_ext {
|
||||
if let Some(e) = path.extension() {
|
||||
new_ext = OsString::from(
|
||||
ext.to_string()
|
||||
+ "."
|
||||
+ e.to_str().ok_or(anyhow!(
|
||||
"Image {} extension is not valid UTF-8",
|
||||
path.display()
|
||||
))?,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
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()))?
|
||||
.to_str()
|
||||
.ok_or(anyhow!("Unable to convert {} to a string", path.display()))?
|
||||
.to_string();
|
||||
|
||||
Ok(new_name)
|
||||
}
|
||||
|
||||
/// Returns the path to the file in the slides dir given the path to the original image
|
||||
fn slide_path(path: &Path, file_name: &str) -> PathBuf {
|
||||
let mut new_path = path.to_path_buf();
|
||||
new_path.pop();
|
||||
new_path.push("slides");
|
||||
new_path.push(file_name);
|
||||
new_path
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn image_paths() {
|
||||
let img = Image::new(PathBuf::from("foo/bar/image.jpg"), String::new()).unwrap();
|
||||
assert_eq!(
|
||||
img.thumb_path,
|
||||
PathBuf::from("foo/bar/slides/image.thumb.jpg")
|
||||
);
|
||||
assert_eq!(
|
||||
img.screen_path,
|
||||
PathBuf::from("foo/bar/slides/image.screen.jpg")
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue