Initial commit — AllMusicGuide scraper and music metadata tagger
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user