Initial commit — AllMusicGuide scraper and music metadata tagger

This commit is contained in:
2026-05-10 02:49:16 +00:00
commit 4a541ca04b
194 changed files with 46364 additions and 0 deletions
@@ -0,0 +1,69 @@
using System;
using System.Collections.Generic;
using MusicMetaTagger.Core.Model;
using MusicMetaTagger.Core.Services;
using MusicMetaTagger.Core.Utility;
namespace MusicMetaTagger.Client.AllMusicGuide.Services
{
public class MusicGuideCache : IMusicGuide
{
private readonly Func<string, Album> _getAlbum;
private readonly Func<string, Artist> _getArtist;
private readonly Func<string, Song> _getSong;
private readonly Func<string, List<ArtistResult>> _searchArtist;
private readonly Func<string, List<AlbumResult>> _searchAlbum;
private readonly Func<string, List<SongResult>> _searchSong;
public MusicGuideCache(IMusicGuide @base)
{
Func<string, Album> getAlbum = @base.GetAlbum;
_getAlbum = getAlbum.MemoizeFunction();
Func<string, Artist> getArtist = @base.GetArtist;
_getArtist = getArtist.MemoizeFunction();
Func<string, Song> getSong = @base.GetSong;
_getSong = getSong.MemoizeFunction();
Func<string, List<ArtistResult>> searchArtist = @base.SearchArtist;
_searchArtist = searchArtist.MemoizeFunction();
Func<string, List<AlbumResult>> searchAlbum = @base.SearchAlbum;
_searchAlbum = searchAlbum.MemoizeFunction();
Func<string, List<SongResult>> searchSong = @base.SearchSong;
_searchSong = searchSong.MemoizeFunction();
}
public Artist GetArtist(string artistId)
{
return _getArtist(artistId);
}
public Album GetAlbum(string albumId)
{
return _getAlbum(albumId);
}
public Song GetSong(string songId)
{
return _getSong(songId);
}
public List<ArtistResult> SearchArtist(string artistName)
{
return _searchArtist(artistName);
}
public List<AlbumResult> SearchAlbum(string albumName)
{
return _searchAlbum(albumName);
}
public List<SongResult> SearchSong(string songName)
{
return _searchSong(songName);
}
}
}
@@ -0,0 +1,74 @@
using System.Linq;
using System.Threading;
using MusicMetaTagger.Core.Model;
using MusicMetaTagger.Core.Queries;
using MusicMetaTagger.Core.Services;
using MusicMetaTagger.Core.Utility;
namespace MusicMetaTagger.Client.AllMusicGuide.Services.Searchers
{
public class AlbumArtistSearcher : ITrackSearcher
{
private readonly IMusicGuide _musicGuide;
public AlbumArtistSearcher(IMusicGuide musicGuide)
{
_musicGuide = musicGuide;
}
public event StatusHandler SearchStatusUpdate;
private void OnStatusUpdate(string status)
{
if (SearchStatusUpdate != null)
SearchStatusUpdate.Invoke(new StatusEventArgs(status));
}
public Track SearchTrack(TrackQuery trackQuery, CancellationToken canellationToken)
{
if (string.IsNullOrEmpty(trackQuery.AlbumTitle)
|| string.IsNullOrEmpty(trackQuery.ArtistName))
return null;
var albumResults = ScoreUtility.ScoreResults(_musicGuide.SearchAlbum, trackQuery.AlbumTitle).ToList();
var artistName = GetArtistName(trackQuery);
var artistResults = ScoreUtility.ScoreResults(_musicGuide.SearchArtist, artistName);
if (canellationToken.IsCancellationRequested)
return null;
var matchingAlbums = ScoreUtility.Match(
albumResults, artistResults,
(album, artist) =>
album.ArtistId == artist.ArtistId).ToList();
if (trackQuery.AlbumPartOfCompilation)
matchingAlbums = matchingAlbums.Union(
from album in albumResults
where
album.Obj.ArtistId == null
|| album.Obj.AlbumTitle.Contains("Soundtrack")
select album
).ToList();
if (canellationToken.IsCancellationRequested)
return null;
OnStatusUpdate("Found " + matchingAlbums.Count() + " potential matching albums");
return (from album in matchingAlbums
let tracks = _musicGuide.GetAlbum(album.Obj.AlbumId).Tracks
let match = TrackSimilarity.MatchTrack(trackQuery, tracks)
where match != null
select match).FirstOrDefault();
}
private static string GetArtistName(TrackQuery trackQuery)
{
var artistName = trackQuery.ArtistName;
if (!string.IsNullOrEmpty(trackQuery.AlbumArtistName) && !trackQuery.AlbumArtistName.Contains("Various"))
artistName = trackQuery.AlbumArtistName;
return artistName;
}
}
}
@@ -0,0 +1,40 @@
using System.Linq;
using System.Threading;
using MusicMetaTagger.Core.Model;
using MusicMetaTagger.Core.Queries;
using MusicMetaTagger.Core.Services;
using MusicMetaTagger.Core.Utility;
namespace MusicMetaTagger.Client.AllMusicGuide.Services.Searchers
{
public class AlbumSearcher : ITrackSearcher
{
private readonly IMusicGuide _musicGuide;
public AlbumSearcher(IMusicGuide musicGuide)
{
_musicGuide = musicGuide;
}
public event StatusHandler SearchStatusUpdate;
public Track SearchTrack(TrackQuery trackQuery, CancellationToken canellationToken)
{
if (string.IsNullOrEmpty(trackQuery.AlbumTitle))
return null;
var albumResults = ScoreUtility.ScoreResults(_musicGuide.SearchAlbum, trackQuery.AlbumTitle);
foreach (ScoreContainer<AlbumResult> album in albumResults.Take(10))
{
if (canellationToken.IsCancellationRequested)
return null;
var tracks = _musicGuide.GetAlbum(album.Obj.AlbumId).Tracks;
var match = TrackSimilarity.MatchTrack(trackQuery, tracks);
if (match != null) return match;
}
return null;
}
}
}
@@ -0,0 +1,59 @@
using System.Linq;
using System.Threading;
using MusicMetaTagger.Core.Model;
using MusicMetaTagger.Core.Queries;
using MusicMetaTagger.Core.Services;
using MusicMetaTagger.Core.Utility;
namespace MusicMetaTagger.Client.AllMusicGuide.Services.Searchers
{
public class SongArtistSearcher : ITrackSearcher
{
private readonly IMusicGuide _musicGuide;
public SongArtistSearcher(IMusicGuide musicGuide)
{
_musicGuide = musicGuide;
}
public event StatusHandler SearchStatusUpdate;
public Track SearchTrack(TrackQuery trackQuery, CancellationToken canellationToken)
{
if (string.IsNullOrEmpty(trackQuery.TrackTitle)
|| string.IsNullOrEmpty(trackQuery.ArtistName))
return null;
var songResults = ScoreUtility.ScoreResults(_musicGuide.SearchSong, trackQuery.TrackTitle);
var artistName = GetArtistName(trackQuery);
var artistResults = ScoreUtility.ScoreResults(_musicGuide.SearchArtist, artistName);
if (canellationToken.IsCancellationRequested)
return null;
var matchingSongs = ScoreUtility.Match(songResults, artistResults, (s, a) => s.ArtistId == a.ArtistId).ToList();
foreach (var song in matchingSongs)
{
foreach (var album in _musicGuide.GetSong(song.Obj.SongId).AppearsOnAlbum)
{
if (canellationToken.IsCancellationRequested)
return null;
var tracks = _musicGuide.GetAlbum(album.AlbumId).Tracks;
var match = TrackSimilarity.MatchTrack(trackQuery, tracks);
if (match != null) return match;
}
}
return null;
}
private static string GetArtistName(TrackQuery trackQuery)
{
var artistName = trackQuery.ArtistName;
if (!string.IsNullOrEmpty(trackQuery.AlbumArtistName) && !trackQuery.AlbumArtistName.Contains("Various"))
artistName = trackQuery.AlbumArtistName;
return artistName;
}
}
}
@@ -0,0 +1,25 @@
using MusicMetaTagger.Core.Model;
using MusicMetaTagger.Core.Services;
namespace MusicMetaTagger.Client.AllMusicGuide.Services
{
public class TrackOriginalRelease : ITrackOriginalRelease
{
private readonly IMusicGuide _musicGuide;
public TrackOriginalRelease(IMusicGuide musicGuide)
{
_musicGuide = musicGuide;
}
public Track GetTrackOriginalRelease(Track track)
{
var song = _musicGuide.GetSong(track.TrackId);
// TODO: iterate through albums until track is found
var firstAlbum = song.AppearsOnAlbum[0];
var album = _musicGuide.GetAlbum(firstAlbum.AlbumId);
return TrackSimilarity.MatchTrack(track, album.Tracks);
}
}
}
@@ -0,0 +1,57 @@
using System;
using System.Linq;
using MusicMetaTagger.Core.Model;
using MusicMetaTagger.Core.Services;
namespace MusicMetaTagger.Client.AllMusicGuide.Services
{
public class TrackRater : ITrackRater
{
private readonly IMusicGuide _musicGuide;
public TrackRater(IMusicGuide musicGuide)
{
_musicGuide = musicGuide;
}
public int? RateTrack(Track track)
{
if (track == null)
throw new ArgumentNullException("track");
if (track.PerformerIds == null || track.PerformerIds.Count == 0)
return null;
// get the track pick statistics for this album
var albumInfo = _musicGuide.GetAlbum(track.AlbumId);
if (!albumInfo.Rating.HasValue)
return null;
// get the album rating statistics for this artist
double artistAlbumRatingMean = 0;
var artistInfo = _musicGuide.GetArtist(albumInfo.ArtistId);
if (artistInfo != null)
{
var currentDi =
artistInfo.Discography.Find(di => di.AlbumId == albumInfo.AlbumId);
artistAlbumRatingMean =
(from di in artistInfo.Discography
where currentDi == null || di.ReleaseType == currentDi.ReleaseType
where di.Rating.HasValue
select di.Rating.Value).Average();
}
// calculate the track rating
var trackRating = albumInfo.Rating.Value;
if (albumInfo.Rating.Value >= 9 && albumInfo.Tracks.Any(t => t.Pick)) trackRating -= 1;
if (artistAlbumRatingMean >= 8 && albumInfo.Tracks.Any(t => t.Pick)) trackRating -= 1;
if (albumInfo.Pick) trackRating += 1;
if (track.Pick) trackRating += 2;
return Math.Min(Math.Max(0, trackRating), 10);
}
}
}
@@ -0,0 +1,87 @@
using System;
using System.Threading;
using MusicMetaTagger.Client.AllMusicGuide.Services.Searchers;
using MusicMetaTagger.Core.Model;
using MusicMetaTagger.Core.Queries;
using MusicMetaTagger.Core.Services;
namespace MusicMetaTagger.Client.AllMusicGuide.Services
{
public class TrackSearcher : ITrackSearcher
{
private readonly IMusicGuide _musicGuide;
private readonly ITrackSearcher[] _searchers;
public TrackSearcher(IMusicGuide musicGuide)
{
_musicGuide = musicGuide;
_searchers = new ITrackSearcher[]
{
new AlbumArtistSearcher(_musicGuide),
new SongArtistSearcher(_musicGuide),
new AlbumSearcher(_musicGuide),
};
foreach (var searcher in _searchers)
searcher.SearchStatusUpdate += NestedSearcher_SearchStatusUpdate;
}
public event StatusHandler SearchStatusUpdate;
private void OnStatusUpdate(string status)
{
if (SearchStatusUpdate != null)
SearchStatusUpdate.Invoke(new StatusEventArgs(status));
}
void NestedSearcher_SearchStatusUpdate(StatusEventArgs data)
{
if (SearchStatusUpdate != null)
SearchStatusUpdate.Invoke(data);
}
public Track SearchTrack(TrackQuery trackQuery, CancellationToken canellationToken)
{
var statusSearch =
String.Format("Searching for Album - Artist - Track: '{0} - {1} - {2}'",
trackQuery.AlbumTitle ?? "unknown", trackQuery.ArtistName ?? "unknown",
trackQuery.TrackTitle);
OnStatusUpdate(statusSearch);
Track matchingTrack = null;
foreach (var searcher in _searchers)
{
var track = searcher.SearchTrack(trackQuery, canellationToken);
if (track != null)
{
matchingTrack = track;
break;
}
if (canellationToken.IsCancellationRequested)
return null;
}
if (matchingTrack != null)
{
TrackMatchFound(matchingTrack);
return matchingTrack;
}
OnStatusUpdate("No Matches Found");
return null;
}
/// <summary>
/// Updates the callback with the matching track
/// </summary>
private void TrackMatchFound(Track matchTrack)
{
var artistName = !string.IsNullOrEmpty(matchTrack.PerformerIds[0])
? _musicGuide.GetArtist(matchTrack.PerformerIds[0]).ArtistName
: "Unknown";
var matchAlbumInfo = _musicGuide.GetAlbum(matchTrack.AlbumId);
var status = String.Format("Found Match: {0} - {1} - {2} - {3}", artistName, matchAlbumInfo.Title, matchTrack.TrackTitle, matchTrack.TrackLength);
OnStatusUpdate(status);
}
}
}