Skip to content

Commit 1b38f68

Browse files
committed
Merge remote-tracking branch 'origin/main' into cq4
2 parents 6c398fd + 55808d1 commit 1b38f68

18 files changed

+209
-104
lines changed

.editorconfig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ root = true
33
[*]
44

55
# Spell checker configuration
6+
spelling_languages = en-us,en-gb
67
spelling_exclusion_path = spelling.dic
78

89
[*.cs]

MetadataExtractor/Formats/Apple/BplistReader.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
// 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.
22

3+
using System.Buffers;
4+
35
namespace MetadataExtractor.Formats.Apple;
46

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

122124
static Dictionary<byte, byte> HandleDict(ref BufferReader reader, byte count)
123125
{
124-
var keyRefs = new byte[count];
126+
var keyRefs = ArrayPool<byte>.Shared.Rent(count);
125127

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

140+
ArrayPool<byte>.Shared.Return(keyRefs);
141+
138142
return map;
139143
}
140144

MetadataExtractor/Formats/Exif/ExifTiffHandler.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -385,7 +385,7 @@ public override void EndingIfd(in TiffReaderContext context)
385385
if (directory.GetObject(ExifDirectoryBase.TagGeoTiffGeoKeys) is ushort[] geoKeys)
386386
{
387387
// GetTIFF stores data in its own format within TIFF. It is TIFF-like, but different.
388-
// It can reference data frm tags that have not been visited yet, so we must unpack it
388+
// It can reference data from tags that have not been visited yet, so we must unpack it
389389
// once the directory is complete.
390390
ProcessGeoTiff(geoKeys, directory);
391391
}

MetadataExtractor/Formats/Flir/FlirReader.cs

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// 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.
22

3+
using System.Buffers;
34
using MetadataExtractor.Formats.Jpeg;
45
using static MetadataExtractor.Formats.Flir.FlirCameraInfoDirectory;
56

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

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

41-
foreach (var segment in segments)
41+
try
4242
{
43-
// Skip segments not starting with the required preamble
44-
if (segment.Span.StartsWith(preamble))
43+
using var merged = new MemoryStream(buffer);
44+
45+
foreach (var segment in segments)
4546
{
46-
merged.Write(segment.Bytes, preambleLength, segment.Bytes.Length - preambleLength);
47+
// Skip segments not starting with the required preamble
48+
if (segment.Span.StartsWith(preamble))
49+
{
50+
merged.Write(segment.Bytes, preambleLength, segment.Bytes.Length - preambleLength);
51+
}
4752
}
48-
}
4953

50-
return Extract(new ByteArrayReader(buffer));
54+
return Extract(new ByteArrayReader(buffer));
55+
}
56+
finally
57+
{
58+
ArrayPool<byte>.Shared.Return(buffer);
59+
}
5160
}
5261

