Files

44 lines
2.0 KiB
C#
Raw Permalink Normal View History

using System.Security.Cryptography;
namespace MusicBridge.Agent.Media;
internal sealed record Artwork(string Id, string ContentType, byte[] Bytes);
// Only the current cover is retained. IDs are content hashes, never file paths or URLs.
internal sealed class ArtworkCache
{
public const int MaxBytes = 2 * 1024 * 1024;
private Artwork? current;
public Artwork? Current => Volatile.Read(ref current);
public Artwork? Get(string id) => Current is { } artwork && artwork.Id == id ? artwork : null;
public void Clear() => Volatile.Write(ref current, null);
public async Task<Artwork> LoadAsync(Stream stream, CancellationToken cancellationToken)
{
Clear(); // A failed/new cover must not leave the previous track's image attached.
using var output = new MemoryStream();
var buffer = new byte[16 * 1024];
while (true)
{
var count = await stream.ReadAsync(buffer, cancellationToken);
if (count == 0) break;
if (output.Length + count > MaxBytes) throw new InvalidDataException("Обложка больше 2 МиБ.");
output.Write(buffer, 0, count);
}
var bytes = output.ToArray();
var type = DetectType(bytes) ?? throw new InvalidDataException("Неподдерживаемый формат обложки.");
var artwork = new Artwork(Convert.ToHexString(SHA256.HashData(bytes)), type, bytes);
Volatile.Write(ref current, artwork);
return artwork;
}
private static string? DetectType(ReadOnlySpan<byte> data)
{
if (data.StartsWith(new byte[] { 137, 80, 78, 71, 13, 10, 26, 10 })) return "image/png";
if (data.StartsWith(new byte[] { 255, 216, 255 })) return "image/jpeg";
if (data.StartsWith("GIF87a"u8) || data.StartsWith("GIF89a"u8)) return "image/gif";
if (data.Length >= 12 && data[..4].SequenceEqual("RIFF"u8) && data.Slice(8, 4).SequenceEqual("WEBP"u8)) return "image/webp";
return null;
}
}