Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into cq4
Browse files Browse the repository at this point in the history
  • Loading branch information
drewnoakes committed Feb 4, 2024
2 parents 6c398fd + 55808d1 commit 1b38f68
Show file tree
Hide file tree
Showing 18 changed files with 209 additions and 104 deletions.
1 change: 1 addition & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ root = true
[*]

# Spell checker configuration
spelling_languages = en-us,en-gb
spelling_exclusion_path = spelling.dic

[*.cs]
Expand Down
6 changes: 5 additions & 1 deletion MetadataExtractor/Formats/Apple/BplistReader.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
// Copyright (c) Drew Noakes and contributors. All Rights Reserved. Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.

using System.Buffers;

namespace MetadataExtractor.Formats.Apple;

/// <summary>
Expand Down Expand Up @@ -121,7 +123,7 @@ static object HandleInt(ref BufferReader reader, byte marker)

static Dictionary<byte, byte> HandleDict(ref BufferReader reader, byte count)
{
var keyRefs = new byte[count];
var keyRefs = ArrayPool<byte>.Shared.Rent(count);

for (int j = 0; j < count; j++)
{
Expand All @@ -135,6 +137,8 @@ static Dictionary<byte, byte> HandleDict(ref BufferReader reader, byte count)
map.Add(keyRefs[j], reader.GetByte());
}

ArrayPool<byte>.Shared.Return(keyRefs);

return map;
}

Expand Down
2 changes: 1 addition & 1 deletion MetadataExtractor/Formats/Exif/ExifTiffHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -385,7 +385,7 @@ public override void EndingIfd(in TiffReaderContext context)
if (directory.GetObject(ExifDirectoryBase.TagGeoTiffGeoKeys) is ushort[] geoKeys)
{
// GetTIFF stores data in its own format within TIFF. It is TIFF-like, but different.
// It can reference data frm tags that have not been visited yet, so we must unpack it
// It can reference data from tags that have not been visited yet, so we must unpack it
// once the directory is complete.
ProcessGeoTiff(geoKeys, directory);
}
Expand Down
30 changes: 20 additions & 10 deletions MetadataExtractor/Formats/Flir/FlirReader.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// Copyright (c) Drew Noakes and contributors. All Rights Reserved. Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.

using System.Buffers;
using MetadataExtractor.Formats.Jpeg;
using static MetadataExtractor.Formats.Flir.FlirCameraInfoDirectory;

Expand Down Expand Up @@ -35,26 +36,35 @@ public IEnumerable<Directory> ReadJpegSegments(IEnumerable<JpegSegment> segments
if (length == 0)
return [];

var buffer = new byte[length];
using var merged = new MemoryStream(buffer);
byte[] buffer = ArrayPool<byte>.Shared.Rent(length);

foreach (var segment in segments)
try
{
// Skip segments not starting with the required preamble
if (segment.Span.StartsWith(preamble))
using var merged = new MemoryStream(buffer);

foreach (var segment in segments)
{
merged.Write(segment.Bytes, preambleLength, segment.Bytes.Length - preambleLength);
// Skip segments not starting with the required preamble
if (segment.Span.StartsWith(preamble))
{
merged.Write(segment.Bytes, preambleLength, segment.Bytes.Length - preambleLength);
}
}
}

return Extract(new ByteArrayReader(buffer));
return Extract(new ByteArrayReader(buffer));
}
finally
{
ArrayPool<byte>.Shared.Return(buffer);
}
}

