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,14 @@
using System.Xml;
using MusicMetaTagger.Client.AllMusicGuide.Properties;
namespace MusicMetaTagger.Client.AllMusicGuide.RemoteDataAccess.Client
{
public class AlbumClient : XmlClientBase
{
public override XmlDocument Read(string albumId)
{
var settings = AlbumScraper.Default;
return ReadFromCriteria(settings.AlbumUrl, albumId);
}
}
}
@@ -0,0 +1,14 @@
using System.Xml;
using MusicMetaTagger.Client.AllMusicGuide.Properties;
namespace MusicMetaTagger.Client.AllMusicGuide.RemoteDataAccess.Client
{
public class AlbumResultPageClient : XmlClientBase
{
public override XmlDocument Read(string criteria)
{
var settings = AlbumResultPageScraper.Default;
return ReadFromCriteria(settings.AlbumResultUrl, criteria);
}
}
}
@@ -0,0 +1,14 @@
using System.Xml;
using MusicMetaTagger.Client.AllMusicGuide.Properties;
namespace MusicMetaTagger.Client.AllMusicGuide.RemoteDataAccess.Client
{
public class ArtistClient : XmlClientBase
{
public override XmlDocument Read(string artistId)
{
var settings = ArtistScaper.Default;
return ReadFromCriteria(settings.ArtistUrl, artistId);
}
}
}
@@ -0,0 +1,14 @@
using System.Xml;
using MusicMetaTagger.Client.AllMusicGuide.Properties;
namespace MusicMetaTagger.Client.AllMusicGuide.RemoteDataAccess.Client
{
public class ArtistDiscographyAlbumPageClient : XmlClientBase
{
public override XmlDocument Read(string artistId)
{
var settings = ArtistDiscographyPageScraper.Default;
return ReadFromCriteria(settings.DiscographyAlbumUrl, artistId);
}
}
}
@@ -0,0 +1,14 @@
using System.Xml;
using MusicMetaTagger.Client.AllMusicGuide.Properties;
namespace MusicMetaTagger.Client.AllMusicGuide.RemoteDataAccess.Client
{
public class ArtistDiscographyCompilationPageClient : XmlClientBase
{
public override XmlDocument Read(string artistId)
{
var settings = ArtistDiscographyPageScraper.Default;
return ReadFromCriteria(settings.DiscographyCompilationUrl, artistId);
}
}
}
@@ -0,0 +1,14 @@
using System.Xml;
using MusicMetaTagger.Client.AllMusicGuide.Properties;
namespace MusicMetaTagger.Client.AllMusicGuide.RemoteDataAccess.Client
{
public class ArtistDiscographyEpSinglesPageClient : XmlClientBase
{
public override XmlDocument Read(string artistId)
{
var settings = ArtistDiscographyPageScraper.Default;
return ReadFromCriteria(settings.DiscographyEpSinglesUrl, artistId);
}
}
}
@@ -0,0 +1,14 @@
using System.Xml;
using MusicMetaTagger.Client.AllMusicGuide.Properties;
namespace MusicMetaTagger.Client.AllMusicGuide.RemoteDataAccess.Client
{
public class ArtistResultPageClient : XmlClientBase
{
public override XmlDocument Read(string criteria)
{
var settings = ArtistResultPageScraper.Default;
return ReadFromCriteria(settings.ArtistResultUrl, criteria);
}
}
}
@@ -0,0 +1,9 @@
using System.Xml;
namespace MusicMetaTagger.Client.AllMusicGuide.RemoteDataAccess.Client
{
public interface IXmlClientBase
{
XmlDocument Read(string criteria);
}
}
@@ -0,0 +1,14 @@
using System.Xml;
using MusicMetaTagger.Client.AllMusicGuide.Properties;
namespace MusicMetaTagger.Client.AllMusicGuide.RemoteDataAccess.Client
{
public class SongClient : XmlClientBase
{
public override XmlDocument Read(string criteria)
{
var settings = SongScraper.Default;
return ReadFromCriteria(settings.SongUrl, criteria);
}
}
}
@@ -0,0 +1,14 @@
using System.Xml;
using MusicMetaTagger.Client.AllMusicGuide.Properties;
namespace MusicMetaTagger.Client.AllMusicGuide.RemoteDataAccess.Client
{
public class SongResultPageClient : XmlClientBase
{
public override XmlDocument Read(string criteria)
{
var settings = SongResultPageScraper.Default;
return ReadFromCriteria(settings.SongResultUrl, criteria);
}
}
}
@@ -0,0 +1,129 @@
using System;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
using System.Xml;
using HtmlAgilityPack;
using MusicMetaTagger.Core.Model;
namespace MusicMetaTagger.Client.AllMusicGuide.RemoteDataAccess.Client
{
public static class WebXmlClient
{
/// <summary>
/// Returns the HTML grabbed from the passed in URL.
/// </summary>
/// <param name="url"> The URL. </param>
/// <returns> A string containing html </returns>
public static string GetHtml(Uri url)
{
var tries = 0;
while (true)
{
try
{
var client = new WebClient();
using (var data = client.OpenRead(url))
using (var reader = new StreamReader(data))
{
return reader.ReadToEnd();
}
}
catch (Exception ex)
{
if (tries++ > 3)
throw new WebException(ex.Message + " : " + url, ex);
Thread.Sleep(100);
}
}
}
public static void SaveFile(Uri url, string filename)
{
var client = new WebClient();
client.DownloadFile(url, filename);
}
/// <summary>
/// Returns the HTML parsed into a standard XmlDocument. Uses the HtmlAgilityPack library for "out of the web" (poorly formatted) html file support.
/// </summary>
/// <param name="html"> The HTML </param>
/// <returns> An XmlDocument from the HTML </returns>
public static XmlDocument GetHtmlXml(string html)
{
// Sometimes the biography will have "<..." in it. Annoying.
html = html.Replace("<...", "");
html = html.Replace("<class=\"subtitle\">", "");
html = html.Replace("< ", "");
var htmlDoc = new HtmlDocument {OptionOutputAsXml = true};
htmlDoc.LoadHtml(html);
// this element will have unencoded entities in an attribute, messing up the xml
var selectSingleNode = htmlDoc.DocumentNode.SelectSingleNode("//*[@class='rovi-share']");
if (selectSingleNode != null)
selectSingleNode.Remove();
selectSingleNode = htmlDoc.DocumentNode.SelectSingleNode("//*[@id='similar-albums']");
if (selectSingleNode != null)
selectSingleNode.Remove();
// fix for ajax results having more than one root node
if (htmlDoc.DocumentNode.ChildNodes.All(n => n.Name != "html"))
{
var el = htmlDoc.CreateElement("html");
var nodes = htmlDoc.DocumentNode.ChildNodes;
el.AppendChildren(nodes);
htmlDoc.DocumentNode.RemoveAllChildren();
htmlDoc.DocumentNode.AppendChild(el);
}
var xmlDocument = new XmlDocument();
using (Stream stream = new MemoryStream())
using (var xmlTextWriter = new XmlTextWriter(stream, Encoding.UTF8))
{
htmlDoc.Save(xmlTextWriter);
stream.Seek(0, SeekOrigin.Begin);
using (var streamReader = new StreamReader(stream))
xmlDocument.Load(streamReader);
}
return xmlDocument;
}
/// <summary>
/// Returns the HTML grabbed from the passed in URL parsed into a standard XmlDocument. Uses the HtmlAgilityPack library for "out of the web" html file support.
/// </summary>
/// <param name="url"> The URL. </param>
/// <returns> An XmlDocument from the HTML </returns>
public static XmlDocument GetHtmlXml(Uri url)
{
return GetHtmlXml(GetHtml(url));
}
// TODO: Refactor!
/// <summary>
/// Downloads the cover art to a local file. If the album doesn't have cover art, this will return null.
/// </summary>
/// <exception cref="HttpException">Thrown on HTTP error.</exception>
/// <returns> </returns>
public static FileInfo DownloadCoverArt(Album album)
{
if (String.IsNullOrEmpty(album.CoverUrl))
return null;
if (album.CoverUrl.Contains("no_cover"))
return null;
var directoryInfo = new DirectoryInfo(".\\ImageCache");
if (!Directory.Exists(directoryInfo.FullName))
directoryInfo.Create();
var fileInfo = new FileInfo(directoryInfo.FullName + "\\" + album.AlbumId + ".jpg");
if (!fileInfo.Exists)
SaveFile(new Uri(album.CoverUrl), fileInfo.FullName);
return fileInfo;
}
}
}
@@ -0,0 +1,22 @@
using System;
using System.Xml;
namespace MusicMetaTagger.Client.AllMusicGuide.RemoteDataAccess.Client
{
public abstract class XmlClientBase : IXmlClientBase
{
public abstract XmlDocument Read(string criteria);
protected static XmlDocument ReadFromCriteria(string urlFormat, string criteria)
{
if (String.IsNullOrEmpty(criteria))
return new XmlDocument();
//throw new ArgumentNullException("criteria");
criteria = Uri.EscapeUriString(criteria);
var url = String.Format(urlFormat, criteria);
return WebXmlClient.GetHtmlXml(new Uri(url));
}
}
}