aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CONTRIBUTORS.md1
-rw-r--r--Emby.Dlna/PlayTo/Device.cs41
-rw-r--r--Emby.Dlna/PlayTo/PlayToManager.cs5
-rw-r--r--Emby.Dlna/PlayTo/SsdpHttpClient.cs15
-rw-r--r--Emby.Server.Implementations/ApplicationHost.cs2
-rw-r--r--Emby.Server.Implementations/Library/LibraryManager.cs2
-rw-r--r--Emby.Server.Implementations/Library/PathExtensions.cs5
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs5
-rw-r--r--Emby.Server.Implementations/MediaEncoder/EncodingManager.cs5
-rw-r--r--Jellyfin.Api/Controllers/LibraryController.cs4
-rw-r--r--Jellyfin.Api/Controllers/SessionController.cs16
-rw-r--r--Jellyfin.Api/Controllers/SubtitleController.cs66
-rw-r--r--Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs7
-rw-r--r--Jellyfin.Networking/Manager/NetworkManager.cs23
-rw-r--r--Jellyfin.Server.Implementations/Users/UserManager.cs21
-rw-r--r--Jellyfin.Server/Jellyfin.Server.csproj2
-rw-r--r--Jellyfin.Server/Middleware/IpBasedAccessValidationMiddleware.cs29
-rw-r--r--Jellyfin.sln13
-rw-r--r--MediaBrowser.Common/Net/INetworkManager.cs7
-rw-r--r--MediaBrowser.Common/Providers/ProviderIdParsers.cs125
-rw-r--r--MediaBrowser.Controller/LiveTv/ChannelInfo.cs6
-rw-r--r--MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs6
-rw-r--r--MediaBrowser.Controller/Providers/DirectoryService.cs6
-rw-r--r--MediaBrowser.Controller/Providers/MetadataResult.cs12
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs2
-rw-r--r--MediaBrowser.Model/Dlna/StreamBuilder.cs33
-rw-r--r--MediaBrowser.Providers/Manager/ItemImageProvider.cs1
-rw-r--r--MediaBrowser.Providers/Manager/MetadataService.cs24
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs1
-rw-r--r--MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs134
-rw-r--r--MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs6
-rw-r--r--MediaBrowser.XbmcMetadata/Parsers/MovieNfoParser.cs17
-rw-r--r--MediaBrowser.XbmcMetadata/Parsers/SeasonNfoParser.cs6
-rw-r--r--MediaBrowser.XbmcMetadata/Parsers/SeriesNfoParser.cs9
-rw-r--r--MediaBrowser.XbmcMetadata/Providers/AlbumNfoProvider.cs8
-rw-r--r--MediaBrowser.XbmcMetadata/Providers/ArtistNfoProvider.cs8
-rw-r--r--MediaBrowser.XbmcMetadata/Providers/BaseVideoNfoProvider.cs9
-rw-r--r--MediaBrowser.XbmcMetadata/Providers/EpisodeNfoProvider.cs8
-rw-r--r--MediaBrowser.XbmcMetadata/Providers/MovieNfoProvider.cs6
-rw-r--r--MediaBrowser.XbmcMetadata/Providers/MusicVideoNfoProvider.cs6
-rw-r--r--MediaBrowser.XbmcMetadata/Providers/SeasonNfoProvider.cs8
-rw-r--r--MediaBrowser.XbmcMetadata/Providers/SeriesNfoProvider.cs8
-rw-r--r--MediaBrowser.XbmcMetadata/Providers/VideoNfoProvider.cs6
-rw-r--r--tests/Jellyfin.Common.Tests/Extensions/CopyToExtensionsTests.cs40
-rw-r--r--tests/Jellyfin.Common.Tests/Providers/ProviderIdParserTests.cs85
-rw-r--r--tests/Jellyfin.Controller.Tests/DirectoryServiceTests.cs200
-rw-r--r--tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj1
-rw-r--r--tests/Jellyfin.Networking.Tests/NetworkParseTests.cs64
-rw-r--r--tests/Jellyfin.XbmcMetadata.Tests/Parsers/EpisodeNfoProviderTests.cs11
-rw-r--r--tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs68
-rw-r--r--tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicAlbumNfoProviderTests.cs11
-rw-r--r--tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicArtistNfoParserTests.cs9
-rw-r--r--tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicVideoNfoParserTests.cs9
-rw-r--r--tests/Jellyfin.XbmcMetadata.Tests/Parsers/SeasonNfoProviderTests.cs9
-rw-r--r--tests/Jellyfin.XbmcMetadata.Tests/Parsers/SeriesNfoParserTests.cs3
-rw-r--r--tests/Jellyfin.XbmcMetadata.Tests/Test Data/Justice League.nfo5
-rw-r--r--tests/Jellyfin.XbmcMetadata.Tests/Test Data/Radarr.nfo2
57 files changed, 1023 insertions, 218 deletions
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
index 954315f0e..9b1ac0673 100644
--- a/CONTRIBUTORS.md
+++ b/CONTRIBUTORS.md
@@ -17,6 +17,7 @@
- [bugfixin](https://github.com/bugfixin)
- [chaosinnovator](https://github.com/chaosinnovator)
- [ckcr4lyf](https://github.com/ckcr4lyf)
+ - [cocool97](https://github.com/cocool97)
- [ConfusedPolarBear](https://github.com/ConfusedPolarBear)
- [crankdoofus](https://github.com/crankdoofus)
- [crobibero](https://github.com/crobibero)
diff --git a/Emby.Dlna/PlayTo/Device.cs b/Emby.Dlna/PlayTo/Device.cs
index 7bf7047fb..abd99bbc3 100644
--- a/Emby.Dlna/PlayTo/Device.cs
+++ b/Emby.Dlna/PlayTo/Device.cs
@@ -219,7 +219,7 @@ namespace Emby.Dlna.PlayTo
{
var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false);
- var command = rendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "SetMute");
+ var command = rendererCommands?.ServiceActions.FirstOrDefault(c => c.Name == "SetMute");
if (command == null)
{
return false;
@@ -259,7 +259,7 @@ namespace Emby.Dlna.PlayTo
{
var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false);
- var command = rendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "SetVolume");
+ var command = rendererCommands?.ServiceActions.FirstOrDefault(c => c.Name == "SetVolume");
if (command == null)
{
return;
@@ -290,7 +290,7 @@ namespace Emby.Dlna.PlayTo
{
var avCommands = await GetAVProtocolAsync(cancellationToken).ConfigureAwait(false);
- var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "Seek");
+ var command = avCommands?.ServiceActions.FirstOrDefault(c => c.Name == "Seek");
if (command == null)
{
return;
@@ -323,7 +323,7 @@ namespace Emby.Dlna.PlayTo
_logger.LogDebug("{0} - SetAvTransport Uri: {1} DlnaHeaders: {2}", Properties.Name, url, header);
- var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "SetAVTransportURI");
+ var command = avCommands?.ServiceActions.FirstOrDefault(c => c.Name == "SetAVTransportURI");
if (command == null)
{
return;
@@ -403,6 +403,10 @@ namespace Emby.Dlna.PlayTo
public async Task SetPlay(CancellationToken cancellationToken)
{
var avCommands = await GetAVProtocolAsync(cancellationToken).ConfigureAwait(false);
+ if (avCommands == null)
+ {
+ return;
+ }
await SetPlay(avCommands, cancellationToken).ConfigureAwait(false);
@@ -413,7 +417,7 @@ namespace Emby.Dlna.PlayTo
{
var avCommands = await GetAVProtocolAsync(cancellationToken).ConfigureAwait(false);
- var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "Stop");
+ var command = avCommands?.ServiceActions.FirstOrDefault(c => c.Name == "Stop");
if (command == null)
{
return;
@@ -437,7 +441,7 @@ namespace Emby.Dlna.PlayTo
{
var avCommands = await GetAVProtocolAsync(cancellationToken).ConfigureAwait(false);
- var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "Pause");
+ var command = avCommands?.ServiceActions.FirstOrDefault(c => c.Name == "Pause");
if (command == null)
{
return;
@@ -565,7 +569,7 @@ namespace Emby.Dlna.PlayTo
var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false);
- var command = rendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetVolume");
+ var command = rendererCommands?.ServiceActions.FirstOrDefault(c => c.Name == "GetVolume");
if (command == null)
{
return;
@@ -615,7 +619,7 @@ namespace Emby.Dlna.PlayTo
var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false);
- var command = rendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetMute");
+ var command = rendererCommands?.ServiceActions.FirstOrDefault(c => c.Name == "GetMute");
if (command == null)
{
return;
@@ -702,6 +706,10 @@ namespace Emby.Dlna.PlayTo
}
var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false);
+ if (rendererCommands == null)
+ {
+ return null;
+ }
var result = await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(
Properties.BaseUrl,
@@ -770,6 +778,11 @@ namespace Emby.Dlna.PlayTo
var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false);
+ if (rendererCommands == null)
+ {
+ return (false, null);
+ }
+
var result = await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(
Properties.BaseUrl,
service,
@@ -951,6 +964,10 @@ namespace Emby.Dlna.PlayTo
var httpClient = new SsdpHttpClient(_httpClientFactory);
var document = await httpClient.GetDataAsync(url, cancellationToken).ConfigureAwait(false);
+ if (document == null)
+ {
+ return null;
+ }
AvCommands = TransportCommands.Create(document);
return AvCommands;
@@ -979,6 +996,10 @@ namespace Emby.Dlna.PlayTo
var httpClient = new SsdpHttpClient(_httpClientFactory);
_logger.LogDebug("Dlna Device.GetRenderingProtocolAsync");
var document = await httpClient.GetDataAsync(url, cancellationToken).ConfigureAwait(false);
+ if (document == null)
+ {
+ return null;
+ }
RendererCommands = TransportCommands.Create(document);
return RendererCommands;
@@ -1010,6 +1031,10 @@ namespace Emby.Dlna.PlayTo
var ssdpHttpClient = new SsdpHttpClient(httpClientFactory);
var document = await ssdpHttpClient.GetDataAsync(url.ToString(), cancellationToken).ConfigureAwait(false);
+ if (document == null)
+ {
+ return null;
+ }
var friendlyNames = new List<string>();
diff --git a/Emby.Dlna/PlayTo/PlayToManager.cs b/Emby.Dlna/PlayTo/PlayToManager.cs
index a6793a708..8272e505a 100644
--- a/Emby.Dlna/PlayTo/PlayToManager.cs
+++ b/Emby.Dlna/PlayTo/PlayToManager.cs
@@ -178,6 +178,11 @@ namespace Emby.Dlna.PlayTo
if (controller == null)
{
var device = await Device.CreateuPnpDeviceAsync(uri, _httpClientFactory, _logger, cancellationToken).ConfigureAwait(false);
+ if (device == null)
+ {
+ _logger.LogError("Ignoring device as xml response is invalid.");
+ return;
+ }
string deviceName = device.Properties.Name;
diff --git a/Emby.Dlna/PlayTo/SsdpHttpClient.cs b/Emby.Dlna/PlayTo/SsdpHttpClient.cs
index b7643fb27..e750f5bbc 100644
--- a/Emby.Dlna/PlayTo/SsdpHttpClient.cs
+++ b/Emby.Dlna/PlayTo/SsdpHttpClient.cs
@@ -94,10 +94,17 @@ namespace Emby.Dlna.PlayTo
options.Headers.TryAddWithoutValidation("FriendlyName.DLNA.ORG", FriendlyName);
using var response = await _httpClientFactory.CreateClient(NamedClient.Default).SendAsync(options, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
- return await XDocument.LoadAsync(
- stream,
- LoadOptions.PreserveWhitespace,
- cancellationToken).ConfigureAwait(false);
+ try
+ {
+ return await XDocument.LoadAsync(
+ stream,
+ LoadOptions.PreserveWhitespace,
+ cancellationToken).ConfigureAwait(false);
+ }
+ catch
+ {
+ return null;
+ }
}
private async Task<HttpResponseMessage> PostSoapDataAsync(
diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs
index 164e6d49d..3846de5fd 100644
--- a/Emby.Server.Implementations/ApplicationHost.cs
+++ b/Emby.Server.Implementations/ApplicationHost.cs
@@ -697,6 +697,8 @@ namespace Emby.Server.Implementations
ServiceCollection.AddScoped<MediaInfoHelper>();
ServiceCollection.AddScoped<AudioHelper>();
ServiceCollection.AddScoped<DynamicHlsHelper>();
+
+ ServiceCollection.AddSingleton<IDirectoryService, DirectoryService>();
}
/// <summary>
diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs
index 46a7feb7f..c18838caf 100644
--- a/Emby.Server.Implementations/Library/LibraryManager.cs
+++ b/Emby.Server.Implementations/Library/LibraryManager.cs
@@ -208,7 +208,7 @@ namespace Emby.Server.Implementations.Library
/// Gets or sets the list of entity resolution ignore rules.
/// </summary>
/// <value>The entity resolution ignore rules.</value>
- private IResolverIgnoreRule[] EntityResolutionIgnoreRules { get; set; }
+ private IResolverIgnoreRule[] EntityResolutionIgnoreRules { get; set; } = Array.Empty<IResolverIgnoreRule>();
/// <summary>
/// Gets or sets the list of currently registered entity resolvers.
diff --git a/Emby.Server.Implementations/Library/PathExtensions.cs b/Emby.Server.Implementations/Library/PathExtensions.cs
index 57d0c26b9..6eaecff0f 100644
--- a/Emby.Server.Implementations/Library/PathExtensions.cs
+++ b/Emby.Server.Implementations/Library/PathExtensions.cs
@@ -4,6 +4,7 @@ using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Text.RegularExpressions;
+using MediaBrowser.Common.Providers;
namespace Emby.Server.Implementations.Library
{
@@ -43,8 +44,8 @@ namespace Emby.Server.Implementations.Library
// for imdbid we also accept pattern matching
if (string.Equals(attribute, "imdbid", StringComparison.OrdinalIgnoreCase))
{
- var m = Regex.Match(str, "tt([0-9]{7,8})", RegexOptions.IgnoreCase);
- return m.Success ? m.Value : null;
+ var match = ProviderIdParsers.TryFindImdbId(str, out var imdbId);
+ return match ? imdbId.ToString() : null;
}
return null;
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs
index c4f173c7a..2af635492 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs
@@ -133,6 +133,11 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
channel.ImageUrl = value;
}
+ if (attributes.TryGetValue("group-title", out string groupTitle))
+ {
+ channel.ChannelGroup = groupTitle;
+ }
+
channel.Name = GetChannelName(extInf, attributes);
channel.Number = GetChannelNumber(extInf, attributes, mediaUrl);
diff --git a/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs b/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs
index c6e931448..a9dab9138 100644
--- a/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs
+++ b/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs
@@ -82,11 +82,6 @@ namespace Emby.Server.Implementations.MediaEncoder
return false;
}
- if (video.VideoType == VideoType.Dvd)
- {
- return false;
- }
-
if (video.IsShortcut)
{
return false;
diff --git a/Jellyfin.Api/Controllers/LibraryController.cs b/Jellyfin.Api/Controllers/LibraryController.cs
index f8e8825ef..1d4bbe61e 100644
--- a/Jellyfin.Api/Controllers/LibraryController.cs
+++ b/Jellyfin.Api/Controllers/LibraryController.cs
@@ -114,7 +114,7 @@ namespace Jellyfin.Api.Controllers
return NotFound();
}
- return PhysicalFile(item.Path, MimeTypes.GetMimeType(item.Path));
+ return PhysicalFile(item.Path, MimeTypes.GetMimeType(item.Path), true);
}
/// <summary>
@@ -666,7 +666,7 @@ namespace Jellyfin.Api.Controllers
}
// TODO determine non-ASCII validity.
- return PhysicalFile(path, MimeTypes.GetMimeType(path), filename);
+ return PhysicalFile(path, MimeTypes.GetMimeType(path), filename, true);
}
/// <summary>
diff --git a/Jellyfin.Api/Controllers/SessionController.cs b/Jellyfin.Api/Controllers/SessionController.cs
index e2269a2ce..0703d4255 100644
--- a/Jellyfin.Api/Controllers/SessionController.cs
+++ b/Jellyfin.Api/Controllers/SessionController.cs
@@ -153,6 +153,10 @@ namespace Jellyfin.Api.Controllers
/// <param name="playCommand">The type of play command to issue (PlayNow, PlayNext, PlayLast). Clients who have not yet implemented play next and play last may play now.</param>
/// <param name="itemIds">The ids of the items to play, comma delimited.</param>
/// <param name="startPositionTicks">The starting position of the first item.</param>
+ /// <param name="mediaSourceId">Optional. The media source id.</param>
+ /// <param name="audioStreamIndex">Optional. The index of the audio stream to play.</param>
+ /// <param name="subtitleStreamIndex">Optional. The index of the subtitle stream to play.</param>
+ /// <param name="startIndex">Optional. The start index.</param>
/// <response code="204">Instruction sent to session.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpPost("Sessions/{sessionId}/Playing")]
@@ -162,13 +166,21 @@ namespace Jellyfin.Api.Controllers
[FromRoute, Required] string sessionId,
[FromQuery, Required] PlayCommand playCommand,
[FromQuery, Required, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] itemIds,
- [FromQuery] long? startPositionTicks)
+ [FromQuery] long? startPositionTicks,
+ [FromQuery] string? mediaSourceId,
+ [FromQuery] int? audioStreamIndex,
+ [FromQuery] int? subtitleStreamIndex,
+ [FromQuery] int? startIndex)
{
var playRequest = new PlayRequest
{
ItemIds = itemIds,
StartPositionTicks = startPositionTicks,
- PlayCommand = playCommand
+ PlayCommand = playCommand,
+ MediaSourceId = mediaSourceId,
+ AudioStreamIndex = audioStreamIndex,
+ SubtitleStreamIndex = subtitleStreamIndex,
+ StartIndex = startIndex
};
_sessionManager.SendPlayCommand(
diff --git a/Jellyfin.Api/Controllers/SubtitleController.cs b/Jellyfin.Api/Controllers/SubtitleController.cs
index 16a47f2d8..1669a659d 100644
--- a/Jellyfin.Api/Controllers/SubtitleController.cs
+++ b/Jellyfin.Api/Controllers/SubtitleController.cs
@@ -182,6 +182,10 @@ namespace Jellyfin.Api.Controllers
/// <summary>
/// Gets subtitles in a specified format.
/// </summary>
+ /// <param name="routeItemId">The (route) item id.</param>
+ /// <param name="routeMediaSourceId">The (route) media source id.</param>
+ /// <param name="routeIndex">The (route) subtitle stream index.</param>
+ /// <param name="routeFormat">The (route) format of the returned subtitle.</param>
/// <param name="itemId">The item id.</param>
/// <param name="mediaSourceId">The media source id.</param>
/// <param name="index">The subtitle stream index.</param>
@@ -189,22 +193,32 @@ namespace Jellyfin.Api.Controllers
/// <param name="endPositionTicks">Optional. The end position of the subtitle in ticks.</param>
/// <param name="copyTimestamps">Optional. Whether to copy the timestamps.</param>
/// <param name="addVttTimeMap">Optional. Whether to add a VTT time map.</param>
- /// <param name="startPositionTicks">Optional. The start position of the subtitle in ticks.</param>
+ /// <param name="startPositionTicks">The start position of the subtitle in ticks.</param>
/// <response code="200">File returned.</response>
/// <returns>A <see cref="FileContentResult"/> with the subtitle file.</returns>
- [HttpGet("Videos/{itemId}/{mediaSourceId}/Subtitles/{index}/Stream.{format}")]
+ [HttpGet("Videos/{routeItemId}/routeMediaSourceId/Subtitles/{routeIndex}/Stream.{routeFormat}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesFile("text/*")]
public async Task<ActionResult> GetSubtitle(
- [FromRoute, Required] Guid itemId,
- [FromRoute, Required] string mediaSourceId,
- [FromRoute, Required] int index,
- [FromRoute, Required] string format,
+ [FromRoute, Required] Guid routeItemId,
+ [FromRoute, Required] string routeMediaSourceId,
+ [FromRoute, Required] int routeIndex,
+ [FromRoute, Required] string routeFormat,
+ [FromQuery, ParameterObsolete] Guid? itemId,
+ [FromQuery, ParameterObsolete] string? mediaSourceId,
+ [FromQuery, ParameterObsolete] int? index,
+ [FromQuery, ParameterObsolete] string? format,
[FromQuery] long? endPositionTicks,
[FromQuery] bool copyTimestamps = false,
[FromQuery] bool addVttTimeMap = false,
[FromQuery] long startPositionTicks = 0)
{
+ // Set parameters to route value if not provided via query.
+ itemId ??= routeItemId;
+ mediaSourceId ??= routeMediaSourceId;
+ index ??= routeIndex;
+ format ??= routeFormat;
+
if (string.Equals(format, "js", StringComparison.OrdinalIgnoreCase))
{
format = "json";
@@ -212,9 +226,9 @@ namespace Jellyfin.Api.Controllers
if (string.IsNullOrEmpty(format))
{
- var item = (Video)_libraryManager.GetItemById(itemId);
+ var item = (Video)_libraryManager.GetItemById(itemId.Value);
- var idString = itemId.ToString("N", CultureInfo.InvariantCulture);
+ var idString = itemId.Value.ToString("N", CultureInfo.InvariantCulture);
var mediaSource = _mediaSourceManager.GetStaticMediaSources(item, false)
.First(i => string.Equals(i.Id, mediaSourceId ?? idString, StringComparison.Ordinal));
@@ -226,7 +240,7 @@ namespace Jellyfin.Api.Controllers
if (string.Equals(format, "vtt", StringComparison.OrdinalIgnoreCase) && addVttTimeMap)
{
- await using Stream stream = await EncodeSubtitles(itemId, mediaSourceId, index, format, startPositionTicks, endPositionTicks, copyTimestamps).ConfigureAwait(false);
+ await using Stream stream = await EncodeSubtitles(itemId.Value, mediaSourceId, index.Value, format, startPositionTicks, endPositionTicks, copyTimestamps).ConfigureAwait(false);
using var reader = new StreamReader(stream);
var text = await reader.ReadToEndAsync().ConfigureAwait(false);
@@ -238,9 +252,9 @@ namespace Jellyfin.Api.Controllers
return File(
await EncodeSubtitles(
- itemId,
+ itemId.Value,
mediaSourceId,
- index,
+ index.Value,
format,
startPositionTicks,
endPositionTicks,
@@ -251,30 +265,44 @@ namespace Jellyfin.Api.Controllers
/// <summary>
/// Gets subtitles in a specified format.
/// </summary>
+ /// <param name="routeItemId">The (route) item id.</param>
+ /// <param name="routeMediaSourceId">The (route) media source id.</param>
+ /// <param name="routeIndex">The (route) subtitle stream index.</param>
+ /// <param name="routeStartPositionTicks">The (route) start position of the subtitle in ticks.</param>
+ /// <param name="routeFormat">The (route) format of the returned subtitle.</param>
/// <param name="itemId">The item id.</param>
/// <param name="mediaSourceId">The media source id.</param>
/// <param name="index">The subtitle stream index.</param>
- /// <param name="startPositionTicks">Optional. The start position of the subtitle in ticks.</param>
+ /// <param name="startPositionTicks">The start position of the subtitle in ticks.</param>
/// <param name="format">The format of the returned subtitle.</param>
/// <param name="endPositionTicks">Optional. The end position of the subtitle in ticks.</param>
/// <param name="copyTimestamps">Optional. Whether to copy the timestamps.</param>
/// <param name="addVttTimeMap">Optional. Whether to add a VTT time map.</param>
/// <response code="200">File returned.</response>
/// <returns>A <see cref="FileContentResult"/> with the subtitle file.</returns>
- [HttpGet("Videos/{itemId}/{mediaSourceId}/Subtitles/{index}/{startPositionTicks}/Stream.{format}")]
+ [HttpGet("Videos/{routeItemId}/{routeMediaSourceId}/Subtitles/{routeIndex}/{routeStartPositionTicks}/Stream.{routeFormat}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesFile("text/*")]
public Task<ActionResult> GetSubtitleWithTicks(
- [FromRoute, Required] Guid itemId,
- [FromRoute, Required] string mediaSourceId,
- [FromRoute, Required] int index,
- [FromRoute, Required] long startPositionTicks,
- [FromRoute, Required] string format,
+ [FromRoute, Required] Guid routeItemId,
+ [FromRoute, Required] string routeMediaSourceId,
+ [FromRoute, Required] int routeIndex,
+ [FromRoute, Required] long routeStartPositionTicks,
+ [FromRoute, Required] string routeFormat,
+ [FromQuery, ParameterObsolete] Guid? itemId,
+ [FromQuery, ParameterObsolete] string? mediaSourceId,
+ [FromQuery, ParameterObsolete] int? index,
+ [FromQuery, ParameterObsolete] long? startPositionTicks,
+ [FromQuery, ParameterObsolete] string? format,
[FromQuery] long? endPositionTicks,
[FromQuery] bool copyTimestamps = false,
[FromQuery] bool addVttTimeMap = false)
{
return GetSubtitle(
+ routeItemId,
+ routeMediaSourceId,
+ routeIndex,
+ routeFormat,
itemId,
mediaSourceId,
index,
@@ -282,7 +310,7 @@ namespace Jellyfin.Api.Controllers
endPositionTicks,
copyTimestamps,
addVttTimeMap,
- startPositionTicks);
+ startPositionTicks ?? routeStartPositionTicks);
}
/// <summary>
diff --git a/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs b/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs
index f828b1d9d..b0fd59e5e 100644
--- a/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs
+++ b/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs
@@ -46,7 +46,8 @@ namespace Jellyfin.Api.Helpers
if (isHeadRequest)
{
- return new FileContentResult(Array.Empty<byte>(), contentType);
+ httpContext.Response.Headers[HeaderNames.ContentType] = contentType;
+ return new OkResult();
}
return new FileStreamResult(await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false), contentType);
@@ -68,10 +69,10 @@ namespace Jellyfin.Api.Helpers
{
httpContext.Response.ContentType = contentType;
- // if the request is a head request, return a NoContent result with the same headers as it would with a GET request
+ // if the request is a head request, return an OkResult (200) with the same headers as it would with a GET request
if (isHeadRequest)
{
- return new NoContentResult();
+ return new OkResult();
}
return new PhysicalFileResult(path, contentType) { EnableRangeProcessing = true };
diff --git a/Jellyfin.Networking/Manager/NetworkManager.cs b/Jellyfin.Networking/Manager/NetworkManager.cs
index d2e9dcf9e..785a058d3 100644
--- a/Jellyfin.Networking/Manager/NetworkManager.cs
+++ b/Jellyfin.Networking/Manager/NetworkManager.cs
@@ -576,6 +576,29 @@ namespace Jellyfin.Networking.Manager
return false;
}
+ /// <inheritdoc/>
+ public bool HasRemoteAccess(IPAddress remoteIp)
+ {
+ var config = _configurationManager.GetNetworkConfiguration();
+ if (config.EnableRemoteAccess)
+ {
+ // Comma separated list of IP addresses or IP/netmask entries for networks that will be allowed to connect remotely.
+ // If left blank, all remote addresses will be allowed.
+ if (RemoteAddressFilter.Count > 0 && !IsInLocalNetwork(remoteIp))
+ {
+ // remoteAddressFilter is a whitelist or blacklist.
+ return RemoteAddressFilter.ContainsAddress(remoteIp) == !config.IsRemoteIPFilterBlacklist;
+ }
+ }
+ else if (!IsInLocalNetwork(remoteIp))
+ {
+ // Remote not enabled. So everyone should be LAN.
+ return false;
+ }
+
+ return true;
+ }
+
/// <summary>
/// Reloads all settings and re-initialises the instance.
/// </summary>
diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs
index 50d7612f2..a2c11cb8a 100644
--- a/Jellyfin.Server.Implementations/Users/UserManager.cs
+++ b/Jellyfin.Server.Implementations/Users/UserManager.cs
@@ -407,27 +407,18 @@ namespace Jellyfin.Server.Implementations.Users
}
var user = Users.FirstOrDefault(i => string.Equals(username, i.Username, StringComparison.OrdinalIgnoreCase));
- bool success;
- IAuthenticationProvider? authenticationProvider;
+ var authResult = await AuthenticateLocalUser(username, password, user, remoteEndPoint)
+ .ConfigureAwait(false);
+ var authenticationProvider = authResult.authenticationProvider;
+ var success = authResult.success;
- if (user != null)
- {
- var authResult = await AuthenticateLocalUser(username, password, user, remoteEndPoint)
- .ConfigureAwait(false);
- authenticationProvider = authResult.authenticationProvider;
- success = authResult.success;
- }
- else
+ if (user == null)
{
- var authResult = await AuthenticateLocalUser(username, password, null, remoteEndPoint)
- .ConfigureAwait(false);
- authenticationProvider = authResult.authenticationProvider;
string updatedUsername = authResult.username;
- success = authResult.success;
if (success
&& authenticationProvider != null
- && !(authenticationProvider is DefaultAuthenticationProvider))
+ && authenticationProvider is not DefaultAuthenticationProvider)
{
// Trust the username returned by the authentication provider
username = updatedUsername;
diff --git a/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj
index 09799307b..b7406d849 100644
--- a/Jellyfin.Server/Jellyfin.Server.csproj
+++ b/Jellyfin.Server/Jellyfin.Server.csproj
@@ -41,7 +41,7 @@
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="5.0.3" />
<PackageReference Include="prometheus-net" Version="4.1.1" />
<PackageReference Include="prometheus-net.AspNetCore" Version="4.1.1" />
- <PackageReference Include="Serilog.AspNetCore" Version="3.4.0" />
+ <PackageReference Include="Serilog.AspNetCore" Version="4.1.0" />
<PackageReference Include="Serilog.Enrichers.Thread" Version="3.1.0" />
<PackageReference Include="Serilog.Settings.Configuration" Version="3.1.0" />
<PackageReference Include="Serilog.Sinks.Async" Version="1.4.0" />
diff --git a/Jellyfin.Server/Middleware/IpBasedAccessValidationMiddleware.cs b/Jellyfin.Server/Middleware/IpBasedAccessValidationMiddleware.cs
index 525cd9ffe..7d92bd7d3 100644
--- a/Jellyfin.Server/Middleware/IpBasedAccessValidationMiddleware.cs
+++ b/Jellyfin.Server/Middleware/IpBasedAccessValidationMiddleware.cs
@@ -29,9 +29,8 @@ namespace Jellyfin.Server.Middleware
/// </summary>
/// <param name="httpContext">The current HTTP context.</param>
/// <param name="networkManager">The network manager.</param>
- /// <param name="serverConfigurationManager">The server configuration manager.</param>
/// <returns>The async task.</returns>
- public async Task Invoke(HttpContext httpContext, INetworkManager networkManager, IServerConfigurationManager serverConfigurationManager)
+ public async Task Invoke(HttpContext httpContext, INetworkManager networkManager)
{
if (httpContext.IsLocal())
{
@@ -42,32 +41,8 @@ namespace Jellyfin.Server.Middleware
var remoteIp = httpContext.Connection.RemoteIpAddress ?? IPAddress.Loopback;
- if (serverConfigurationManager.GetNetworkConfiguration().EnableRemoteAccess)
+ if (!networkManager.HasRemoteAccess(remoteIp))
{
- // Comma separated list of IP addresses or IP/netmask entries for networks that will be allowed to connect remotely.
- // If left blank, all remote addresses will be allowed.
- var remoteAddressFilter = networkManager.RemoteAddressFilter;
-
- if (remoteAddressFilter.Count > 0 && !networkManager.IsInLocalNetwork(remoteIp))
- {
- // remoteAddressFilter is a whitelist or blacklist.
- bool isListed = remoteAddressFilter.ContainsAddress(remoteIp);
- if (!serverConfigurationManager.GetNetworkConfiguration().IsRemoteIPFilterBlacklist)
- {
- // Black list, so flip over.
- isListed = !isListed;
- }
-
- if (!isListed)
- {
- // If your name isn't on the list, you arn't coming in.
- return;
- }
- }
- }
- else if (!networkManager.IsInLocalNetwork(remoteIp))
- {
- // Remote not enabled. So everyone should be LAN.
return;
}
diff --git a/Jellyfin.sln b/Jellyfin.sln
index 0f36e283c..8626a4b1b 100644
--- a/Jellyfin.sln
+++ b/Jellyfin.sln
@@ -1,4 +1,5 @@
-Microsoft Visual Studio Solution File, Format Version 12.00
+
+Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30503.244
MinimumVisualStudioVersion = 10.0.40219.1
@@ -70,11 +71,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Networking", "Jell
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Dlna.Tests", "tests\Jellyfin.Dlna.Tests\Jellyfin.Dlna.Tests.csproj", "{B8AE4B9D-E8D3-4B03-A95E-7FD8CECECC50}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.XbmcMetadata.Tests", "tests\Jellyfin.XbmcMetadata.Tests\Jellyfin.XbmcMetadata.Tests.csproj", "{30922383-D513-4F4D-B890-A940B57FA353}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.XbmcMetadata.Tests", "tests\Jellyfin.XbmcMetadata.Tests\Jellyfin.XbmcMetadata.Tests.csproj", "{30922383-D513-4F4D-B890-A940B57FA353}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Model.Tests", "tests\Jellyfin.Model.Tests\Jellyfin.Model.Tests.csproj", "{FC1BC0CE-E8D2-4AE9-A6AB-8A02143B335D}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Model.Tests", "tests\Jellyfin.Model.Tests\Jellyfin.Model.Tests.csproj", "{FC1BC0CE-E8D2-4AE9-A6AB-8A02143B335D}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Networking.Tests", "tests\Jellyfin.Networking.Tests\Jellyfin.Networking.Tests.csproj", "{42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Networking.Tests", "tests\Jellyfin.Networking.Tests\Jellyfin.Networking.Tests.csproj", "{42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Server.Tests", "tests\Jellyfin.Server.Tests\Jellyfin.Server.Tests.csproj", "{3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9}"
EndProject
@@ -210,6 +211,10 @@ Global
{42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}.Release|Any CPU.Build.0 = Release|Any CPU
+ {25E40B0B-7C89-4230-8911-CBBBCE83FC5B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {25E40B0B-7C89-4230-8911-CBBBCE83FC5B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {25E40B0B-7C89-4230-8911-CBBBCE83FC5B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {25E40B0B-7C89-4230-8911-CBBBCE83FC5B}.Release|Any CPU.Build.0 = Release|Any CPU
{3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9}.Release|Any CPU.ActiveCfg = Release|Any CPU
diff --git a/MediaBrowser.Common/Net/INetworkManager.cs b/MediaBrowser.Common/Net/INetworkManager.cs
index b6c390d23..012824f65 100644
--- a/MediaBrowser.Common/Net/INetworkManager.cs
+++ b/MediaBrowser.Common/Net/INetworkManager.cs
@@ -229,5 +229,12 @@ namespace MediaBrowser.Common.Net
/// <param name="filter">Optional filter for the list.</param>
/// <returns>Returns a filtered list of LAN addresses.</returns>
Collection<IPObject> GetFilteredLANSubnets(Collection<IPObject>? filter = null);
+
+ /// <summary>
+ /// Checks to see if <paramref name="remoteIp"/> has access.
+ /// </summary>
+ /// <param name="remoteIp">IP Address of client.</param>
+ /// <returns><b>True</b> if has access, otherwise <b>false</b>.</returns>
+ bool HasRemoteAccess(IPAddress remoteIp);
}
}
diff --git a/MediaBrowser.Common/Providers/ProviderIdParsers.cs b/MediaBrowser.Common/Providers/ProviderIdParsers.cs
new file mode 100644
index 000000000..64c2e1976
--- /dev/null
+++ b/MediaBrowser.Common/Providers/ProviderIdParsers.cs
@@ -0,0 +1,125 @@
+#nullable enable
+
+using System;
+using System.Diagnostics.CodeAnalysis;
+
+namespace MediaBrowser.Common.Providers
+{
+ /// <summary>
+ /// Parsers for provider ids.
+ /// </summary>
+ public static class ProviderIdParsers
+ {
+ private const int ImdbMinNumbers = 7;
+ private const int ImdbMaxNumbers = 8;
+ private const string ImdbPrefix = "tt";
+
+ /// <summary>
+ /// Parses an IMDb id from a string.
+ /// </summary>
+ /// <param name="text">The text to parse.</param>
+ /// <param name="imdbId">The parsed IMDb id.</param>
+ /// <returns>True if parsing was successful, false otherwise.</returns>
+ public static bool TryFindImdbId(ReadOnlySpan<char> text, [NotNullWhen(true)] out ReadOnlySpan<char> imdbId)
+ {
+ // imdb id is at least 9 chars (tt + 7 numbers)
+ while (text.Length >= 2 + ImdbMinNumbers)
+ {
+ var ttPos = text.IndexOf(ImdbPrefix);
+ if (ttPos == -1)
+ {
+ imdbId = default;
+ return false;
+ }
+
+ text = text.Slice(ttPos);
+ var i = 2;
+ var limit = Math.Min(text.Length, ImdbMaxNumbers + 2);
+ for (; i < limit; i++)
+ {
+ var c = text[i];
+ if (!IsDigit(c))
+ {
+ break;
+ }
+ }
+
+ // skip if more than 8 digits + 2 chars for tt
+ if (i <= ImdbMaxNumbers + 2 && i >= ImdbMinNumbers + 2)
+ {
+ imdbId = text.Slice(0, i);
+ return true;
+ }
+
+ text = text.Slice(i);
+ }
+
+ imdbId = default;
+ return false;
+ }
+
+ /// <summary>
+ /// Parses an TMDb id from a movie url.
+ /// </summary>
+ /// <param name="text">The text with the url to parse.</param>
+ /// <param name="tmdbId">The parsed TMDb id.</param>
+ /// <returns>True if parsing was successful, false otherwise.</returns>
+ public static bool TryFindTmdbMovieId(ReadOnlySpan<char> text, [NotNullWhen(true)] out ReadOnlySpan<char> tmdbId)
+ => TryFindProviderId(text, "themoviedb.org/movie/", out tmdbId);
+
+ /// <summary>
+ /// Parses an TMDb id from a series url.
+ /// </summary>
+ /// <param name="text">The text with the url to parse.</param>
+ /// <param name="tmdbId">The parsed TMDb id.</param>
+ /// <returns>True if parsing was successful, false otherwise.</returns>
+ public static bool TryFindTmdbSeriesId(ReadOnlySpan<char> text, [NotNullWhen(true)] out ReadOnlySpan<char> tmdbId)
+ => TryFindProviderId(text, "themoviedb.org/tv/", out tmdbId);
+
+ /// <summary>
+ /// Parses an TVDb id from a url.
+ /// </summary>
+ /// <param name="text">The text with the url to parse.</param>
+ /// <param name="tvdbId">The parsed TVDb id.</param>
+ /// <returns>True if parsing was successful, false otherwise.</returns>
+ public static bool TryFindTvdbId(ReadOnlySpan<char> text, [NotNullWhen(true)] out ReadOnlySpan<char> tvdbId)
+ => TryFindProviderId(text, "thetvdb.com/?tab=series&id=", out tvdbId);
+
+ private static bool TryFindProviderId(ReadOnlySpan<char> text, ReadOnlySpan<char> searchString, [NotNullWhen(true)] out ReadOnlySpan<char> providerId)
+ {
+ var searchPos = text.IndexOf(searchString);
+ if (searchPos == -1)
+ {
+ providerId = default;
+ return false;
+ }
+
+ text = text.Slice(searchPos + searchString.Length);
+
+ int i = 0;
+ for (; i < text.Length; i++)
+ {
+ var c = text[i];
+
+ if (!IsDigit(c))
+ {
+ break;
+ }
+ }
+
+ if (i >= 1)
+ {
+ providerId = text.Slice(0, i);
+ return true;
+ }
+
+ providerId = default;
+ return false;
+ }
+
+ private static bool IsDigit(char c)
+ {
+ return c >= '0' && c <= '9';
+ }
+ }
+}
diff --git a/MediaBrowser.Controller/LiveTv/ChannelInfo.cs b/MediaBrowser.Controller/LiveTv/ChannelInfo.cs
index 44bd38b54..166c4d77c 100644
--- a/MediaBrowser.Controller/LiveTv/ChannelInfo.cs
+++ b/MediaBrowser.Controller/LiveTv/ChannelInfo.cs
@@ -46,6 +46,12 @@ namespace MediaBrowser.Controller.LiveTv
public ChannelType ChannelType { get; set; }
/// <summary>
+ /// Gets or sets the group of the channel.
+ /// </summary>
+ /// <value>The group of the channel.</value>
+ public string ChannelGroup { get; set; }
+
+ /// <summary>
/// Supply the image path if it can be accessed directly from the file system.
/// </summary>
/// <value>The image path.</value>
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
index 09080e7b2..92b9a8c7e 100644
--- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
+++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
@@ -313,6 +313,12 @@ namespace MediaBrowser.Controller.MediaEncoding
return null;
}
+ // ISO files don't have an ffmpeg format
+ if (string.Equals(container, "iso", StringComparison.OrdinalIgnoreCase))
+ {
+ return null;
+ }
+
return container;
}
diff --git a/MediaBrowser.Controller/Providers/DirectoryService.cs b/MediaBrowser.Controller/Providers/DirectoryService.cs
index 16fd1d42b..5c92069b4 100644
--- a/MediaBrowser.Controller/Providers/DirectoryService.cs
+++ b/MediaBrowser.Controller/Providers/DirectoryService.cs
@@ -12,11 +12,11 @@ namespace MediaBrowser.Controller.Providers
{
private readonly IFileSystem _fileSystem;
- private readonly ConcurrentDictionary<string, FileSystemMetadata[]> _cache = new ConcurrentDictionary<string, FileSystemMetadata[]>(StringComparer.OrdinalIgnoreCase);
+ private readonly ConcurrentDictionary<string, FileSystemMetadata[]> _cache = new (StringComparer.Ordinal);
- private readonly ConcurrentDictionary<string, FileSystemMetadata> _fileCache = new ConcurrentDictionary<string, FileSystemMetadata>(StringComparer.OrdinalIgnoreCase);
+ private readonly ConcurrentDictionary<string, FileSystemMetadata> _fileCache = new (StringComparer.Ordinal);
- private readonly ConcurrentDictionary<string, List<string>> _filePathCache = new ConcurrentDictionary<string, List<string>>(StringComparer.OrdinalIgnoreCase);
+ private readonly ConcurrentDictionary<string, List<string>> _filePathCache = new (StringComparer.Ordinal);
public DirectoryService(IFileSystem fileSystem)
{
diff --git a/MediaBrowser.Controller/Providers/MetadataResult.cs b/MediaBrowser.Controller/Providers/MetadataResult.cs
index 1c695cafa..864cb3050 100644
--- a/MediaBrowser.Controller/Providers/MetadataResult.cs
+++ b/MediaBrowser.Controller/Providers/MetadataResult.cs
@@ -4,21 +4,25 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using MediaBrowser.Controller.Entities;
+using MediaBrowser.Model.Entities;
namespace MediaBrowser.Controller.Providers
{
public class MetadataResult<T>
{
- public List<LocalImageInfo> Images { get; set; }
-
- public List<UserItemData> UserDataList { get; set; }
-
public MetadataResult()
{
Images = new List<LocalImageInfo>();
+ RemoteImages = new List<(string url, ImageType type)>();
ResultLanguage = "en";
}
+ public List<LocalImageInfo> Images { get; set; }
+
+ public List<(string url, ImageType type)> RemoteImages { get; set; }
+
+ public List<UserItemData> UserDataList { get; set; }
+
public List<PersonInfo> People { get; set; }
public bool HasMetadata { get; set; }
diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
index 47cf020b4..205933ae2 100644
--- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
@@ -370,7 +370,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
public string GetInputArgument(string inputFile, MediaSourceInfo mediaSource)
{
var prefix = "file";
- if (mediaSource.VideoType == VideoType.BluRay || mediaSource.VideoType == VideoType.Iso)
+ if (mediaSource.VideoType == VideoType.BluRay)
{
prefix = "bluray";
}
diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs
index bf33691c7..8299059a5 100644
--- a/MediaBrowser.Model/Dlna/StreamBuilder.cs
+++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs
@@ -674,7 +674,7 @@ namespace MediaBrowser.Model.Dlna
var videoStream = item.VideoStream;
- // TODO: This doesn't accout for situation of device being able to handle media bitrate, but wifi connection not fast enough
+ // TODO: This doesn't account for situations where the device is able to handle the media's bitrate, but the connection isn't fast enough
var directPlayEligibilityResult = IsEligibleForDirectPlay(item, GetBitrateForDirectPlayCheck(item, options, true) ?? 0, subtitleStream, options, PlayMethod.DirectPlay);
var directStreamEligibilityResult = IsEligibleForDirectPlay(item, options.GetMaxBitrate(false) ?? 0, subtitleStream, options, PlayMethod.DirectStream);
bool isEligibleForDirectPlay = options.EnableDirectPlay && (options.ForceDirectPlay || directPlayEligibilityResult.Item1);
@@ -1017,14 +1017,15 @@ namespace MediaBrowser.Model.Dlna
}
DeviceProfile profile = options.Profile;
+ string container = mediaSource.Container;
// See if it can be direct played
DirectPlayProfile directPlay = null;
- foreach (var i in profile.DirectPlayProfiles)
+ foreach (var p in profile.DirectPlayProfiles)
{
- if (i.Type == DlnaProfileType.Video && IsVideoDirectPlaySupported(i, mediaSource, videoStream, audioStream))
+ if (p.Type == DlnaProfileType.Video && IsVideoDirectPlaySupported(p, container, videoStream, audioStream))
{
- directPlay = i;
+ directPlay = p;
break;
}
}
@@ -1032,23 +1033,23 @@ namespace MediaBrowser.Model.Dlna
if (directPlay == null)
{
_logger.LogInformation(
- "Profile: {0}, No video direct play profiles found for {1} with codec {2}",
- profile?.Name ?? "Unknown Profile",
- mediaSource?.Path ?? "Unknown path",
- videoStream?.Codec ?? "Unknown codec");
+ "Container: {Container}, Video: {Video}, Audio: {Audio} cannot be direct played by profile: {Profile} for path: {Path}",
+ container,
+ videoStream?.Codec ?? "no video",
+ audioStream?.Codec ?? "no audio",
+ profile.Name ?? "unknown profile",
+ mediaSource.Path ?? "unknown path");
return (null, GetTranscodeReasonsFromDirectPlayProfile(mediaSource, videoStream, audioStream, profile.DirectPlayProfiles));
}
- string container = mediaSource.Container;
-
var conditions = new List<ProfileCondition>();
- foreach (var i in profile.ContainerProfiles)
+ foreach (var p in profile.ContainerProfiles)
{
- if (i.Type == DlnaProfileType.Video
- && i.ContainsContainer(container))
+ if (p.Type == DlnaProfileType.Video
+ && p.ContainsContainer(container))
{
- foreach (var c in i.Conditions)
+ foreach (var c in p.Conditions)
{
conditions.Add(c);
}
@@ -1896,10 +1897,10 @@ namespace MediaBrowser.Model.Dlna
return true;
}
- private bool IsVideoDirectPlaySupported(DirectPlayProfile profile, MediaSourceInfo item, MediaStream videoStream, MediaStream audioStream)
+ private bool IsVideoDirectPlaySupported(DirectPlayProfile profile, string container, MediaStream videoStream, MediaStream audioStream)
{
// Check container type
- if (!profile.SupportsContainer(item.Container))
+ if (!profile.SupportsContainer(container))
{
return false;
}
diff --git a/MediaBrowser.Providers/Manager/ItemImageProvider.cs b/MediaBrowser.Providers/Manager/ItemImageProvider.cs
index ffc6889fa..4471a25b2 100644
--- a/MediaBrowser.Providers/Manager/ItemImageProvider.cs
+++ b/MediaBrowser.Providers/Manager/ItemImageProvider.cs
@@ -469,6 +469,7 @@ namespace MediaBrowser.Providers.Manager
try
{
using var response = await provider.GetImageResponse(url, cancellationToken).ConfigureAwait(false);
+ response.EnsureSuccessStatusCode();
await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
await _providerManager.SaveImage(
diff --git a/MediaBrowser.Providers/Manager/MetadataService.cs b/MediaBrowser.Providers/Manager/MetadataService.cs
index 8b3ca17ca..437b43eca 100644
--- a/MediaBrowser.Providers/Manager/MetadataService.cs
+++ b/MediaBrowser.Providers/Manager/MetadataService.cs
@@ -473,7 +473,7 @@ namespace MediaBrowser.Providers.Manager
if ((originalPremiereDate ?? DateTime.MinValue) != (item.PremiereDate ?? DateTime.MinValue) ||
(originalProductionYear ?? -1) != (item.ProductionYear ?? -1))
{
- updateType = updateType | ItemUpdateType.MetadataEdit;
+ updateType |= ItemUpdateType.MetadataEdit;
}
return updateType;
@@ -493,7 +493,7 @@ namespace MediaBrowser.Providers.Manager
if (currentList.Length != item.Genres.Length || !currentList.OrderBy(i => i).SequenceEqual(item.Genres.OrderBy(i => i), StringComparer.OrdinalIgnoreCase))
{
- updateType = updateType | ItemUpdateType.MetadataEdit;
+ updateType |= ItemUpdateType.MetadataEdit;
}
}
@@ -514,7 +514,7 @@ namespace MediaBrowser.Providers.Manager
if (currentList.Length != item.Studios.Length || !currentList.OrderBy(i => i).SequenceEqual(item.Studios.OrderBy(i => i), StringComparer.OrdinalIgnoreCase))
{
- updateType = updateType | ItemUpdateType.MetadataEdit;
+ updateType |= ItemUpdateType.MetadataEdit;
}
}
@@ -529,7 +529,7 @@ namespace MediaBrowser.Providers.Manager
{
if (item.UpdateRatingToItems(children))
{
- updateType = updateType | ItemUpdateType.MetadataEdit;
+ updateType |= ItemUpdateType.MetadataEdit;
}
}
@@ -686,7 +686,7 @@ namespace MediaBrowser.Providers.Manager
var remoteResult = await ExecuteRemoteProviders(temp, logName, id, providers.OfType<IRemoteMetadataProvider<TItemType, TIdType>>(), cancellationToken)
.ConfigureAwait(false);
- refreshResult.UpdateType = refreshResult.UpdateType | remoteResult.UpdateType;
+ refreshResult.UpdateType |= remoteResult.UpdateType;
refreshResult.ErrorMessage = remoteResult.ErrorMessage;
refreshResult.Failures += remoteResult.Failures;
}
@@ -706,9 +706,15 @@ namespace MediaBrowser.Providers.Manager
if (localItem.HasMetadata)
{
+ foreach (var remoteImage in localItem.RemoteImages)
+ {
+ await ProviderManager.SaveImage(item, remoteImage.url, remoteImage.type, null, cancellationToken).ConfigureAwait(false);
+ refreshResult.UpdateType |= ItemUpdateType.ImageUpdate;
+ }
+
if (imageService.MergeImages(item, localItem.Images))
{
- refreshResult.UpdateType = refreshResult.UpdateType | ItemUpdateType.ImageUpdate;
+ refreshResult.UpdateType |= ItemUpdateType.ImageUpdate;
}
if (localItem.UserDataList != null)
@@ -717,7 +723,7 @@ namespace MediaBrowser.Providers.Manager
}
MergeData(localItem, temp, Array.Empty<MetadataField>(), !options.ReplaceAllMetadata, true);
- refreshResult.UpdateType = refreshResult.UpdateType | ItemUpdateType.MetadataImport;
+ refreshResult.UpdateType |= ItemUpdateType.MetadataImport;
// Only one local provider allowed per item
if (item.IsLocked || localItem.Item.IsLocked || IsFullLocalMetadata(localItem.Item))
@@ -749,7 +755,7 @@ namespace MediaBrowser.Providers.Manager
var remoteResult = await ExecuteRemoteProviders(temp, logName, id, providers.OfType<IRemoteMetadataProvider<TItemType, TIdType>>(), cancellationToken)
.ConfigureAwait(false);
- refreshResult.UpdateType = refreshResult.UpdateType | remoteResult.UpdateType;
+ refreshResult.UpdateType |= remoteResult.UpdateType;
refreshResult.ErrorMessage = remoteResult.ErrorMessage;
refreshResult.Failures += remoteResult.Failures;
}
@@ -845,7 +851,7 @@ namespace MediaBrowser.Providers.Manager
MergeData(result, temp, Array.Empty<MetadataField>(), false, false);
MergeNewData(temp.Item, id);
- refreshResult.UpdateType = refreshResult.UpdateType | ItemUpdateType.MetadataDownload;
+ refreshResult.UpdateType |= ItemUpdateType.MetadataDownload;
}
else
{
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs
index 4963777bc..833d1ae38 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs
@@ -175,6 +175,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
var movie = new Movie
{
Name = movieResult.Title ?? movieResult.OriginalTitle,
+ OriginalTitle = movieResult.OriginalTitle,
Overview = movieResult.Overview?.Replace("\n\n", "\n", StringComparison.InvariantCulture),
Tagline = movieResult.Tagline,
ProductionLocations = movieResult.ProductionCountries.Select(pc => pc.Name).ToArray()
diff --git a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs
index c4bbaf301..ff9f11eab 100644
--- a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs
+++ b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs
@@ -6,11 +6,12 @@ using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
-using System.Text.RegularExpressions;
using System.Threading;
using System.Xml;
using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.Providers;
using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
@@ -27,6 +28,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers
private readonly IConfigurationManager _config;
private readonly IUserManager _userManager;
private readonly IUserDataManager _userDataManager;
+ private readonly IDirectoryService _directoryService;
private Dictionary<string, string> _validProviderIds;
/// <summary>
@@ -37,12 +39,14 @@ namespace MediaBrowser.XbmcMetadata.Parsers
/// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param>
/// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
/// <param name="userDataManager">Instance of the <see cref="IUserDataManager"/> interface.</param>
+ /// <param name="directoryService">Instance of the <see cref="IDirectoryService"/> interface.</param>
public BaseNfoParser(
ILogger logger,
IConfigurationManager config,
IProviderManager providerManager,
IUserManager userManager,
- IUserDataManager userDataManager)
+ IUserDataManager userDataManager,
+ IDirectoryService directoryService)
{
Logger = logger;
_config = config;
@@ -50,6 +54,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers
_validProviderIds = new Dictionary<string, string>();
_userManager = userManager;
_userDataManager = userDataManager;
+ _directoryService = directoryService;
}
protected CultureInfo UsCulture { get; } = new CultureInfo("en-US");
@@ -63,8 +68,6 @@ namespace MediaBrowser.XbmcMetadata.Parsers
protected virtual bool SupportsUrlAfterClosingXmlTag => false;
- protected virtual string MovieDbParserSearchString => "themoviedb.org/movie/";
-
/// <summary>
/// Fetches metadata for an item from one xml file.
/// </summary>
@@ -181,8 +184,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers
}
else
{
- // If the file is just an Imdb url, handle that
-
+ // If the file is just provider urls, handle that
ParseProviderLinks(item.Item, xml);
return;
@@ -221,50 +223,29 @@ namespace MediaBrowser.XbmcMetadata.Parsers
protected void ParseProviderLinks(T item, string xml)
{
- // Look for a match for the Regex pattern "tt" followed by 7 or 8 digits
- var m = Regex.Match(xml, "tt([0-9]{7,8})", RegexOptions.IgnoreCase);
- if (m.Success)
+ if (ProviderIdParsers.TryFindImdbId(xml, out var imdbId))
{
- item.SetProviderId(MetadataProvider.Imdb, m.Value);
+ item.SetProviderId(MetadataProvider.Imdb, imdbId.ToString());
}
- // Support Tmdb
- // https://www.themoviedb.org/movie/30287-fallo
- var srch = MovieDbParserSearchString;
- var index = xml.IndexOf(srch, StringComparison.OrdinalIgnoreCase);
-
- if (index != -1)
+ if (item is Movie)
{
- var tmdbId = xml.AsSpan().Slice(index + srch.Length).TrimEnd('/');
- index = tmdbId.IndexOf('-');
- if (index != -1)
- {
- tmdbId = tmdbId.Slice(0, index);
- }
-
- if (!tmdbId.IsEmpty
- && !tmdbId.IsWhiteSpace()
- && int.TryParse(tmdbId, NumberStyles.Integer, CultureInfo.InvariantCulture, out var value))
+ if (ProviderIdParsers.TryFindTmdbMovieId(xml, out var tmdbId))
{
- item.SetProviderId(MetadataProvider.Tmdb, value.ToString(UsCulture));
+ item.SetProviderId(MetadataProvider.Tmdb, tmdbId.ToString());
}
}
if (item is Series)
{
- srch = "thetvdb.com/?tab=series&id=";
-
- index = xml.IndexOf(srch, StringComparison.OrdinalIgnoreCase);
+ if (ProviderIdParsers.TryFindTmdbSeriesId(xml, out var tmdbId))
+ {
+ item.SetProviderId(MetadataProvider.Tmdb, tmdbId.ToString());
+ }
- if (index != -1)
+ if (ProviderIdParsers.TryFindTvdbId(xml, out var tvdbId))
{
- var tvdbId = xml.AsSpan().Slice(index + srch.Length).TrimEnd('/');
- if (!tvdbId.IsEmpty
- && !tvdbId.IsWhiteSpace()
- && int.TryParse(tvdbId, NumberStyles.Integer, CultureInfo.InvariantCulture, out var value))
- {
- item.SetProviderId(MetadataProvider.Tvdb, value.ToString(UsCulture));
- }
+ item.SetProviderId(MetadataProvider.Tvdb, tvdbId.ToString());
}
}
}
@@ -785,6 +766,64 @@ namespace MediaBrowser.XbmcMetadata.Parsers
break;
}
+ case "thumb":
+ {
+ var artType = reader.GetAttribute("aspect");
+ var val = reader.ReadElementContentAsString();
+
+ // skip:
+ // - empty aspect tag
+ // - empty uri
+ // - tag containing '.' because we can't set images for seasons, episodes or movie sets within series or movies
+ if (string.IsNullOrEmpty(artType) || string.IsNullOrEmpty(val) || artType.Contains('.', StringComparison.Ordinal))
+ {
+ break;
+ }
+
+ ImageType imageType = GetImageType(artType);
+
+ if (!Uri.TryCreate(val, UriKind.Absolute, out var uri))
+ {
+ Logger.LogError("Image location {Path} specified in nfo file for {ItemName} is not a valid URL or file path.", val, item.Name);
+ break;
+ }
+
+ if (uri.IsFile)
+ {
+ // only allow one item of each type
+ if (itemResult.Images.Any(x => x.Type == imageType))
+ {
+ break;
+ }
+
+ var fileSystemMetadata = _directoryService.GetFile(val);
+ // non existing file returns null
+ if (fileSystemMetadata == null || !fileSystemMetadata.Exists)
+ {
+ Logger.LogWarning("Artwork file {Path} specified in nfo file for {ItemName} does not exist.", uri, item.Name);
+ break;
+ }
+
+ itemResult.Images.Add(new LocalImageInfo()
+ {
+ FileInfo = fileSystemMetadata,
+ Type = imageType
+ });
+ }
+ else
+ {
+ // only allow one item of each type
+ if (itemResult.RemoteImages.Any(x => x.type == imageType))
+ {
+ break;
+ }
+
+ itemResult.RemoteImages.Add((uri.ToString(), imageType));
+ }
+
+ break;
+ }
+
default:
string readerName = reader.Name;
if (_validProviderIds.TryGetValue(readerName, out string? providerIdValue))
@@ -1178,5 +1217,24 @@ namespace MediaBrowser.XbmcMetadata.Parsers
return string.IsNullOrWhiteSpace(value) ? Array.Empty<string>() : value.Split(separator, StringSplitOptions.RemoveEmptyEntries);
}
+
+ /// <summary>
+ /// Parses the ImageType from the nfo aspect property.
+ /// </summary>
+ /// <param name="aspect">The nfo aspect property.</param>
+ /// <returns>The image type.</returns>
+ private static ImageType GetImageType(string aspect)
+ {
+ return aspect switch
+ {
+ "banner" => ImageType.Banner,
+ "clearlogo" => ImageType.Logo,
+ "discart" => ImageType.Disc,
+ "landscape" => ImageType.Thumb,
+ "clearart" => ImageType.Art,
+ // unknown type (including "poster") --> primary
+ _ => ImageType.Primary,
+ };
+ }
}
}
diff --git a/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs
index eb93148c6..6b1607530 100644
--- a/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs
+++ b/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs
@@ -25,13 +25,15 @@ namespace MediaBrowser.XbmcMetadata.Parsers
/// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param>
/// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
/// <param name="userDataManager">Instance of the <see cref="IUserDataManager"/> interface.</param>
+ /// <param name="directoryService">Instance of the <see cref="IDirectoryService"/> interface.</param>
public EpisodeNfoParser(
ILogger logger,
IConfigurationManager config,
IProviderManager providerManager,
IUserManager userManager,
- IUserDataManager userDataManager)
- : base(logger, config, providerManager, userManager, userDataManager)
+ IUserDataManager userDataManager,
+ IDirectoryService directoryService)
+ : base(logger, config, providerManager, userManager, userDataManager, directoryService)
{
}
diff --git a/MediaBrowser.XbmcMetadata/Parsers/MovieNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/MovieNfoParser.cs
index 2d0eb8433..e51055725 100644
--- a/MediaBrowser.XbmcMetadata/Parsers/MovieNfoParser.cs
+++ b/MediaBrowser.XbmcMetadata/Parsers/MovieNfoParser.cs
@@ -25,13 +25,15 @@ namespace MediaBrowser.XbmcMetadata.Parsers
/// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param>
/// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
/// <param name="userDataManager">Instance of the <see cref="IUserDataManager"/> interface.</param>
+ /// <param name="directoryService">Instance of the <see cref="DirectoryService"/> interface.</param>
public MovieNfoParser(
ILogger logger,
IConfigurationManager config,
IProviderManager providerManager,
IUserManager userManager,
- IUserDataManager userDataManager)
- : base(logger, config, providerManager, userManager, userDataManager)
+ IUserDataManager userDataManager,
+ IDirectoryService directoryService)
+ : base(logger, config, providerManager, userManager, userDataManager, directoryService)
{
}
@@ -47,12 +49,19 @@ namespace MediaBrowser.XbmcMetadata.Parsers
{
case "id":
{
+ // get ids from attributes
string? imdbId = reader.GetAttribute("IMDB");
string? tmdbId = reader.GetAttribute("TMDB");
- if (string.IsNullOrWhiteSpace(imdbId))
+ // read id from content
+ var contentId = reader.ReadElementContentAsString();
+ if (contentId.Contains("tt", StringComparison.Ordinal) && string.IsNullOrEmpty(imdbId))
{
- imdbId = reader.ReadElementContentAsString();
+ imdbId = contentId;
+ }
+ else if (string.IsNullOrEmpty(tmdbId))
+ {
+ tmdbId = contentId;
}
if (!string.IsNullOrWhiteSpace(imdbId))
diff --git a/MediaBrowser.XbmcMetadata/Parsers/SeasonNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/SeasonNfoParser.cs
index bd2607bd8..2f5fd40e2 100644
--- a/MediaBrowser.XbmcMetadata/Parsers/SeasonNfoParser.cs
+++ b/MediaBrowser.XbmcMetadata/Parsers/SeasonNfoParser.cs
@@ -21,13 +21,15 @@ namespace MediaBrowser.XbmcMetadata.Parsers
/// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param>
/// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
/// <param name="userDataManager">Instance of the <see cref="IUserDataManager"/> interface.</param>
+ /// <param name="directoryService">Instance of the <see cref="DirectoryService"/> interface.</param>
public SeasonNfoParser(
ILogger logger,
IConfigurationManager config,
IProviderManager providerManager,
IUserManager userManager,
- IUserDataManager userDataManager)
- : base(logger, config, providerManager, userManager, userDataManager)
+ IUserDataManager userDataManager,
+ IDirectoryService directoryService)
+ : base(logger, config, providerManager, userManager, userDataManager, directoryService)
{
}
diff --git a/MediaBrowser.XbmcMetadata/Parsers/SeriesNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/SeriesNfoParser.cs
index fbab8b521..2c893ac9f 100644
--- a/MediaBrowser.XbmcMetadata/Parsers/SeriesNfoParser.cs
+++ b/MediaBrowser.XbmcMetadata/Parsers/SeriesNfoParser.cs
@@ -22,13 +22,15 @@ namespace MediaBrowser.XbmcMetadata.Parsers
/// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param>
/// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
/// <param name="userDataManager">Instance of the <see cref="IUserDataManager"/> interface.</param>
+ /// <param name="directoryService">Instance of the <see cref="IDirectoryService"/> interface.</param>
public SeriesNfoParser(
ILogger logger,
IConfigurationManager config,
IProviderManager providerManager,
IUserManager userManager,
- IUserDataManager userDataManager)
- : base(logger, config, providerManager, userManager, userDataManager)
+ IUserDataManager userDataManager,
+ IDirectoryService directoryService)
+ : base(logger, config, providerManager, userManager, userDataManager, directoryService)
{
}
@@ -36,9 +38,6 @@ namespace MediaBrowser.XbmcMetadata.Parsers
protected override bool SupportsUrlAfterClosingXmlTag => true;
/// <inheritdoc />
- protected override string MovieDbParserSearchString => "themoviedb.org/tv/";
-
- /// <inheritdoc />
protected override void FetchDataFromXmlNode(XmlReader reader, MetadataResult<Series> itemResult)
{
var item = itemResult.Item;
diff --git a/MediaBrowser.XbmcMetadata/Providers/AlbumNfoProvider.cs b/MediaBrowser.XbmcMetadata/Providers/AlbumNfoProvider.cs
index 24f127411..bd557d783 100644
--- a/MediaBrowser.XbmcMetadata/Providers/AlbumNfoProvider.cs
+++ b/MediaBrowser.XbmcMetadata/Providers/AlbumNfoProvider.cs
@@ -20,6 +20,7 @@ namespace MediaBrowser.XbmcMetadata.Providers
private readonly IProviderManager _providerManager;
private readonly IUserManager _userManager;
private readonly IUserDataManager _userDataManager;
+ private readonly IDirectoryService _directoryService;
/// <summary>
/// Initializes a new instance of the <see cref="AlbumNfoProvider"/> class.
@@ -30,13 +31,15 @@ namespace MediaBrowser.XbmcMetadata.Providers
/// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param>
/// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
/// <param name="userDataManager">Instance of the <see cref="IUserDataManager"/> interface.</param>
+ /// <param name="directoryService">Instance of the <see cref="IDirectoryService"/> interface.</param>
public AlbumNfoProvider(
ILogger<AlbumNfoProvider> logger,
IFileSystem fileSystem,
IConfigurationManager config,
IProviderManager providerManager,
IUserManager userManager,
- IUserDataManager userDataManager)
+ IUserDataManager userDataManager,
+ IDirectoryService directoryService)
: base(fileSystem)
{
_logger = logger;
@@ -44,12 +47,13 @@ namespace MediaBrowser.XbmcMetadata.Providers
_providerManager = providerManager;
_userManager = userManager;
_userDataManager = userDataManager;
+ _directoryService = directoryService;
}
/// <inheritdoc />
protected override void Fetch(MetadataResult<MusicAlbum> result, string path, CancellationToken cancellationToken)
{
- new BaseNfoParser<MusicAlbum>(_logger, _config, _providerManager, _userManager, _userDataManager).Fetch(result, path, cancellationToken);
+ new BaseNfoParser<MusicAlbum>(_logger, _config, _providerManager, _userManager, _userDataManager, _directoryService).Fetch(result, path, cancellationToken);
}
/// <inheritdoc />
diff --git a/MediaBrowser.XbmcMetadata/Providers/ArtistNfoProvider.cs b/MediaBrowser.XbmcMetadata/Providers/ArtistNfoProvider.cs
index fac28ab59..54bb83114 100644
--- a/MediaBrowser.XbmcMetadata/Providers/ArtistNfoProvider.cs
+++ b/MediaBrowser.XbmcMetadata/Providers/ArtistNfoProvider.cs
@@ -20,6 +20,7 @@ namespace MediaBrowser.XbmcMetadata.Providers
private readonly IProviderManager _providerManager;
private readonly IUserManager _userManager;
private readonly IUserDataManager _userDataManager;
+ private readonly IDirectoryService _directoryService;
/// <summary>
/// Initializes a new instance of the <see cref="ArtistNfoProvider"/> class.
@@ -30,13 +31,15 @@ namespace MediaBrowser.XbmcMetadata.Providers
/// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param>
/// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
/// <param name="userDataManager">Instance of the <see cref="IUserDataManager"/> interface.</param>
+ /// <param name="directoryService">Instance of the <see cref="IDirectoryService"/> interface.</param>
public ArtistNfoProvider(
IFileSystem fileSystem,
ILogger<ArtistNfoProvider> logger,
IConfigurationManager config,
IProviderManager providerManager,
IUserManager userManager,
- IUserDataManager userDataManager)
+ IUserDataManager userDataManager,
+ IDirectoryService directoryService)
: base(fileSystem)
{
_logger = logger;
@@ -44,12 +47,13 @@ namespace MediaBrowser.XbmcMetadata.Providers
_providerManager = providerManager;
_userManager = userManager;
_userDataManager = userDataManager;
+ _directoryService = directoryService;
}
/// <inheritdoc />
protected override void Fetch(MetadataResult<MusicArtist> result, string path, CancellationToken cancellationToken)
{
- new BaseNfoParser<MusicArtist>(_logger, _config, _providerManager, _userManager, _userDataManager).Fetch(result, path, cancellationToken);
+ new BaseNfoParser<MusicArtist>(_logger, _config, _providerManager, _userManager, _userDataManager, _directoryService).Fetch(result, path, cancellationToken);
}
/// <inheritdoc />
diff --git a/MediaBrowser.XbmcMetadata/Providers/BaseVideoNfoProvider.cs b/MediaBrowser.XbmcMetadata/Providers/BaseVideoNfoProvider.cs
index 64cfc098f..8574be3f3 100644
--- a/MediaBrowser.XbmcMetadata/Providers/BaseVideoNfoProvider.cs
+++ b/MediaBrowser.XbmcMetadata/Providers/BaseVideoNfoProvider.cs
@@ -21,6 +21,7 @@ namespace MediaBrowser.XbmcMetadata.Providers
private readonly IProviderManager _providerManager;
private readonly IUserManager _userManager;
private readonly IUserDataManager _userDataManager;
+ private readonly IDirectoryService _directoryService;
protected BaseVideoNfoProvider(
ILogger<BaseVideoNfoProvider<T>> logger,
@@ -28,7 +29,8 @@ namespace MediaBrowser.XbmcMetadata.Providers
IConfigurationManager config,
IProviderManager providerManager,
IUserManager userManager,
- IUserDataManager userDataManager)
+ IUserDataManager userDataManager,
+ IDirectoryService directoryService)
: base(fileSystem)
{
_logger = logger;
@@ -36,6 +38,7 @@ namespace MediaBrowser.XbmcMetadata.Providers
_providerManager = providerManager;
_userManager = userManager;
_userDataManager = userDataManager;
+ _directoryService = directoryService;
}
/// <inheritdoc />
@@ -45,10 +48,12 @@ namespace MediaBrowser.XbmcMetadata.Providers
{
Item = result.Item
};
- new MovieNfoParser(_logger, _config, _providerManager, _userManager, _userDataManager).Fetch(tmpItem, path, cancellationToken);
+ new MovieNfoParser(_logger, _config, _providerManager, _userManager, _userDataManager, _directoryService).Fetch(tmpItem, path, cancellationToken);
result.Item = (T)tmpItem.Item;
result.People = tmpItem.People;
+ result.Images = tmpItem.Images;
+ result.RemoteImages = tmpItem.RemoteImages;
if (tmpItem.UserDataList != null)
{
diff --git a/MediaBrowser.XbmcMetadata/Providers/EpisodeNfoProvider.cs b/MediaBrowser.XbmcMetadata/Providers/EpisodeNfoProvider.cs
index 7233f99dc..64b208345 100644
--- a/MediaBrowser.XbmcMetadata/Providers/EpisodeNfoProvider.cs
+++ b/MediaBrowser.XbmcMetadata/Providers/EpisodeNfoProvider.cs
@@ -20,6 +20,7 @@ namespace MediaBrowser.XbmcMetadata.Providers
private readonly IProviderManager _providerManager;
private readonly IUserManager _userManager;
private readonly IUserDataManager _userDataManager;
+ private readonly IDirectoryService _directoryService;
/// <summary>
/// Initializes a new instance of the <see cref="EpisodeNfoProvider"/> class.
@@ -30,13 +31,15 @@ namespace MediaBrowser.XbmcMetadata.Providers
/// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param>
/// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
/// <param name="userDataManager">Instance of the <see cref="IUserDataManager"/> interface.</param>
+ /// <param name="directoryService">Instance of the <see cref="IDirectoryService"/> interface.</param>
public EpisodeNfoProvider(
ILogger<EpisodeNfoProvider> logger,
IFileSystem fileSystem,
IConfigurationManager config,
IProviderManager providerManager,
IUserManager userManager,
- IUserDataManager userDataManager)
+ IUserDataManager userDataManager,
+ IDirectoryService directoryService)
: base(fileSystem)
{
_logger = logger;
@@ -44,12 +47,13 @@ namespace MediaBrowser.XbmcMetadata.Providers
_providerManager = providerManager;
_userManager = userManager;
_userDataManager = userDataManager;
+ _directoryService = directoryService;
}
/// <inheritdoc />
protected override void Fetch(MetadataResult<Episode> result, string path, CancellationToken cancellationToken)
{
- new EpisodeNfoParser(_logger, _config, _providerManager, _userManager, _userDataManager).Fetch(result, path, cancellationToken);
+ new EpisodeNfoParser(_logger, _config, _providerManager, _userManager, _userDataManager, _directoryService).Fetch(result, path, cancellationToken);
}
/// <inheritdoc />
diff --git a/MediaBrowser.XbmcMetadata/Providers/MovieNfoProvider.cs b/MediaBrowser.XbmcMetadata/Providers/MovieNfoProvider.cs
index 811d39a9d..cdbc5a918 100644
--- a/MediaBrowser.XbmcMetadata/Providers/MovieNfoProvider.cs
+++ b/MediaBrowser.XbmcMetadata/Providers/MovieNfoProvider.cs
@@ -21,14 +21,16 @@ namespace MediaBrowser.XbmcMetadata.Providers
/// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param>
/// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
/// <param name="userDataManager">Instance of the <see cref="IUserDataManager"/> interface.</param>
+ /// <param name="directoryService">Instance of the <see cref="IDirectoryService"/> interface.</param>
public MovieNfoProvider(
ILogger<MovieNfoProvider> logger,
IFileSystem fileSystem,
IConfigurationManager config,
IProviderManager providerManager,
IUserManager userManager,
- IUserDataManager userDataManager)
- : base(logger, fileSystem, config, providerManager, userManager, userDataManager)
+ IUserDataManager userDataManager,
+ IDirectoryService directoryService)
+ : base(logger, fileSystem, config, providerManager, userManager, userDataManager, directoryService)
{
}
}
diff --git a/MediaBrowser.XbmcMetadata/Providers/MusicVideoNfoProvider.cs b/MediaBrowser.XbmcMetadata/Providers/MusicVideoNfoProvider.cs
index 09df509ee..9d1f3e61d 100644
--- a/MediaBrowser.XbmcMetadata/Providers/MusicVideoNfoProvider.cs
+++ b/MediaBrowser.XbmcMetadata/Providers/MusicVideoNfoProvider.cs
@@ -21,14 +21,16 @@ namespace MediaBrowser.XbmcMetadata.Providers
/// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param>
/// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
/// <param name="userDataManager">Instance of the <see cref="IUserDataManager"/> interface.</param>
+ /// <param name="directoryService">Instance of the <see cref="IDirectoryService"/> interface.</param>
public MusicVideoNfoProvider(
ILogger<MusicVideoNfoProvider> logger,
IFileSystem fileSystem,
IConfigurationManager config,
IProviderManager providerManager,
IUserManager userManager,
- IUserDataManager userDataManager)
- : base(logger, fileSystem, config, providerManager, userManager, userDataManager)
+ IUserDataManager userDataManager,
+ IDirectoryService directoryService)
+ : base(logger, fileSystem, config, providerManager, userManager, userDataManager, directoryService)
{
}
}
diff --git a/MediaBrowser.XbmcMetadata/Providers/SeasonNfoProvider.cs b/MediaBrowser.XbmcMetadata/Providers/SeasonNfoProvider.cs
index 8f0ed6df7..97220cf7e 100644
--- a/MediaBrowser.XbmcMetadata/Providers/SeasonNfoProvider.cs
+++ b/MediaBrowser.XbmcMetadata/Providers/SeasonNfoProvider.cs
@@ -20,6 +20,7 @@ namespace MediaBrowser.XbmcMetadata.Providers
private readonly IProviderManager _providerManager;
private readonly IUserManager _userManager;
private readonly IUserDataManager _userDataManager;
+ private readonly IDirectoryService _directoryService;
/// <summary>
/// Initializes a new instance of the <see cref="SeasonNfoProvider"/> class.
@@ -30,13 +31,15 @@ namespace MediaBrowser.XbmcMetadata.Providers
/// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param>
/// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
/// <param name="userDataManager">Instance of the <see cref="IUserDataManager"/> interface.</param>
+ /// <param name="directoryService">Instance of the <see cref="IDirectoryService"/> interface.</param>
public SeasonNfoProvider(
ILogger<SeasonNfoProvider> logger,
IFileSystem fileSystem,
IConfigurationManager config,
IProviderManager providerManager,
IUserManager userManager,
- IUserDataManager userDataManager)
+ IUserDataManager userDataManager,
+ IDirectoryService directoryService)
: base(fileSystem)
{
_logger = logger;
@@ -44,12 +47,13 @@ namespace MediaBrowser.XbmcMetadata.Providers
_providerManager = providerManager;
_userManager = userManager;
_userDataManager = userDataManager;
+ _directoryService = directoryService;
}
/// <inheritdoc />
protected override void Fetch(MetadataResult<Season> result, string path, CancellationToken cancellationToken)
{
- new SeasonNfoParser(_logger, _config, _providerManager, _userManager, _userDataManager).Fetch(result, path, cancellationToken);
+ new SeasonNfoParser(_logger, _config, _providerManager, _userManager, _userDataManager, _directoryService).Fetch(result, path, cancellationToken);
}
/// <inheritdoc />
diff --git a/MediaBrowser.XbmcMetadata/Providers/SeriesNfoProvider.cs b/MediaBrowser.XbmcMetadata/Providers/SeriesNfoProvider.cs
index 3e496dc58..9a9b94123 100644
--- a/MediaBrowser.XbmcMetadata/Providers/SeriesNfoProvider.cs
+++ b/MediaBrowser.XbmcMetadata/Providers/SeriesNfoProvider.cs
@@ -20,6 +20,7 @@ namespace MediaBrowser.XbmcMetadata.Providers
private readonly IProviderManager _providerManager;
private readonly IUserManager _userManager;
private readonly IUserDataManager _userDataManager;
+ private readonly IDirectoryService _directoryService;
/// <summary>
/// Initializes a new instance of the <see cref="SeriesNfoProvider"/> class.
@@ -30,13 +31,15 @@ namespace MediaBrowser.XbmcMetadata.Providers
/// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param>
/// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
/// <param name="userDataManager">Instance of the <see cref="IUserDataManager"/> interface.</param>
+ /// <param name="directoryService">Instance of the <see cref="IDirectoryService"/> interface.</param>
public SeriesNfoProvider(
ILogger<SeriesNfoProvider> logger,
IFileSystem fileSystem,
IConfigurationManager config,
IProviderManager providerManager,
IUserManager userManager,
- IUserDataManager userDataManager)
+ IUserDataManager userDataManager,
+ IDirectoryService directoryService)
: base(fileSystem)
{
_logger = logger;
@@ -44,12 +47,13 @@ namespace MediaBrowser.XbmcMetadata.Providers
_providerManager = providerManager;
_userManager = userManager;
_userDataManager = userDataManager;
+ _directoryService = directoryService;
}
/// <inheritdoc />
protected override void Fetch(MetadataResult<Series> result, string path, CancellationToken cancellationToken)
{
- new SeriesNfoParser(_logger, _config, _providerManager, _userManager, _userDataManager).Fetch(result, path, cancellationToken);
+ new SeriesNfoParser(_logger, _config, _providerManager, _userManager, _userDataManager, _directoryService).Fetch(result, path, cancellationToken);
}
/// <inheritdoc />
diff --git a/MediaBrowser.XbmcMetadata/Providers/VideoNfoProvider.cs b/MediaBrowser.XbmcMetadata/Providers/VideoNfoProvider.cs
index 4717d81e6..93b1be62f 100644
--- a/MediaBrowser.XbmcMetadata/Providers/VideoNfoProvider.cs
+++ b/MediaBrowser.XbmcMetadata/Providers/VideoNfoProvider.cs
@@ -21,14 +21,16 @@ namespace MediaBrowser.XbmcMetadata.Providers
/// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param>
/// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
/// <param name="userDataManager">Instance of the <see cref="IUserDataManager"/> interface.</param>
+ /// <param name="directoryService">Instance of the <see cref="IDirectoryService"/> interface.</param>
public VideoNfoProvider(
ILogger<VideoNfoProvider> logger,
IFileSystem fileSystem,
IConfigurationManager config,
IProviderManager providerManager,
IUserManager userManager,
- IUserDataManager userDataManager)
- : base(logger, fileSystem, config, providerManager, userManager, userDataManager)
+ IUserDataManager userDataManager,
+ IDirectoryService directoryService)
+ : base(logger, fileSystem, config, providerManager, userManager, userDataManager, directoryService)
{
}
}
diff --git a/tests/Jellyfin.Common.Tests/Extensions/CopyToExtensionsTests.cs b/tests/Jellyfin.Common.Tests/Extensions/CopyToExtensionsTests.cs
new file mode 100644
index 000000000..9903409fa
--- /dev/null
+++ b/tests/Jellyfin.Common.Tests/Extensions/CopyToExtensionsTests.cs
@@ -0,0 +1,40 @@
+using System;
+using System.Collections.Generic;
+using MediaBrowser.Common.Extensions;
+using Xunit;
+
+namespace Jellyfin.Common.Tests.Extensions
+{
+ public static class CopyToExtensionsTests
+ {
+ public static IEnumerable<object[]> CopyTo_Valid_Correct_TestData()
+ {
+ yield return new object[] { new[] { 0, 1, 2, 3, 4, 5 }, new[] { 0, 0, 0, 0, 0, 0 }, 0, new[] { 0, 1, 2, 3, 4, 5 } };
+ yield return new object[] { new[] { 0, 1, 2 }, new[] { 5, 4, 3, 2, 1, 0 }, 2, new[] { 5, 4, 0, 1, 2, 0 } };
+ }
+
+ [Theory]
+ [MemberData(nameof(CopyTo_Valid_Correct_TestData))]
+ public static void CopyTo_Valid_Correct<T>(IReadOnlyList<T> source, IList<T> destination, int index, IList<T> expected)
+ {
+ source.CopyTo(destination, index);
+ Assert.Equal(expected, destination);
+ }
+
+ public static IEnumerable<object[]> CopyTo_Invalid_ThrowsArgumentOutOfRangeException_TestData()
+ {
+ yield return new object[] { new[] { 0, 1, 2, 3, 4, 5 }, new[] { 0, 0, 0, 0, 0, 0 }, -1 };
+ yield return new object[] { new[] { 0, 1, 2 }, new[] { 5, 4, 3, 2, 1, 0 }, 6 };
+ yield return new object[] { new[] { 0, 1, 2 }, Array.Empty<int>(), 0 };
+ yield return new object[] { new[] { 0, 1, 2, 3, 4, 5 }, new[] { 0 }, 0 };
+ yield return new object[] { new[] { 0, 1, 2, 3, 4, 5 }, new[] { 0, 0, 0, 0, 0, 0 }, 1 };
+ }
+
+ [Theory]
+ [MemberData(nameof(CopyTo_Invalid_ThrowsArgumentOutOfRangeException_TestData))]
+ public static void CopyTo_Invalid_ThrowsArgumentOutOfRangeException<T>(IReadOnlyList<T> source, IList<T> destination, int index)
+ {
+ Assert.Throws<ArgumentOutOfRangeException>(() => source.CopyTo(destination, index));
+ }
+ }
+}
diff --git a/tests/Jellyfin.Common.Tests/Providers/ProviderIdParserTests.cs b/tests/Jellyfin.Common.Tests/Providers/ProviderIdParserTests.cs
new file mode 100644
index 000000000..ef9d31cc1
--- /dev/null
+++ b/tests/Jellyfin.Common.Tests/Providers/ProviderIdParserTests.cs
@@ -0,0 +1,85 @@
+using System;
+using MediaBrowser.Common.Providers;
+using Xunit;
+
+namespace Jellyfin.Common.Tests.Providers
+{
+ public class ProviderIdParserTests
+ {
+ [Theory]
+ [InlineData("tt1234567", "tt1234567")]
+ [InlineData("tt12345678", "tt12345678")]
+ [InlineData("https://www.imdb.com/title/tt1234567", "tt1234567")]
+ [InlineData("https://www.imdb.com/title/tt12345678", "tt12345678")]
+ [InlineData(@"multiline\nhttps://www.imdb.com/title/tt1234567", "tt1234567")]
+ [InlineData(@"multiline\nhttps://www.imdb.com/title/tt12345678", "tt12345678")]
+ [InlineData("tt1234567tt7654321", "tt1234567")]
+ [InlineData("tt12345678tt7654321", "tt12345678")]
+ [InlineData("tt123456789", "tt12345678")]
+ public void FindImdbId_Valid_Success(string text, string expected)
+ {
+ Assert.True(ProviderIdParsers.TryFindImdbId(text, out ReadOnlySpan<char> parsedId));
+ Assert.Equal(expected, parsedId.ToString());
+ }
+
+ [Theory]
+ [InlineData("tt123456")]
+ [InlineData("https://www.imdb.com/title/tt123456")]
+ [InlineData("Jellyfin")]
+ public void FindImdbId_Invalid_Success(string text)
+ {
+ Assert.False(ProviderIdParsers.TryFindImdbId(text, out _));
+ }
+
+ [Theory]
+ [InlineData("https://www.themoviedb.org/movie/30287-fallo", "30287")]
+ [InlineData("themoviedb.org/movie/30287", "30287")]
+ public void FindTmdbMovieId_Valid_Success(string text, string expected)
+ {
+ Assert.True(ProviderIdParsers.TryFindTmdbMovieId(text, out ReadOnlySpan<char> parsedId));
+ Assert.Equal(expected, parsedId.ToString());
+ }
+
+ [Theory]
+ [InlineData("https://www.themoviedb.org/movie/fallo-30287")]
+ [InlineData("https://www.themoviedb.org/tv/1668-friends")]
+ public void FindTmdbMovieId_Invalid_Success(string text)
+ {
+ Assert.False(ProviderIdParsers.TryFindTmdbMovieId(text, out _));
+ }
+
+ [Theory]
+ [InlineData("https://www.themoviedb.org/tv/1668-friends", "1668")]
+ [InlineData("themoviedb.org/tv/1668", "1668")]
+ public void FindTmdbSeriesId_Valid_Success(string text, string expected)
+ {
+ Assert.True(ProviderIdParsers.TryFindTmdbSeriesId(text, out ReadOnlySpan<char> parsedId));
+ Assert.Equal(expected, parsedId.ToString());
+ }
+
+ [Theory]
+ [InlineData("https://www.themoviedb.org/tv/friends-1668")]
+ [InlineData("https://www.themoviedb.org/movie/30287-fallo")]
+ public void FindTmdbSeriesId_Invalid_Success(string text)
+ {
+ Assert.False(ProviderIdParsers.TryFindTmdbSeriesId(text, out _));
+ }
+
+ [Theory]
+ [InlineData("https://www.thetvdb.com/?tab=series&id=121361", "121361")]
+ [InlineData("thetvdb.com/?tab=series&id=121361", "121361")]
+ public void FindTvdbId_Valid_Success(string text, string expected)
+ {
+ Assert.True(ProviderIdParsers.TryFindTvdbId(text, out ReadOnlySpan<char> parsedId));
+ Assert.Equal(expected, parsedId.ToString());
+ }
+
+ [Theory]
+ [InlineData("thetvdb.com/?tab=series&id=Jellyfin121361")]
+ [InlineData("https://www.themoviedb.org/tv/1668-friends")]
+ public void FindTvdbId_Invalid_Success(string text)
+ {
+ Assert.False(ProviderIdParsers.TryFindTvdbId(text, out _));
+ }
+ }
+}
diff --git a/tests/Jellyfin.Controller.Tests/DirectoryServiceTests.cs b/tests/Jellyfin.Controller.Tests/DirectoryServiceTests.cs
new file mode 100644
index 000000000..feffb50e8
--- /dev/null
+++ b/tests/Jellyfin.Controller.Tests/DirectoryServiceTests.cs
@@ -0,0 +1,200 @@
+using System.Linq;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.IO;
+using Moq;
+using Xunit;
+
+namespace Jellyfin.Controller.Tests
+{
+ public class DirectoryServiceTests
+ {
+ private const string LowerCasePath = "/music/someartist";
+ private const string UpperCasePath = "/music/SOMEARTIST";
+
+ private static readonly FileSystemMetadata[] _lowerCaseFileSystemMetadata =
+ {
+ new ()
+ {
+ FullName = LowerCasePath + "/Artwork",
+ IsDirectory = true
+ },
+ new ()
+ {
+ FullName = LowerCasePath + "/Some Other Folder",
+ IsDirectory = true
+ },
+ new ()
+ {
+ FullName = LowerCasePath + "/Song 2.mp3",
+ IsDirectory = false
+ },
+ new ()
+ {
+ FullName = LowerCasePath + "/Song 3.mp3",
+ IsDirectory = false
+ }
+ };
+
+ private static readonly FileSystemMetadata[] _upperCaseFileSystemMetadata =
+ {
+ new ()
+ {
+ FullName = UpperCasePath + "/Lyrics",
+ IsDirectory = true
+ },
+ new ()
+ {
+ FullName = UpperCasePath + "/Song 1.mp3",
+ IsDirectory = false
+ }
+ };
+
+ [Fact]
+ public void GetFileSystemEntries_GivenPathsWithDifferentCasing_CachesAll()
+ {
+ var fileSystemMock = new Mock<IFileSystem>();
+ fileSystemMock.Setup(f => f.GetFileSystemEntries(It.Is<string>(x => x == UpperCasePath), false)).Returns(_upperCaseFileSystemMetadata);
+ fileSystemMock.Setup(f => f.GetFileSystemEntries(It.Is<string>(x => x == LowerCasePath), false)).Returns(_lowerCaseFileSystemMetadata);
+ var directoryService = new DirectoryService(fileSystemMock.Object);
+
+ var upperCaseResult = directoryService.GetFileSystemEntries(UpperCasePath);
+ var lowerCaseResult = directoryService.GetFileSystemEntries(LowerCasePath);
+
+ Assert.Equal(_upperCaseFileSystemMetadata, upperCaseResult);
+ Assert.Equal(_lowerCaseFileSystemMetadata, lowerCaseResult);
+ }
+
+ [Fact]
+ public void GetFiles_GivenPathsWithDifferentCasing_ReturnsCorrectFiles()
+ {
+ var fileSystemMock = new Mock<IFileSystem>();
+ fileSystemMock.Setup(f => f.GetFileSystemEntries(It.Is<string>(x => x == UpperCasePath), false)).Returns(_upperCaseFileSystemMetadata);
+ fileSystemMock.Setup(f => f.GetFileSystemEntries(It.Is<string>(x => x == LowerCasePath), false)).Returns(_lowerCaseFileSystemMetadata);
+ var directoryService = new DirectoryService(fileSystemMock.Object);
+
+ var upperCaseResult = directoryService.GetFiles(UpperCasePath);
+ var lowerCaseResult = directoryService.GetFiles(LowerCasePath);
+
+ Assert.Equal(_upperCaseFileSystemMetadata.Where(f => !f.IsDirectory), upperCaseResult);
+ Assert.Equal(_lowerCaseFileSystemMetadata.Where(f => !f.IsDirectory), lowerCaseResult);
+ }
+
+ [Fact]
+ public void GetFile_GivenFilePathsWithDifferentCasing_ReturnsCorrectFile()
+ {
+ const string lowerCasePath = "/music/someartist/song 1.mp3";
+ var lowerCaseFileSystemMetadata = new FileSystemMetadata
+ {
+ FullName = lowerCasePath,
+ Exists = true
+ };
+ const string upperCasePath = "/music/SOMEARTIST/SONG 1.mp3";
+ var upperCaseFileSystemMetadata = new FileSystemMetadata
+ {
+ FullName = upperCasePath,
+ Exists = false
+ };
+ var fileSystemMock = new Mock<IFileSystem>();
+ fileSystemMock.Setup(f => f.GetFileInfo(It.Is<string>(x => x == upperCasePath))).Returns(upperCaseFileSystemMetadata);
+ fileSystemMock.Setup(f => f.GetFileInfo(It.Is<string>(x => x == lowerCasePath))).Returns(lowerCaseFileSystemMetadata);
+ var directoryService = new DirectoryService(fileSystemMock.Object);
+
+ var lowerCaseResult = directoryService.GetFile(lowerCasePath);
+ var upperCaseResult = directoryService.GetFile(upperCasePath);
+
+ Assert.Equal(lowerCaseFileSystemMetadata, lowerCaseResult);
+ Assert.Null(upperCaseResult);
+ }
+
+ [Fact]
+ public void GetFile_GivenCachedPath_ReturnsCachedFile()
+ {
+ const string path = "/music/someartist/song 1.mp3";
+ var cachedFileSystemMetadata = new FileSystemMetadata
+ {
+ FullName = path,
+ Exists = true
+ };
+ var newFileSystemMetadata = new FileSystemMetadata
+ {
+ FullName = "/music/SOMEARTIST/song 1.mp3",
+ Exists = true
+ };
+
+ var fileSystemMock = new Mock<IFileSystem>();
+ fileSystemMock.Setup(f => f.GetFileInfo(It.Is<string>(x => x == path))).Returns(cachedFileSystemMetadata);
+ var directoryService = new DirectoryService(fileSystemMock.Object);
+
+ var result = directoryService.GetFile(path);
+ fileSystemMock.Setup(f => f.GetFileInfo(It.Is<string>(x => x == path))).Returns(newFileSystemMetadata);
+ var secondResult = directoryService.GetFile(path);
+
+ Assert.Equal(cachedFileSystemMetadata, result);
+ Assert.Equal(cachedFileSystemMetadata, secondResult);
+ }
+
+ [Fact]
+ public void GetFilePaths_GivenCachedFilePathWithoutClear_ReturnsOnlyCachedPaths()
+ {
+ const string path = "/music/someartist";
+
+ var cachedPaths = new[]
+ {
+ "/music/someartist/song 1.mp3",
+ "/music/someartist/song 2.mp3",
+ "/music/someartist/song 3.mp3",
+ "/music/someartist/song 4.mp3",
+ };
+ var newPaths = new[]
+ {
+ "/music/someartist/song 5.mp3",
+ "/music/someartist/song 6.mp3",
+ "/music/someartist/song 7.mp3",
+ "/music/someartist/song 8.mp3",
+ };
+
+ var fileSystemMock = new Mock<IFileSystem>();
+ fileSystemMock.Setup(f => f.GetFilePaths(It.Is<string>(x => x == path), false)).Returns(cachedPaths);
+ var directoryService = new DirectoryService(fileSystemMock.Object);
+
+ var result = directoryService.GetFilePaths(path);
+ fileSystemMock.Setup(f => f.GetFilePaths(It.Is<string>(x => x == path), false)).Returns(newPaths);
+ var secondResult = directoryService.GetFilePaths(path);
+
+ Assert.Equal(cachedPaths, result);
+ Assert.Equal(cachedPaths, secondResult);
+ }
+
+ [Fact]
+ public void GetFilePaths_GivenCachedFilePathWithClear_ReturnsNewPaths()
+ {
+ const string path = "/music/someartist";
+
+ var cachedPaths = new[]
+ {
+ "/music/someartist/song 1.mp3",
+ "/music/someartist/song 2.mp3",
+ "/music/someartist/song 3.mp3",
+ "/music/someartist/song 4.mp3",
+ };
+ var newPaths = new[]
+ {
+ "/music/someartist/song 5.mp3",
+ "/music/someartist/song 6.mp3",
+ "/music/someartist/song 7.mp3",
+ "/music/someartist/song 8.mp3",
+ };
+
+ var fileSystemMock = new Mock<IFileSystem>();
+ fileSystemMock.Setup(f => f.GetFilePaths(It.Is<string>(x => x == path), false)).Returns(cachedPaths);
+ var directoryService = new DirectoryService(fileSystemMock.Object);
+
+ var result = directoryService.GetFilePaths(path);
+ fileSystemMock.Setup(f => f.GetFilePaths(It.Is<string>(x => x == path), false)).Returns(newPaths);
+ var secondResult = directoryService.GetFilePaths(path, true);
+
+ Assert.Equal(cachedPaths, result);
+ Assert.Equal(newPaths, secondResult);
+ }
+ }
+}
diff --git a/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj b/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj
index 6dec25aa4..c56ccb365 100644
--- a/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj
+++ b/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj
@@ -16,6 +16,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.1" />
+ <PackageReference Include="Moq" Version="4.16.1" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
<PackageReference Include="coverlet.collector" Version="3.0.3" />
diff --git a/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs b/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs
index d8541d76b..caea0d265 100644
--- a/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs
+++ b/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs
@@ -57,7 +57,7 @@ namespace Jellyfin.Networking.Tests
/// <summary>
/// Checks IP address formats.
/// </summary>
- /// <param name="address"></param>
+ /// <param name="address">IP Address.</param>
[Theory]
[InlineData("127.0.0.1")]
[InlineData("127.0.0.1:123")]
@@ -81,7 +81,7 @@ namespace Jellyfin.Networking.Tests
/// <summary>
/// Checks IP address formats.
/// </summary>
- /// <param name="address"></param>
+ /// <param name="address">IP Address.</param>
[Theory]
[InlineData("127.0.0.1")]
[InlineData("fd23:184f:2029:0:3139:7386:67d7:d517")]
@@ -175,29 +175,29 @@ namespace Jellyfin.Networking.Tests
using var nm = new NetworkManager(GetMockConfig(conf), new NullLogger<NetworkManager>());
// Test included.
- Collection<IPObject> nc = nm.CreateIPCollection(settings.Split(","), false);
+ Collection<IPObject> nc = nm.CreateIPCollection(settings.Split(','), false);
Assert.Equal(nc.AsString(), result1);
// Test excluded.
- nc = nm.CreateIPCollection(settings.Split(","), true);
+ nc = nm.CreateIPCollection(settings.Split(','), true);
Assert.Equal(nc.AsString(), result3);
conf.EnableIPV6 = false;
nm.UpdateSettings(conf);
// Test IP4 included.
- nc = nm.CreateIPCollection(settings.Split(","), false);
+ nc = nm.CreateIPCollection(settings.Split(','), false);
Assert.Equal(nc.AsString(), result2);
// Test IP4 excluded.
- nc = nm.CreateIPCollection(settings.Split(","), true);
+ nc = nm.CreateIPCollection(settings.Split(','), true);
Assert.Equal(nc.AsString(), result4);
conf.EnableIPV6 = true;
nm.UpdateSettings(conf);
// Test network addresses of collection.
- nc = nm.CreateIPCollection(settings.Split(","), false);
+ nc = nm.CreateIPCollection(settings.Split(','), false);
nc = nc.AsNetworks();
Assert.Equal(nc.AsString(), result5);
}
@@ -236,8 +236,8 @@ namespace Jellyfin.Networking.Tests
using var nm = new NetworkManager(GetMockConfig(conf), new NullLogger<NetworkManager>());
- Collection<IPObject> nc1 = nm.CreateIPCollection(settings.Split(","), false);
- Collection<IPObject> nc2 = nm.CreateIPCollection(compare.Split(","), false);
+ Collection<IPObject> nc1 = nm.CreateIPCollection(settings.Split(','), false);
+ Collection<IPObject> nc2 = nm.CreateIPCollection(compare.Split(','), false);
Assert.Equal(nc1.Union(nc2).AsString(), result);
}
@@ -346,10 +346,10 @@ namespace Jellyfin.Networking.Tests
using var nm = new NetworkManager(GetMockConfig(conf), new NullLogger<NetworkManager>());
// Test included, IP6.
- Collection<IPObject> ncSource = nm.CreateIPCollection(source.Split(","));
- Collection<IPObject> ncDest = nm.CreateIPCollection(dest.Split(","));
+ Collection<IPObject> ncSource = nm.CreateIPCollection(source.Split(','));
+ Collection<IPObject> ncDest = nm.CreateIPCollection(dest.Split(','));
Collection<IPObject> ncResult = ncSource.Union(ncDest);
- Collection<IPObject> resultCollection = nm.CreateIPCollection(result.Split(","));
+ Collection<IPObject> resultCollection = nm.CreateIPCollection(result.Split(','));
Assert.True(ncResult.Compare(resultCollection));
}
@@ -488,5 +488,45 @@ namespace Jellyfin.Networking.Tests
Assert.Equal(intf, result);
}
+
+ [Theory]
+ [InlineData("185.10.10.10,200.200.200.200", "79.2.3.4", true)]
+ [InlineData("185.10.10.10", "185.10.10.10", false)]
+ [InlineData("", "100.100.100.100", false)]
+
+ public void HasRemoteAccess_GivenWhitelist_AllowsOnlyIpsInWhitelist(string addresses, string remoteIp, bool denied)
+ {
+ // Comma separated list of IP addresses or IP/netmask entries for networks that will be allowed to connect remotely.
+ // If left blank, all remote addresses will be allowed.
+ var conf = new NetworkConfiguration()
+ {
+ EnableIPV4 = true,
+ RemoteIPFilter = addresses.Split(','),
+ IsRemoteIPFilterBlacklist = false
+ };
+ using var nm = new NetworkManager(GetMockConfig(conf), new NullLogger<NetworkManager>());
+
+ Assert.NotEqual(nm.HasRemoteAccess(IPAddress.Parse(remoteIp)), denied);
+ }
+
+ [Theory]
+ [InlineData("185.10.10.10", "79.2.3.4", false)]
+ [InlineData("185.10.10.10", "185.10.10.10", true)]
+ [InlineData("", "100.100.100.100", false)]
+ public void HasRemoteAccess_GivenBlacklist_BlacklistTheIps(string addresses, string remoteIp, bool denied)
+ {
+ // Comma separated list of IP addresses or IP/netmask entries for networks that will be allowed to connect remotely.
+ // If left blank, all remote addresses will be allowed.
+ var conf = new NetworkConfiguration()
+ {
+ EnableIPV4 = true,
+ RemoteIPFilter = addresses.Split(','),
+ IsRemoteIPFilterBlacklist = true
+ };
+
+ using var nm = new NetworkManager(GetMockConfig(conf), new NullLogger<NetworkManager>());
+
+ Assert.NotEqual(nm.HasRemoteAccess(IPAddress.Parse(remoteIp)), denied);
+ }
}
}
diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/EpisodeNfoProviderTests.cs b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/EpisodeNfoProviderTests.cs
index 053e0a89e..9ad093a2b 100644
--- a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/EpisodeNfoProviderTests.cs
+++ b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/EpisodeNfoProviderTests.cs
@@ -37,8 +37,15 @@ namespace Jellyfin.XbmcMetadata.Tests.Parsers
.Returns(new XbmcMetadataOptions());
var user = new Mock<IUserManager>();
var userData = new Mock<IUserDataManager>();
-
- _parser = new EpisodeNfoParser(new NullLogger<EpisodeNfoParser>(), config.Object, providerManager.Object, user.Object, userData.Object);
+ var directoryService = new Mock<IDirectoryService>();
+
+ _parser = new EpisodeNfoParser(
+ new NullLogger<EpisodeNfoParser>(),
+ config.Object,
+ providerManager.Object,
+ user.Object,
+ userData.Object,
+ directoryService.Object);
}
[Fact]
diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs
index ff4795569..b58151b3b 100644
--- a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs
+++ b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs
@@ -9,7 +9,9 @@ using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.IO;
using MediaBrowser.Model.Providers;
+using MediaBrowser.Model.System;
using MediaBrowser.Providers.Plugins.Tmdb.Movies;
using MediaBrowser.XbmcMetadata.Parsers;
using Microsoft.Extensions.Logging.Abstractions;
@@ -23,6 +25,7 @@ namespace Jellyfin.XbmcMetadata.Tests.Parsers
private readonly MovieNfoParser _parser;
private readonly IUserDataManager _userDataManager;
private readonly User _testUser;
+ private readonly FileSystemMetadata _localImageFileMetadata;
public MovieNfoParserTests()
{
@@ -52,8 +55,25 @@ namespace Jellyfin.XbmcMetadata.Tests.Parsers
userData.Setup(x => x.GetUserData(_testUser, It.IsAny<BaseItem>()))
.Returns(new UserItemData());
+ var directoryService = new Mock<IDirectoryService>();
+ _localImageFileMetadata = new FileSystemMetadata()
+ {
+ Exists = true,
+ FullName = MediaBrowser.Common.System.OperatingSystem.Id == OperatingSystemId.Windows ?
+ "C:\\media\\movies\\Justice League (2017).jpg"
+ : "/media/movies/Justice League (2017).jpg"
+ };
+ directoryService.Setup(x => x.GetFile(_localImageFileMetadata.FullName))
+ .Returns(_localImageFileMetadata);
+
_userDataManager = userData.Object;
- _parser = new MovieNfoParser(new NullLogger<MovieNfoParser>(), configManager.Object, providerManager.Object, user.Object, userData.Object);
+ _parser = new MovieNfoParser(
+ new NullLogger<MovieNfoParser>(),
+ configManager.Object,
+ providerManager.Object,
+ user.Object,
+ userData.Object,
+ directoryService.Object);
}
[Fact]
@@ -134,6 +154,37 @@ namespace Jellyfin.XbmcMetadata.Tests.Parsers
// Movie set
Assert.Equal("702342", item.ProviderIds[MetadataProvider.TmdbCollection.ToString()]);
Assert.Equal("Justice League Collection", item.CollectionName);
+
+ // Images
+ Assert.Equal(6, result.RemoteImages.Count);
+
+ var posters = result.RemoteImages.Where(x => x.type == ImageType.Primary).ToList();
+ Assert.Single(posters);
+ Assert.Equal("http://image.tmdb.org/t/p/original/9rtrRGeRnL0JKtu9IMBWsmlmmZz.jpg", posters[0].url);
+
+ var logos = result.RemoteImages.Where(x => x.type == ImageType.Logo).ToList();
+ Assert.Single(logos);
+ Assert.Equal("https://assets.fanart.tv/fanart/movies/141052/hdmovielogo/justice-league-5865bf95cbadb.png", logos[0].url);
+
+ var banners = result.RemoteImages.Where(x => x.type == ImageType.Banner).ToList();
+ Assert.Single(banners);
+ Assert.Equal("https://assets.fanart.tv/fanart/movies/141052/moviebanner/justice-league-586017e95adbd.jpg", banners[0].url);
+
+ var thumbs = result.RemoteImages.Where(x => x.type == ImageType.Thumb).ToList();
+ Assert.Single(thumbs);
+ Assert.Equal("https://assets.fanart.tv/fanart/movies/141052/moviethumb/justice-league-585fb155c3743.jpg", thumbs[0].url);
+
+ var art = result.RemoteImages.Where(x => x.type == ImageType.Art).ToList();
+ Assert.Single(art);
+ Assert.Equal("https://assets.fanart.tv/fanart/movies/141052/hdmovieclearart/justice-league-5865c23193041.png", art[0].url);
+
+ var discArt = result.RemoteImages.Where(x => x.type == ImageType.Disc).ToList();
+ Assert.Single(discArt);
+ Assert.Equal("https://assets.fanart.tv/fanart/movies/141052/moviedisc/justice-league-5a3af26360617.png", discArt[0].url);
+
+ // Local Image - contains only one item depending on operating system
+ Assert.Single(result.Images);
+ Assert.Equal(_localImageFileMetadata.Name, result.Images[0].FileInfo.Name);
}
[Theory]
@@ -153,6 +204,21 @@ namespace Jellyfin.XbmcMetadata.Tests.Parsers
}
[Fact]
+ public void Parse_RadarrUrlFile_Success()
+ {
+ var result = new MetadataResult<Video>()
+ {
+ Item = new Movie()
+ };
+
+ _parser.Fetch(result, "Test Data/Radarr.nfo", CancellationToken.None);
+ var item = (Movie)result.Item;
+
+ Assert.Equal("583689", item.ProviderIds[MetadataProvider.Tmdb.ToString()]);
+ Assert.Equal("tt4154796", item.ProviderIds[MetadataProvider.Imdb.ToString()]);
+ }
+
+ [Fact]
public void Fetch_WithNullItem_ThrowsArgumentException()
{
var result = new MetadataResult<Video>();
diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicAlbumNfoProviderTests.cs b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicAlbumNfoProviderTests.cs
index 63f6dfce8..2129f3422 100644
--- a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicAlbumNfoProviderTests.cs
+++ b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicAlbumNfoProviderTests.cs
@@ -1,7 +1,6 @@
#pragma warning disable CA5369
using System;
-using System.Linq;
using System.Threading;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Entities.Audio;
@@ -11,7 +10,6 @@ using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Providers;
using MediaBrowser.Providers.Music;
-using MediaBrowser.Providers.Plugins.MusicBrainz;
using MediaBrowser.XbmcMetadata.Parsers;
using Microsoft.Extensions.Logging.Abstractions;
using Moq;
@@ -38,8 +36,15 @@ namespace Jellyfin.XbmcMetadata.Tests.Parsers
.Returns(new XbmcMetadataOptions());
var user = new Mock<IUserManager>();
var userData = new Mock<IUserDataManager>();
+ var directoryService = new Mock<IDirectoryService>();
- _parser = new BaseNfoParser<MusicAlbum>(new NullLogger<BaseNfoParser<MusicAlbum>>(), config.Object, providerManager.Object, user.Object, userData.Object);
+ _parser = new BaseNfoParser<MusicAlbum>(
+ new NullLogger<BaseNfoParser<MusicAlbum>>(),
+ config.Object,
+ providerManager.Object,
+ user.Object,
+ userData.Object,
+ directoryService.Object);
}
[Fact]
diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicArtistNfoParserTests.cs b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicArtistNfoParserTests.cs
index 438d47cac..3d8e13e96 100644
--- a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicArtistNfoParserTests.cs
+++ b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicArtistNfoParserTests.cs
@@ -35,8 +35,15 @@ namespace Jellyfin.XbmcMetadata.Tests.Parsers
.Returns(new XbmcMetadataOptions());
var user = new Mock<IUserManager>();
var userData = new Mock<IUserDataManager>();
+ var directoryService = new Mock<IDirectoryService>();
- _parser = new BaseNfoParser<MusicArtist>(new NullLogger<BaseNfoParser<MusicArtist>>(), config.Object, providerManager.Object, user.Object, userData.Object);
+ _parser = new BaseNfoParser<MusicArtist>(
+ new NullLogger<BaseNfoParser<MusicArtist>>(),
+ config.Object,
+ providerManager.Object,
+ user.Object,
+ userData.Object,
+ directoryService.Object);
}
[Fact]
diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicVideoNfoParserTests.cs b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicVideoNfoParserTests.cs
index 898554936..bf887cab1 100644
--- a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicVideoNfoParserTests.cs
+++ b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicVideoNfoParserTests.cs
@@ -30,8 +30,15 @@ namespace Jellyfin.XbmcMetadata.Tests.Parsers
var user = new Mock<IUserManager>();
var userData = new Mock<IUserDataManager>();
+ var directoryService = new Mock<IDirectoryService>();
- _parser = new MovieNfoParser(new NullLogger<BaseNfoParser<MusicVideo>>(), config.Object, providerManager.Object, user.Object, userData.Object);
+ _parser = new MovieNfoParser(
+ new NullLogger<BaseNfoParser<MusicVideo>>(),
+ config.Object,
+ providerManager.Object,
+ user.Object,
+ userData.Object,
+ directoryService.Object);
}
[Fact]
diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/SeasonNfoProviderTests.cs b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/SeasonNfoProviderTests.cs
index a677cd724..0e61fa2a1 100644
--- a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/SeasonNfoProviderTests.cs
+++ b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/SeasonNfoProviderTests.cs
@@ -31,8 +31,15 @@ namespace Jellyfin.XbmcMetadata.Tests.Parsers
.Returns(new XbmcMetadataOptions());
var user = new Mock<IUserManager>();
var userData = new Mock<IUserDataManager>();
+ var directoryService = new Mock<IDirectoryService>();
- _parser = new SeasonNfoParser(new NullLogger<SeasonNfoParser>(), config.Object, providerManager.Object, user.Object, userData.Object);
+ _parser = new SeasonNfoParser(
+ new NullLogger<SeasonNfoParser>(),
+ config.Object,
+ providerManager.Object,
+ user.Object,
+ userData.Object,
+ directoryService.Object);
}
[Fact]
diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/SeriesNfoParserTests.cs b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/SeriesNfoParserTests.cs
index 80923957d..bdedae205 100644
--- a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/SeriesNfoParserTests.cs
+++ b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/SeriesNfoParserTests.cs
@@ -29,8 +29,9 @@ namespace Jellyfin.XbmcMetadata.Tests.Parsers
.Returns(new XbmcMetadataOptions());
var user = new Mock<IUserManager>();
var userData = new Mock<IUserDataManager>();
+ var directoryService = new Mock<IDirectoryService>();
- _parser = new SeriesNfoParser(new NullLogger<SeriesNfoParser>(), config.Object, providerManager.Object, user.Object, userData.Object);
+ _parser = new SeriesNfoParser(new NullLogger<SeriesNfoParser>(), config.Object, providerManager.Object, user.Object, userData.Object, directoryService.Object);
}
[Fact]
diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Test Data/Justice League.nfo b/tests/Jellyfin.XbmcMetadata.Tests/Test Data/Justice League.nfo
index 72e27fe50..b0c5e3c57 100644
--- a/tests/Jellyfin.XbmcMetadata.Tests/Test Data/Justice League.nfo
+++ b/tests/Jellyfin.XbmcMetadata.Tests/Test Data/Justice League.nfo
@@ -59,6 +59,8 @@
<thumb aspect="poster" preview="http://image.tmdb.org/t/p/w500/exLtrlI7JjKcfQVTccI7XdQRFMz.jpg">http://image.tmdb.org/t/p/original/exLtrlI7JjKcfQVTccI7XdQRFMz.jpg</thumb>
<thumb aspect="poster" preview="http://image.tmdb.org/t/p/w500/paLcue01KpfQftorfjKqqD4qvlL.jpg">http://image.tmdb.org/t/p/original/paLcue01KpfQftorfjKqqD4qvlL.jpg</thumb>
<thumb aspect="poster" preview="http://image.tmdb.org/t/p/w500/yVDIfiKIsCbdFcgLXW34bAsnQvy.jpg">http://image.tmdb.org/t/p/original/yVDIfiKIsCbdFcgLXW34bAsnQvy.jpg</thumb>
+ <thumb aspect="poster">C:\media\movies\Justice League (2017).jpg</thumb>
+ <thumb aspect="poster">/media/movies/Justice League (2017).jpg</thumb>
<thumb aspect="clearlogo" preview="https://assets.fanart.tv/preview/movies/141052/hdmovielogo/justice-league-5865bf95cbadb.png">https://assets.fanart.tv/fanart/movies/141052/hdmovielogo/justice-league-5865bf95cbadb.png</thumb>
<thumb aspect="clearlogo" preview="https://assets.fanart.tv/preview/movies/141052/hdmovielogo/justice-league-585e9ca3bcf6a.png">https://assets.fanart.tv/fanart/movies/141052/hdmovielogo/justice-league-585e9ca3bcf6a.png</thumb>
<thumb aspect="clearlogo" preview="https://assets.fanart.tv/preview/movies/141052/hdmovielogo/justice-league-57b476a831d74.png">https://assets.fanart.tv/fanart/movies/141052/hdmovielogo/justice-league-57b476a831d74.png</thumb>
@@ -91,7 +93,8 @@
</fanart>
<mpaa>Australia:M</mpaa>
<id>tt0974015</id>
- <uniqueid type="imdb" default="true">tt0974015</uniqueid>
+ <uniqueid type="imdb">tt0974015</uniqueid>
+ <uniqueid type="tmdb">141052</uniqueid>
<genre>Action</genre>
<genre>Adventure</genre>
<genre>Fantasy</genre>
diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Test Data/Radarr.nfo b/tests/Jellyfin.XbmcMetadata.Tests/Test Data/Radarr.nfo
new file mode 100644
index 000000000..43da4881c
--- /dev/null
+++ b/tests/Jellyfin.XbmcMetadata.Tests/Test Data/Radarr.nfo
@@ -0,0 +1,2 @@
+https://www.themoviedb.org/movie/583689
+https://www.imdb.com/title/tt4154796