diff --git a/Program.cs b/Program.cs index ab0df1d..9990ac4 100644 --- a/Program.cs +++ b/Program.cs @@ -15,6 +15,7 @@ using SixLabors.ImageSharp.Formats.Jpeg; using System; using System.Diagnostics; using System.Runtime.CompilerServices; +using System.Text; using System.Xml.Linq; namespace SemiColinGames; @@ -199,8 +200,8 @@ public class Photo { DateTimeOriginal = creationTime; ImageInfo info = Image.Identify(filename); Size = new(info.Size.Width, info.Size.Height); + Rating = ParseRating(info.Metadata.XmpProfile); ParseExif(info.Metadata.ExifProfile); - TryParseRating(info.Metadata.XmpProfile, out Rating); } public async void LoadAsync() { @@ -221,7 +222,7 @@ public class Photo { } } - public async void SaveAsJpeg(string outputRoot, JpegEncoder encoder) { + public async void SaveAsJpegAsync(string outputRoot, JpegEncoder encoder) { // FIXME: if nothing was changed about this image, just copy the file bytes directly, possibly with metadata changed? string directory = System.IO.Path.Combine( outputRoot, @@ -231,7 +232,7 @@ public class Photo { Directory.CreateDirectory(directory); string filename = System.IO.Path.Combine(directory, System.IO.Path.GetFileName(Filename)); Console.WriteLine("saving " + filename); - // FIXME: update Rating data. + // FIXME: what if we also saved Exif rating? // FIXME: add comments / captions as ImageDescription? // FIXME: strip some Exif tags for privacy reasons? // FIXME: warn if the file already exists? @@ -249,31 +250,50 @@ public class Photo { now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second); exif.SetValue(ExifTag.DateTime, datetime); + image.Metadata.XmpProfile = UpdateXmp(image.Metadata.XmpProfile); + await image.SaveAsync(filename, encoder); } } - private bool TryParseRating(XmpProfile? xmp, out int rating) { - rating = 0; + private XElement? GetXmpRoot(XmpProfile? xmp) { if (xmp == null) { - return false; + return null; } XDocument? doc = xmp.GetDocument(); if (doc == null) { - return false; + return null; } - XElement? root = doc.Root; + return doc.Root; + } + + private int ParseRating(XmpProfile? xmp) { + XElement? root = GetXmpRoot(xmp); if (root == null) { - return false; + return 0; } foreach (XElement elt in root.Descendants()) { if (elt.Name == "{http://ns.adobe.com/xap/1.0/}Rating") { + int rating = 0; if (int.TryParse(elt.Value, out rating)) { - return true; + return rating; } } } - return false; + return 0; + } + + private XmpProfile? UpdateXmp(XmpProfile? xmp) { + if (xmp == null) { + return null; + } + string xmlIn = Encoding.UTF8.GetString(xmp.ToByteArray()); + int index = xmlIn.IndexOf(""); + if (index == -1) { + return xmp; + } + string xmlOut = xmlIn.Substring(0, index - 1) + Rating.ToString() + xmlIn.Substring(index); + return new XmpProfile(Encoding.UTF8.GetBytes(xmlOut)); } // Exif (and other image metadata) reference, from the now-defunct Metadata Working Group: @@ -627,6 +647,7 @@ public class Game : GameWindow { bool ctrlIsDown = input.IsKeyDown(Keys.LeftControl) || input.IsKeyDown(Keys.RightControl); // FIXME: add a confirm dialog before closing. (Also for the window-close button.) + // FIXME: don't quit if there's pending file-write operations. // Close when Escape is pressed. if (input.IsKeyPressed(Keys.Escape)) { Close(); @@ -826,7 +847,7 @@ public class Game : GameWindow { // string[] files = Directory.GetFiles(@"c:\users\colin\desktop\photos-test\"); // string[] files = Directory.GetFiles(@"c:\users\colin\pictures\photos\2023\07\14\"); string[] files = Directory.GetFiles(@"G:\DCIM\100EOSR6\"); - // string[] files = Directory.GetFiles(@"c:\users\colin\desktop\totte-output\2023\07\28"); + // string[] files = Directory.GetFiles(@"c:\users\colin\desktop\totte-output\2023\07\31"); // string[] files = Directory.GetFiles(@"C:\Users\colin\Pictures\photos\2018\06\23"); // string[] files = Directory.GetFiles(@"C:\Users\colin\Desktop\Germany all\104D7000"); // string[] files = Directory.GetFiles(@"C:\Users\colin\Desktop\many-birds\"); @@ -913,9 +934,10 @@ public class Game : GameWindow { // FIXME: show a progress bar or something. private async void ExportPhotos() { JpegEncoder encoder = new JpegEncoder() { Quality = 100 }; - string outputRoot = @"c:\users\colin\pictures\photos\"; + string outputRoot = @"c:\users\colin\desktop\totte-output"; + // string outputRoot = @"c:\users\colin\pictures\photos"; foreach (Photo p in photos) { - await Task.Run( () => { p.SaveAsJpeg(outputRoot, encoder); }); + await Task.Run( () => { p.SaveAsJpegAsync(outputRoot, encoder); }); } } @@ -1088,7 +1110,8 @@ static class Program { nwSettings.WindowState = WindowState.Normal; nwSettings.CurrentMonitor = bestMonitor.Handle; nwSettings.Location = new Vector2i(bestMonitor.WorkArea.Min.X + 1, bestMonitor.WorkArea.Min.Y + 31); - nwSettings.Size = new Vector2i(bestMonitor.WorkArea.Size.X - 2, bestMonitor.WorkArea.Size.Y - 32); + // nwSettings.Size = new Vector2i(bestMonitor.WorkArea.Size.X - 2, bestMonitor.WorkArea.Size.Y - 32); + nwSettings.Size = new Vector2i(1600, 900); nwSettings.MinimumSize = UiGeometry.MIN_WINDOW_SIZE; nwSettings.Title = "Totte"; nwSettings.IsEventDriven = false;