using Microsoft.Extensions.Configuration; using Roadie.Library.Caching; using Roadie.Library.Data; using Roadie.Library.Extensions; using Roadie.Library.Logging; using Roadie.Library.MetaData.Audio; using Roadie.Library.SearchEngines.MetaData; using Roadie.Library.Utility; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Net.Http; using System.Threading.Tasks; namespace Roadie.Library.MetaData.MusicBrainz { public class MusicBrainzProvider : MetaDataProviderBase, IArtistSearchEngine, IReleaseSearchEngine { public override bool IsEnabled { get { return this.Configuration.GetValue("Integrations:MusicBrainzProviderEnabled", true); } } public MusicBrainzProvider(IConfiguration configuration, ICacheManager cacheManager, ILogger logger) : base(configuration, cacheManager, logger) { } public async Task CoverArtForMusicBrainzReleaseById(string musicBrainzId) { return await MusicBrainzRequestHelper.GetAsync(MusicBrainzRequestHelper.CreateCoverArtReleaseUrl(musicBrainzId)); } public async Task MusicBrainzReleaseById(string musicBrainzId) { if (string.IsNullOrEmpty(musicBrainzId)) { return null; } Release release = null; try { var artistCacheKey = string.Format("uri:musicbrainz:MusicBrainzReleaseById:{0}", musicBrainzId); release = this.CacheManager.Get(artistCacheKey); if (release == null) { release = await MusicBrainzRequestHelper.GetAsync(MusicBrainzRequestHelper.CreateLookupUrl("release", musicBrainzId, "labels+aliases+recordings+release-groups+media+url-rels")); if (release != null) { var coverUrls = await this.CoverArtForMusicBrainzReleaseById(musicBrainzId); if (coverUrls != null) { var frontCover = coverUrls.images.FirstOrDefault(i => i.front); release.imageUrls = coverUrls.images.Select(x => x.image).ToList(); if (frontCover != null) { release.coverThumbnailUrl = frontCover.image; release.imageUrls = release.imageUrls.Where(x => x != release.coverThumbnailUrl).ToList(); } } this.CacheManager.Add(artistCacheKey, release); } } } catch (HttpRequestException) { } if (release == null) { this.Logger.Warning("MusicBrainzReleaseById: MusicBrainzId [{0}], No MusicBrainz Release Found", musicBrainzId); } return release; } public async Task> MusicBrainzReleaseTracks(string artistName, string releaseTitle) { try { if (string.IsNullOrEmpty(artistName) && string.IsNullOrEmpty(releaseTitle)) { return null; } // Find the Artist var artistCacheKey = string.Format("uri:musicbrainz:artist:{0}", artistName); var artistSearch = await this.PerformArtistSearch(artistName, 1); if (!artistSearch.IsSuccess) { return null; } var artist = artistSearch.Data.First(); if (artist == null) { return null; } var ReleaseCacheKey = string.Format("uri:musicbrainz:release:{0}", releaseTitle); var release = this.CacheManager.Get(ReleaseCacheKey); if (release == null) { // Now Get Artist Details including Releases var ReleaseResult = artist.Releases.FirstOrDefault(x => x.ReleaseTitle.Equals(releaseTitle, StringComparison.InvariantCultureIgnoreCase)); if (ReleaseResult == null) { ReleaseResult = artist.Releases.FirstOrDefault(x => x.ReleaseTitle.EndsWith(releaseTitle, StringComparison.InvariantCultureIgnoreCase)); if (ReleaseResult == null) { ReleaseResult = artist.Releases.FirstOrDefault(x => x.ReleaseTitle.StartsWith(releaseTitle, StringComparison.InvariantCultureIgnoreCase)); if (ReleaseResult == null) { return null; } } } // Now get The Release Details release = await MusicBrainzRequestHelper.GetAsync(MusicBrainzRequestHelper.CreateLookupUrl("release", ReleaseResult.MusicBrainzId, "recordings")); if (release == null) { return null; } this.CacheManager.Add(ReleaseCacheKey, release); } var result = new List(); foreach (var media in release.media) { foreach (var track in media.tracks) { int date = 0; if (!string.IsNullOrEmpty(release.date)) { if (release.date.Length > 4) { DateTime ReleaseDate = DateTime.MinValue; if (DateTime.TryParse(release.date, out ReleaseDate)) { date = ReleaseDate.Year; } } else { int.TryParse(release.date, out date); } } result.Add(new AudioMetaData { ReleaseMusicBrainzId = release.id, MusicBrainzId = track.id, Artist = artist.ArtistName, Release = release.title, Title = track.title, Time = track.length.HasValue ? (TimeSpan?)TimeSpan.FromMilliseconds(track.length.Value) : null, TrackNumber = SafeParser.ToNumber(track.position ?? track.number) ?? 0, Disk = media.position, Year = date > 0 ? (int?)date : null, TotalTrackNumbers = media.trackcount, //tagFile.Tag.Pictures.Select(x => new AudoMetaDataImage //{ // Data = x.Data.Data, // Description = x.Description, // MimeType = x.MimeType, // Type = (AudioMetaDataImageType)x.Type //}).ToArray() }); } } return result; } catch (Exception) { } return null; } public async Task>> PerformArtistSearch(string query, int resultsCount) { ArtistSearchResult result = null; try { this.Logger.Trace("MusicBrainzProvider:PerformArtistSearch:{0}", query); // Find the Artist var artistCacheKey = string.Format("uri:musicbrainz:ArtistSearchResult:{0}", query); result = this.CacheManager.Get(artistCacheKey); if (result == null) { ArtistResult artistResult = null; try { artistResult = await MusicBrainzRequestHelper.GetAsync(MusicBrainzRequestHelper.CreateSearchTemplate("artist", query, resultsCount, 0)); } catch (Exception ex) { this.Logger.Error(ex); } if (artistResult == null || artistResult.artists == null || artistResult.count < 1) { return new OperationResult>(); } var a = artistResult.artists.First(); var mbArtist = await MusicBrainzRequestHelper.GetAsync(MusicBrainzRequestHelper.CreateLookupUrl("artist", artistResult.artists.First().id, "releases")); if (mbArtist == null) { return new OperationResult>(); } result = new ArtistSearchResult { ArtistName = mbArtist.name, ArtistSortName = mbArtist.sortname, MusicBrainzId = mbArtist.id, ArtistType = mbArtist.type, IPIs = mbArtist.ipis, ISNIs = mbArtist.isnis }; if (mbArtist.lifespan != null) { result.BeginDate = SafeParser.ToDateTime(mbArtist.lifespan.begin); result.EndDate = SafeParser.ToDateTime(mbArtist.lifespan.end); } if (a.aliases != null) { result.AlternateNames = a.aliases.Select(x => x.name).Distinct().ToArray(); } if (a.tags != null) { result.Tags = a.tags.Select(x => x.name).Distinct().ToArray(); } var mbFilteredReleases = new List(); var filteredPlaces = new List { "US", "WORLDWIDE", "XW", "GB" }; foreach (var release in mbArtist.releases) { if (filteredPlaces.Contains((release.country ?? string.Empty).ToUpper())) { mbFilteredReleases.Add(release); } } result.Releases = new List(); var bag = new ConcurrentBag(); var filteredReleaseDetails = mbFilteredReleases.Select(async release => { bag.Add(await this.MusicBrainzReleaseById(release.id)); }); await Task.WhenAll(filteredReleaseDetails); foreach (var mbRelease in bag.Where(x => x != null)) { var release = new ReleaseSearchResult { MusicBrainzId = mbRelease.id, ReleaseTitle = mbRelease.title, ReleaseThumbnailUrl = mbRelease.coverThumbnailUrl }; if (mbRelease.imageUrls != null) { release.ImageUrls = mbRelease.imageUrls; } if (mbRelease.releaseevents != null) { release.ReleaseDate = SafeParser.ToDateTime(mbRelease.releaseevents.First().date); } // Labels if (mbRelease.media != null) { var releaseMedias = new List(); foreach (var mbMedia in mbRelease.media) { var releaseMedia = new ReleaseMediaSearchResult { ReleaseMediaNumber = SafeParser.ToNumber(mbMedia.position), TrackCount = mbMedia.trackcount }; if (mbMedia.tracks != null) { var releaseTracks = new List(); foreach (var mbTrack in mbMedia.tracks) { releaseTracks.Add(new TrackSearchResult { MusicBrainzId = mbTrack.id, TrackNumber = SafeParser.ToNumber(mbTrack.number), Title = mbTrack.title, Duration = mbTrack.length }); } releaseMedia.Tracks = releaseTracks; } releaseMedias.Add(releaseMedia); } release.ReleaseMedia = releaseMedias; } result.Releases.Add(release); }; this.CacheManager.Add(artistCacheKey, result); } } catch (HttpRequestException) { } catch (Exception ex) { this.Logger.Error(ex); } if (result == null) { this.Logger.Warning("MusicBrainzArtist: ArtistName [{0}], No MusicBrainz Artist Found", query); } else { this.Logger.Trace("MusicBrainzArtist: Result [{0}]", query, result.ToString()); } return new OperationResult> { IsSuccess = result != null, Data = new ArtistSearchResult[] { result } }; } public async Task>> PerformReleaseSearch(string artistName, string query, int resultsCount) { ReleaseSearchResult result = null; try { var releaseInfosForArtist = await this.ReleasesForArtist(artistName); if (releaseInfosForArtist != null) { var releaseInfo = releaseInfosForArtist.FirstOrDefault(x => x.title.Equals(query, StringComparison.OrdinalIgnoreCase)); if (releaseInfo != null) { var mbRelease = await this.MusicBrainzReleaseById(releaseInfo.id); if (mbRelease != null) { result = new ReleaseSearchResult { ReleaseDate = mbRelease.releasegroup != null ? SafeParser.ToDateTime(mbRelease.releasegroup.firstreleasedate) : null, ReleaseTitle = mbRelease.title, MusicBrainzId = mbRelease.id, ReleaseType = mbRelease.releasegroup != null ? mbRelease.releasegroup.primarytype : null, }; if (mbRelease.labelinfo != null) { var releaseLabels = new List(); foreach (var mbLabel in mbRelease.labelinfo) { releaseLabels.Add(new ReleaseLabelSearchResult { CatalogNumber = mbLabel.catalognumber, Label = new LabelSearchResult { LabelName = mbLabel.label.name, MusicBrainzId = mbLabel.label.id, LabelSortName = mbLabel.label.sortname, AlternateNames = mbLabel.label.aliases.Select(x => x.name).ToList() } }); } result.ReleaseLabel = releaseLabels; } if (mbRelease.media != null) { var releaseMedia = new List(); foreach (var mbMedia in mbRelease.media.OrderBy(x => x.position)) { var mediaTracks = new List(); short trackLooper = 0; foreach (var mbTrack in mbMedia.tracks.OrderBy(x => x.position)) { trackLooper++; mediaTracks.Add(new TrackSearchResult { Title = mbTrack.title, TrackNumber = trackLooper, Duration = mbTrack.length, MusicBrainzId = mbTrack.id, AlternateNames = mbTrack.recording != null && mbTrack.recording.aliases != null ? mbTrack.recording.aliases.Select(x => x.name).ToList() : null }); } releaseMedia.Add(new ReleaseMediaSearchResult { ReleaseMediaNumber = SafeParser.ToNumber(mbMedia.position), ReleaseMediaSubTitle = mbMedia.title, TrackCount = SafeParser.ToNumber(mbMedia.trackcount), Tracks = mediaTracks }); } result.ReleaseMedia = releaseMedia; } } } } } catch (Exception ex) { this.Logger.Error(ex, ex.Serialize()); } if (result == null) { this.Logger.Warning("MusicBrainzArtist: ArtistName [{0}], ReleaseTitle [{0}], No MusicBrainz Release Found", artistName, query); } else { this.Logger.Trace("MusicBrainzArtist: Result [{0}]", query, result.ToString()); } return new OperationResult> { IsSuccess = result != null, Data = new ReleaseSearchResult[] { result } }; } public async Task ReleaseForMusicBrainzReleaseById(string musicBrainzId) { var release = await MusicBrainzReleaseById(musicBrainzId); if (release == null) { return null; } var media = release.media.First(); if (media == null) { return null; } var result = new Data.Release { Title = release.title.ToTitleCase(false), ReleaseDate = SafeParser.ToDateTime(release.date), MusicBrainzId = release.id }; var releaseMedia = new Data.ReleaseMedia { Tracks = media.tracks.Select(m => new Data.Track { TrackNumber = SafeParser.ToNumber(m.position ?? m.number), Title = m.title.ToTitleCase(false), MusicBrainzId = m.id, }).ToList() }; result.Medias = new List { releaseMedia }; return result; } public async Task> ReleasesForArtist(string artist, string artistMusicBrainzId = null) { try { var artistSearch = await this.PerformArtistSearch(artist, 1); if (artistSearch == null || !artistSearch.IsSuccess) { return null; } var mbArtist = artistSearch.Data.First(); if (string.IsNullOrEmpty(artistMusicBrainzId)) { if (mbArtist == null) { return null; } artistMusicBrainzId = mbArtist.MusicBrainzId; } var cacheKey = string.Format("uri:musicbrainz:ReleasesForArtist:{0}", artistMusicBrainzId); var result = this.CacheManager.Get>(cacheKey); if (result == null) { var pageSize = 50; var page = 0; var url = MusicBrainzRequestHelper.CreateArtistBrowseTemplate(artistMusicBrainzId, pageSize, 0); var mbReleaseBrowseResult = await MusicBrainzRequestHelper.GetAsync(url); var totalReleases = mbReleaseBrowseResult != null ? mbReleaseBrowseResult.releasecount : 0; var totalPages = Math.Ceiling((decimal)totalReleases / (decimal)pageSize); result = new List(); do { if (mbReleaseBrowseResult != null) { result.AddRange(mbReleaseBrowseResult.releases); } page++; mbReleaseBrowseResult = await MusicBrainzRequestHelper.GetAsync(MusicBrainzRequestHelper.CreateArtistBrowseTemplate(artistMusicBrainzId, pageSize, pageSize * page)); } while (page < totalPages); result = result.OrderBy(x => x.date).ThenBy(x => x.title).ToList(); this.CacheManager.Add(cacheKey, result); } return result; } catch (HttpRequestException) { } catch (Exception ex) { this.Logger.Error(ex); } return null; } } }