Compare commits
5 commits
main
...
feature/re
| Author | SHA1 | Date | |
|---|---|---|---|
| c151ac409b | |||
| ace35c7c86 | |||
| 04932b2c77 | |||
| 89f6ea5335 | |||
| 16699d86a8 |
7 changed files with 555 additions and 560 deletions
994
Cargo.lock
generated
994
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
34
Cargo.toml
34
Cargo.toml
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "photojawn"
|
name = "photojawn"
|
||||||
version = "0.2.0"
|
version = "0.2.0-pre.1"
|
||||||
description = "A static site generator for photo albums"
|
description = "A static site generator for photo albums"
|
||||||
authors = ["Nick Pegg <nick@nickpegg.com>"]
|
authors = ["Nick Pegg <nick@nickpegg.com>"]
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|
@ -8,21 +8,21 @@ repository = "https://github.com/nickpegg/photojawn"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0"
|
anyhow = "^1.0"
|
||||||
clap = { version = "4.5", features = ["derive"] }
|
clap = { version = "^4.5", features = ["derive"] }
|
||||||
env_logger = "0.11.8"
|
env_logger = "^0.11.8"
|
||||||
fs_extra = "1.3"
|
fs_extra = "^1.3.0"
|
||||||
image = "0.25.6"
|
image = "^0.25.6"
|
||||||
indicatif = "0.17.11"
|
indicatif = "^0.17.11"
|
||||||
kamadak-exif = "0.6.1"
|
kamadak-exif = "^0.6.1"
|
||||||
log = "0.4.27"
|
log = "^0.4.27"
|
||||||
pulldown-cmark = "0.13.0"
|
pulldown-cmark = "^0.13.0"
|
||||||
rayon = "1.10"
|
rayon = "^1.10.0"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "^1.0", features = ["derive"] }
|
||||||
serde_yaml_ng = "0.10.0"
|
serde_yml = "^0.0.12"
|
||||||
tera = { version = "1.20", default-features = false }
|
tera = { version = "^1.20", default-features = false }
|
||||||
thiserror = "2.0"
|
thiserror = "^2.0"
|
||||||
time = { version = "0.3.41", features = ["formatting", "macros", "parsing"] }
|
time = { version = "^0.3.41", features = ["formatting", "macros", "parsing"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
mktemp = "0.5.1"
|
mktemp = "^0.5.1"
|
||||||
|
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 50 KiB |
|
|
@ -23,7 +23,7 @@ impl Config {
|
||||||
config_path.display(),
|
config_path.display(),
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
let cfg = serde_yaml_ng::from_slice(&content)
|
let cfg = serde_yml::from_slice(&content)
|
||||||
.with_context(|| format!("Failed to parse config from {}", config_path.display()))?;
|
.with_context(|| format!("Failed to parse config from {}", config_path.display()))?;
|
||||||
Ok(cfg)
|
Ok(cfg)
|
||||||
}
|
}
|
||||||
|
|
@ -56,11 +56,11 @@ mod test {
|
||||||
fn from_yaml() {
|
fn from_yaml() {
|
||||||
// Empty YAML gives full default values
|
// Empty YAML gives full default values
|
||||||
let default_cfg = Config::default();
|
let default_cfg = Config::default();
|
||||||
let cfg: Config = serde_yaml_ng::from_str("").unwrap();
|
let cfg: Config = serde_yml::from_str("").unwrap();
|
||||||
assert_eq!(cfg, default_cfg);
|
assert_eq!(cfg, default_cfg);
|
||||||
|
|
||||||
// Default values for any unspecified fields
|
// Default values for any unspecified fields
|
||||||
let cfg: Config = serde_yaml_ng::from_str("thumbnail_size: [1, 1]").unwrap();
|
let cfg: Config = serde_yml::from_str("thumbnail_size: [1, 1]").unwrap();
|
||||||
assert_ne!(cfg, default_cfg);
|
assert_ne!(cfg, default_cfg);
|
||||||
assert_eq!(cfg.thumbnail_size, (1, 1));
|
assert_eq!(cfg.thumbnail_size, (1, 1));
|
||||||
assert_eq!(cfg.view_size, default_cfg.view_size);
|
assert_eq!(cfg.view_size, default_cfg.view_size);
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ pub struct AlbumDir {
|
||||||
|
|
||||||
impl AlbumDir {
|
impl AlbumDir {
|
||||||
/// Returns an iterator over all images in the album and subalbums
|
/// Returns an iterator over all images in the album and subalbums
|
||||||
pub fn iter_all_images(&self) -> AlbumImageIter<'_> {
|
pub fn iter_all_images(&self) -> AlbumImageIter {
|
||||||
AlbumImageIter::new(self)
|
AlbumImageIter::new(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -79,20 +79,20 @@ impl AlbumDir {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if entry_path.is_dir()
|
} else if entry_path.is_dir() {
|
||||||
&& let Some(dirname) = entry_path.file_name().and_then(|n| n.to_str())
|
if let Some(dirname) = entry_path.file_name().and_then(|n| n.to_str()) {
|
||||||
{
|
if dirname.starts_with("_") {
|
||||||
if dirname.starts_with("_") {
|
// Likely a templates or static dir
|
||||||
// Likely a templates or static dir
|
continue;
|
||||||
continue;
|
} else if dirname == "site" {
|
||||||
} else if dirname == "site" {
|
// Is a generated site dir, don't descend into it
|
||||||
// Is a generated site dir, don't descend into it
|
continue;
|
||||||
continue;
|
} else if dirname == "slides" {
|
||||||
} else if dirname == "slides" {
|
continue;
|
||||||
continue;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
children.push(AlbumDir::from_path(&entry_path, root)?);
|
children.push(AlbumDir::from_path(&entry_path, root)?);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -55,15 +55,17 @@ impl Image {
|
||||||
/// return "blah.thumb"
|
/// return "blah.thumb"
|
||||||
fn slide_filename(path: &Path, ext: &str, keep_ext: bool) -> anyhow::Result<String> {
|
fn slide_filename(path: &Path, ext: &str, keep_ext: bool) -> anyhow::Result<String> {
|
||||||
let mut new_ext: OsString = ext.into();
|
let mut new_ext: OsString = ext.into();
|
||||||
if keep_ext && let Some(e) = path.extension() {
|
if keep_ext {
|
||||||
new_ext = OsString::from(
|
if let Some(e) = path.extension() {
|
||||||
ext.to_string()
|
new_ext = OsString::from(
|
||||||
+ "."
|
ext.to_string()
|
||||||
+ e.to_str().ok_or(anyhow!(
|
+ "."
|
||||||
"Image {} extension is not valid UTF-8",
|
+ e.to_str().ok_or(anyhow!(
|
||||||
path.display()
|
"Image {} extension is not valid UTF-8",
|
||||||
))?,
|
path.display()
|
||||||
)
|
))?,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let new_path = path.with_extension(new_ext);
|
let new_path = path.with_extension(new_ext);
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use anyhow::{Context, anyhow};
|
use anyhow::{anyhow, Context};
|
||||||
use image::ImageReader;
|
use image::ImageReader;
|
||||||
use std::ffi::OsStr;
|
use std::ffi::OsStr;
|
||||||
use std::fs::{File, rename};
|
use std::fs::{rename, File};
|
||||||
use std::io::BufReader;
|
use std::io::BufReader;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::str::from_utf8;
|
use std::str::from_utf8;
|
||||||
|
|
@ -51,10 +51,6 @@ fn get_renames(dir: &Path) -> anyhow::Result<Vec<(PathBuf, PathBuf)>> {
|
||||||
for entry in dir.read_dir()? {
|
for entry in dir.read_dir()? {
|
||||||
let entry = entry?;
|
let entry = entry?;
|
||||||
|
|
||||||
if !entry.path().is_file() {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only bother with image files, because those are the only hope for EXIF
|
// Only bother with image files, because those are the only hope for EXIF
|
||||||
let is_image: bool = ImageReader::open(entry.path())?
|
let is_image: bool = ImageReader::open(entry.path())?
|
||||||
.with_guessed_format()?
|
.with_guessed_format()?
|
||||||
|
|
@ -138,6 +134,7 @@ fn get_exif_datetime(path: PathBuf) -> anyhow::Result<UtcDateTime> {
|
||||||
|
|
||||||
let file = File::open(&path).with_context(|| format!("Couldn't open {}", path.display()))?;
|
let file = File::open(&path).with_context(|| format!("Couldn't open {}", path.display()))?;
|
||||||
let mut bufreader = BufReader::new(file);
|
let mut bufreader = BufReader::new(file);
|
||||||
|
// TODO: Return a better error if EXIF is not supported
|
||||||
let exif = exif::Reader::new()
|
let exif = exif::Reader::new()
|
||||||
.read_from_container(&mut bufreader)
|
.read_from_container(&mut bufreader)
|
||||||
.with_context(|| format!("Couldn't read EXIF data from {}", path.display()))?;
|
.with_context(|| format!("Couldn't read EXIF data from {}", path.display()))?;
|
||||||
|
|
@ -155,7 +152,8 @@ fn get_exif_datetime(path: PathBuf) -> anyhow::Result<UtcDateTime> {
|
||||||
Err(_) => PrimitiveDateTime::parse(s, format_without_offset)?.as_utc(),
|
Err(_) => PrimitiveDateTime::parse(s, format_without_offset)?.as_utc(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => return Err(OrganizeError::ExifNoDateTime(path).into()),
|
// TODO: return some error
|
||||||
|
_ => todo!(),
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(dt)
|
Ok(dt)
|
||||||
|
|
@ -191,7 +189,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_basic_renames() {
|
fn basic_renames() {
|
||||||
init();
|
init();
|
||||||
let tmp_album_dir = make_test_album();
|
let tmp_album_dir = make_test_album();
|
||||||
let dir = tmp_album_dir.join("with_description");
|
let dir = tmp_album_dir.join("with_description");
|
||||||
|
|
@ -212,27 +210,10 @@ mod tests {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
/// get_renames() should ignore other stuff in the directory
|
|
||||||
fn test_other_junk() {
|
|
||||||
init();
|
|
||||||
let tmp_album_dir = make_test_album();
|
|
||||||
|
|
||||||
let renames = get_renames(&tmp_album_dir).unwrap();
|
|
||||||
// No mountain.jpg since it doesn't have EXIF data
|
|
||||||
assert_eq!(
|
|
||||||
renames,
|
|
||||||
vec![(
|
|
||||||
tmp_album_dir.join("moon.jpg"),
|
|
||||||
tmp_album_dir.join("19700101_133700_moon.jpg")
|
|
||||||
)]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
/// The rename function will prepend date and time to the original filenames. If we do it a
|
/// The rename function will prepend date and time to the original filenames. If we do it a
|
||||||
/// second time, it should be a no-op instead of continuing to prepend date and time.
|
/// second time, it should be a no-op instead of continuing to prepend date and time.
|
||||||
fn test_rerename() {
|
fn rerename() {
|
||||||
let tmp_album_dir = make_test_album();
|
let tmp_album_dir = make_test_album();
|
||||||
let dir = tmp_album_dir.join("with_description");
|
let dir = tmp_album_dir.join("with_description");
|
||||||
reorganize(&dir, false).unwrap();
|
reorganize(&dir, false).unwrap();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue