Skip to content

Commit

Permalink
Fixes invalid millisecond calculations for SubSecOriginal tag where
Browse files Browse the repository at this point in the history
value is not in Canon's format.
  • Loading branch information
Caleb9 committed Apr 5, 2021
1 parent 8c3e798 commit 1439249
Show file tree
Hide file tree
Showing 5 changed files with 82 additions and 15 deletions.
54 changes: 54 additions & 0 deletions CameraUtility.Tests/CameraFiles/ImageFileTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using System;
using CameraUtility.CameraFiles;
using CameraUtility.Exif;
using FluentAssertions;
using Xunit;

namespace CameraUtility.Tests.CameraFiles
{
public sealed class ImageFileTests
{
[Theory]
[InlineData("42", 420)]
[InlineData("042", 42)]
[InlineData("042000", 42)]
public void SubSecondsOriginal_tag_gets_converted_to_milliseconds(
string subSecondsTagValue,
int expectedMilliseconds)
{
var exifTags = new[]
{
new Tag
{
Type = 0x9003,
Value = "2021:04:05 10:56:13"
},
new Tag
{
Type = 0x9291,
Value = subSecondsTagValue
}
};

var sut = ImageFile.Create(new CameraFilePath("file.jpg"), exifTags).Value;

var expected = new DateTime(2021, 04, 05, 10, 56, 13).AddMilliseconds(expectedMilliseconds);
sut.Created.Should().Be(expected);
}

private sealed class Tag :
ITag
{
public int Type { get; internal init; }
public string Directory => string.Empty;
public string Value { get; internal init; }
}

private sealed record TestTag(string Value) :
ITag
{
public int Type => 0x9003;
public string Directory => string.Empty;
}
}
}
2 changes: 1 addition & 1 deletion CameraUtility/CameraFiles/AbstractCameraFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
namespace CameraUtility.CameraFiles
{
[DebuggerDisplay("{FullName} {Created}")]
internal abstract class AbstractCameraFile : ICameraFile
public abstract class AbstractCameraFile
{
protected AbstractCameraFile(
CameraFilePath fullName)
Expand Down
2 changes: 1 addition & 1 deletion CameraUtility/CameraFiles/ICameraFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

namespace CameraUtility.CameraFiles
{
internal interface ICameraFile
public interface ICameraFile
{
CameraFilePath FullName { get; }
string Extension { get; }
Expand Down
34 changes: 22 additions & 12 deletions CameraUtility/CameraFiles/ImageFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ namespace CameraUtility.CameraFiles
/// <summary>
/// Jpeg (Android and Canon) or Cr2 raw Canon photo.
/// </summary>
internal sealed class ImageFile
public sealed class ImageFile
: AbstractCameraFile, ICameraFile
{
/// <summary>
Expand All @@ -19,11 +19,11 @@ internal sealed class ImageFile
private const int DateTimeOriginalTagType = 0x9003;

/// <summary>
/// Some older cameras don't use the 0x9003. We will try to read it from 0x0132 tag.
/// Some older cameras don't use the 0x9003. We will try to read it from 0x0132 tag (ModifyDate).
/// </summary>
private const int FallbackDateTimeTagType = 0x0132;

private const int SubSecondTagType = 0x9291;
private const int SubSecTimeOriginalTagType = 0x9291;

private ImageFile(
CameraFilePath fullName,
Expand All @@ -36,7 +36,7 @@ private ImageFile(
public override DateTime Created { get; }
public override string DestinationNamePrefix => "IMG_";

internal static Result<ICameraFile> Create(
public static Result<ICameraFile> Create(
CameraFilePath fullName,
IEnumerable<ITag> exifTags)
{
Expand All @@ -54,14 +54,13 @@ internal static Result<ICameraFile> Create(

return new ImageFile(
fullName,
parsedDateTimeResult.Value.AddMilliseconds(FindSubSeconds(enumeratedExifTags)));
parsedDateTimeResult.Value.Add(FindSubSeconds(enumeratedExifTags)));
}

private static Result<ITag> FindCreatedDateTimeTag(
IList<ITag> exifTags)
{
var tag = exifTags.FirstOrDefault(t => t.Type == DateTimeOriginalTagType)
/* Try fallback tag, if not found then an exception will be thrown */
?? exifTags.FirstOrDefault(t => t.Type == FallbackDateTimeTagType);
return
tag is not null
Expand All @@ -84,17 +83,28 @@ private static Result<DateTime> ParseCreatedDateTime(
return Result.Failure<DateTime>("Invalid metadata");
}

private static int FindSubSeconds(
private static TimeSpan FindSubSeconds(
IEnumerable<ITag> exifTags)
{
var subSeconds = exifTags.FirstOrDefault(t => t.Type == SubSecondTagType);
return subSeconds is null ? 0 : ToMilliseconds(int.Parse(subSeconds.Value));
var subSeconds = exifTags.FirstOrDefault(t => t.Type == SubSecTimeOriginalTagType);
return subSeconds is null ? TimeSpan.Zero : ToMilliseconds(subSeconds.Value);
}

private static int ToMilliseconds(
int subSeconds)
/// <summary>
/// EXIF specifies that SubSecOriginal tag contains "fractions" of a second. Depending on length of the
/// value a different fractional unit can be used, e.g. "042" is 42 milliseconds (0.042 of a second) but
/// "42" is 420 milliseconds (0.42 of a second).
/// </summary>
private static TimeSpan ToMilliseconds(
string subSeconds)
{
return subSeconds * 10;
if (int.TryParse(subSeconds, out var tagIntValue) is false)
{
return TimeSpan.Zero;
}
var subSecondDenominator = Math.Pow(10, subSeconds.Trim().Length);
var millisecondMultiplier = 1000 / subSecondDenominator;
return TimeSpan.FromMilliseconds(tagIntValue * millisecondMultiplier);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,10 @@ int IOrchestrator.Execute(AbstractTransferImageFilesCommand.OptionArgs args)
catch (Exception exception)
{
OnException(this, (cameraFilePath, exception));
if (!args.KeepGoing) return ErrorResultCode;
if (!args.KeepGoing)
{
return ErrorResultCode;
}

result = ErrorResultCode;
}
Expand Down

0 comments on commit 1439249

Please sign in to comment.