[niepce/lr-import: 10/20] Issue #40 - npc-engine: Add image to albums and set properties
- From: Hubert Figuière <hub src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [niepce/lr-import: 10/20] Issue #40 - npc-engine: Add image to albums and set properties
- Date: Mon, 20 Dec 2021 05:37:15 +0000 (UTC)
commit d6bba01cb79e4c502c90818c8ed679932fb7c203
Author: Hubert Figuière <hub figuiere net>
Date: Wed Nov 17 22:07:54 2021 -0500
Issue #40 - npc-engine: Add image to albums and set properties
- more properties need to be handled
- LrImport imports the collections into albums
https://gitlab.gnome.org/GNOME/niepce/-/issues/40
Cargo.lock | 4 +-
crates/npc-engine/Cargo.toml | 2 +-
crates/npc-engine/src/db/library.rs | 79 ++++++++++++-
crates/npc-engine/src/db/props.rs | 2 +
crates/npc-engine/src/importer/lrimporter.rs | 124 +++++++++++++++------
crates/npc-engine/src/library/commands.rs | 43 +++++++
crates/npc-engine/src/library/notification.rs | 3 +
crates/npc-engine/src/libraryclient.rs | 11 ++
.../src/libraryclient/clientinterface.rs | 7 ++
9 files changed, 233 insertions(+), 42 deletions(-)
---
diff --git a/Cargo.lock b/Cargo.lock
index ce38620..f7874ed 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -680,9 +680,9 @@ dependencies = [
[[package]]
name = "lrcat-extractor"
-version = "0.1.0"
+version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ee7ae5136c7085d85886d3a31e9bda2bce03c750b143beca4d8d1073e30bef7e"
+checksum = "fafe70f346fd8591519612694441d6e6a0f7aa4402a5e007b8e916754686b633"
dependencies = [
"chrono",
"peg",
diff --git a/crates/npc-engine/Cargo.toml b/crates/npc-engine/Cargo.toml
index 0c2090a..2425eaf 100644
--- a/crates/npc-engine/Cargo.toml
+++ b/crates/npc-engine/Cargo.toml
@@ -21,7 +21,7 @@ glib = "*"
lazy_static = "^1.2.0"
libc = "0.2.39"
maplit = "1.0.2"
-lrcat-extractor = { version = "0.1.0" }
+lrcat-extractor = { version = "0.2.0" }
rusqlite = { version = "0.26.1", features = ["functions"] }
npc-fwk = { path = "../npc-fwk" }
diff --git a/crates/npc-engine/src/db/library.rs b/crates/npc-engine/src/db/library.rs
index 6e010a4..48aaa0b 100644
--- a/crates/npc-engine/src/db/library.rs
+++ b/crates/npc-engine/src/db/library.rs
@@ -26,8 +26,6 @@ use std::result;
use chrono::Utc;
use rusqlite::{functions::FunctionFlags, params};
-use super::props::NiepceProperties as Np;
-use super::props::NiepcePropertyIdx::*;
use super::{FromDb, LibraryId};
use crate::db::album::Album;
use crate::db::filebundle::{FileBundle, Sidecar};
@@ -39,6 +37,9 @@ use crate::db::libfolder;
use crate::db::libfolder::LibFolder;
use crate::db::libmetadata::LibMetadata;
use crate::library::notification::LibNotification;
+use crate::NiepceProperties as Np;
+use crate::NiepcePropertyBag;
+use crate::NiepcePropertyIdx::*;
use npc_fwk::toolkit;
use npc_fwk::PropertyValue;
@@ -54,6 +55,7 @@ const DATABASENAME: &str = "niepcelibrary.db";
#[derive(Debug)]
pub enum Error {
+ Unimplemented,
NotFound,
NoSqlDb,
IncorrectDbVersion,
@@ -662,6 +664,21 @@ impl Library {
Err(Error::NoSqlDb)
}
+ /// Add an image to an album.
+ pub fn add_to_album(&self, image_id: LibraryId, album_id: LibraryId) -> Result<()> {
+ if let Some(ref conn) = self.dbconn {
+ let c = conn.execute(
+ "INSERT INTO albuming (file_id, album_id) VALUES(?1, ?2)",
+ params![image_id, album_id],
+ )?;
+ if c != 1 {
+ return Err(Error::InvalidResult);
+ }
+ return Ok(());
+ }
+ Err(Error::NoSqlDb)
+ }
+
pub fn add_fs_file<P: AsRef<Path>>(&self, f: P) -> Result<LibraryId> {
if let Some(ref conn) = self.dbconn {
let file = f.as_ref().to_string_lossy();
@@ -936,6 +953,24 @@ impl Library {
Err(Error::NoSqlDb)
}
+ /// Set properties for an image.
+ pub fn set_image_properties(
+ &self,
+ image_id: LibraryId,
+ props: &NiepcePropertyBag,
+ ) -> Result<()> {
+ if let Some(ref conn) = self.dbconn {
+ if let Some(PropertyValue::String(xmp)) = props.get(&Np::Index(NpNiepceXmpPacket)) {
+ conn.execute(
+ "UPDATE files SET xmp=?1 WHERE id=?2;",
+ params![xmp, image_id],
+ )?;
+ }
+ return Ok(());
+ }
+ Err(Error::NoSqlDb)
+ }
+
fn set_internal_metadata(&self, file_id: LibraryId, column: &str, value: i32) -> Result<()> {
if let Some(ref conn) = self.dbconn {
let c = conn.execute(
@@ -968,7 +1003,6 @@ impl Library {
}
pub fn set_metadata(&self, file_id: LibraryId, meta: Np, value: &PropertyValue) -> Result<()> {
- #[allow(non_upper_case_globals)]
match meta {
Np::Index(NpXmpRatingProp)
| Np::Index(NpXmpLabelProp)
@@ -1201,6 +1235,9 @@ impl Library {
#[cfg(test)]
mod test {
use crate::db::filebundle::FileBundle;
+ use crate::NiepceProperties as Np;
+ use crate::NiepcePropertyBag;
+ use crate::NiepcePropertyIdx as NpI;
use super::{Library, Managed};
@@ -1293,11 +1330,21 @@ mod test {
let bundle_id = lib.add_bundle(folder_added.id(), &bundle, Managed::NO);
assert!(bundle_id.is_ok());
- assert!(bundle_id.ok().unwrap() > 0);
+ assert!(bundle_id.unwrap() > 0);
}
+ const XMP_PACKET: &str =
+ "<x:xmpmeta xmlns:x=\"adobe:ns:meta/\" x:xmptk=\"Exempi + XMP Core 5.1.2\"> \
+ <rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\"> \
+ </rdf:RDF> \
+</x:xmpmeta>";
+
#[test]
fn file_bundle_import() {
+ use npc_fwk::utils::exempi::XmpMeta;
+
+ exempi::init();
+
let lib = Library::new_in_memory();
assert!(lib.is_ok());
@@ -1320,6 +1367,28 @@ mod test {
let bundle_id = lib.add_bundle(folder_added.id(), &bundle, Managed::NO);
assert!(bundle_id.is_ok());
- assert!(bundle_id.ok().unwrap() > 0);
+ let bundle_id = bundle_id.unwrap();
+ assert!(bundle_id > 0);
+
+ // Test setting properties
+
+ let mut props = NiepcePropertyBag::new();
+ props.set_value(Np::Index(NpI::NpNiepceXmpPacket), XMP_PACKET.into());
+ // one of the problem with XMP packet serialisation is that the version
+ // of the XMP SDK is written in the header so we can do comparisons
+ // byte by byte
+ let original_xmp_packet =
+ exempi::Xmp::from_buffer(XMP_PACKET.as_bytes()).expect("XMP packet created");
+ let original_xmp_packet = XmpMeta::new_with_xmp(original_xmp_packet);
+ let result = lib.set_image_properties(bundle_id, &props);
+ result.expect("Setting the XMP works");
+
+ let result = lib.get_metadata(bundle_id);
+ let metadata = result.expect("Have retrieved metadata");
+ let xmp_packet = metadata.serialize_inline();
+ assert_eq!(
+ xmp_packet.as_str(),
+ original_xmp_packet.serialize_inline().as_str()
+ );
}
}
diff --git a/crates/npc-engine/src/db/props.rs b/crates/npc-engine/src/db/props.rs
index 180fff0..35c705b 100644
--- a/crates/npc-engine/src/db/props.rs
+++ b/crates/npc-engine/src/db/props.rs
@@ -35,6 +35,8 @@ pub enum NiepcePropertyIdx {
NpIptcDescriptionProp,
NpIptcKeywordsProp,
NpNiepceFlagProp,
+ /// The XMP Packet
+ NpNiepceXmpPacket,
}
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[allow(unused_parens)]
diff --git a/crates/npc-engine/src/importer/lrimporter.rs b/crates/npc-engine/src/importer/lrimporter.rs
index 7f6c602..7db02e1 100644
--- a/crates/npc-engine/src/importer/lrimporter.rs
+++ b/crates/npc-engine/src/importer/lrimporter.rs
@@ -23,15 +23,19 @@ use std::collections::BTreeMap;
use std::path::Path;
use lrcat::{
- Catalog, CatalogVersion, Collection, Folder, Keyword, KeywordTree, LibraryFile, LrId, LrObject,
+ Catalog, CatalogVersion, Collection, Folder, Image, Keyword, KeywordTree, LibraryFile, LrId,
+ LrObject,
};
use super::libraryimporter::LibraryImporter;
use crate::db::filebundle::FileBundle;
+use crate::db::props::NiepceProperties as Np;
+use crate::db::props::NiepcePropertyIdx as NpI;
use crate::db::LibraryId;
-use crate::libraryclient::{ClientInterfaceSync, LibraryClient};
+use crate::libraryclient::{ClientInterface, ClientInterfaceSync, LibraryClient};
+use crate::NiepcePropertyBag;
-/// Library importer for Lightroom
+/// Library importer for Lightroom™
pub struct LrImporter {
/// map keyword LrId to LibraryId
folder_map: BTreeMap<LrId, (LibraryId, String)>,
@@ -41,6 +45,10 @@ pub struct LrImporter {
collection_map: BTreeMap<LrId, LibraryId>,
/// map files LrId to file LibraryId
file_map: BTreeMap<LrId, LibraryId>,
+ /// map image LrId to file LibraryId
+ ///
+ /// XXX longer term is to have an image table.
+ image_map: BTreeMap<LrId, LibraryId>,
}
impl LrImporter {
@@ -71,10 +79,52 @@ impl LrImporter {
self.folder_map.insert(folder.id(), (nid, path.into()));
}
- fn import_collection(&mut self, collection: &Collection, libclient: &mut LibraryClient) {
+ fn import_collection(
+ &mut self,
+ collection: &Collection,
+ images: Option<&Vec<LrId>>,
+ libclient: &mut LibraryClient,
+ ) {
let parent = self.collection_map.get(&collection.parent).unwrap_or(&-1);
let nid = libclient.create_album_sync(collection.name.clone(), *parent);
self.collection_map.insert(collection.id(), nid);
+
+ if let Some(images) = images {
+ dbg_out!("Has images");
+ images.iter().for_each(|id| {
+ self.image_map.get(&id).map(|npc_image_id| {
+ dbg_out!("adding {} to album {}", npc_image_id, nid);
+ libclient.add_to_album(*npc_image_id, nid);
+ });
+ });
+ }
+ }
+
+ fn populate_bundle(file: &LibraryFile, folder_path: &str, bundle: &mut FileBundle) {
+ let mut xmp_file: Option<String> = None;
+ let mut jpeg_file: Option<String> = None;
+ let sidecar_exts = file.sidecar_extensions.split(",");
+ sidecar_exts.for_each(|ext| {
+ if !ext.is_empty() {
+ return;
+ }
+ if ext.to_lowercase() == "xmp" {
+ xmp_file = Some(format!("{}/{}.{}", &folder_path, &file.basename, &ext));
+ } else if jpeg_file.is_some() {
+ err_out!("JPEG sidecar already set: {}", ext);
+ } else {
+ jpeg_file = Some(format!("{}/{}.{}", &folder_path, &file.basename, &ext));
+ }
+ });
+
+ if let Some(jpeg_file) = jpeg_file {
+ dbg_out!("Adding JPEG {}", &jpeg_file);
+ bundle.add(jpeg_file);
+ }
+ if let Some(xmp_file) = xmp_file {
+ dbg_out!("Adding XMP {}", &xmp_file);
+ bundle.add(xmp_file);
+ }
}
fn import_library_file(&mut self, file: &LibraryFile, libclient: &mut LibraryClient) {
@@ -85,35 +135,28 @@ impl LrImporter {
bundle.add(main_file);
if !file.sidecar_extensions.is_empty() {
- let mut xmp_file: Option<String> = None;
- let mut jpeg_file: Option<String> = None;
- let sidecar_exts = file.sidecar_extensions.split(",");
- sidecar_exts.for_each(|ext| {
- if !ext.is_empty() {
- return;
- }
- if ext.to_lowercase() == "xmp" {
- xmp_file = Some(format!("{}/{}.{}", &folder_id.1, &file.basename, &ext));
- } else if jpeg_file.is_some() {
- err_out!("JPEG sidecar already set: {}", ext);
- } else {
- jpeg_file = Some(format!("{}/{}.{}", &folder_id.1, &file.basename, &ext));
- }
- });
-
- if let Some(jpeg_file) = jpeg_file {
- dbg_out!("Adding JPEG {}", &jpeg_file);
- bundle.add(jpeg_file);
- }
- if let Some(xmp_file) = xmp_file {
- dbg_out!("Adding XMP {}", &xmp_file);
- bundle.add(xmp_file);
- }
+ Self::populate_bundle(file, &folder_id.1, &mut bundle);
}
+
let nid = libclient.add_bundle_sync(&bundle, folder_id.0);
self.file_map.insert(file.id(), nid);
}
}
+
+ fn import_image(&mut self, image: &Image, libclient: &mut LibraryClient) {
+ let root_file = image.root_file;
+ if let Some(file_id) = self.file_map.get(&root_file) {
+ let mut metadata = NiepcePropertyBag::new();
+ metadata.set_value(
+ Np::Index(NpI::NpTiffOrientationProp),
+ image.exif_orientation().into(),
+ );
+ metadata.set_value(Np::Index(NpI::NpNiepceFlagProp), (image.pick as i32).into());
+ metadata.set_value(Np::Index(NpI::NpNiepceXmpPacket), image.xmp.as_str().into());
+ libclient.set_image_properties(*file_id, &metadata);
+ self.image_map.insert(image.id(), *file_id);
+ }
+ }
}
impl LibraryImporter for LrImporter {
@@ -123,6 +166,7 @@ impl LibraryImporter for LrImporter {
keyword_map: BTreeMap::new(),
collection_map: BTreeMap::new(),
file_map: BTreeMap::new(),
+ image_map: BTreeMap::new(),
}
}
@@ -149,18 +193,30 @@ impl LibraryImporter for LrImporter {
let keywords = catalog.load_keywords();
self.import_keyword(root_keyword_id, libclient, keywords, &keywordtree);
- let collections = catalog.load_collections();
+ let library_files = catalog.load_library_files();
+ library_files.iter().for_each(|library_file| {
+ self.import_library_file(library_file, libclient);
+ });
+
+ let images = catalog.load_images();
+ images.iter().for_each(|image| {
+ self.import_image(image, libclient);
+ });
+
+ catalog.load_collections();
+ let collections = catalog.collections();
collections.iter().for_each(|collection| {
if !collection.system_only {
dbg_out!("Found collection {}", &collection.name);
- self.import_collection(&collection, libclient);
+ let images = catalog.images_for_collection(collection.id()).ok();
+ dbg_out!(
+ "Found {} images in collection",
+ images.as_ref().map(Vec::len).unwrap_or(0)
+ );
+ self.import_collection(&collection, images.as_ref(), libclient);
}
});
- let library_files = catalog.load_library_files();
- library_files.iter().for_each(|library_file| {
- self.import_library_file(library_file, libclient);
- });
true
}
diff --git a/crates/npc-engine/src/library/commands.rs b/crates/npc-engine/src/library/commands.rs
index 38e66f2..a60999a 100644
--- a/crates/npc-engine/src/library/commands.rs
+++ b/crates/npc-engine/src/library/commands.rs
@@ -30,6 +30,7 @@ use crate::db::library;
use crate::db::library::{Library, Managed};
use crate::db::props::NiepceProperties as Np;
use crate::db::LibraryId;
+use crate::NiepcePropertyBag;
use npc_fwk::PropertyValue;
pub fn cmd_list_all_keywords(lib: &Library) -> bool {
@@ -213,6 +214,30 @@ pub fn cmd_create_album(lib: &Library, name: &str, parent: LibraryId) -> Library
}
}
+/// Command to add an image to an album.
+pub fn cmd_add_to_album(lib: &Library, image_id: LibraryId, album_id: LibraryId) -> bool {
+ match lib.add_to_album(image_id, album_id) {
+ Ok(_) => {
+ if lib
+ .notify(LibNotification::AddedToAlbum((image_id, album_id)))
+ .is_err()
+ {
+ err_out!("Failed to notify AddedToAlbum");
+ }
+ true
+ }
+ Err(err) => {
+ err_out_line!(
+ "Adding image {} to album {} failed {:?}",
+ image_id,
+ album_id,
+ err
+ );
+ false
+ }
+ }
+}
+
pub fn cmd_request_metadata(lib: &Library, file_id: LibraryId) -> bool {
match lib.get_metadata(file_id) {
Ok(lm) => {
@@ -233,6 +258,24 @@ pub fn cmd_request_metadata(lib: &Library, file_id: LibraryId) -> bool {
}
}
+/// Command to set image properties.
+pub fn cmd_set_image_properties(
+ lib: &Library,
+ image_id: LibraryId,
+ props: &NiepcePropertyBag,
+) -> bool {
+ match lib.set_image_properties(image_id, props) {
+ Ok(_) => {
+ // XXX set the image properties.
+ true
+ }
+ Err(err) => {
+ err_out_line!("Setting image metadata failed {:?}", err);
+ false
+ }
+ }
+}
+
pub fn cmd_query_folder_content(lib: &Library, folder_id: LibraryId) -> bool {
match lib.get_folder_content(folder_id) {
Ok(fl) => {
diff --git a/crates/npc-engine/src/library/notification.rs b/crates/npc-engine/src/library/notification.rs
index 86bf61e..9747ce2 100644
--- a/crates/npc-engine/src/library/notification.rs
+++ b/crates/npc-engine/src/library/notification.rs
@@ -39,6 +39,7 @@ pub enum NotificationType {
ADDED_KEYWORD,
ADDED_LABEL,
ADDED_ALBUM,
+ ADDED_TO_ALBUM,
FOLDER_CONTENT_QUERIED,
FOLDER_DELETED,
FOLDER_COUNTED,
@@ -123,6 +124,7 @@ pub enum LibNotification {
AddedKeyword(Keyword),
AddedLabel(Label),
AddedAlbum(Album),
+ AddedToAlbum((LibraryId, LibraryId)),
FileMoved(FileMove),
FileStatusChanged(FileStatusChange),
FolderContentQueried(QueriedContent),
@@ -178,6 +180,7 @@ pub unsafe extern "C" fn engine_library_notification_type(
Some(&LibNotification::AddedKeyword(_)) => NotificationType::ADDED_KEYWORD,
Some(&LibNotification::AddedLabel(_)) => NotificationType::ADDED_LABEL,
Some(&LibNotification::AddedAlbum(_)) => NotificationType::ADDED_ALBUM,
+ Some(&LibNotification::AddedToAlbum(_)) => NotificationType::ADDED_TO_ALBUM,
Some(&LibNotification::FileMoved(_)) => NotificationType::FILE_MOVED,
Some(&LibNotification::FileStatusChanged(_)) => NotificationType::FILE_STATUS_CHANGED,
Some(&LibNotification::FolderContentQueried(_)) => NotificationType::FOLDER_CONTENT_QUERIED,
diff --git a/crates/npc-engine/src/libraryclient.rs b/crates/npc-engine/src/libraryclient.rs
index e6bb2bc..36c262c 100644
--- a/crates/npc-engine/src/libraryclient.rs
+++ b/crates/npc-engine/src/libraryclient.rs
@@ -38,6 +38,7 @@ use crate::db::{NiepceProperties, NiepcePropertyIdx};
use crate::library::commands;
use crate::library::notification::{LcChannel, LibNotification};
use crate::library::op::Op;
+use crate::NiepcePropertyBag;
use npc_fwk::base::PropertyValue;
use npc_fwk::toolkit::PortableChannel;
use npc_fwk::utils::files::FileList;
@@ -198,6 +199,11 @@ impl ClientInterface for LibraryClient {
self.schedule_op(move |lib| commands::cmd_create_album(&lib, &name, parent) != 0);
}
+ /// Add an image to an album.
+ fn add_to_album(&mut self, image_id: LibraryId, album_id: LibraryId) {
+ self.schedule_op(move |lib| commands::cmd_add_to_album(&lib, image_id, album_id));
+ }
+
fn request_metadata(&mut self, file_id: LibraryId) {
self.schedule_op(move |lib| commands::cmd_request_metadata(&lib, file_id));
}
@@ -207,6 +213,11 @@ impl ClientInterface for LibraryClient {
let value2 = value.clone();
self.schedule_op(move |lib| commands::cmd_set_metadata(&lib, file_id, meta, &value2));
}
+ fn set_image_properties(&mut self, image_id: LibraryId, props: &NiepcePropertyBag) {
+ let props = props.clone();
+ self.schedule_op(move |lib| commands::cmd_set_image_properties(&lib, image_id, &props));
+ }
+
fn write_metadata(&mut self, file_id: LibraryId) {
self.schedule_op(move |lib| commands::cmd_write_metadata(&lib, file_id));
}
diff --git a/crates/npc-engine/src/libraryclient/clientinterface.rs
b/crates/npc-engine/src/libraryclient/clientinterface.rs
index 80ba1ee..ab666d8 100644
--- a/crates/npc-engine/src/libraryclient/clientinterface.rs
+++ b/crates/npc-engine/src/libraryclient/clientinterface.rs
@@ -23,6 +23,7 @@ use crate::db::filebundle::FileBundle;
use crate::db::library::Managed;
use crate::db::props::NiepceProperties as Np;
use crate::db::LibraryId;
+use crate::NiepcePropertyBag;
use npc_fwk::base::PropertyValue;
/// Client interface.
@@ -39,12 +40,18 @@ pub trait ClientInterface {
fn create_folder(&mut self, name: String, path: Option<String>);
fn delete_folder(&mut self, id: LibraryId);
+ /// get all the albums
fn get_all_albums(&mut self);
+ /// Create an album (async)
fn create_album(&mut self, name: String, parent: LibraryId);
+ /// Add an image to an album.
+ fn add_to_album(&mut self, image_id: LibraryId, album_id: LibraryId);
fn request_metadata(&mut self, id: LibraryId);
/// set the metadata
fn set_metadata(&mut self, id: LibraryId, meta: Np, value: &PropertyValue);
+ /// set some properties for an image.
+ fn set_image_properties(&mut self, id: LibraryId, props: &NiepcePropertyBag);
fn write_metadata(&mut self, id: LibraryId);
fn move_file_to_folder(&mut self, file_id: LibraryId, from: LibraryId, to: LibraryId);
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]