Added AudioFileScanner and AudioImageExtractor logic.

This commit is contained in:
2025-02-25 00:42:34 -05:00
parent 960c6b9ec7
commit b38e4dc858
8 changed files with 181 additions and 22 deletions

View File

@@ -0,0 +1,27 @@
using Harmonia.Core.Tags;
namespace Harmonia.Core.Imaging;
public class AudioImageExtractor(ITagResolver tagResolver) : IAudioImageExtractor
{
public SongPictureInfo ExtractImage(string fileName)
{
SongTagInfo songTagInfo = tagResolver.GetSongTagInfo(fileName);
return ExtractImage(fileName, songTagInfo);
}
public SongPictureInfo ExtractImage(string fileName, SongTagInfo songTagInfo)
{
if (songTagInfo.FrontCover != null)
{
using MemoryStream memoryStream = new(songTagInfo.FrontCover.Data);
return SongPictureInfo.FromStream(memoryStream);
}
else
{
return SongPictureInfo.FromFile(fileName);
}
}
}

View File

@@ -0,0 +1,9 @@
using Harmonia.Core.Tags;
namespace Harmonia.Core.Imaging;
public interface IAudioImageExtractor
{
SongPictureInfo ExtractImage(string fileName);
SongPictureInfo ExtractImage(string fileName, SongTagInfo songTagInfo);
}

View File

@@ -0,0 +1,59 @@
using System.Security.Cryptography;
namespace Harmonia.Core.Imaging;
public class SongPictureInfo : IDisposable
{
public required Stream Stream { get; init; }
public string? ImageHash { get; init; }
public required string ImageName { get; init; }
public ulong Size { get; init; }
private SongPictureInfo()
{
}
public static SongPictureInfo FromStream(Stream stream)
{
string imageHash = ComputeImageHash(stream);
return GetSongPictureInfo(stream, "Embedded", imageHash);
}
private static string ComputeImageHash(Stream stream)
{
using var md5 = MD5.Create();
byte[] hash = md5.ComputeHash(stream);
return Convert.ToHexStringLower(hash);
}
public static SongPictureInfo FromFile(string fileName)
{
using FileStream fileStream = File.OpenRead(fileName);
return GetSongPictureInfo(fileStream, fileName);
}
private static SongPictureInfo GetSongPictureInfo(Stream stream, string imageName, string? imageHash = null)
{
stream.Seek(0, SeekOrigin.Begin);
SongPictureInfo songPictureInfo = new()
{
Stream = stream,
ImageHash = imageHash,
ImageName = imageName,
Size = (ulong)stream.Length
};
return songPictureInfo;
}
public void Dispose()
{
Stream.Dispose();
GC.SuppressFinalize(this);
}
}

View File

@@ -0,0 +1,57 @@
using Harmonia.Core.Imaging;
using Harmonia.Core.Models;
using Harmonia.Core.Tags;
namespace Harmonia.Core.Scanner;
public class AudioFileScanner(ITagResolver tagResolver, IAudioImageExtractor audioImageExtractor) : IAudioFileScanner
{
public Song[] GetSongs(string[] fileNames)
{
List<Song> songs = [];
foreach (string fileName in fileNames)
{
if (string.IsNullOrWhiteSpace(fileName) || File.Exists(fileName) == false)
continue;
Song? song = GetSong(fileName);
if (song == null)
continue;
songs.Add(song);
}
return [.. songs];
}
private Song GetSong(string fileName)
{
FileInfo fileInfo = new(fileName);
SongTagInfo songTagInfo = tagResolver.GetSongTagInfo(fileName);
using SongPictureInfo songPictureInfo = audioImageExtractor.ExtractImage(fileName, songTagInfo);
Song song = new()
{
FileName = fileName,
Size = (ulong)fileInfo.Length,
LastModified = fileInfo.LastWriteTime,
Title = songTagInfo.Title,
Album = songTagInfo.Album,
Artists = songTagInfo.Artists,
AlbumArtists = songTagInfo.AlbumArtists,
DiscNumber = songTagInfo.DiscNumber,
TrackNumber = songTagInfo.TrackNumber,
Length = songTagInfo.Length,
Year = songTagInfo.Year,
Genre = songTagInfo.Genre,
BitRate = songTagInfo.BitRate,
SampleRate = songTagInfo.SampleRate,
ImageName = songPictureInfo.ImageName,
ImageHash = songPictureInfo.ImageHash
};
return song;
}
}

View File

