2019-01-13 15:03:10 -05:00
using System ;
2014-02-19 23:53:15 -05:00
using System.Collections.Generic ;
2014-03-13 23:23:58 -04:00
using System.Linq ;
2020-02-23 04:53:51 -05:00
using System.Net.Http ;
2013-11-12 12:12:11 -05:00
using System.Threading ;
using System.Threading.Tasks ;
2022-04-07 06:15:25 -04:00
using Jellyfin.Extensions ;
2019-01-13 14:26:31 -05:00
using MediaBrowser.Controller.Entities.Audio ;
using MediaBrowser.Controller.Providers ;
using MediaBrowser.Model.Entities ;
2023-02-19 10:16:34 -05:00
using MediaBrowser.Model.Plugins ;
2019-01-13 14:26:31 -05:00
using MediaBrowser.Model.Providers ;
2022-04-07 06:15:25 -04:00
using MediaBrowser.Providers.Music ;
2023-02-19 10:16:34 -05:00
using MediaBrowser.Providers.Plugins.MusicBrainz.Configuration ;
2022-04-07 06:15:25 -04:00
using MetaBrainz.MusicBrainz ;
using MetaBrainz.MusicBrainz.Interfaces.Entities ;
using MetaBrainz.MusicBrainz.Interfaces.Searches ;
2022-12-03 10:47:50 -05:00
using Microsoft.Extensions.Logging ;
2019-03-12 11:37:18 -04:00
2022-04-07 06:15:25 -04:00
namespace MediaBrowser.Providers.Plugins.MusicBrainz ;
2019-03-12 11:37:18 -04:00
2022-04-07 06:15:25 -04:00
/// <summary>
/// Music album metadata provider for MusicBrainz.
/// </summary>
public class MusicBrainzAlbumProvider : IRemoteMetadataProvider < MusicAlbum , AlbumInfo > , IHasOrder , IDisposable
{
2022-12-03 10:47:50 -05:00
private readonly ILogger < MusicBrainzAlbumProvider > _logger ;
2023-02-19 10:16:34 -05:00
private Query _musicBrainzQuery ;
2019-03-13 16:31:21 -04:00
2022-04-07 06:15:25 -04:00
/// <summary>
/// Initializes a new instance of the <see cref="MusicBrainzAlbumProvider"/> class.
/// </summary>
2022-12-03 10:47:50 -05:00
/// <param name="logger">The logger.</param>
public MusicBrainzAlbumProvider ( ILogger < MusicBrainzAlbumProvider > logger )
2022-04-07 06:15:25 -04:00
{
2022-12-03 10:47:50 -05:00
_logger = logger ;
2022-04-07 06:15:25 -04:00
_musicBrainzQuery = new Query ( ) ;
2023-02-19 10:16:34 -05:00
ReloadConfig ( null , MusicBrainz . Plugin . Instance ! . Configuration ) ;
MusicBrainz . Plugin . Instance ! . ConfigurationChanged + = ReloadConfig ;
2022-04-07 06:15:25 -04:00
}
2013-11-12 12:12:11 -05:00
2022-04-07 06:15:25 -04:00
/// <inheritdoc />
public string Name = > "MusicBrainz" ;
2020-09-07 07:20:39 -04:00
2022-04-07 06:15:25 -04:00
/// <inheritdoc />
public int Order = > 0 ;
2019-09-10 16:37:53 -04:00
2023-02-19 10:16:34 -05:00
private void ReloadConfig ( object? sender , BasePluginConfiguration e )
{
var configuration = ( PluginConfiguration ) e ;
if ( Uri . TryCreate ( configuration . Server , UriKind . Absolute , out var server ) )
{
Query . DefaultServer = server . DnsSafeHost ;
Query . DefaultPort = server . Port ;
Query . DefaultUrlScheme = server . Scheme ;
}
else
{
// Fallback to official server
_logger . LogWarning ( "Invalid MusicBrainz server specified, falling back to official server" ) ;
2023-02-20 10:04:30 -05:00
var defaultServer = new Uri ( PluginConfiguration . DefaultServer ) ;
2023-02-19 10:16:34 -05:00
Query . DefaultServer = defaultServer . Host ;
Query . DefaultPort = defaultServer . Port ;
Query . DefaultUrlScheme = defaultServer . Scheme ;
}
Query . DelayBetweenRequests = configuration . RateLimit ;
_musicBrainzQuery = new Query ( ) ;
}
2022-04-07 06:15:25 -04:00
/// <inheritdoc />
public async Task < IEnumerable < RemoteSearchResult > > GetSearchResults ( AlbumInfo searchInfo , CancellationToken cancellationToken )
{
var releaseId = searchInfo . GetReleaseId ( ) ;
var releaseGroupId = searchInfo . GetReleaseGroupId ( ) ;
2019-09-10 16:37:53 -04:00
2022-04-07 06:15:25 -04:00
if ( ! string . IsNullOrEmpty ( releaseId ) )
2014-02-19 23:53:15 -05:00
{
2023-02-19 11:54:59 -05:00
var releaseResult = await _musicBrainzQuery . LookupReleaseAsync ( new Guid ( releaseId ) , Include . Artists | Include . ReleaseGroups , cancellationToken ) . ConfigureAwait ( false ) ;
2022-04-27 07:44:49 -04:00
return GetReleaseResult ( releaseResult ) . SingleItemAsEnumerable ( ) ;
2014-02-19 23:53:15 -05:00
}
2022-04-07 06:15:25 -04:00
if ( ! string . IsNullOrEmpty ( releaseGroupId ) )
2014-03-13 23:23:58 -04:00
{
2023-02-19 11:54:59 -05:00
var releaseGroupResult = await _musicBrainzQuery . LookupReleaseGroupAsync ( new Guid ( releaseGroupId ) , Include . Releases , null , cancellationToken ) . ConfigureAwait ( false ) ;
2022-04-27 07:44:49 -04:00
return GetReleaseGroupResult ( releaseGroupResult . Releases ) ;
2014-03-13 23:23:58 -04:00
}
2022-04-07 06:15:25 -04:00
var artistMusicBrainzId = searchInfo . GetMusicBrainzArtistId ( ) ;
2014-01-31 14:55:21 -05:00
2022-04-07 06:15:25 -04:00
if ( ! string . IsNullOrWhiteSpace ( artistMusicBrainzId ) )
{
var releaseSearchResults = await _musicBrainzQuery . FindReleasesAsync ( $"\" { searchInfo . Name } \ " AND arid:{artistMusicBrainzId}" , null , null , false , cancellationToken )
. ConfigureAwait ( false ) ;
2013-11-12 12:12:11 -05:00
2022-04-07 06:15:25 -04:00
if ( releaseSearchResults . Results . Count > 0 )
2013-11-12 12:12:11 -05:00
{
2022-04-27 07:44:49 -04:00
return GetReleaseSearchResult ( releaseSearchResults . Results ) ;
2013-11-12 12:12:11 -05:00
}
2022-04-07 06:15:25 -04:00
}
else
{
// I'm sure there is a better way but for now it resolves search for 12" Mixes
var queryName = searchInfo . Name . Replace ( "\"" , string . Empty , StringComparison . Ordinal ) ;
2013-11-12 12:12:11 -05:00
2022-04-07 06:15:25 -04:00
var releaseSearchResults = await _musicBrainzQuery . FindReleasesAsync ( $"\" { queryName } \ " AND artist:\"{searchInfo.GetAlbumArtist()}\"c" , null , null , false , cancellationToken )
. ConfigureAwait ( false ) ;
2015-08-07 10:21:29 -04:00
2022-04-07 06:15:25 -04:00
if ( releaseSearchResults . Results . Count > 0 )
2014-02-07 17:40:03 -05:00
{
2022-04-27 07:44:49 -04:00
return GetReleaseSearchResult ( releaseSearchResults . Results ) ;
2014-02-07 17:40:03 -05:00
}
2013-11-12 12:12:11 -05:00
}
2022-04-07 06:15:25 -04:00
return Enumerable . Empty < RemoteSearchResult > ( ) ;
}
2014-06-23 12:05:19 -04:00
2022-04-27 07:44:49 -04:00
private IEnumerable < RemoteSearchResult > GetReleaseSearchResult ( IEnumerable < ISearchResult < IRelease > > ? releaseSearchResults )
2022-04-07 06:15:25 -04:00
{
if ( releaseSearchResults is null )
{
yield break ;
2013-11-12 12:12:11 -05:00
}
2022-04-07 06:15:25 -04:00
foreach ( var result in releaseSearchResults )
2013-11-12 12:12:11 -05:00
{
2022-04-27 07:44:49 -04:00
yield return GetReleaseResult ( result . Item ) ;
2013-11-12 12:12:11 -05:00
}
2022-04-07 06:15:25 -04:00
}
2013-11-12 12:12:11 -05:00
2022-04-27 07:44:49 -04:00
private IEnumerable < RemoteSearchResult > GetReleaseGroupResult ( IEnumerable < IRelease > ? releaseSearchResults )
2022-04-07 06:15:25 -04:00
{
if ( releaseSearchResults is null )
2013-11-12 12:12:11 -05:00
{
2022-04-07 06:15:25 -04:00
yield break ;
2013-11-12 12:12:11 -05:00
}
2022-04-07 06:15:25 -04:00
foreach ( var result in releaseSearchResults )
2017-10-28 00:20:18 -04:00
{
2023-02-19 11:54:59 -05:00
// Fetch full release info, otherwise artists are missing
var fullResult = _musicBrainzQuery . LookupRelease ( result . Id , Include . Artists | Include . ReleaseGroups ) ;
yield return GetReleaseResult ( fullResult ) ;
2017-10-28 00:20:18 -04:00
}
2022-04-07 06:15:25 -04:00
}
2017-10-28 00:20:18 -04:00
2022-04-27 07:44:49 -04:00
private RemoteSearchResult GetReleaseResult ( IRelease releaseSearchResult )
2022-04-07 06:15:25 -04:00
{
var searchResult = new RemoteSearchResult
2017-10-28 00:20:18 -04:00
{
2022-04-07 06:15:25 -04:00
Name = releaseSearchResult . Title ,
ProductionYear = releaseSearchResult . Date ? . Year ,
2023-02-19 11:54:59 -05:00
PremiereDate = releaseSearchResult . Date ? . NearestDate ,
SearchProviderName = Name
2022-04-07 06:15:25 -04:00
} ;
2017-10-28 00:20:18 -04:00
2023-02-19 11:54:59 -05:00
// Add artists and use first as album artist
var artists = releaseSearchResult . ArtistCredit ;
if ( artists is not null & & artists . Count > 0 )
2022-04-07 06:15:25 -04:00
{
2023-02-28 18:44:57 -05:00
var artistResults = new RemoteSearchResult [ artists . Count ] ;
for ( int i = 0 ; i < artists . Count ; i + + )
2017-10-28 00:20:18 -04:00
{
2023-02-28 18:44:57 -05:00
var artist = artists [ i ] ;
2023-02-19 11:54:59 -05:00
var artistResult = new RemoteSearchResult
{
Name = artist . Name
} ;
if ( artist . Artist ? . Id is not null )
{
artistResult . SetProviderId ( MetadataProvider . MusicBrainzArtist , artist . Artist ! . Id . ToString ( ) ) ;
}
2023-02-28 18:44:57 -05:00
artistResults [ i ] = artistResult ;
2017-10-28 00:20:18 -04:00
}
2023-02-19 11:54:59 -05:00
searchResult . AlbumArtist = artistResults [ 0 ] ;
2023-02-28 18:44:57 -05:00
searchResult . Artists = artistResults ;
2017-10-28 00:20:18 -04:00
}
2022-04-07 06:15:25 -04:00
searchResult . SetProviderId ( MetadataProvider . MusicBrainzAlbum , releaseSearchResult . Id . ToString ( ) ) ;
2017-10-28 00:20:18 -04:00
2022-04-07 06:15:25 -04:00
if ( releaseSearchResult . ReleaseGroup ? . Id is not null )
{
searchResult . SetProviderId ( MetadataProvider . MusicBrainzReleaseGroup , releaseSearchResult . ReleaseGroup . Id . ToString ( ) ) ;
2017-10-28 00:20:18 -04:00
}
2022-04-07 06:15:25 -04:00
return searchResult ;
}
2017-03-15 15:57:18 -04:00
2022-04-07 06:15:25 -04:00
/// <inheritdoc />
public async Task < MetadataResult < MusicAlbum > > GetMetadata ( AlbumInfo info , CancellationToken cancellationToken )
{
// TODO: This sets essentially nothing. As-is, it's mostly useless. Make it actually pull metadata and use it.
var releaseId = info . GetReleaseId ( ) ;
var releaseGroupId = info . GetReleaseGroupId ( ) ;
2017-10-20 12:16:56 -04:00
2022-04-07 06:15:25 -04:00
var result = new MetadataResult < MusicAlbum >
{
Item = new MusicAlbum ( )
} ;
2017-03-15 15:57:18 -04:00
2022-04-07 06:15:25 -04:00
// If there is a release group, but no release ID, try to match the release
if ( string . IsNullOrWhiteSpace ( releaseId ) & & ! string . IsNullOrWhiteSpace ( releaseGroupId ) )
{
// TODO: Actually try to match the release. Simply taking the first result is stupid.
2022-04-27 07:44:49 -04:00
var releaseGroup = await _musicBrainzQuery . LookupReleaseGroupAsync ( new Guid ( releaseGroupId ) , Include . None , null , cancellationToken ) . ConfigureAwait ( false ) ;
2022-04-07 06:15:25 -04:00
var release = releaseGroup . Releases ? . Count > 0 ? releaseGroup . Releases [ 0 ] : null ;
2022-12-05 09:01:13 -05:00
if ( release is not null )
2022-04-27 07:44:49 -04:00
{
releaseId = release . Id . ToString ( ) ;
result . HasMetadata = true ;
}
2017-03-15 15:57:18 -04:00
}
2022-04-07 06:15:25 -04:00
// If there is no release ID, lookup a release with the info we have
if ( string . IsNullOrWhiteSpace ( releaseId ) )
2013-11-12 12:12:11 -05:00
{
2022-04-07 06:15:25 -04:00
var artistMusicBrainzId = info . GetMusicBrainzArtistId ( ) ;
IRelease ? releaseResult = null ;
2013-11-12 12:12:11 -05:00
2022-04-07 06:15:25 -04:00
if ( ! string . IsNullOrEmpty ( artistMusicBrainzId ) )
2016-06-15 16:14:04 -04:00
{
2022-04-07 06:15:25 -04:00
var releaseSearchResults = await _musicBrainzQuery . FindReleasesAsync ( $"\" { info . Name } \ " AND arid:{artistMusicBrainzId}" , null , null , false , cancellationToken )
. ConfigureAwait ( false ) ;
releaseResult = releaseSearchResults . Results . Count > 0 ? releaseSearchResults . Results [ 0 ] . Item : null ;
2016-06-15 16:14:04 -04:00
}
2022-04-07 06:15:25 -04:00
else if ( ! string . IsNullOrEmpty ( info . GetAlbumArtist ( ) ) )
2016-06-15 16:14:04 -04:00
{
2022-04-07 06:15:25 -04:00
var releaseSearchResults = await _musicBrainzQuery . FindReleasesAsync ( $"\" { info . Name } \ " AND artist:{info.GetAlbumArtist()}" , null , null , false , cancellationToken )
. ConfigureAwait ( false ) ;
releaseResult = releaseSearchResults . Results . Count > 0 ? releaseSearchResults . Results [ 0 ] . Item : null ;
2016-06-15 16:14:04 -04:00
}
2016-10-27 15:03:23 -04:00
2022-12-05 09:01:13 -05:00
if ( releaseResult is not null )
2020-10-02 20:14:57 -04:00
{
2022-04-07 06:15:25 -04:00
releaseId = releaseResult . Id . ToString ( ) ;
2020-10-02 20:27:43 -04:00
2022-04-07 06:15:25 -04:00
if ( releaseResult . ReleaseGroup ? . Id is not null )
2019-03-15 15:29:04 -04:00
{
2022-04-07 06:15:25 -04:00
releaseGroupId = releaseResult . ReleaseGroup . Id . ToString ( ) ;
2020-10-02 20:14:57 -04:00
}
2020-10-02 20:19:35 -04:00
2022-04-07 06:15:25 -04:00
result . HasMetadata = true ;
result . Item . ProductionYear = releaseResult . Date ? . Year ;
result . Item . Overview = releaseResult . Annotation ;
2019-03-15 15:29:04 -04:00
}
2013-11-12 12:12:11 -05:00
}
2022-04-07 06:15:25 -04:00
// If we have a release ID but not a release group ID, lookup the release group
if ( ! string . IsNullOrWhiteSpace ( releaseId ) & & string . IsNullOrWhiteSpace ( releaseGroupId ) )
2014-02-19 23:53:15 -05:00
{
2022-04-07 06:15:25 -04:00
var release = await _musicBrainzQuery . LookupReleaseAsync ( new Guid ( releaseId ) , Include . Releases , cancellationToken ) . ConfigureAwait ( false ) ;
releaseGroupId = release . ReleaseGroup ? . Id . ToString ( ) ;
result . HasMetadata = true ;
2014-02-19 23:53:15 -05:00
}
2021-07-22 20:33:19 -04:00
2022-04-07 06:15:25 -04:00
// If we have a release ID and a release group ID
if ( ! string . IsNullOrWhiteSpace ( releaseId ) | | ! string . IsNullOrWhiteSpace ( releaseGroupId ) )
2021-07-22 23:16:38 -04:00
{
2022-04-07 06:15:25 -04:00
result . HasMetadata = true ;
2021-07-22 23:16:38 -04:00
}
2022-04-07 06:15:25 -04:00
if ( result . HasMetadata )
2021-07-22 23:16:38 -04:00
{
2022-04-07 06:15:25 -04:00
if ( ! string . IsNullOrEmpty ( releaseId ) )
2021-07-22 20:33:19 -04:00
{
2022-04-07 06:15:25 -04:00
result . Item . SetProviderId ( MetadataProvider . MusicBrainzAlbum , releaseId ) ;
2021-07-22 20:33:19 -04:00
}
2022-04-07 06:15:25 -04:00
if ( ! string . IsNullOrEmpty ( releaseGroupId ) )
2021-07-22 20:33:19 -04:00
{
2022-04-07 06:15:25 -04:00
result . Item . SetProviderId ( MetadataProvider . MusicBrainzReleaseGroup , releaseGroupId ) ;
2021-07-22 20:33:19 -04:00
}
2022-04-07 06:15:25 -04:00
}
2021-07-22 20:33:19 -04:00
2022-04-07 06:15:25 -04:00
return result ;
}
2021-07-22 20:33:19 -04:00
2022-04-07 06:15:25 -04:00
/// <inheritdoc />
public Task < HttpResponseMessage > GetImageResponse ( string url , CancellationToken cancellationToken )
{
throw new NotImplementedException ( ) ;
}
2021-07-22 20:33:19 -04:00
2022-04-07 06:15:25 -04:00
/// <inheritdoc />
public void Dispose ( )
{
Dispose ( true ) ;
GC . SuppressFinalize ( this ) ;
}
2021-07-22 20:33:19 -04:00
2022-04-07 06:15:25 -04:00
/// <summary>
/// Dispose all resources.
/// </summary>
/// <param name="disposing">Whether to dispose.</param>
protected virtual void Dispose ( bool disposing )
{
if ( disposing )
{
_musicBrainzQuery . Dispose ( ) ;
2021-07-22 20:33:19 -04:00
}
2013-11-12 12:12:11 -05:00
}
2018-12-28 10:48:26 -05:00
}