/// <summary> /// Some image editing apps such as Lightroom, On1, etc., do not persist the keyword metadata /// in the images by default. This can mean you keyword-tag them, but those keywords are only /// stored in the sidecars. Damselfly only scans keyword metadata from the EXIF image data /// itself. /// So to rectify this, we can either read the sidecar files for those keywords, and optionally /// write the missing keywords to the Exif Metadata as we index them. /// </summary> /// <param name="img"></param> /// <param name="keywords"></param> private void ProcessSideCarKeywords(Image img, string[] keywords) { var sideCarTags = new List <string>(); var sidecarSearch = Path.ChangeExtension(img.FileName, "*"); DirectoryInfo dir = new DirectoryInfo(img.Folder.Path); var files = dir.GetFiles(sidecarSearch); var on1Sidecar = files.FirstOrDefault(x => x.Extension.Equals(".on1", StringComparison.OrdinalIgnoreCase)); if (on1Sidecar != null) { var on1MetaData = On1Sidecar.LoadMetadata(on1Sidecar); if (on1MetaData != null) { var missingKeywords = on1MetaData.Keywords .Except(keywords, StringComparer.OrdinalIgnoreCase) .ToList(); if (missingKeywords.Any()) { Logging.LogVerbose($"Image {img.FileName} is missing {missingKeywords.Count} keywords present in the On1 Sidecar."); sideCarTags = sideCarTags.Union(missingKeywords, StringComparer.OrdinalIgnoreCase).ToList(); } } } var xmpSidecar = files.FirstOrDefault(x => x.Extension.Equals(".xmp", StringComparison.OrdinalIgnoreCase)); if (xmpSidecar != null) { using var stream = File.OpenRead(xmpSidecar.FullName); IXmpMeta xmp = XmpMetaFactory.Parse(stream); var xmpKeywords = xmp.Properties.FirstOrDefault(x => x.Path == "pdf:Keywords"); if (xmpKeywords != null) { var missingKeywords = xmpKeywords.Value .Split(",") .Select(x => x.Trim()) .Except(keywords) .ToList(); if (missingKeywords.Any()) { Logging.LogVerbose($"Image {img.FileName} is missing {missingKeywords.Count} keywords present in the XMP Sidecar."); sideCarTags = sideCarTags.Union(missingKeywords, StringComparer.OrdinalIgnoreCase).ToList(); } } } if (sideCarTags.Any()) { // Now, submit the tags; note they won't get created immediately, but in batch. Logging.Log($"Applying {sideCarTags.Count} keywords from sidecar files to image {img.FileName}"); _ = MetaDataService.Instance.UpdateTagsAsync(new[] { img }, sideCarTags, null); } }
/// <summary> /// Given a sidecar object, parses the files (ON1 or XMP) and pulls out /// the list of keywords in the sidecar file. /// </summary> /// <param name="sidecar"></param> /// <returns></returns> public static IList <string> GetKeywords(this ImageSideCar sidecar) { var sideCarTags = new List <string>(); // If there's an On1 sidecar, read it try { if (sidecar.Type == SidecarUtils.SidecarType.ON1) { var on1MetaData = On1Sidecar.LoadMetadata(sidecar.Filename); if (on1MetaData != null && on1MetaData.Keywords != null && on1MetaData.Keywords.Any()) { sideCarTags = on1MetaData.Keywords .Select(x => x.Trim()) .ToList(); } } // If there's an XMP sidecar if (sidecar.Type == SidecarUtils.SidecarType.XMP) { using var stream = File.OpenRead(sidecar.Filename.FullName); IXmpMeta xmp = XmpMetaFactory.Parse(stream); var xmpKeywords = xmp.Properties.FirstOrDefault(x => x.Path == "pdf:Keywords"); if (xmpKeywords != null) { sideCarTags = xmpKeywords.Value.Split(",") .Select(x => x.Trim()) .ToList(); } } } catch (Exception ex) { Logging.LogError($"Exception processing {sidecar.Type} sidecar: {sidecar.Filename.FullName}: {ex.Message}"); } return(sideCarTags); }