5362
public IEnumerable<Directory> Extract(IndexedReader reader)
5463
{
55-
var header = reader.GetUInt32(0);
64+
Span<byte> header = stackalloc byte[4];
65+
reader.GetBytes(0, header);
5666

57-
if (header != 0x46464600)
67+
if (!header.SequenceEqual("FFF\0"u8))
5868
{
5969
var flirHeaderDirectory = new FlirHeaderDirectory();
6070
flirHeaderDirectory.AddError("Unexpected FFF header bytes.");

MetadataExtractor/Formats/Gif/GifReader.cs

Lines changed: 70 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// 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.
22

3+
using System.Buffers;
34
using MetadataExtractor.Formats.Icc;
45
using MetadataExtractor.Formats.Xmp;
56

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

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

239-
switch (extensionType)
240+
reader.GetBytes(extensionType);
241+
242+
if (extensionType.SequenceEqual("XMP DataXMP"u8))
240243
{
241-
case "XMP DataXMP":
242-
{
243-
// XMP data extension
244-
var xmpBytes = GatherBytes(reader);
245-
int xmpLength = xmpBytes.Length - 257; // Exclude the "magic trailer", see XMP Specification Part 3, 1.1.2 GIF
246-
// Only extract valid blocks
247-
return xmpLength > 0
248-
? new XmpReader().Extract(xmpBytes, 0, xmpBytes.Length - 257)
249-
: null;
250-
}
251-
case "ICCRGBG1012":
252-
{
253-
// ICC profile extension
254-
var iccBytes = GatherBytes(reader, reader.GetByte());
255-
return iccBytes.Length != 0
256-
? new IccReader().Extract(new ByteArrayReader(iccBytes))
257-
: null;
258-
}
259-
case "NETSCAPE2.0":
260-
{
261-
reader.Skip(2);
262-
// Netscape's animated GIF extension
263-
// Iteration count (0 means infinite)
264-
var iterationCount = reader.GetUInt16();
265-
// Skip terminator
266-
reader.Skip(1);
267-
var animationDirectory = new GifAnimationDirectory();
268-
animationDirectory.Set(GifAnimationDirectory.TagIterationCount, iterationCount);
269-
return animationDirectory;
270-
}
271-
default:
272-
{
273-
SkipBlocks(reader);
274-
return null;
275-
}
244+
// XMP data extension
245+
var xmpBytes = GatherXmpBytes(reader);
246+
return xmpBytes is not null
247+
? new XmpReader().Extract(xmpBytes)
248+
: null;
249+
}
250+
else if (extensionType.SequenceEqual("ICCRGBG1012"u8))
251+
{
252+
// ICC profile extension
253+
var iccBytes = GatherBytes(reader, reader.GetByte());
254+
return iccBytes.Length != 0
255+
? new IccReader().Extract(new ByteArrayReader(iccBytes))
256+
: null;
257+
}
258+
else if (extensionType.SequenceEqual("NETSCAPE2.0"u8))
259+
{
260+
reader.Skip(2);
261+
// Netscape's animated GIF extension
262+
// Iteration count (0 means infinite)
263+
var iterationCount = reader.GetUInt16();
264+
// Skip terminator
265+
reader.Skip(1);
266+
var animationDirectory = new GifAnimationDirectory();
267+
animationDirectory.Set(GifAnimationDirectory.TagIterationCount, iterationCount);
268+
return animationDirectory;
269+
}
270+
else
271+
{
272+
SkipBlocks(reader);
273+
return null;
276274
}
277275
}
278276

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

330328
#region Utility methods
331329

332-
private static byte[] GatherBytes(SequentialReader reader)
330+
private static byte[]? GatherXmpBytes(SequentialReader reader)
333331
{
334-
var bytes = new MemoryStream();
335-
var buffer = new byte[257];
332+
// GatherXmpBytes differs from GatherBytes in that this method includes the "length"
333+
// bytes in its output.
334+
335+
var stream = new MemoryStream();
336+
var buffer = ArrayPool<byte>.Shared.Rent(byte.MaxValue + 1);
336337

337338
while (true)
338339
{
339-
var b = reader.GetByte();
340-
if (b == 0)
341-
return bytes.ToArray();
342-
buffer[0] = b;
343-
reader.GetBytes(buffer, 1, b);
344-
bytes.Write(buffer, 0, b + 1);
340+
var len = reader.GetByte();
341+
if (len == 0)
342+
break;
343+
buffer[0] = len;
344+
reader.GetBytes(buffer, offset: 1, count: len);
345+
stream.Write(buffer, 0, len + 1);
345346
}
347+
348+
ArrayPool<byte>.Shared.Return(buffer);
349+
350+
// Exclude the "magic trailer", see XMP Specification Part 3, 1.1.2 GIF
351+
int xmpLength = checked((int)stream.Length) - 257;
352+
353+
if (xmpLength <= 0)
354+
{
355+
return null;
356+
}
357+
358+
stream.SetLength(xmpLength);
359+
360+
return stream.ToArray();
346361
}
347362

348-
private static byte[] GatherBytes(SequentialReader reader, int firstLength)
363+
private static byte[] GatherBytes(SequentialReader reader, byte firstLength)
349364
{
350-
var buffer = new MemoryStream();
365+
var stream = new MemoryStream();
366+
var buffer = ArrayPool<byte>.Shared.Rent(byte.MaxValue);
351367

352368
var length = firstLength;
353369

354370
while (length > 0)
355371
{
356-
buffer.Write(reader.GetBytes(length), 0, length);
372+
reader.GetBytes(buffer.AsSpan().Slice(0, length));
373+
374+
stream.Write(buffer, 0, length);
357375

358376
length = reader.GetByte();
359377
}
360378

361-
return buffer.ToArray();
379+
ArrayPool<byte>.Shared.Return(buffer);
380+
381+
return stream.ToArray();
362382
}
363383

364384
private static void SkipBlocks(SequentialReader reader)

MetadataExtractor/Formats/Icc/IccReader.cs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// 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.
22

3+
using System.Buffers;
34
using MetadataExtractor.Formats.Jpeg;
45

56
namespace MetadataExtractor.Formats.Icc
@@ -37,14 +38,14 @@ public IEnumerable<Directory> ReadJpegSegments(IEnumerable<JpegSegment> segments
3738
byte[] buffer;
3839
if (iccSegments.Count == 1)
3940
{
40-
buffer = new byte[iccSegments[0].Bytes.Length - JpegSegmentPreambleLength];
41+
buffer = ArrayPool<byte>.Shared.Rent(iccSegments[0].Bytes.Length - JpegSegmentPreambleLength);
4142
Array.Copy(iccSegments[0].Bytes, JpegSegmentPreambleLength, buffer, 0, iccSegments[0].Bytes.Length - JpegSegmentPreambleLength);
4243
}
4344
else
4445
{
4546
// Concatenate all buffers
4647
var totalLength = iccSegments.Sum(s => s.Bytes.Length - JpegSegmentPreambleLength);
47-
buffer = new byte[totalLength];
48+
buffer = ArrayPool<byte>.Shared.Rent(totalLength);
4849
for (int i = 0, pos = 0; i < iccSegments.Count; i++)
4950
{
5051
var segment = iccSegments[i];
@@ -53,7 +54,11 @@ public IEnumerable<Directory> ReadJpegSegments(IEnumerable<JpegSegment> segments
5354
}
5455
}
5556

56-
return new Directory[] { Extract(new ByteArrayReader(buffer)) };
57+
Directory directory = Extract(new ByteArrayReader(buffer));
58+
59+
ArrayPool<byte>.Shared.Return(buffer);
60+
61+
return [directory];
5762
}
5863

5964
public IccDirectory Extract(IndexedReader reader)

MetadataExtractor/Formats/QuickTime/QuickTimeReaderExtensions.cs

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,29 +7,32 @@ namespace MetadataExtractor.Formats.QuickTime
77
/// </summary>
88
public static class QuickTimeReaderExtensions
99
{
10-
#if NETSTANDARD2_1
11-
public static string Get4ccString(this SequentialReader reader)
10+
#if NET462 || NETSTANDARD1_3
11+
public static unsafe string Get4ccString(this SequentialReader reader)
1212
#else
13-
public unsafe static string Get4ccString(this SequentialReader reader)
13+
public static string Get4ccString(this SequentialReader reader)
1414
#endif
1515
{
16+
// https://en.wikipedia.org/wiki/FourCC
17+
1618
Span<byte> bytes = stackalloc byte[4];
1719
Span<char> chars = stackalloc char[4];
1820

1921
reader.GetBytes(bytes);
2022

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

26-
#if NETSTANDARD2_1
27-
return new string(chars);
28-
#else
29-
fixed (char* c = chars)
29+
#if NET462 || NETSTANDARD1_3
30+
fixed (char* pChars = chars)
3031
{
31-
return new string(c, 0, 4);
32+
return new string(pChars, startIndex: 0, length: 4);
3233
}
34+
#else
35+
return new string(chars);
3336
#endif
3437
}
3538

MetadataExtractor/Formats/QuickTime/QuickTimeTypeChecker.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ internal sealed class QuickTimeTypeChecker : ITypeChecker
5454
{ Util.FileType.Heif, "hevc"u8 },
5555
{ Util.FileType.Heif, "hevx"u8 },
5656

57+
// AVIF
58+
{ Util.FileType.Avif, "avif"u8 },
59+
5760
// CRX
5861
{ Util.FileType.Crx, "crx "u8 }
5962
};

MetadataExtractor/Formats/Raf/RafMetadataReader.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// 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.
22

3+
using System.Buffers;
34
using MetadataExtractor.Formats.Jpeg;
45

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

17-
var data = new byte[512];
18+
var data = ArrayPool<byte>.Shared.Rent(512);
19+
1820
var bytesRead = stream.Read(data, 0, 512);
1921

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

37+
ArrayPool<byte>.Shared.Return(data);
38+
3539
return JpegMetadataReader.ReadMetadata(stream);
3640
}
3741
}

MetadataExtractor/Formats/Tga/TgaExtensionReader.cs

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
// 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.
22

3+
using System.Buffers;
4+
35
namespace MetadataExtractor.Formats.Tga
46
{
57
/// <summary>Reads TGA image file extension area.</summary>
@@ -55,12 +57,7 @@ protected override void Populate(Stream stream, int offset, TgaExtensionDirector
5557

5658
string GetString(int length)
5759
{
58-
var buffer = new byte[length];
59-
reader.GetBytes(buffer, 0, length);
60-
int i = 0;
61-
while (i < buffer.Length && buffer[i] != '\0')
62-
++i;
63-
return Encoding.ASCII.GetString(buffer, 0, i).TrimEnd();
60+
return reader.GetNullTerminatedString(length, Encoding.ASCII, moveToMaxLength: true).TrimEnd();
6461
}
6562

6663
bool TryGetDateTime(out DateTime dateTime)

0 commit comments

Comments
 (0)