diff --git a/LyricsScraperNET.Client/Program.cs b/LyricsScraperNET.Client/Program.cs
index 93b9575..c3eb549 100644
--- a/LyricsScraperNET.Client/Program.cs
+++ b/LyricsScraperNET.Client/Program.cs
@@ -12,6 +12,7 @@
using LyricsScraperNET.Models.Responses;
using System.Threading.Tasks;
using System;
+using Microsoft.Extensions.Logging;
class Program
{
@@ -28,11 +29,23 @@ static async Task Main()
//var result = ExampleWithCertainProvider(artistToSearch, songToSearch);
//// Checking if something was found.
- //// If not, search errors can be found in the logs.
+ //// May not be found in two cases:
+ //// 1) search errors can be found in the logs or in response fields like ResponseStatusCode and ResponseMessage.
+ //// 2) The requested song contains only the instrumental, no lyrics. In this case the flag will be true.
if (result.IsEmpty())
{
- Console.ForegroundColor = ConsoleColor.Red;
- Console.WriteLine($"Can't find lyrics for: {artistToSearch} - {songToSearch}");
+ if (result.Instrumental)
+ {
+ Console.ForegroundColor = ConsoleColor.Gray;
+ Console.WriteLine("This song is instrumental.\r\nIt does not contain any lyrics");
+ }
+ else
+ {
+ Console.ForegroundColor = ConsoleColor.Red;
+ Console.WriteLine($"Can't find lyrics for: [ {artistToSearch} - {songToSearch} ]. " +
+ $"Status code: [ {result.ResponseStatusCode} ]. " +
+ $"Response message: [ {result.ResponseMessage} ].");
+ }
Console.ResetColor();
Console.ReadLine();
@@ -41,13 +54,13 @@ static async Task Main()
//// Output result to console
Console.ForegroundColor = ConsoleColor.Yellow;
- Console.WriteLine($"{artistToSearch} - {songToSearch}\r\n");
+ Console.WriteLine($"[ {artistToSearch} - {songToSearch} ]\r\n");
Console.ResetColor();
Console.WriteLine(result.LyricText);
Console.ForegroundColor = ConsoleColor.Magenta;
- Console.WriteLine($"\r\nThis text was found by {result.ExternalProviderType}\r\n");
+ Console.WriteLine($"\r\nThis lyric was found by [ {result.ExternalProviderType} ]\r\n");
Console.ResetColor();
Console.ReadLine();
@@ -105,6 +118,16 @@ ILyricsScraperClient lyricsScraperClient
.WithSongLyrics()
.WithLyricFind();
+ // To enable library logging, the LoggerFactory must be configured and passed to the client.
+ var loggerFactory = LoggerFactory.Create(builder =>
+ {
+ builder.AddFilter("Microsoft", LogLevel.Warning)
+ .AddFilter("System", LogLevel.Warning)
+ .AddFilter("LyricsScraperNET", LogLevel.Trace)
+ .AddConsole();
+ });
+ lyricsScraperClient.WithLogger(loggerFactory);
+
//// Another way to configure:
//// 1. First create instance of LyricScraperClient.
// ILyricsScraperClient lyricsScraperClient = new LyricsScraperClient();
diff --git a/LyricsScraperNET/Extensions/SearchResultExtensions.cs b/LyricsScraperNET/Extensions/SearchResultExtensions.cs
index b82bb9e..97b7ce5 100644
--- a/LyricsScraperNET/Extensions/SearchResultExtensions.cs
+++ b/LyricsScraperNET/Extensions/SearchResultExtensions.cs
@@ -29,5 +29,11 @@ internal static SearchResult AppendResponseMessage(this SearchResult searchResul
: responseMessage;
return searchResult;
}
+
+ internal static SearchResult AddInstrumental(this SearchResult searchResult, bool instrumental)
+ {
+ searchResult.Instrumental = instrumental;
+ return searchResult;
+ }
}
}
diff --git a/LyricsScraperNET/ILyricsScraperClient.cs b/LyricsScraperNET/ILyricsScraperClient.cs
index e493176..a12e6b6 100644
--- a/LyricsScraperNET/ILyricsScraperClient.cs
+++ b/LyricsScraperNET/ILyricsScraperClient.cs
@@ -2,6 +2,7 @@
using LyricsScraperNET.Models.Responses;
using LyricsScraperNET.Providers.Abstract;
using LyricsScraperNET.Providers.Models;
+using Microsoft.Extensions.Logging;
using System.Threading.Tasks;
namespace LyricsScraperNET
@@ -49,5 +50,11 @@ public interface ILyricsScraperClient
/// Calling the lyrics search method will return an empty result.
///
void Disable();
+
+ ///
+ /// Creates a new ILogger instance from .
+ /// Can be useful for error analysis if the lyric text is not found.
+ ///
+ void WithLogger(ILoggerFactory loggerFactory);
}
}
diff --git a/LyricsScraperNET/LyricsScraperClient.cs b/LyricsScraperNET/LyricsScraperClient.cs
index dc5e33f..7437b65 100644
--- a/LyricsScraperNET/LyricsScraperClient.cs
+++ b/LyricsScraperNET/LyricsScraperClient.cs
@@ -16,8 +16,8 @@ namespace LyricsScraperNET
{
public sealed class LyricsScraperClient : ILyricsScraperClient
{
-
- private readonly ILogger _logger;
+ private ILoggerFactory _loggerFactory;
+ private ILogger _logger;
private List _externalProviders;
private readonly ILyricScraperClientConfig _lyricScraperClientConfig;
@@ -200,7 +200,11 @@ public void AddProvider(IExternalProvider provider)
if (IsEmptyProvidersList())
_externalProviders = new List();
if (!_externalProviders.Contains(provider))
+ {
+ if (_loggerFactory != null)
+ provider.WithLogger(_loggerFactory);
_externalProviders.Add(provider);
+ }
else
_logger?.LogWarning($"External provider {provider} already added");
}
@@ -219,9 +223,7 @@ public void Enable()
return;
foreach (var provider in _externalProviders)
- {
provider.Enable();
- }
}
public void Disable()
@@ -230,9 +232,19 @@ public void Disable()
return;
foreach (var provider in _externalProviders)
- {
provider.Disable();
- }
+ }
+
+ public void WithLogger(ILoggerFactory loggerFactory)
+ {
+ _loggerFactory = loggerFactory;
+ _logger = loggerFactory.CreateLogger();
+
+ if (IsEmptyProvidersList())
+ return;
+
+ foreach (var provider in _externalProviders)
+ provider.WithLogger(loggerFactory);
}
private bool IsEmptyProvidersList() => _externalProviders == null || !_externalProviders.Any();
diff --git a/LyricsScraperNET/Models/Responses/SearchResult.cs b/LyricsScraperNET/Models/Responses/SearchResult.cs
index b27cf73..7e09bfe 100644
--- a/LyricsScraperNET/Models/Responses/SearchResult.cs
+++ b/LyricsScraperNET/Models/Responses/SearchResult.cs
@@ -47,6 +47,11 @@ internal SearchResult(string lyricText, ExternalProviderType externalProviderTyp
///
public string ResponseMessage { get; internal set; } = string.Empty;
+ ///
+ /// The flag indicates that the search results are for music only, without text.
+ ///
+ public bool Instrumental { get; internal set; } = false;
+
///
/// Returns true if the field is empty.
///
diff --git a/LyricsScraperNET/Providers/AZLyrics/AZLyricsProvider.cs b/LyricsScraperNET/Providers/AZLyrics/AZLyricsProvider.cs
index c2a349b..3264fb2 100644
--- a/LyricsScraperNET/Providers/AZLyrics/AZLyricsProvider.cs
+++ b/LyricsScraperNET/Providers/AZLyrics/AZLyricsProvider.cs
@@ -11,7 +11,7 @@ namespace LyricsScraperNET.Providers.AZLyrics
{
public sealed class AZLyricsProvider : ExternalProviderBase
{
- private readonly ILogger _logger;
+ private ILogger _logger;
private readonly IExternalUriConverter _uriConverter;
private const string _lyricStart = "";
@@ -96,6 +96,11 @@ protected override async Task SearchLyricAsync(Uri uri)
#endregion
+ public override void WithLogger(ILoggerFactory loggerFactory)
+ {
+ _logger = loggerFactory.CreateLogger();
+ }
+
private SearchResult PostProcessLyric(Uri uri, string text)
{
if (string.IsNullOrEmpty(text))
diff --git a/LyricsScraperNET/Providers/Abstract/ExternalProviderBase.cs b/LyricsScraperNET/Providers/Abstract/ExternalProviderBase.cs
index ec0ef29..acfcdba 100644
--- a/LyricsScraperNET/Providers/Abstract/ExternalProviderBase.cs
+++ b/LyricsScraperNET/Providers/Abstract/ExternalProviderBase.cs
@@ -1,6 +1,7 @@
using LyricsScraperNET.Models.Requests;
using LyricsScraperNET.Models.Responses;
using LyricsScraperNET.Network.Abstract;
+using Microsoft.Extensions.Logging;
using System;
using System.Threading.Tasks;
@@ -95,5 +96,8 @@ public void Disable()
if (Options != null)
Options.Enabled = false;
}
+
+ public virtual void WithLogger(ILoggerFactory loggerFactory)
+ => throw new NotImplementedException();
}
}
diff --git a/LyricsScraperNET/Providers/Abstract/IExternalProvider.cs b/LyricsScraperNET/Providers/Abstract/IExternalProvider.cs
index d91639f..a581ef7 100644
--- a/LyricsScraperNET/Providers/Abstract/IExternalProvider.cs
+++ b/LyricsScraperNET/Providers/Abstract/IExternalProvider.cs
@@ -1,6 +1,7 @@
using LyricsScraperNET.Models.Requests;
using LyricsScraperNET.Models.Responses;
using LyricsScraperNET.Network.Abstract;
+using Microsoft.Extensions.Logging;
using System.Threading.Tasks;
namespace LyricsScraperNET.Providers.Abstract
@@ -27,5 +28,7 @@ public interface IExternalProvider
void Enable();
void Disable();
+
+ void WithLogger(ILoggerFactory loggerFactory);
}
}
diff --git a/LyricsScraperNET/Providers/Genius/GeniusProvider.cs b/LyricsScraperNET/Providers/Genius/GeniusProvider.cs
index 515404c..5720ea5 100644
--- a/LyricsScraperNET/Providers/Genius/GeniusProvider.cs
+++ b/LyricsScraperNET/Providers/Genius/GeniusProvider.cs
@@ -17,7 +17,7 @@ namespace LyricsScraperNET.Providers.Genius
{
public sealed class GeniusProvider : ExternalProviderBase
{
- private readonly ILogger _logger;
+ private ILogger _logger;
private readonly IExternalUriConverter _uriConverter;
// Format: "artist song". Example: "Parkway Drive Carrion".
@@ -26,6 +26,10 @@ public sealed class GeniusProvider : ExternalProviderBase
private const string _referentFragmentNodesXPath = "//a[contains(@class, 'ReferentFragmentVariantdesktop') or contains(@class, 'ReferentFragmentdesktop')]";
private const string _lyricsContainerNodesXPath = "//div[@data-lyrics-container]";
+ // In case of instrumental song without a lyric.
+ private const string _lyricsPlaceholderNodesXPath = "//div[contains(@class, 'LyricsPlaceholder')]";
+ private const string _instrumentalLyricText = "This song is an instrumental";
+
#region Constructors
public GeniusProvider()
@@ -71,7 +75,10 @@ protected override SearchResult SearchLyric(Uri uri)
{
var htmlPageBody = WebClient.Load(uri);
- return new SearchResult(GetParsedLyricFromHtmlPageBody(htmlPageBody), Models.ExternalProviderType.Genius);
+ var lyricResult = GetParsedLyricFromHtmlPageBody(htmlPageBody, out var instrumental);
+
+ return new SearchResult(lyricResult, Models.ExternalProviderType.Genius)
+ .AddInstrumental(instrumental);
}
protected override SearchResult SearchLyric(string artist, string song)
@@ -94,7 +101,7 @@ protected override SearchResult SearchLyric(string artist, string song)
return !string.IsNullOrWhiteSpace(lyricUrl)
? SearchLyric(new Uri(lyricUrl))
- : new SearchResult();
+ : new SearchResult(Models.ExternalProviderType.Genius);
}
#endregion
@@ -105,7 +112,10 @@ protected override async Task SearchLyricAsync(Uri uri)
{
var htmlPageBody = await WebClient.LoadAsync(uri);
- return new SearchResult(GetParsedLyricFromHtmlPageBody(htmlPageBody), Models.ExternalProviderType.Genius);
+ var lyricResult = GetParsedLyricFromHtmlPageBody(htmlPageBody, out var instrumental);
+
+ return new SearchResult(lyricResult, Models.ExternalProviderType.Genius)
+ .AddInstrumental(instrumental);
}
protected override async Task SearchLyricAsync(string artist, string song)
@@ -128,11 +138,16 @@ protected override async Task SearchLyricAsync(string artist, stri
return !string.IsNullOrWhiteSpace(lyricUrl)
? await SearchLyricAsync(new Uri(lyricUrl))
- : new SearchResult();
+ : new SearchResult(Models.ExternalProviderType.Genius);
}
#endregion
+ public override void WithLogger(ILoggerFactory loggerFactory)
+ {
+ _logger = loggerFactory.CreateLogger();
+ }
+
private string GetLyricUrlWithoutApiKey(string artist, string song)
{
var htmlPageBody = WebClient.Load(_uriConverter.GetLyricUri(artist, song));
@@ -160,8 +175,10 @@ private string GetLyricUrlWithoutApiKey(string artist, string song)
return string.Empty;
}
- private string GetParsedLyricFromHtmlPageBody(string htmlPageBody)
+ private string GetParsedLyricFromHtmlPageBody(string htmlPageBody, out bool instrumental)
{
+ instrumental = false;
+
var htmlDocument = new HtmlDocument();
htmlDocument.LoadHtml(htmlPageBody.Replace("https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js", ""));
@@ -175,8 +192,20 @@ private string GetParsedLyricFromHtmlPageBody(string htmlPageBody)
spanNode.Remove();
var lyricNodes = htmlDocument.DocumentNode.SelectNodes(_lyricsContainerNodesXPath);
+ if (lyricNodes == null)
+ {
+ // lyricNodes could be null in case of instrumental.
+ var instrumentalNodes = htmlDocument.DocumentNode.SelectNodes(_lyricsPlaceholderNodesXPath);
+ if (instrumentalNodes != null
+ && instrumentalNodes.Any(node => node.InnerHtml.Contains(_instrumentalLyricText)))
+ {
+ instrumental = true;
+ return string.Empty;
+ }
+ _logger?.LogWarning($"Genius. Can't parse lyric from the page.");
+ }
- return Parser.Parse(string.Join("", lyricNodes.Select(Node => Node.InnerHtml)));
+ return Parser.Parse(string.Join("", lyricNodes.Select(node => node.InnerHtml)));
}
private string GetLyricUrlFromSearchResponse(SearchResponse searchResponse, string artist, string song)
diff --git a/LyricsScraperNET/Providers/LyricFind/LyricFindProvider.cs b/LyricsScraperNET/Providers/LyricFind/LyricFindProvider.cs
index 549c064..a28b009 100644
--- a/LyricsScraperNET/Providers/LyricFind/LyricFindProvider.cs
+++ b/LyricsScraperNET/Providers/LyricFind/LyricFindProvider.cs
@@ -11,7 +11,7 @@ namespace LyricsScraperNET.Providers.LyricFind
{
public sealed class LyricFindProvider : ExternalProviderBase
{
- private readonly ILogger _logger;
+ private ILogger _logger;
private readonly IExternalUriConverter _uriConverter;
private const string _lyricStart = "\"lyrics\"";
@@ -95,6 +95,11 @@ protected override async Task SearchLyricAsync(Uri uri)
#endregion
+ public override void WithLogger(ILoggerFactory loggerFactory)
+ {
+ _logger = loggerFactory.CreateLogger();
+ }
+
private SearchResult PostProcessLyric(Uri uri, string text)
{
if (string.IsNullOrEmpty(text))
diff --git a/LyricsScraperNET/Providers/Musixmatch/MusixmatchProvider.cs b/LyricsScraperNET/Providers/Musixmatch/MusixmatchProvider.cs
index 01747fa..3be6855 100644
--- a/LyricsScraperNET/Providers/Musixmatch/MusixmatchProvider.cs
+++ b/LyricsScraperNET/Providers/Musixmatch/MusixmatchProvider.cs
@@ -1,4 +1,5 @@
-using LyricsScraperNET.Helpers;
+using LyricsScraperNET.Extensions;
+using LyricsScraperNET.Helpers;
using LyricsScraperNET.Models.Responses;
using LyricsScraperNET.Providers.Abstract;
using Microsoft.Extensions.Caching.Memory;
@@ -17,7 +18,7 @@ namespace LyricsScraperNET.Providers.Musixmatch
{
public sealed class MusixmatchProvider : ExternalProviderBase
{
- private readonly ILogger _logger;
+ private ILogger _logger;
// Musixmatch Token memory cache
private static readonly IMemoryCache _memoryCache;
@@ -80,7 +81,7 @@ public MusixmatchProvider(IOptionsSnapshot options)
// TODO: search by uri from the site. Example: https://www.musixmatch.com/lyrics/Parkway-Drive/Idols-and-Anchors
protected override SearchResult SearchLyric(Uri uri)
{
- return new SearchResult();
+ return new SearchResult(Models.ExternalProviderType.Musixmatch);
}
protected override SearchResult SearchLyric(string artist, string song)
@@ -97,14 +98,19 @@ protected override SearchResult SearchLyric(string artist, string song)
if (trackId != null)
{
Lyrics lyrics = client.GetTrackLyrics(trackId.Value);
- return lyrics.Instrumental != 1
- ? new SearchResult(lyrics.LyricsBody, Models.ExternalProviderType.Musixmatch)
- : new SearchResult(); // lyrics.LyricsBody is null when the track is instrumental
+
+ // lyrics.LyricsBody is null when the track is instrumental
+ if (lyrics.Instrumental != 1)
+ return new SearchResult(lyrics.LyricsBody, Models.ExternalProviderType.Musixmatch);
+
+ // Instrumental music without lyric
+ return new SearchResult(Models.ExternalProviderType.Musixmatch)
+ .AddInstrumental(true);
}
else
{
_logger?.LogWarning($"Musixmatch. Can't find any information about artist {artist} and song {song}");
- return new SearchResult();
+ return new SearchResult(Models.ExternalProviderType.Musixmatch);
}
}
catch (MusixmatchRequestException requestException) when (requestException.StatusCode == StatusCode.AuthFailed)
@@ -113,7 +119,7 @@ protected override SearchResult SearchLyric(string artist, string song)
regenerateToken = true;
}
}
- return new SearchResult();
+ return new SearchResult(Models.ExternalProviderType.Musixmatch);
}
#endregion
@@ -123,7 +129,7 @@ protected override SearchResult SearchLyric(string artist, string song)
// TODO: search by uri from the site. Example: https://www.musixmatch.com/lyrics/Parkway-Drive/Idols-and-Anchors
protected override Task SearchLyricAsync(Uri uri)
{
- return Task.FromResult(new SearchResult());
+ return Task.FromResult(new SearchResult(Models.ExternalProviderType.Musixmatch));
}
protected override async Task SearchLyricAsync(string artist, string song)
@@ -141,14 +147,19 @@ protected override async Task SearchLyricAsync(string artist, stri
if (trackId != null)
{
Lyrics lyrics = await client.GetTrackLyricsAsync(trackId.Value);
- return lyrics.Instrumental != 1
- ? new SearchResult(lyrics.LyricsBody, Models.ExternalProviderType.Musixmatch)
- : new SearchResult(); // lyrics.LyricsBody is null when the track is instrumental
+
+ // lyrics.LyricsBody is null when the track is instrumental
+ if (lyrics.Instrumental != 1)
+ return new SearchResult(lyrics.LyricsBody, Models.ExternalProviderType.Musixmatch);
+
+ // Instrumental music without lyric
+ return new SearchResult(Models.ExternalProviderType.Musixmatch)
+ .AddInstrumental(true);
}
else
{
_logger?.LogWarning($"Musixmatch. Can't find any information about artist {artist} and song {song}");
- return new SearchResult();
+ return new SearchResult(Models.ExternalProviderType.Musixmatch);
}
}
catch (MusixmatchRequestException requestException) when (requestException.StatusCode == StatusCode.AuthFailed)
@@ -157,11 +168,16 @@ protected override async Task SearchLyricAsync(string artist, stri
regenerateToken = true;
}
}
- return new SearchResult();
+ return new SearchResult(Models.ExternalProviderType.Musixmatch);
}
#endregion
+ public override void WithLogger(ILoggerFactory loggerFactory)
+ {
+ _logger = loggerFactory.CreateLogger();
+ }
+
private MusixmatchClient GetMusixmatchClient(bool regenerateToken = false)
{
// TODO: uncomment after the fix of https://github.com/Eimaen/MusixmatchClientLib/issues/21
diff --git a/LyricsScraperNET/Providers/SongLyrics/SongLyricsProvider.cs b/LyricsScraperNET/Providers/SongLyrics/SongLyricsProvider.cs
index f517b5a..c6cd306 100644
--- a/LyricsScraperNET/Providers/SongLyrics/SongLyricsProvider.cs
+++ b/LyricsScraperNET/Providers/SongLyrics/SongLyricsProvider.cs
@@ -13,7 +13,7 @@ namespace LyricsScraperNET.Providers.SongLyrics
{
public sealed class SongLyricsProvider : ExternalProviderBase
{
- private readonly ILogger _logger;
+ private ILogger _logger;
private readonly IExternalUriConverter _uriConverter;
@@ -59,7 +59,6 @@ public SongLyricsProvider(IOptionsSnapshot options)
public override IExternalProviderOptions Options { get; }
-
#region Sync
protected override SearchResult SearchLyric(string artist, string song)
@@ -80,7 +79,6 @@ protected override SearchResult SearchLyric(Uri uri)
#endregion
-
#region Async
protected override async Task SearchLyricAsync(string artist, string song)
@@ -101,6 +99,10 @@ protected override async Task SearchLyricAsync(Uri uri)
#endregion
+ public override void WithLogger(ILoggerFactory loggerFactory)
+ {
+ _logger = loggerFactory.CreateLogger();
+ }
private SearchResult GetParsedLyricFromHtmlPageBody(Uri uri, string htmlPageBody)
{