actually do renames, preserve original filename in new, don't rerename files
This commit is contained in:
parent
04932b2c77
commit
ace35c7c86
3 changed files with 69 additions and 13 deletions
|
@ -4,7 +4,7 @@ mod image;
|
|||
use crate::config::Config;
|
||||
use crate::generate::image::Image;
|
||||
use album_dir::AlbumDir;
|
||||
use anyhow::{anyhow, Context};
|
||||
use anyhow::{Context, anyhow};
|
||||
use indicatif::ProgressBar;
|
||||
use rayon::prelude::*;
|
||||
use serde::Serialize;
|
||||
|
@ -304,8 +304,6 @@ struct SlideContext {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::generate;
|
||||
use crate::skel::make_skeleton;
|
||||
use mktemp::Temp;
|
||||
use std::collections::{HashSet, VecDeque};
|
||||
use std::ffi::OsStr;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
@ -315,6 +313,7 @@ mod tests {
|
|||
#[test]
|
||||
/// Test that the generate function creates a rendered site as we expect it
|
||||
fn test_generate() {
|
||||
init();
|
||||
let album_path = make_test_album();
|
||||
let output_path = generate(&album_path.to_path_buf(), false).unwrap();
|
||||
|
||||
|
|
|
@ -50,6 +50,11 @@ enum Commands {
|
|||
},
|
||||
/// Reorganize photos in an album by date
|
||||
Reorganize {
|
||||
/// Directory of images you want to reorganize. Only image files will be moved.
|
||||
///
|
||||
/// The new image filenames will be the date and time taken, followed by the original
|
||||
/// filename. For example:
|
||||
/// original_filename.jpg -> YYYYMMDD_HHSS_original_filename.jpg
|
||||
#[arg()]
|
||||
path: String,
|
||||
/// Don't actually reorganize, just say what renames would happen
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use anyhow::{anyhow, Context};
|
||||
use image::ImageReader;
|
||||
use std::fs::File;
|
||||
use std::ffi::OsStr;
|
||||
use std::fs::{rename, File};
|
||||
use std::io::BufReader;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::str::from_utf8;
|
||||
|
@ -17,9 +18,27 @@ pub enum OrganizeError {
|
|||
}
|
||||
|
||||
pub fn reorganize(dir: &Path, dry_run: bool) -> anyhow::Result<()> {
|
||||
let renames = get_renames(dir);
|
||||
let renames = get_renames(dir)?;
|
||||
|
||||
if renames.len() == 0 {
|
||||
println!("Nothing to rename");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Either do the renames, or if dry-run print what the names would be
|
||||
if dry_run {
|
||||
for (src, dst) in renames {
|
||||
println!("{} -> {}", src.display(), dst.display());
|
||||
}
|
||||
println!("Would have renamed the above files");
|
||||
} else {
|
||||
for (src, dst) in renames {
|
||||
println!("{} -> {}", src.display(), dst.display());
|
||||
rename(&src, &dst).with_context(|| {
|
||||
format!("Failed to rename {} to {}", src.display(), dst.display())
|
||||
})?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -32,7 +51,7 @@ fn get_renames(dir: &Path) -> anyhow::Result<Vec<(PathBuf, PathBuf)>> {
|
|||
for entry in dir.read_dir()? {
|
||||
let entry = entry?;
|
||||
|
||||
// Only bother with image files, because we return an error if we fail to read EXIF
|
||||
// Only bother with image files, because those are the only hope for EXIF
|
||||
let is_image: bool = ImageReader::open(entry.path())?
|
||||
.with_guessed_format()?
|
||||
.format()
|
||||
|
@ -53,6 +72,13 @@ fn get_renames(dir: &Path) -> anyhow::Result<Vec<(PathBuf, PathBuf)>> {
|
|||
);
|
||||
continue;
|
||||
};
|
||||
let orig_filename = entry
|
||||
.path()
|
||||
.file_name()
|
||||
.unwrap_or(OsStr::new(""))
|
||||
.to_string_lossy()
|
||||
.into_owned();
|
||||
|
||||
let ext = entry
|
||||
.path()
|
||||
.extension()
|
||||
|
@ -64,11 +90,18 @@ fn get_renames(dir: &Path) -> anyhow::Result<Vec<(PathBuf, PathBuf)>> {
|
|||
.to_string();
|
||||
|
||||
let new_filename_base = dt.format(format_description!(
|
||||
"[year][month][day]_[hour][minute][second]"
|
||||
"[year][month][day]_[hour][minute][second]_"
|
||||
))?;
|
||||
|
||||
// Renaming an already-renamed file should be a no-op
|
||||
if orig_filename.starts_with(&new_filename_base) {
|
||||
log::info!("{orig_filename} looks like it was already renamed, skiping");
|
||||
continue;
|
||||
}
|
||||
|
||||
let new_path = entry
|
||||
.path()
|
||||
.with_file_name(new_filename_base)
|
||||
.with_file_name(new_filename_base + &orig_filename)
|
||||
.with_extension(ext);
|
||||
|
||||
renames.push((entry.path(), new_path.clone()));
|
||||
|
@ -84,6 +117,10 @@ fn get_renames(dir: &Path) -> anyhow::Result<Vec<(PathBuf, PathBuf)>> {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort renames by the destination
|
||||
renames.sort_by_key(|(_, dst)| dst.clone());
|
||||
|
||||
Ok(renames)
|
||||
}
|
||||
|
||||
|
@ -110,9 +147,9 @@ fn get_exif_datetime(path: PathBuf) -> anyhow::Result<UtcDateTime> {
|
|||
let s = from_utf8(&v[0])?;
|
||||
log::debug!("Date string from file: {s}");
|
||||
|
||||
match OffsetDateTime::parse(&s, format_with_offset) {
|
||||
match OffsetDateTime::parse(s, format_with_offset) {
|
||||
Ok(v) => v.to_utc(),
|
||||
Err(_) => PrimitiveDateTime::parse(&s, format_without_offset)?.as_utc(),
|
||||
Err(_) => PrimitiveDateTime::parse(s, format_without_offset)?.as_utc(),
|
||||
}
|
||||
}
|
||||
// TODO: return some error
|
||||
|
@ -163,10 +200,25 @@ mod tests {
|
|||
assert_eq!(
|
||||
renames,
|
||||
vec![
|
||||
(dir.join("mountains.jpg"), dir.join("19700103_133700.jpg")),
|
||||
(dir.join("moon.jpg"), dir.join("19700102_133700.jpg")),
|
||||
(dir.join("moon.txt"), dir.join("19700102_133700.txt")),
|
||||
(dir.join("moon.jpg"), dir.join("19700102_133700_moon.jpg")),
|
||||
(dir.join("moon.txt"), dir.join("19700102_133700_moon.txt")),
|
||||
(
|
||||
dir.join("mountains.jpg"),
|
||||
dir.join("19700103_133700_mountains.jpg")
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// 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.
|
||||
fn rerename() {
|
||||
let tmp_album_dir = make_test_album();
|
||||
let dir = tmp_album_dir.join("with_description");
|
||||
reorganize(&dir, false).unwrap();
|
||||
|
||||
let renames = get_renames(&dir).unwrap();
|
||||
assert_eq!(renames, Vec::new());
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue