mirror of
https://github.com/storytold/lightcraft.git
synced 2026-10-27 03:27:14 -06:00
LR-LIB-BYDATE + LR-META-COPYRIGHT-DEFAULT: By Date day tree, default copyright/creator on import
- By Date: year → month → day rows with counts and disclosure triangles; a row filters by
that date prefix (catalog date_groups now carries days).
- Settings → Import → Metadata: copyright and creator given to imported photos that don't
carry their own (library.preferences {import: {copyright, creator}}), saved with the library.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
852e8fca45
commit
b9f7ceb2d7
@@ -42,6 +42,12 @@ fn prefs(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
if let Some(v) = i.get("perCamera").and_then(Value::as_bool) {
|
||||
d.per_camera = v;
|
||||
}
|
||||
if let Some(v) = i.get("copyright").and_then(Value::as_str) {
|
||||
d.copyright = v.trim().to_string();
|
||||
}
|
||||
if let Some(v) = i.get("creator").and_then(Value::as_str) {
|
||||
d.creator = v.trim().to_string();
|
||||
}
|
||||
if let Some(a) = i.get("cameras").and_then(Value::as_array) {
|
||||
d.cameras.clear();
|
||||
for c in a {
|
||||
@@ -84,7 +90,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
"Library Preferences",
|
||||
[],
|
||||
None,
|
||||
"{import?: {rawPreset?: presetId|\"default\", otherPreset?: presetId|\"default\", perCamera?: bool, cameras?: [{camera: \"Make Model\", preset: presetId|null}]}, camera?: {camera, preset?, remove?: bool}, cacheMb?: n (0 = default)} — develop defaults applied on import (raws / other images / per camera) and the thumbnail cache size, saved with the library → {xmp, import, cacheMb, persistent}",
|
||||
"{import?: {rawPreset?: presetId|\"default\", otherPreset?: presetId|\"default\", perCamera?: bool, cameras?: [{camera: \"Make Model\", preset: presetId|null}], copyright?: text, creator?: text (given to imported photos without one)}, camera?: {camera, preset?, remove?: bool}, cacheMb?: n (0 = default)} — develop defaults applied on import (raws / other images / per camera) and the thumbnail cache size, saved with the library → {xmp, import, cacheMb, persistent}",
|
||||
always,
|
||||
prefs
|
||||
)]
|
||||
|
||||
@@ -120,6 +120,10 @@ pub struct ImportDefaults {
|
||||
pub cameras: Vec<CameraDefault>,
|
||||
/// Preset applied to non-raw images (JPEG, PNG, TIFF, HEIC…; `None` = none).
|
||||
pub other_preset: Option<String>,
|
||||
/// Copyright notice given to imported photos that don't carry one (empty = none).
|
||||
pub copyright: String,
|
||||
/// Creator given to imported photos that don't name one (empty = none).
|
||||
pub creator: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
|
||||
@@ -149,6 +153,14 @@ impl ImportDefaults {
|
||||
/// Give a freshly imported photo its default settings: the camera defaults, then the matching
|
||||
/// default preset ([`ImportDefaults::preset_for`]), remembered as the photo's import look.
|
||||
pub fn apply_import_defaults(s: &Session, p: &mut Photo) {
|
||||
// metadata defaults fill gaps only: the file's own copyright / creator win
|
||||
let d = &s.import_defaults;
|
||||
if p.meta.copyright.trim().is_empty() && !d.copyright.trim().is_empty() {
|
||||
p.meta.copyright = d.copyright.trim().to_string();
|
||||
}
|
||||
if p.meta.creator.trim().is_empty() && !d.creator.trim().is_empty() {
|
||||
p.meta.creator = d.creator.trim().to_string();
|
||||
}
|
||||
let base = p.camera_defaults();
|
||||
let raw = p.kind == lightcraft_catalog::MediaKind::Raw;
|
||||
let preset = s.import_defaults.preset_for(raw, &p.meta.camera).and_then(|id| s.presets.iter().find(|x| x.id == id));
|
||||
|
||||
@@ -137,3 +137,29 @@ fn preferences_persist_with_the_library() {
|
||||
assert_eq!(s.import_defaults, Default::default());
|
||||
let _ = std::fs::remove_dir_all(&lib);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_copyright_and_creator_fill_gaps_on_import() {
|
||||
let dir = temp_dir("copyright");
|
||||
let lib = dir.join("lib");
|
||||
png(&dir.join("plain.png"));
|
||||
// a DNG that names its own artist keeps it
|
||||
let meta = lightcraft_meta::Metadata { artist: Some("Someone Else".into()), ..Default::default() };
|
||||
std::fs::write(dir.join("own.dng"), crate::tests_xmp::synthetic_dng_with(None, meta)).unwrap();
|
||||
let mut s = Session::new().with_fs();
|
||||
s.open_library(&lib, false).unwrap();
|
||||
let r = s.execute("library.preferences", &json!({"import": {"copyright": " © 2026 Me ", "creator": "Me"}})).unwrap();
|
||||
assert_eq!(r["import"]["copyright"], "© 2026 Me");
|
||||
s.execute("library.import", &json!({"paths": [dir.join("plain.png").to_string_lossy(), dir.join("own.dng").to_string_lossy()]})).unwrap();
|
||||
let plain = photo_named(&s, "plain.png");
|
||||
assert_eq!((plain.meta.copyright.as_str(), plain.meta.creator.as_str()), ("© 2026 Me", "Me"));
|
||||
let own = photo_named(&s, "own.dng");
|
||||
assert_eq!(own.meta.creator, "Someone Else", "the file's own creator wins");
|
||||
assert_eq!(own.meta.copyright, "© 2026 Me");
|
||||
// saved with the library
|
||||
drop(s);
|
||||
let mut s = Session::new().with_fs();
|
||||
s.open_library(&lib, false).unwrap();
|
||||
assert_eq!(s.import_defaults.creator, "Me");
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user