Merge remote-tracking branch 'upstream/master' into support-injecting-iconfiguration
This commit is contained in:
commit
76957213e6
|
@ -666,9 +666,8 @@ namespace Emby.Server.Implementations
|
|||
|
||||
serviceCollection.AddSingleton(JsonSerializer);
|
||||
|
||||
serviceCollection.AddSingleton(LoggerFactory);
|
||||
serviceCollection.AddLogging();
|
||||
serviceCollection.AddSingleton(Logger);
|
||||
// TODO: Support for injecting ILogger should be deprecated in favour of ILogger<T> and this removed
|
||||
serviceCollection.AddSingleton<ILogger>(Logger);
|
||||
|
||||
serviceCollection.AddSingleton(FileSystemManager);
|
||||
serviceCollection.AddSingleton<TvDbClientManager>();
|
||||
|
|
|
@ -8,7 +8,6 @@ namespace Emby.Server.Implementations
|
|||
public static Dictionary<string, string> Configuration => new Dictionary<string, string>
|
||||
{
|
||||
{ "HttpListenerHost:DefaultRedirectPath", "web/index.html" },
|
||||
{ "MusicBrainz:BaseUrl", "https://www.musicbrainz.org" },
|
||||
{ FfmpegProbeSizeKey, "1G" },
|
||||
{ FfmpegAnalyzeDurationKey, "200M" }
|
||||
};
|
||||
|
|
|
@ -944,7 +944,6 @@ namespace Emby.Server.Implementations.Library
|
|||
IncludeItemTypes = new[] { typeof(T).Name },
|
||||
Name = name,
|
||||
DtoOptions = options
|
||||
|
||||
}).Cast<MusicArtist>()
|
||||
.OrderBy(i => i.IsAccessedByName ? 1 : 0)
|
||||
.Cast<T>()
|
||||
|
@ -1080,7 +1079,7 @@ namespace Emby.Server.Implementations.Library
|
|||
|
||||
var innerProgress = new ActionableProgress<double>();
|
||||
|
||||
innerProgress.RegisterAction(pct => progress.Report(pct * pct * 0.96));
|
||||
innerProgress.RegisterAction(pct => progress.Report(pct * 0.96));
|
||||
|
||||
// Validate the entire media library
|
||||
await RootFolder.ValidateChildren(innerProgress, cancellationToken, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), recursive: true).ConfigureAwait(false);
|
||||
|
|
|
@ -1,43 +1,35 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Playlists;
|
||||
using MediaBrowser.Controller.Resolvers;
|
||||
using MediaBrowser.LocalMetadata.Savers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
|
||||
namespace Emby.Server.Implementations.Library.Resolvers
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="IItemResolver"/> for <see cref="Playlist"/> library items.
|
||||
/// </summary>
|
||||
public class PlaylistResolver : FolderResolver<Playlist>
|
||||
{
|
||||
private string[] SupportedCollectionTypes = new string[] {
|
||||
|
||||
private string[] _musicPlaylistCollectionTypes = new string[] {
|
||||
string.Empty,
|
||||
CollectionType.Music
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Resolves the specified args.
|
||||
/// </summary>
|
||||
/// <param name="args">The args.</param>
|
||||
/// <returns>BoxSet.</returns>
|
||||
/// <inheritdoc/>
|
||||
protected override Playlist Resolve(ItemResolveArgs args)
|
||||
{
|
||||
// It's a boxset if all of the following conditions are met:
|
||||
// Is a Directory
|
||||
// Contains [playlist] in the path
|
||||
if (args.IsDirectory)
|
||||
{
|
||||
var filename = Path.GetFileName(args.Path);
|
||||
|
||||
if (string.IsNullOrEmpty(filename))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (filename.IndexOf("[playlist]", StringComparison.OrdinalIgnoreCase) != -1)
|
||||
// It's a boxset if the path is a directory with [playlist] in it's the name
|
||||
// TODO: Should this use Path.GetDirectoryName() instead?
|
||||
bool isBoxSet = Path.GetFileName(args.Path)
|
||||
?.Contains("[playlist]", StringComparison.OrdinalIgnoreCase)
|
||||
?? false;
|
||||
if (isBoxSet)
|
||||
{
|
||||
return new Playlist
|
||||
{
|
||||
|
@ -45,21 +37,32 @@ namespace Emby.Server.Implementations.Library.Resolvers
|
|||
Name = Path.GetFileName(args.Path).Replace("[playlist]", string.Empty, StringComparison.OrdinalIgnoreCase).Trim()
|
||||
};
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (SupportedCollectionTypes.Contains(args.CollectionType ?? string.Empty, StringComparer.OrdinalIgnoreCase))
|
||||
|
||||
// It's a directory-based playlist if the directory contains a playlist file
|
||||
var filePaths = Directory.EnumerateFiles(args.Path);
|
||||
if (filePaths.Any(f => f.EndsWith(PlaylistXmlSaver.DefaultPlaylistFilename, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
var extension = Path.GetExtension(args.Path);
|
||||
if (Playlist.SupportedExtensions.Contains(extension ?? string.Empty, StringComparer.OrdinalIgnoreCase))
|
||||
return new Playlist
|
||||
{
|
||||
return new Playlist
|
||||
{
|
||||
Path = args.Path,
|
||||
Name = Path.GetFileNameWithoutExtension(args.Path),
|
||||
IsInMixedFolder = true
|
||||
};
|
||||
}
|
||||
Path = args.Path,
|
||||
Name = Path.GetFileName(args.Path)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Check if this is a music playlist file
|
||||
// It should have the correct collection type and a supported file extension
|
||||
else if (_musicPlaylistCollectionTypes.Contains(args.CollectionType ?? string.Empty, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
var extension = Path.GetExtension(args.Path);
|
||||
if (Playlist.SupportedExtensions.Contains(extension ?? string.Empty, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
return new Playlist
|
||||
{
|
||||
Path = args.Path,
|
||||
Name = Path.GetFileNameWithoutExtension(args.Path),
|
||||
IsInMixedFolder = true
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
"Collections": "Gyűjtemények",
|
||||
"DeviceOfflineWithName": "{0} kijelentkezett",
|
||||
"DeviceOnlineWithName": "{0} belépett",
|
||||
"FailedLoginAttemptWithUserName": "Sikertelen bejelentkezési kísérlet {0}",
|
||||
"FailedLoginAttemptWithUserName": "Sikertelen bejelentkezési kísérlet tőle: {0}",
|
||||
"Favorites": "Kedvencek",
|
||||
"Folders": "Könyvtárak",
|
||||
"Genres": "Műfajok",
|
||||
|
@ -27,7 +27,7 @@
|
|||
"HeaderNextUp": "Következik",
|
||||
"HeaderRecordingGroups": "Felvételi csoportok",
|
||||
"HomeVideos": "Házi videók",
|
||||
"Inherit": "Öröklés",
|
||||
"Inherit": "Örökölt",
|
||||
"ItemAddedWithName": "{0} hozzáadva a könyvtárhoz",
|
||||
"ItemRemovedWithName": "{0} eltávolítva a könyvtárból",
|
||||
"LabelIpAddressValue": "IP cím: {0}",
|
||||
|
@ -73,7 +73,7 @@
|
|||
"ServerNameNeedsToBeRestarted": "{0}-t újra kell indítani",
|
||||
"Shows": "Műsorok",
|
||||
"Songs": "Dalok",
|
||||
"StartupEmbyServerIsLoading": "A Jellyfin Szerver betöltődik. Kérlek próbáld újra később.",
|
||||
"StartupEmbyServerIsLoading": "A Jellyfin Szerver betöltődik. Kérlek, próbáld újra hamarosan.",
|
||||
"SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}",
|
||||
"SubtitleDownloadFailureFromForItem": "Nem sikerült a felirat letöltése innen: {0} ehhez: {1}",
|
||||
"SubtitlesDownloadedForItem": "Letöltött feliratok a következőhöz: {0}",
|
||||
|
@ -86,11 +86,11 @@
|
|||
"UserDownloadingItemWithValues": "{0} letölti {1}",
|
||||
"UserLockedOutWithName": "{0} felhasználó zárolva van",
|
||||
"UserOfflineFromDevice": "{0} kijelentkezett innen: {1}",
|
||||
"UserOnlineFromDevice": "{0} online itt: {1}",
|
||||
"UserOnlineFromDevice": "{0} online innen: {1}",
|
||||
"UserPasswordChangedWithName": "Jelszó megváltozott a következő felhasználó számára: {0}",
|
||||
"UserPolicyUpdatedWithName": "A felhasználói házirend frissítve lett neki: {0}",
|
||||
"UserStartedPlayingItemWithValues": "{0} elkezdte játszani a következőt: {1} itt: {2}",
|
||||
"UserStoppedPlayingItemWithValues": "{0} befejezte a következőt: {1} itt: {2}",
|
||||
"UserStoppedPlayingItemWithValues": "{0} befejezte {1} lejátászását itt: {2}",
|
||||
"ValueHasBeenAddedToLibrary": "{0} hozzáadva a médiatárhoz",
|
||||
"ValueSpecialEpisodeName": "Special - {0}",
|
||||
"VersionNumber": "Verzió: {0}"
|
||||
|
|
|
@ -91,5 +91,6 @@
|
|||
"NotificationOptionVideoPlayback": "Pemutaran video dimulai",
|
||||
"NotificationOptionAudioPlaybackStopped": "Pemutaran audio berhenti",
|
||||
"NotificationOptionAudioPlayback": "Pemutaran audio dimulai",
|
||||
"MixedContent": "Konten campur"
|
||||
"MixedContent": "Konten campur",
|
||||
"PluginUninstalledWithName": "{0} telah dihapus"
|
||||
}
|
||||
|
|
|
@ -68,5 +68,29 @@
|
|||
"Artists": "Изведувач",
|
||||
"Application": "Апликација",
|
||||
"AppDeviceValues": "Аплиакција: {0}, Уред: {1}",
|
||||
"Albums": "Албуми"
|
||||
"Albums": "Албуми",
|
||||
"VersionNumber": "Верзија {0}",
|
||||
"ValueSpecialEpisodeName": "Специјално - {0}",
|
||||
"ValueHasBeenAddedToLibrary": "{0} е додадено во твојата библиотека",
|
||||
"UserStoppedPlayingItemWithValues": "{0} заврши со репродукција {1} во {2}",
|
||||
"UserStartedPlayingItemWithValues": "{0} пушти {1} на {2}",
|
||||
"UserPolicyUpdatedWithName": "Полисата на користење беше надоградена за {0}",
|
||||
"UserPasswordChangedWithName": "Лозинката е сменета за корисникот {0}",
|
||||
"UserOnlineFromDevice": "{0} е приклучен од {1}",
|
||||
"UserOfflineFromDevice": "{0} е дисконектиран од {1}",
|
||||
"UserLockedOutWithName": "Корисникот {0} е заклучен",
|
||||
"UserDownloadingItemWithValues": "{0} се спушта {1}",
|
||||
"UserDeletedWithName": "Корисникот {0} е избришан",
|
||||
"UserCreatedWithName": "Корисникот {0} е креиран",
|
||||
"User": "Корисник",
|
||||
"TvShows": "ТВ Серии",
|
||||
"System": "Систем",
|
||||
"Sync": "Синхронизација",
|
||||
"SubtitlesDownloadedForItem": "Спуштање превод за {0}",
|
||||
"SubtitleDownloadFailureFromForItem": "Преводот неуспешно се спушти од {0} за {1}",
|
||||
"StartupEmbyServerIsLoading": "Jellyfin Server се пушта. Ве молиме причекајте.",
|
||||
"Songs": "Песни",
|
||||
"Shows": "Серии",
|
||||
"ServerNameNeedsToBeRestarted": "{0} треба да се рестартира",
|
||||
"ScheduledTaskStartedWithName": "{0} започна"
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ using Microsoft.Extensions.DependencyInjection.Extensions;
|
|||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Serilog;
|
||||
using Serilog.Events;
|
||||
using Serilog.Extensions.Logging;
|
||||
using SQLitePCL;
|
||||
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||
|
@ -262,6 +263,7 @@ namespace Jellyfin.Server
|
|||
}
|
||||
})
|
||||
.ConfigureAppConfiguration(config => config.ConfigureAppConfiguration(appPaths))
|
||||
.UseSerilog()
|
||||
.UseContentRoot(appHost.ContentRoot)
|
||||
.ConfigureServices(services =>
|
||||
{
|
||||
|
|
|
@ -1,6 +1,12 @@
|
|||
{
|
||||
"Serilog": {
|
||||
"MinimumLevel": "Information",
|
||||
"MinimumLevel": {
|
||||
"Default": "Information",
|
||||
"Override": {
|
||||
"Microsoft": "Warning",
|
||||
"System": "Warning"
|
||||
}
|
||||
},
|
||||
"WriteTo": [
|
||||
{
|
||||
"Name": "Console",
|
||||
|
|
|
@ -815,7 +815,7 @@ namespace MediaBrowser.Api.Library
|
|||
if (!string.IsNullOrWhiteSpace(filename))
|
||||
{
|
||||
// Kestrel doesn't support non-ASCII characters in headers
|
||||
if (Regex.IsMatch(filename, "[^[:ascii:]]"))
|
||||
if (Regex.IsMatch(filename, @"[^\p{IsBasicLatin}]"))
|
||||
{
|
||||
// Manually encoding non-ASCII characters, following https://tools.ietf.org/html/rfc5987#section-3.2.2
|
||||
headers[HeaderNames.ContentDisposition] = "attachment; filename*=UTF-8''" + WebUtility.UrlEncode(filename);
|
||||
|
|
|
@ -198,6 +198,7 @@ namespace MediaBrowser.Controller.Entities.Audio
|
|||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return base.RequiresRefresh();
|
||||
}
|
||||
|
||||
|
|
|
@ -387,15 +387,12 @@ namespace MediaBrowser.Controller.Entities
|
|||
|
||||
while (thisMarker < s1.Length)
|
||||
{
|
||||
if (thisMarker >= s1.Length)
|
||||
{
|
||||
break;
|
||||
}
|
||||
char thisCh = s1[thisMarker];
|
||||
|
||||
var thisChunk = new StringBuilder();
|
||||
bool isNumeric = char.IsDigit(thisCh);
|
||||
|
||||
while ((thisMarker < s1.Length) && (thisChunk.Length == 0 || SortHelper.InChunk(thisCh, thisChunk[0])))
|
||||
while (thisMarker < s1.Length && char.IsDigit(thisCh) == isNumeric)
|
||||
{
|
||||
thisChunk.Append(thisCh);
|
||||
thisMarker++;
|
||||
|
@ -406,7 +403,6 @@ namespace MediaBrowser.Controller.Entities
|
|||
}
|
||||
}
|
||||
|
||||
var isNumeric = thisChunk.Length > 0 && char.IsDigit(thisChunk[0]);
|
||||
list.Add(new Tuple<StringBuilder, bool>(thisChunk, isNumeric));
|
||||
}
|
||||
|
||||
|
|
|
@ -322,10 +322,10 @@ namespace MediaBrowser.Controller.Entities
|
|||
ProviderManager.OnRefreshProgress(this, 5);
|
||||
}
|
||||
|
||||
//build a dictionary of the current children we have now by Id so we can compare quickly and easily
|
||||
// Build a dictionary of the current children we have now by Id so we can compare quickly and easily
|
||||
var currentChildren = GetActualChildrenDictionary();
|
||||
|
||||
//create a list for our validated children
|
||||
// Create a list for our validated children
|
||||
var newItems = new List<BaseItem>();
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
@ -391,7 +391,7 @@ namespace MediaBrowser.Controller.Entities
|
|||
var folder = this;
|
||||
innerProgress.RegisterAction(p =>
|
||||
{
|
||||
double newPct = .80 * p + 10;
|
||||
double newPct = 0.80 * p + 10;
|
||||
progress.Report(newPct);
|
||||
ProviderManager.OnRefreshProgress(folder, newPct);
|
||||
});
|
||||
|
@ -421,7 +421,7 @@ namespace MediaBrowser.Controller.Entities
|
|||
var folder = this;
|
||||
innerProgress.RegisterAction(p =>
|
||||
{
|
||||
double newPct = .10 * p + 90;
|
||||
double newPct = 0.10 * p + 90;
|
||||
progress.Report(newPct);
|
||||
if (recursive)
|
||||
{
|
||||
|
@ -807,11 +807,45 @@ namespace MediaBrowser.Controller.Entities
|
|||
return false;
|
||||
}
|
||||
|
||||
private static BaseItem[] SortItemsByRequest(InternalItemsQuery query, IReadOnlyList<BaseItem> items)
|
||||
{
|
||||
var ids = query.ItemIds;
|
||||
int size = items.Count;
|
||||
|
||||
// ids can potentially contain non-unique guids, but query result cannot,
|
||||
// so we include only first occurrence of each guid
|
||||
var positions = new Dictionary<Guid, int>(size);
|
||||
int index = 0;
|
||||
for (int i = 0; i < ids.Length; i++)
|
||||
{
|
||||
if (positions.TryAdd(ids[i], index))
|
||||
{
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
var newItems = new BaseItem[size];
|
||||
for (int i = 0; i < size; i++)
|
||||
{
|
||||
var item = items[i];
|
||||
newItems[positions[item.Id]] = item;
|
||||
}
|
||||
|
||||
return newItems;
|
||||
}
|
||||
|
||||
public QueryResult<BaseItem> GetItems(InternalItemsQuery query)
|
||||
{
|
||||
if (query.ItemIds.Length > 0)
|
||||
{
|
||||
return LibraryManager.GetItemsResult(query);
|
||||
var result = LibraryManager.GetItemsResult(query);
|
||||
|
||||
if (query.OrderBy.Count == 0 && query.ItemIds.Length > 1)
|
||||
{
|
||||
result.Items = SortItemsByRequest(query, result.Items);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
return GetItemsInternal(query);
|
||||
|
@ -823,7 +857,14 @@ namespace MediaBrowser.Controller.Entities
|
|||
|
||||
if (query.ItemIds.Length > 0)
|
||||
{
|
||||
return LibraryManager.GetItemList(query);
|
||||
var result = LibraryManager.GetItemList(query);
|
||||
|
||||
if (query.OrderBy.Count == 0 && query.ItemIds.Length > 1)
|
||||
{
|
||||
return SortItemsByRequest(query, result);
|
||||
}
|
||||
|
||||
return result.ToArray();
|
||||
}
|
||||
|
||||
return GetItemsInternal(query).Items;
|
||||
|
|
|
@ -2,7 +2,7 @@ using System.Collections.Generic;
|
|||
using System.Text;
|
||||
using MediaBrowser.Controller.Sorting;
|
||||
|
||||
namespace Emby.Server.Implementations.Sorting
|
||||
namespace MediaBrowser.Controller.Sorting
|
||||
{
|
||||
public class AlphanumComparator : IComparer<string>
|
||||
{
|
||||
|
@ -31,8 +31,9 @@ namespace Emby.Server.Implementations.Sorting
|
|||
|
||||
var thisChunk = new StringBuilder();
|
||||
var thatChunk = new StringBuilder();
|
||||
bool thisNumeric = char.IsDigit(thisCh), thatNumeric = char.IsDigit(thatCh);
|
||||
|
||||
while ((thisMarker < s1.Length) && (thisChunk.Length == 0 || SortHelper.InChunk(thisCh, thisChunk[0])))
|
||||
while (thisMarker < s1.Length && char.IsDigit(thisCh) == thisNumeric)
|
||||
{
|
||||
thisChunk.Append(thisCh);
|
||||
thisMarker++;
|
||||
|
@ -43,7 +44,7 @@ namespace Emby.Server.Implementations.Sorting
|
|||
}
|
||||
}
|
||||
|
||||
while ((thatMarker < s2.Length) && (thatChunk.Length == 0 || SortHelper.InChunk(thatCh, thatChunk[0])))
|
||||
while (thatMarker < s2.Length && char.IsDigit(thatCh) == thatNumeric)
|
||||
{
|
||||
thatChunk.Append(thatCh);
|
||||
thatMarker++;
|
||||
|
@ -54,38 +55,35 @@ namespace Emby.Server.Implementations.Sorting
|
|||
}
|
||||
}
|
||||
|
||||
int result = 0;
|
||||
|
||||
// If both chunks contain numeric characters, sort them numerically
|
||||
if (char.IsDigit(thisChunk[0]) && char.IsDigit(thatChunk[0]))
|
||||
if (thisNumeric && thatNumeric)
|
||||
{
|
||||
if (!int.TryParse(thisChunk.ToString(), out thisNumericChunk))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
if (!int.TryParse(thatChunk.ToString(), out thatNumericChunk))
|
||||
if (!int.TryParse(thisChunk.ToString(), out thisNumericChunk)
|
||||
|| !int.TryParse(thatChunk.ToString(), out thatNumericChunk))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (thisNumericChunk < thatNumericChunk)
|
||||
{
|
||||
result = -1;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (thisNumericChunk > thatNumericChunk)
|
||||
{
|
||||
result = 1;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
result = thisChunk.ToString().CompareTo(thatChunk.ToString());
|
||||
int result = thisChunk.ToString().CompareTo(thatChunk.ToString());
|
||||
if (result != 0)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
if (result != 0)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
|
@ -7,137 +7,25 @@ namespace MediaBrowser.Controller.Sorting
|
|||
{
|
||||
public static class SortExtensions
|
||||
{
|
||||
private static readonly AlphanumComparator _comparer = new AlphanumComparator();
|
||||
public static IEnumerable<T> OrderByString<T>(this IEnumerable<T> list, Func<T, string> getName)
|
||||
{
|
||||
return list.OrderBy(getName, new AlphanumComparator());
|
||||
return list.OrderBy(getName, _comparer);
|
||||
}
|
||||
|
||||
public static IEnumerable<T> OrderByStringDescending<T>(this IEnumerable<T> list, Func<T, string> getName)
|
||||
{
|
||||
return list.OrderByDescending(getName, new AlphanumComparator());
|
||||
return list.OrderByDescending(getName, _comparer);
|
||||
}
|
||||
|
||||
public static IOrderedEnumerable<T> ThenByString<T>(this IOrderedEnumerable<T> list, Func<T, string> getName)
|
||||
{
|
||||
return list.ThenBy(getName, new AlphanumComparator());
|
||||
return list.ThenBy(getName, _comparer);
|
||||
}
|
||||
|
||||
public static IOrderedEnumerable<T> ThenByStringDescending<T>(this IOrderedEnumerable<T> list, Func<T, string> getName)
|
||||
{
|
||||
return list.ThenByDescending(getName, new AlphanumComparator());
|
||||
}
|
||||
|
||||
private class AlphanumComparator : IComparer<string>
|
||||
{
|
||||
private enum ChunkType { Alphanumeric, Numeric };
|
||||
|
||||
private static bool InChunk(char ch, char otherCh)
|
||||
{
|
||||
var type = ChunkType.Alphanumeric;
|
||||
|
||||
if (char.IsDigit(otherCh))
|
||||
{
|
||||
type = ChunkType.Numeric;
|
||||
}
|
||||
|
||||
if ((type == ChunkType.Alphanumeric && char.IsDigit(ch))
|
||||
|| (type == ChunkType.Numeric && !char.IsDigit(ch)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static int CompareValues(string s1, string s2)
|
||||
{
|
||||
if (s1 == null || s2 == null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int thisMarker = 0, thisNumericChunk = 0;
|
||||
int thatMarker = 0, thatNumericChunk = 0;
|
||||
|
||||
while ((thisMarker < s1.Length) || (thatMarker < s2.Length))
|
||||
{
|
||||
if (thisMarker >= s1.Length)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
else if (thatMarker >= s2.Length)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
char thisCh = s1[thisMarker];
|
||||
char thatCh = s2[thatMarker];
|
||||
|
||||
var thisChunk = new StringBuilder();
|
||||
var thatChunk = new StringBuilder();
|
||||
|
||||
while ((thisMarker < s1.Length) && (thisChunk.Length == 0 || InChunk(thisCh, thisChunk[0])))
|
||||
{
|
||||
thisChunk.Append(thisCh);
|
||||
thisMarker++;
|
||||
|
||||
if (thisMarker < s1.Length)
|
||||
{
|
||||
thisCh = s1[thisMarker];
|
||||
}
|
||||
}
|
||||
|
||||
while ((thatMarker < s2.Length) && (thatChunk.Length == 0 || InChunk(thatCh, thatChunk[0])))
|
||||
{
|
||||
thatChunk.Append(thatCh);
|
||||
thatMarker++;
|
||||
|
||||
if (thatMarker < s2.Length)
|
||||
{
|
||||
thatCh = s2[thatMarker];
|
||||
}
|
||||
}
|
||||
|
||||
int result = 0;
|
||||
// If both chunks contain numeric characters, sort them numerically
|
||||
if (char.IsDigit(thisChunk[0]) && char.IsDigit(thatChunk[0]))
|
||||
{
|
||||
if (!int.TryParse(thisChunk.ToString(), out thisNumericChunk))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
if (!int.TryParse(thatChunk.ToString(), out thatNumericChunk))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (thisNumericChunk < thatNumericChunk)
|
||||
{
|
||||
result = -1;
|
||||
}
|
||||
|
||||
if (thisNumericChunk > thatNumericChunk)
|
||||
{
|
||||
result = 1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
result = thisChunk.ToString().CompareTo(thatChunk.ToString());
|
||||
}
|
||||
|
||||
if (result != 0)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public int Compare(string x, string y)
|
||||
{
|
||||
return CompareValues(x, y);
|
||||
}
|
||||
return list.ThenByDescending(getName, _comparer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
namespace MediaBrowser.Controller.Sorting
|
||||
{
|
||||
public static class SortHelper
|
||||
{
|
||||
private enum ChunkType { Alphanumeric, Numeric };
|
||||
|
||||
public static bool InChunk(char ch, char otherCh)
|
||||
{
|
||||
var type = ChunkType.Alphanumeric;
|
||||
|
||||
if (char.IsDigit(otherCh))
|
||||
{
|
||||
type = ChunkType.Numeric;
|
||||
}
|
||||
|
||||
if ((type == ChunkType.Alphanumeric && char.IsDigit(ch))
|
||||
|| (type == ChunkType.Numeric && !char.IsDigit(ch)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -11,6 +11,11 @@ namespace MediaBrowser.LocalMetadata.Savers
|
|||
{
|
||||
public class PlaylistXmlSaver : BaseXmlSaver
|
||||
{
|
||||
/// <summary>
|
||||
/// The default file name to use when creating a new playlist.
|
||||
/// </summary>
|
||||
public const string DefaultPlaylistFilename = "playlist.xml";
|
||||
|
||||
public override bool IsEnabledFor(BaseItem item, ItemUpdateType updateType)
|
||||
{
|
||||
if (!item.SupportsLocalMetadata)
|
||||
|
@ -45,7 +50,7 @@ namespace MediaBrowser.LocalMetadata.Savers
|
|||
return Path.ChangeExtension(itemPath, ".xml");
|
||||
}
|
||||
|
||||
return Path.Combine(path, "playlist.xml");
|
||||
return Path.Combine(path, DefaultPlaylistFilename);
|
||||
}
|
||||
|
||||
public PlaylistXmlSaver(IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, IUserManager userManager, IUserDataManager userDataManager, ILogger logger)
|
||||
|
|
|
@ -34,7 +34,7 @@ namespace MediaBrowser.Model.Configuration
|
|||
public EncodingOptions()
|
||||
{
|
||||
DownMixAudioBoost = 2;
|
||||
EnableThrottling = true;
|
||||
EnableThrottling = false;
|
||||
ThrottleDelaySeconds = 180;
|
||||
EncodingThreadCount = -1;
|
||||
// This is a DRM device that is almost guaranteed to be there on every intel platform, plus it's the default one in ffmpeg if you don't specify anything
|
||||
|
|
|
@ -238,7 +238,7 @@ namespace MediaBrowser.Model.Configuration
|
|||
CodecsUsed = Array.Empty<string>();
|
||||
PathSubstitutions = Array.Empty<PathSubstitution>();
|
||||
IgnoreVirtualInterfaces = false;
|
||||
EnableSimpleArtistDetection = true;
|
||||
EnableSimpleArtistDetection = false;
|
||||
|
||||
DisplaySpecialsWithinSeasons = true;
|
||||
EnableExternalContentInSuggestions = true;
|
||||
|
|
|
@ -24,4 +24,9 @@
|
|||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="Plugins\MusicBrainz\Configuration\config.html" />
|
||||
<EmbeddedResource Include="Plugins\MusicBrainz\Configuration\config.html" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -1,105 +1,9 @@
|
|||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.Audio;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
|
||||
namespace MediaBrowser.Providers.Music
|
||||
{
|
||||
public class MusicBrainzReleaseGroupExternalId : IExternalId
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string Name => "MusicBrainz Release Group";
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Key => MetadataProviders.MusicBrainzReleaseGroup.ToString();
|
||||
|
||||
/// <inheritdoc />
|
||||
public string UrlFormatString => "https://musicbrainz.org/release-group/{0}";
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Supports(IHasProviderIds item)
|
||||
=> item is Audio || item is MusicAlbum;
|
||||
}
|
||||
|
||||
public class MusicBrainzAlbumArtistExternalId : IExternalId
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string Name => "MusicBrainz Album Artist";
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Key => MetadataProviders.MusicBrainzAlbumArtist.ToString();
|
||||
|
||||
/// <inheritdoc />
|
||||
public string UrlFormatString => "https://musicbrainz.org/artist/{0}";
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Supports(IHasProviderIds item)
|
||||
=> item is Audio;
|
||||
}
|
||||
|
||||
public class MusicBrainzAlbumExternalId : IExternalId
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string Name => "MusicBrainz Album";
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Key => MetadataProviders.MusicBrainzAlbum.ToString();
|
||||
|
||||
/// <inheritdoc />
|
||||
public string UrlFormatString => "https://musicbrainz.org/release/{0}";
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Supports(IHasProviderIds item)
|
||||
=> item is Audio || item is MusicAlbum;
|
||||
}
|
||||
|
||||
public class MusicBrainzArtistExternalId : IExternalId
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string Name => "MusicBrainz";
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Key => MetadataProviders.MusicBrainzArtist.ToString();
|
||||
|
||||
/// <inheritdoc />
|
||||
public string UrlFormatString => "https://musicbrainz.org/artist/{0}";
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Supports(IHasProviderIds item) => item is MusicArtist;
|
||||
}
|
||||
|
||||
public class MusicBrainzOtherArtistExternalId : IExternalId
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string Name => "MusicBrainz Artist";
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
public string Key => MetadataProviders.MusicBrainzArtist.ToString();
|
||||
|
||||
/// <inheritdoc />
|
||||
public string UrlFormatString => "https://musicbrainz.org/artist/{0}";
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Supports(IHasProviderIds item)
|
||||
=> item is Audio || item is MusicAlbum;
|
||||
}
|
||||
|
||||
public class MusicBrainzTrackId : IExternalId
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string Name => "MusicBrainz Track";
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Key => MetadataProviders.MusicBrainzTrack.ToString();
|
||||
|
||||
/// <inheritdoc />
|
||||
public string UrlFormatString => "https://musicbrainz.org/track/{0}";
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Supports(IHasProviderIds item) => item is Audio;
|
||||
}
|
||||
|
||||
public class ImvdbId : IExternalId
|
||||
{
|
||||
/// <inheritdoc />
|
||||
|
|
|
@ -15,7 +15,7 @@ using MediaBrowser.Controller.Entities.Audio;
|
|||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Providers;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using MediaBrowser.Providers.Plugins.MusicBrainz;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace MediaBrowser.Providers.Music
|
||||
|
@ -28,7 +28,7 @@ namespace MediaBrowser.Providers.Music
|
|||
/// Be prudent, use a value slightly above the minimun required.
|
||||
/// https://musicbrainz.org/doc/XML_Web_Service/Rate_Limiting
|
||||
/// </summary>
|
||||
private const long MusicBrainzQueryIntervalMs = 1050u;
|
||||
private readonly long _musicBrainzQueryIntervalMs;
|
||||
|
||||
/// <summary>
|
||||
/// For each single MB lookup/search, this is the maximum number of
|
||||
|
@ -50,14 +50,14 @@ namespace MediaBrowser.Providers.Music
|
|||
public MusicBrainzAlbumProvider(
|
||||
IHttpClient httpClient,
|
||||
IApplicationHost appHost,
|
||||
ILogger logger,
|
||||
IConfiguration configuration)
|
||||
ILogger logger)
|
||||
{
|
||||
_httpClient = httpClient;
|
||||
_appHost = appHost;
|
||||
_logger = logger;
|
||||
|
||||
_musicBrainzBaseUrl = configuration["MusicBrainz:BaseUrl"];
|
||||
_musicBrainzBaseUrl = Plugin.Instance.Configuration.Server;
|
||||
_musicBrainzQueryIntervalMs = Plugin.Instance.Configuration.RateLimit;
|
||||
|
||||
// Use a stopwatch to ensure we don't exceed the MusicBrainz rate limit
|
||||
_stopWatchMusicBrainz.Start();
|
||||
|
@ -74,6 +74,12 @@ namespace MediaBrowser.Providers.Music
|
|||
/// <inheritdoc />
|
||||
public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(AlbumInfo searchInfo, CancellationToken cancellationToken)
|
||||
{
|
||||
// TODO maybe remove when artist metadata can be disabled
|
||||
if (!Plugin.Instance.Configuration.Enable)
|
||||
{
|
||||
return Enumerable.Empty<RemoteSearchResult>();
|
||||
}
|
||||
|
||||
var releaseId = searchInfo.GetReleaseId();
|
||||
var releaseGroupId = searchInfo.GetReleaseGroupId();
|
||||
|
||||
|
@ -107,8 +113,8 @@ namespace MediaBrowser.Providers.Music
|
|||
url = string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"/ws/2/release/?query=\"{0}\" AND artist:\"{1}\"",
|
||||
WebUtility.UrlEncode(queryName),
|
||||
WebUtility.UrlEncode(searchInfo.GetAlbumArtist()));
|
||||
WebUtility.UrlEncode(queryName),
|
||||
WebUtility.UrlEncode(searchInfo.GetAlbumArtist()));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -170,7 +176,6 @@ namespace MediaBrowser.Providers.Music
|
|||
}
|
||||
|
||||
return result;
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -187,6 +192,12 @@ namespace MediaBrowser.Providers.Music
|
|||
Item = new MusicAlbum()
|
||||
};
|
||||
|
||||
// TODO maybe remove when artist metadata can be disabled
|
||||
if (!Plugin.Instance.Configuration.Enable)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
// If we have a release group Id but not a release Id...
|
||||
if (string.IsNullOrWhiteSpace(releaseId) && !string.IsNullOrWhiteSpace(releaseGroupId))
|
||||
{
|
||||
|
@ -456,18 +467,6 @@ namespace MediaBrowser.Providers.Music
|
|||
}
|
||||
case "artist-credit":
|
||||
{
|
||||
// TODO
|
||||
|
||||
/*
|
||||
* <artist-credit>
|
||||
<name-credit>
|
||||
<artist id="e225cda5-882d-4b80-b8a3-b36d7175b1ea">
|
||||
<name>SARCASTIC+ZOOKEEPER</name>
|
||||
<sort-name>SARCASTIC+ZOOKEEPER</sort-name>
|
||||
</artist>
|
||||
</name-credit>
|
||||
</artist-credit>
|
||||
*/
|
||||
using (var subReader = reader.ReadSubtree())
|
||||
{
|
||||
var artist = ParseArtistCredit(subReader);
|
||||
|
@ -764,10 +763,10 @@ namespace MediaBrowser.Providers.Music
|
|||
{
|
||||
attempts++;
|
||||
|
||||
if (_stopWatchMusicBrainz.ElapsedMilliseconds < MusicBrainzQueryIntervalMs)
|
||||
if (_stopWatchMusicBrainz.ElapsedMilliseconds < _musicBrainzQueryIntervalMs)
|
||||
{
|
||||
// MusicBrainz is extremely adamant about limiting to one request per second
|
||||
var delayMs = MusicBrainzQueryIntervalMs - _stopWatchMusicBrainz.ElapsedMilliseconds;
|
||||
var delayMs = _musicBrainzQueryIntervalMs - _stopWatchMusicBrainz.ElapsedMilliseconds;
|
||||
await Task.Delay((int)delayMs, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
|
@ -778,7 +777,7 @@ namespace MediaBrowser.Providers.Music
|
|||
|
||||
response = await _httpClient.SendAsync(options, "GET").ConfigureAwait(false);
|
||||
|
||||
// We retry a finite number of times, and only whilst MB is indcating 503 (throttling)
|
||||
// We retry a finite number of times, and only whilst MB is indicating 503 (throttling)
|
||||
}
|
||||
while (attempts < MusicBrainzQueryAttempts && response.StatusCode == HttpStatusCode.ServiceUnavailable);
|
||||
|
|
@ -14,6 +14,7 @@ using MediaBrowser.Controller.Extensions;
|
|||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Providers;
|
||||
using MediaBrowser.Providers.Plugins.MusicBrainz;
|
||||
|
||||
namespace MediaBrowser.Providers.Music
|
||||
{
|
||||
|
@ -22,6 +23,12 @@ namespace MediaBrowser.Providers.Music
|
|||
/// <inheritdoc />
|
||||
public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(ArtistInfo searchInfo, CancellationToken cancellationToken)
|
||||
{
|
||||
// TODO maybe remove when artist metadata can be disabled
|
||||
if (!Plugin.Instance.Configuration.Enable)
|
||||
{
|
||||
return Enumerable.Empty<RemoteSearchResult>();
|
||||
}
|
||||
|
||||
var musicBrainzId = searchInfo.GetMusicBrainzArtistId();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(musicBrainzId))
|
||||
|
@ -226,6 +233,12 @@ namespace MediaBrowser.Providers.Music
|
|||
Item = new MusicArtist()
|
||||
};
|
||||
|
||||
// TODO maybe remove when artist metadata can be disabled
|
||||
if (!Plugin.Instance.Configuration.Enable)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
var musicBrainzId = id.GetMusicBrainzArtistId();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(musicBrainzId))
|
||||
|
@ -237,8 +250,12 @@ namespace MediaBrowser.Providers.Music
|
|||
if (singleResult != null)
|
||||
{
|
||||
musicBrainzId = singleResult.GetProviderId(MetadataProviders.MusicBrainzArtist);
|
||||
//result.Item.Name = singleResult.Name;
|
||||
result.Item.Overview = singleResult.Overview;
|
||||
|
||||
if (Plugin.Instance.Configuration.ReplaceArtistName)
|
||||
{
|
||||
result.Item.Name = singleResult.Name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
using MediaBrowser.Model.Plugins;
|
||||
|
||||
namespace MediaBrowser.Providers.Plugins.MusicBrainz
|
||||
{
|
||||
public class PluginConfiguration : BasePluginConfiguration
|
||||
{
|
||||
private string _server = Plugin.DefaultServer;
|
||||
|
||||
private long _rateLimit = Plugin.DefaultRateLimit;
|
||||
|
||||
public string Server
|
||||
{
|
||||
get
|
||||
{
|
||||
return _server;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
_server = value.TrimEnd('/');
|
||||
}
|
||||
}
|
||||
|
||||
public long RateLimit
|
||||
{
|
||||
get
|
||||
{
|
||||
return _rateLimit;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (value < Plugin.DefaultRateLimit && _server == Plugin.DefaultServer)
|
||||
{
|
||||
RateLimit = Plugin.DefaultRateLimit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool Enable { get; set; }
|
||||
|
||||
public bool ReplaceArtistName { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>MusicBrainz</title>
|
||||
</head>
|
||||
<body>
|
||||
<div data-role="page" class="page type-interior pluginConfigurationPage musicBrainzConfigPage" data-require="emby-input,emby-button,emby-checkbox">
|
||||
<div data-role="content">
|
||||
<div class="content-primary">
|
||||
<form class="musicBrainzConfigForm">
|
||||
<div class="inputContainer">
|
||||
<input is="emby-input" type="text" id="server" required label="Server" />
|
||||
<div class="fieldDescription">This can be a mirror of the official server or even a custom server.</div>
|
||||
</div>
|
||||
<div class="inputContainer">
|
||||
<input is="emby-input" type="number" id="rateLimit" pattern="[0-9]*" required min="0" max="10000" label="Rate Limit" />
|
||||
<div class="fieldDescription">Span of time between requests in milliseconds. The official server is limited to one request every two seconds.</div>
|
||||
</div>
|
||||
<label class="checkboxContainer">
|
||||
<input is="emby-checkbox" type="checkbox" id="enable" />
|
||||
<span>Enable this provider for metadata searches on artists and albums.</span>
|
||||
</label>
|
||||
<label class="checkboxContainer">
|
||||
<input is="emby-checkbox" type="checkbox" id="replaceArtistName" />
|
||||
<span>When an artist is found during a metadata search, replace the artist name with the value on the server.</span>
|
||||
</label>
|
||||
<br />
|
||||
<div>
|
||||
<button is="emby-button" type="submit" class="raised button-submit block"><span>Save</span></button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
var MusicBrainzPluginConfig = {
|
||||
uniquePluginId: "8c95c4d2-e50c-4fb0-a4f3-6c06ff0f9a1a"
|
||||
};
|
||||
|
||||
$('.musicBrainzConfigPage').on('pageshow', function () {
|
||||
Dashboard.showLoadingMsg();
|
||||
ApiClient.getPluginConfiguration(MusicBrainzPluginConfig.uniquePluginId).then(function (config) {
|
||||
$('#server').val(config.Server).change();
|
||||
$('#rateLimit').val(config.RateLimit).change();
|
||||
$('#enable').checked(config.Enable);
|
||||
$('#replaceArtistName').checked(config.ReplaceArtistName);
|
||||
|
||||
Dashboard.hideLoadingMsg();
|
||||
});
|
||||
});
|
||||
|
||||
$('.musicBrainzConfigForm').on('submit', function (e) {
|
||||
Dashboard.showLoadingMsg();
|
||||
|
||||
var form = this;
|
||||
ApiClient.getPluginConfiguration(MusicBrainzPluginConfig.uniquePluginId).then(function (config) {
|
||||
config.Server = $('#server', form).val();
|
||||
config.RateLimit = $('#rateLimit', form).val();
|
||||
config.Enable = $('#enable', form).checked();
|
||||
config.ReplaceArtistName = $('#replaceArtistName', form).checked();
|
||||
|
||||
ApiClient.updatePluginConfiguration(MusicBrainzPluginConfig.uniquePluginId, config).then(Dashboard.processPluginConfigurationUpdateResult);
|
||||
});
|
||||
|
||||
return false;
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
98
MediaBrowser.Providers/Plugins/MusicBrainz/ExternalIds.cs
Normal file
98
MediaBrowser.Providers/Plugins/MusicBrainz/ExternalIds.cs
Normal file
|
@ -0,0 +1,98 @@
|
|||
using MediaBrowser.Controller.Entities.Audio;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Providers.Plugins.MusicBrainz;
|
||||
|
||||
namespace MediaBrowser.Providers.Music
|
||||
{
|
||||
public class MusicBrainzReleaseGroupExternalId : IExternalId
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string Name => "MusicBrainz Release Group";
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Key => MetadataProviders.MusicBrainzReleaseGroup.ToString();
|
||||
|
||||
/// <inheritdoc />
|
||||
public string UrlFormatString => Plugin.Instance.Configuration.Server + "/release-group/{0}";
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Supports(IHasProviderIds item) => item is Audio || item is MusicAlbum;
|
||||
}
|
||||
|
||||
public class MusicBrainzAlbumArtistExternalId : IExternalId
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string Name => "MusicBrainz Album Artist";
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Key => MetadataProviders.MusicBrainzAlbumArtist.ToString();
|
||||
|
||||
/// <inheritdoc />
|
||||
public string UrlFormatString => Plugin.Instance.Configuration.Server + "/artist/{0}";
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Supports(IHasProviderIds item) => item is Audio;
|
||||
}
|
||||
|
||||
public class MusicBrainzAlbumExternalId : IExternalId
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string Name => "MusicBrainz Album";
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Key => MetadataProviders.MusicBrainzAlbum.ToString();
|
||||
|
||||
/// <inheritdoc />
|
||||
public string UrlFormatString => Plugin.Instance.Configuration.Server + "/release/{0}";
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Supports(IHasProviderIds item) => item is Audio || item is MusicAlbum;
|
||||
}
|
||||
|
||||
public class MusicBrainzArtistExternalId : IExternalId
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string Name => "MusicBrainz";
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Key => MetadataProviders.MusicBrainzArtist.ToString();
|
||||
|
||||
/// <inheritdoc />
|
||||
public string UrlFormatString => Plugin.Instance.Configuration.Server + "/artist/{0}";
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Supports(IHasProviderIds item) => item is MusicArtist;
|
||||
}
|
||||
|
||||
public class MusicBrainzOtherArtistExternalId : IExternalId
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string Name => "MusicBrainz Artist";
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
public string Key => MetadataProviders.MusicBrainzArtist.ToString();
|
||||
|
||||
/// <inheritdoc />
|
||||
public string UrlFormatString => Plugin.Instance.Configuration.Server + "/artist/{0}";
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Supports(IHasProviderIds item) => item is Audio || item is MusicAlbum;
|
||||
}
|
||||
|
||||
public class MusicBrainzTrackId : IExternalId
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string Name => "MusicBrainz Track";
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Key => MetadataProviders.MusicBrainzTrack.ToString();
|
||||
|
||||
/// <inheritdoc />
|
||||
public string UrlFormatString => Plugin.Instance.Configuration.Server + "/track/{0}";
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Supports(IHasProviderIds item) => item is Audio;
|
||||
}
|
||||
}
|
39
MediaBrowser.Providers/Plugins/MusicBrainz/Plugin.cs
Normal file
39
MediaBrowser.Providers/Plugins/MusicBrainz/Plugin.cs
Normal file
|
@ -0,0 +1,39 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Plugins;
|
||||
using MediaBrowser.Model.Plugins;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
|
||||
namespace MediaBrowser.Providers.Plugins.MusicBrainz
|
||||
{
|
||||
public class Plugin : BasePlugin<PluginConfiguration>, IHasWebPages
|
||||
{
|
||||
public static Plugin Instance { get; private set; }
|
||||
|
||||
public override Guid Id => new Guid("8c95c4d2-e50c-4fb0-a4f3-6c06ff0f9a1a");
|
||||
|
||||
public override string Name => "MusicBrainz";
|
||||
|
||||
public override string Description => "Get artist and album metadata from any MusicBrainz server.";
|
||||
|
||||
public const string DefaultServer = "https://musicbrainz.org";
|
||||
|
||||
public const long DefaultRateLimit = 2000u;
|
||||
|
||||
public Plugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer)
|
||||
: base(applicationPaths, xmlSerializer)
|
||||
{
|
||||
Instance = this;
|
||||
}
|
||||
|
||||
public IEnumerable<PluginPageInfo> GetPages()
|
||||
{
|
||||
yield return new PluginPageInfo
|
||||
{
|
||||
Name = Name,
|
||||
EmbeddedResourcePath = GetType().Namespace + ".Configuration.config.html"
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user