public IEnumerable<Directory> Extract(IndexedReader reader)
{
var header = reader.GetUInt32(0);
Span<byte> header = stackalloc byte[4];
reader.GetBytes(0, header);

if (header != 0x46464600)
if (!header.SequenceEqual("FFF\0"u8))
{
var flirHeaderDirectory = new FlirHeaderDirectory();
flirHeaderDirectory.AddError("Unexpected FFF header bytes.");
Expand Down
120 changes: 70 additions & 50 deletions MetadataExtractor/Formats/Gif/GifReader.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// Copyright (c) Drew Noakes and contributors. All Rights Reserved. Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.

using System.Buffers;
using MetadataExtractor.Formats.Icc;
using MetadataExtractor.Formats.Xmp;

Expand Down Expand Up @@ -234,45 +235,42 @@ private static GifCommentDirectory ReadCommentBlock(SequentialReader reader, byt
if (blockSizeBytes != 11)
return new ErrorDirectory($"Invalid GIF application extension block size. Expected 11, got {blockSizeBytes}.");

var extensionType = reader.GetString(blockSizeBytes, Encoding.UTF8);
Span<byte> extensionType = stackalloc byte[11];

switch (extensionType)
reader.GetBytes(extensionType);

if (extensionType.SequenceEqual("XMP DataXMP"u8))
{
case "XMP DataXMP":
{
// XMP data extension
var xmpBytes = GatherBytes(reader);
int xmpLength = xmpBytes.Length - 257; // Exclude the "magic trailer", see XMP Specification Part 3, 1.1.2 GIF
// Only extract valid blocks
return xmpLength > 0
? new XmpReader().Extract(xmpBytes, 0, xmpBytes.Length - 257)
: null;
}
case "ICCRGBG1012":
{
// ICC profile extension
var iccBytes = GatherBytes(reader, reader.GetByte());
return iccBytes.Length != 0
? new IccReader().Extract(new ByteArrayReader(iccBytes))
: null;
}
case "NETSCAPE2.0":
{
reader.Skip(2);
// Netscape's animated GIF extension
// Iteration count (0 means infinite)
var iterationCount = reader.GetUInt16();
// Skip terminator
reader.Skip(1);
var animationDirectory = new GifAnimationDirectory();
animationDirectory.Set(GifAnimationDirectory.TagIterationCount, iterationCount);
return animationDirectory;
}
default:
{
SkipBlocks(reader);
return null;
}
// XMP data extension
var xmpBytes = GatherXmpBytes(reader);
return xmpBytes is not null
? new XmpReader().Extract(xmpBytes)
: null;
}
else if (extensionType.SequenceEqual("ICCRGBG1012"u8))
{
// ICC profile extension
var iccBytes = GatherBytes(reader, reader.GetByte());
return iccBytes.Length != 0
? new IccReader().Extract(new ByteArrayReader(iccBytes))
: null;
}
else if (extensionType.SequenceEqual("NETSCAPE2.0"u8))
{
reader.Skip(2);
// Netscape's animated GIF extension
// Iteration count (0 means infinite)
var iterationCount = reader.GetUInt16();
// Skip terminator
reader.Skip(1);
var animationDirectory = new GifAnimationDirectory();
animationDirectory.Set(GifAnimationDirectory.TagIterationCount, iterationCount);
return animationDirectory;
}
else
{
SkipBlocks(reader);
return null;
}
}

Expand Down Expand Up @@ -329,36 +327,58 @@ private static GifImageDirectory ReadImageBlock(SequentialReader reader)

#region Utility methods

private static byte[] GatherBytes(SequentialReader reader)
private static byte[]? GatherXmpBytes(SequentialReader reader)
{
var bytes = new MemoryStream();
var buffer = new byte[257];
// GatherXmpBytes differs from GatherBytes in that this method includes the "length"
// bytes in its output.

var stream = new MemoryStream();
var buffer = ArrayPool<byte>.Shared.Rent(byte.MaxValue + 1);

while (true)
{
var b = reader.GetByte();
if (b == 0)
return bytes.ToArray();
buffer[0] = b;
reader.GetBytes(buffer, 1, b);
bytes.Write(buffer, 0, b + 1);
var len = reader.GetByte();
if (len == 0)
break;
buffer[0] = len;
reader.GetBytes(buffer, offset: 1, count: len);
stream.Write(buffer, 0, len + 1);
}

ArrayPool<byte>.Shared.Return(buffer);

// Exclude the "magic trailer", see XMP Specification Part 3, 1.1.2 GIF
int xmpLength = checked((int)stream.Length) - 257;

if (xmpLength <= 0)
{
return null;
}

stream.SetLength(xmpLength);

return stream.ToArray();
}

private static byte[] GatherBytes(SequentialReader reader, int firstLength)
private static byte[] GatherBytes(SequentialReader reader, byte firstLength)
{
var buffer = new MemoryStream();
var stream = new MemoryStream();
var buffer = ArrayPool<byte>.Shared.Rent(byte.MaxValue);

var length = firstLength;

while (length > 0)
{
buffer.Write(reader.GetBytes(length), 0, length);
reader.GetBytes(buffer.AsSpan().Slice(0, length));

stream.Write(buffer, 0, length);

length = reader.GetByte();
}

return buffer.ToArray();
ArrayPool<byte>.Shared.Return(buffer);

return stream.ToArray();
}

private static void SkipBlocks(SequentialReader reader)
Expand Down
11 changes: 8 additions & 3 deletions MetadataExtractor/Formats/Icc/IccReader.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// Copyright (c) Drew Noakes and contributors. All Rights Reserved. Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.

using System.Buffers;
using MetadataExtractor.Formats.Jpeg;

namespace MetadataExtractor.Formats.Icc
Expand Down Expand Up @@ -37,14 +38,14 @@ public IEnumerable<Directory> ReadJpegSegments(IEnumerable<JpegSegment> segments
byte[] buffer;
if (iccSegments.Count == 1)
{
buffer = new byte[iccSegments[0].Bytes.Length - JpegSegmentPreambleLength];
buffer = ArrayPool<byte>.Shared.Rent(iccSegments[0].Bytes.Length - JpegSegmentPreambleLength);
Array.Copy(iccSegments[0].Bytes, JpegSegmentPreambleLength, buffer, 0, iccSegments[0].Bytes.Length - JpegSegmentPreambleLength);
}
else
{
// Concatenate all buffers
var totalLength = iccSegments.Sum(s => s.Bytes.Length - JpegSegmentPreambleLength);
buffer = new byte[totalLength];
buffer = ArrayPool<byte>.Shared.Rent(totalLength);
for (int i = 0, pos = 0; i < iccSegments.Count; i++)
{
var segment = iccSegments[i];
Expand All @@ -53,7 +54,11 @@ public IEnumerable<Directory> ReadJpegSegments(IEnumerable<JpegSegment> segments
}
}

return new Directory[] { Extract(new ByteArrayReader(buffer)) };
Directory directory = Extract(new ByteArrayReader(buffer));

ArrayPool<byte>.Shared.Return(buffer);

return [directory];
}

public IccDirectory Extract(IndexedReader reader)
Expand Down
19 changes: 11 additions & 8 deletions MetadataExtractor/Formats/QuickTime/QuickTimeReaderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,29 +7,32 @@ namespace MetadataExtractor.Formats.QuickTime
/// </summary>
public static class QuickTimeReaderExtensions
{
#if NETSTANDARD2_1
public static string Get4ccString(this SequentialReader reader)
#if NET462 || NETSTANDARD1_3
public static unsafe string Get4ccString(this SequentialReader reader)
#else
public unsafe static string Get4ccString(this SequentialReader reader)
public static string Get4ccString(this SequentialReader reader)
#endif
{
// https://en.wikipedia.org/wiki/FourCC

Span<byte> bytes = stackalloc byte[4];
Span<char> chars = stackalloc char[4];

reader.GetBytes(bytes);

// NOTE we cannot just use Encoding.ASCII here, as that can replace certain non-printable characters with '?'
chars[0] = (char)bytes[0];
chars[1] = (char)bytes[1];
chars[2] = (char)bytes[2];
chars[3] = (char)bytes[3];

#if NETSTANDARD2_1
return new string(chars);
#else
fixed (char* c = chars)
#if NET462 || NETSTANDARD1_3
fixed (char* pChars = chars)
{
return new string(c, 0, 4);
return new string(pChars, startIndex: 0, length: 4);
}
#else
return new string(chars);
#endif
}

Expand Down
3 changes: 3 additions & 0 deletions MetadataExtractor/Formats/QuickTime/QuickTimeTypeChecker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ internal sealed class QuickTimeTypeChecker : ITypeChecker
{ Util.FileType.Heif, "hevc"u8 },
{ Util.FileType.Heif, "hevx"u8 },

// AVIF
{ Util.FileType.Avif, "avif"u8 },

// CRX
{ Util.FileType.Crx, "crx "u8 }
};
Expand Down
6 changes: 5 additions & 1 deletion MetadataExtractor/Formats/Raf/RafMetadataReader.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// Copyright (c) Drew Noakes and contributors. All Rights Reserved. Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.

using System.Buffers;
using MetadataExtractor.Formats.Jpeg;

namespace MetadataExtractor.Formats.Raf
Expand All @@ -14,7 +15,8 @@ public static IReadOnlyList<Directory> ReadMetadata(Stream stream)
if (!stream.CanSeek)
throw new ArgumentException("Must support seek", nameof(stream));

var data = new byte[512];
var data = ArrayPool<byte>.Shared.Rent(512);

var bytesRead = stream.Read(data, 0, 512);

if (bytesRead == 0)
Expand All @@ -32,6 +34,8 @@ public static IReadOnlyList<Directory> ReadMetadata(Stream stream)
}
}

ArrayPool<byte>.Shared.Return(data);

return JpegMetadataReader.ReadMetadata(stream);
}
}
Expand Down
9 changes: 3 additions & 6 deletions MetadataExtractor/Formats/Tga/TgaExtensionReader.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
// Copyright (c) Drew Noakes and contributors. All Rights Reserved. Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.

using System.Buffers;

namespace MetadataExtractor.Formats.Tga
{
/// <summary>Reads TGA image file extension area.</summary>
Expand Down Expand Up @@ -55,12 +57,7 @@ protected override void Populate(Stream stream, int offset, TgaExtensionDirector

string GetString(int length)
{
var buffer = new byte[length];
reader.GetBytes(buffer, 0, length);
int i = 0;
while (i < buffer.Length && buffer[i] != '\0')
++i;
return Encoding.ASCII.GetString(buffer, 0, i).TrimEnd();
return reader.GetNullTerminatedString(length, Encoding.ASCII, moveToMaxLength: true).TrimEnd();
}

bool TryGetDateTime(out DateTime dateTime)
Expand Down
Loading

0 comments on commit 1b38f68

Please sign in to comment.