@@ -0,0 +1,8 @@
using Harmonia.Core.Models;
namespace Harmonia.Core.Scanner;
public interface IAudioFileScanner
{
Song[] GetSongs(string[] fileNames);
}

View File

@@ -6,13 +6,17 @@ public class SongTagInfo
public string? Album { get; set; }
public List<string> Artists { get; set; } = [];
public List<string> AlbumArtists { get; set; } = [];
public int? DiscNumber { get; set; }
public int? TrackNumber { get; set; }
public byte? DiscNumber { get; set; }
public byte? TrackNumber { get; set; }
public TimeSpan Length { get; set; } = TimeSpan.Zero;
public int? Year { get; set; }
public ushort? Year { get; set; }
public string? Genre { get; set; }
public int BitRate { get; set; }
public int SampleRate { get; set; }
public uint BitRate { get; set; }
public uint SampleRate { get; set; }
public List<PictureTag> Pictures { get; set; } = [];
public Dictionary<string, string> Other { get; set; } = [];
public PictureTag? FrontCover => Pictures.Where(x => x.PictureType != PictureType.NotAPicture)
.OrderBy(x => x.PictureType == PictureType.FrontCover)
.FirstOrDefault();
}

View File

@@ -8,30 +8,30 @@ public class TagLibTagResolver : ITagResolver
using (var tagFile = TagLib.File.Create(fileName))
{
int? discNumber = null;
int? trackNumber = null;
int? year = null;
byte? discNumber = null;
byte? trackNumber = null;
ushort? year = null;
if (tagFile.Tag.Track > 0)
trackNumber = Convert.ToInt32(tagFile.Tag.Track);
trackNumber = (byte)tagFile.Tag.Track;
if (tagFile.Tag.Disc > 0)
discNumber = Convert.ToInt32(tagFile.Tag.Disc);
discNumber = (byte)tagFile.Tag.Disc;
if (tagFile.Tag.Year > 0)
year = Convert.ToInt32(tagFile.Tag.Year);
year = (ushort)tagFile.Tag.Year;
songTagInfo.Title = tagFile.Tag.Title;
songTagInfo.Album = tagFile.Tag.Album;
songTagInfo.Artists = tagFile.Tag.Performers.Select(x => x.Trim()).ToList();
songTagInfo.AlbumArtists = tagFile.Tag.AlbumArtists.Select(x => x.Trim()).ToList();
songTagInfo.Artists = [.. tagFile.Tag.Performers.Select(x => x.Trim())];
songTagInfo.AlbumArtists = [.. tagFile.Tag.AlbumArtists.Select(x => x.Trim())];
songTagInfo.DiscNumber = discNumber;
songTagInfo.TrackNumber = trackNumber;
songTagInfo.Length = tagFile.Properties.Duration;
songTagInfo.Year = year;
songTagInfo.Genre = tagFile.Tag.JoinedGenres;
songTagInfo.BitRate = tagFile.Properties.AudioBitrate;
songTagInfo.SampleRate = tagFile.Properties.AudioSampleRate;
songTagInfo.BitRate = (uint)tagFile.Properties.AudioBitrate;
songTagInfo.SampleRate = (uint)tagFile.Properties.AudioSampleRate;
foreach (var picture in tagFile.Tag.Pictures)
{

View File

@@ -8,7 +8,7 @@ using Shouldly;
namespace Harmonia.Tests;
public class TestAudioPlayer(IAudioEngine audioEngine, IPlaylistRepository playlistRepository)
internal class TestAudioPlayer(IAudioEngine audioEngine, IPlaylistRepository playlistRepository)
: AudioPlayer(audioEngine, playlistRepository)
{
internal void SetPlaylist(Playlist playlist)
@@ -18,10 +18,7 @@ public class TestAudioPlayer(IAudioEngine audioEngine, IPlaylistRepository playl
internal int GetPlayingSongIndex()
{
if (Playlist == null)
return -1;
if (PlayingSong == null)
if (Playlist == null || PlayingSong == null)
return -1;
return Playlist.Songs.IndexOf(PlayingSong);
@@ -68,11 +65,9 @@ public class AudioPlayerTests
_playlistRepository = Substitute.For<IPlaylistRepository>();
_playlistRepository.Get().Returns([playlist]);
//_playlistRepository.GetPlaylist(_playlistSongs[0]).Returns(playlist);
_audioPlayer = new TestAudioPlayer(_audioEngine, _playlistRepository);
_audioPlayer.SetPlaylist(playlist);
//await _audioPlayer.LoadAsync(_playlistSongs[0], PlaybackMode.LoadOnly);
}
[Theory]