From 4d87cf05b9d6eccd1fe6ddfc68775b9a610ecbb9 Mon Sep 17 00:00:00 2001 From: RudyTheDev <3857299+RudyTheDev@users.noreply.github.com> Date: Thu, 4 Aug 2022 13:08:44 +0300 Subject: [PATCH] Read and write miliseconds in photo taken timestamp if present or if manually requested to fix the full problem of #82 --- ExifLibrary/ExifBitConverter.cs | 56 +++++++++++++++++++++++---- ExifLibrary/ExifExtendedProperty.cs | 30 ++++++++++++-- ExifLibrary/ExifPropertyCollection.cs | 8 ++-- ExifLibrary/ExifPropertyFactory.cs | 10 ++--- 4 files changed, 83 insertions(+), 21 deletions(-) diff --git a/ExifLibrary/ExifBitConverter.cs b/ExifLibrary/ExifBitConverter.cs index 54cb56a..4d07d12 100644 --- a/ExifLibrary/ExifBitConverter.cs +++ b/ExifLibrary/ExifBitConverter.cs @@ -59,7 +59,7 @@ public static string ToString(byte[] data) /// /// Returns a DateTime object converted from the given byte array. /// - public static DateTime ToDateTime(byte[] data, bool hastime) + public static DateTime ToDateTime(byte[] data, bool hastime, out bool preserveMilliseconds) { string str = ToAscii(data, Encoding.ASCII); string[] parts = str.Split(new char[] { ':', ' ' }); @@ -68,27 +68,58 @@ public static DateTime ToDateTime(byte[] data, bool hastime) if (hastime && parts.Length == 6) { // yyyy:MM:dd HH:mm:ss - // This is the expected format though some cameras + // This is the expected format through some cameras // can use single digits. See Issue 21. // Also, the seconds can be non-decimal (e.g. 2016:07:31 10:10:20.291), so use double to parse. See Issue 82 - return new DateTime(int.Parse(parts[0]), int.Parse(parts[1]), int.Parse(parts[2]), int.Parse(parts[3]), int.Parse(parts[4]), (int)double.Parse(parts[5])); + + if (parts[5].Contains(".")) + { + string[] secondParts = parts[5].Split('.'); + + preserveMilliseconds = true; + return new DateTime( + int.Parse(parts[0]), + int.Parse(parts[1]), + int.Parse(parts[2]), + int.Parse(parts[3]), + int.Parse(parts[4]), + int.Parse(secondParts[0]), + int.Parse(secondParts[1]) + ); + } + else + { + preserveMilliseconds = false; + return new DateTime( + int.Parse(parts[0]), + int.Parse(parts[1]), + int.Parse(parts[2]), + int.Parse(parts[3]), + int.Parse(parts[4]), + int.Parse(parts[5]) + ); + } } else if (!hastime && parts.Length == 3) { // yyyy:MM:dd + preserveMilliseconds = false; return new DateTime(int.Parse(parts[0]), int.Parse(parts[1]), int.Parse(parts[2])); } else { + preserveMilliseconds = false; return DateTime.MinValue; } } catch (ArgumentOutOfRangeException) { + preserveMilliseconds = false; return DateTime.MinValue; } catch (ArgumentException) { + preserveMilliseconds = false; return DateTime.MinValue; } } @@ -96,9 +127,9 @@ public static DateTime ToDateTime(byte[] data, bool hastime) /// /// Returns a DateTime object converted from the given byte array. /// - public static DateTime ToDateTime(byte[] data) + public static DateTime ToDateTime(byte[] data, out bool preserveMilliseconds) { - return ToDateTime(data, true); + return ToDateTime(data, true, out preserveMilliseconds); } /// @@ -294,13 +325,22 @@ public static byte[] GetBytes(string value, Encoding encoding) /// /// Converts the given datetime to an array of bytes with a null terminator. /// - public static byte[] GetBytes(DateTime value, bool hastime) + public static byte[] GetBytes(DateTime value, bool hastime, bool preserveMilliseconds) { - string str = ""; + string str; + if (hastime) - str = value.ToString("yyyy:MM:dd HH:mm:ss", System.Globalization.CultureInfo.InvariantCulture); + { + if (preserveMilliseconds) + str = value.ToString("yyyy:MM:dd HH:mm:ss.fff", System.Globalization.CultureInfo.InvariantCulture); + else + str = value.ToString("yyyy:MM:dd HH:mm:ss", System.Globalization.CultureInfo.InvariantCulture); + } else + { str = value.ToString("yyyy:MM:dd", System.Globalization.CultureInfo.InvariantCulture); + } + return GetBytes(str, true, Encoding.ASCII); } diff --git a/ExifLibrary/ExifExtendedProperty.cs b/ExifLibrary/ExifExtendedProperty.cs index 070c218..ce50541 100644 --- a/ExifLibrary/ExifExtendedProperty.cs +++ b/ExifLibrary/ExifExtendedProperty.cs @@ -125,25 +125,38 @@ public override ExifInterOperability Interoperability /// public class ExifDateTime : ExifProperty { + private readonly bool mPreserveMilliseconds; protected DateTime mValue; protected override object _Value { get { return Value; } set { Value = (DateTime)value; } } public new DateTime Value { get { return mValue; } set { mValue = value; } } static public implicit operator DateTime(ExifDateTime obj) { return obj.mValue; } - public override string ToString() { return mValue.ToString("yyyy.MM.dd HH:mm:ss"); } + public override string ToString() + { + if (mPreserveMilliseconds) + return mValue.ToString("yyyy.MM.dd HH:mm:ss.fff"); + else + return mValue.ToString("yyyy.MM.dd HH:mm:ss"); + } - public ExifDateTime(ExifTag tag, DateTime value) + public ExifDateTime(ExifTag tag, DateTime value, bool preserveMilliseconds) : base(tag) { mValue = value; + mPreserveMilliseconds = preserveMilliseconds; } public override ExifInterOperability Interoperability { get { - return new ExifInterOperability(ExifTagFactory.GetTagID(mTag), InterOpType.ASCII, (uint)20, ExifBitConverter.GetBytes(mValue, true)); + return new ExifInterOperability( + ExifTagFactory.GetTagID(mTag), + InterOpType.ASCII, + mPreserveMilliseconds ? (uint)24 : (uint)20, + ExifBitConverter.GetBytes(mValue, true, mPreserveMilliseconds) + ); } } } @@ -172,7 +185,16 @@ public override ExifInterOperability Interoperability { get { - return new ExifInterOperability(ExifTagFactory.GetTagID(mTag), InterOpType.ASCII, (uint)11, ExifBitConverter.GetBytes(mValue, false)); + return new ExifInterOperability( + ExifTagFactory.GetTagID(mTag), + InterOpType.ASCII, + (uint)11, + ExifBitConverter.GetBytes( + mValue, + false, + false // does not matter because we don't have time part anyway + ) + ); } } } diff --git a/ExifLibrary/ExifPropertyCollection.cs b/ExifLibrary/ExifPropertyCollection.cs index 10b9d87..9a10479 100644 --- a/ExifLibrary/ExifPropertyCollection.cs +++ b/ExifLibrary/ExifPropertyCollection.cs @@ -159,9 +159,9 @@ public void Add(ExifTag key, object value) /// /// The tag to set. /// The value of tag. - public void Add(ExifTag key, DateTime value) + public void Add(ExifTag key, DateTime value, bool preserveMilliseconds = false) { - AddItem(new ExifDateTime(key, value)); + AddItem(new ExifDateTime(key, value, preserveMilliseconds)); } /// /// Adds an with the specified key. @@ -341,9 +341,9 @@ public void Set(ExifTag key, object value) /// /// The tag to set. /// The value of tag. - public void Set(ExifTag key, DateTime value) + public void Set(ExifTag key, DateTime value, bool preserveMilliseconds = false) { - SetItem(new ExifDateTime(key, value)); + SetItem(new ExifDateTime(key, value, preserveMilliseconds)); } /// /// Sets an with the specified key. diff --git a/ExifLibrary/ExifPropertyFactory.cs b/ExifLibrary/ExifPropertyFactory.cs index 4c8e86f..b6bdf9f 100644 --- a/ExifLibrary/ExifPropertyFactory.cs +++ b/ExifLibrary/ExifPropertyFactory.cs @@ -41,7 +41,7 @@ public static ExifProperty Get(ushort tag, ushort type, uint count, byte[] value else if (tag == 0x128) // ResolutionUnit return new ExifEnumProperty(ExifTag.ResolutionUnit, (ResolutionUnit)conv.ToUInt16(value, 0)); else if (tag == 0x132) // DateTime - return new ExifDateTime(ExifTag.DateTime, ExifBitConverter.ToDateTime(value)); + return new ExifDateTime(ExifTag.DateTime, ExifBitConverter.ToDateTime(value, out bool _), false); else if (tag == 0x9c9b || tag == 0x9c9c || // Windows tags tag == 0x9c9d || tag == 0x9c9e || tag == 0x9c9f) return new WindowsByteString(etag, Encoding.Unicode.GetString(value).TrimEnd('\0')); @@ -80,9 +80,9 @@ public static ExifProperty Get(ushort tag, ushort type, uint count, byte[] value return new ExifEncodedString(ExifTag.UserComment, val, enc); } else if (tag == 0x9003) // DateTimeOriginal - return new ExifDateTime(ExifTag.DateTimeOriginal, ExifBitConverter.ToDateTime(value)); + return new ExifDateTime(ExifTag.DateTimeOriginal, ExifBitConverter.ToDateTime(value, out bool preserveMilliseconds), preserveMilliseconds); else if (tag == 0x9004) // DateTimeDigitized - return new ExifDateTime(ExifTag.DateTimeDigitized, ExifBitConverter.ToDateTime(value)); + return new ExifDateTime(ExifTag.DateTimeDigitized, ExifBitConverter.ToDateTime(value, out bool preserveMilliseconds), preserveMilliseconds); else if (tag == 0x8822) // ExposureProgram return new ExifEnumProperty(ExifTag.ExposureProgram, (ExposureProgram)conv.ToUInt16(value, 0)); else if (tag == 0x9207) // MeteringMode @@ -170,7 +170,7 @@ public static ExifProperty Get(ushort tag, ushort type, uint count, byte[] value else if (tag == 25) // GPSDestDistanceRef return new ExifEnumProperty(ExifTag.GPSDestDistanceRef, (GPSDistanceRef)value[0]); else if (tag == 29) // GPSDateStamp - return new ExifDate(ExifTag.GPSDateStamp, ExifBitConverter.ToDateTime(value, false)); + return new ExifDate(ExifTag.GPSDateStamp, ExifBitConverter.ToDateTime(value, false, out bool _)); else if (tag == 30) // GPSDifferential return new ExifEnumProperty(ExifTag.GPSDifferential, (GPSDifferential)conv.ToUInt16(value, 0)); } @@ -196,7 +196,7 @@ public static ExifProperty Get(ushort tag, ushort type, uint count, byte[] value else if (tag == 0x128) // ResolutionUnit return new ExifEnumProperty(ExifTag.ThumbnailResolutionUnit, (ResolutionUnit)conv.ToUInt16(value, 0)); else if (tag == 0x132) // DateTime - return new ExifDateTime(ExifTag.ThumbnailDateTime, ExifBitConverter.ToDateTime(value)); + return new ExifDateTime(ExifTag.ThumbnailDateTime, ExifBitConverter.ToDateTime(value, out bool _), false); } if (type == 1) // 1 = BYTE An 8-bit unsigned integer.