aboutsummaryrefslogtreecommitdiff
path: root/Emby.Server.Implementations/Library
diff options
context:
space:
mode:
authorcvium <clausvium@gmail.com>2022-01-07 10:23:22 +0100
committercvium <clausvium@gmail.com>2022-01-07 10:23:22 +0100
commitc658a883a2bc84b46ed73d209d2983e8a324cdce (patch)
treedabdbb5ac224e202d5433e7062e0c1b6872d1af7 /Emby.Server.Implementations/Library
parent2899b77cd58456470b8dd4d01d3a8c525a9b5911 (diff)
parent6b4f5a86631e5bde93dae88553380c7ffd99b8e4 (diff)
Merge branch 'master' into keyframe_extraction_v1
# Conflicts: # Jellyfin.Api/Controllers/DynamicHlsController.cs # MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs # MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
Diffstat (limited to 'Emby.Server.Implementations/Library')
-rw-r--r--Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs31
-rw-r--r--Emby.Server.Implementations/Library/LibraryManager.cs219
-rw-r--r--Emby.Server.Implementations/Library/LiveStreamHelper.cs7
-rw-r--r--Emby.Server.Implementations/Library/MediaSourceManager.cs46
-rw-r--r--Emby.Server.Implementations/Library/MediaStreamSelector.cs50
-rw-r--r--Emby.Server.Implementations/Library/MusicManager.cs6
-rw-r--r--Emby.Server.Implementations/Library/PathExtensions.cs32
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs67
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs35
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs21
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs134
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs10
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/ExtraResolver.cs93
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/FolderResolver.cs22
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/GenericFolderResolver.cs27
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/GenericVideoResolver.cs18
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/Movies/BoxSetResolver.cs4
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs137
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/PhotoAlbumResolver.cs13
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs25
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs7
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs3
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs52
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs32
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs74
-rw-r--r--Emby.Server.Implementations/Library/SearchEngine.cs53
-rw-r--r--Emby.Server.Implementations/Library/UserDataManager.cs19
-rw-r--r--Emby.Server.Implementations/Library/UserViewManager.cs41
-rw-r--r--Emby.Server.Implementations/Library/Validators/ArtistsValidator.cs14
-rw-r--r--Emby.Server.Implementations/Library/Validators/CollectionPostScanTask.cs156
-rw-r--r--Emby.Server.Implementations/Library/Validators/PeopleValidator.cs5
-rw-r--r--Emby.Server.Implementations/Library/Validators/StudiosValidator.cs3
32 files changed, 819 insertions, 637 deletions
diff --git a/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs b/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs
index bc5b4499f..e558fbe27 100644
--- a/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs
+++ b/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs
@@ -1,8 +1,9 @@
using System;
using System.IO;
+using Emby.Naming.Audio;
+using Emby.Naming.Common;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Resolvers;
using MediaBrowser.Model.IO;
@@ -13,17 +14,17 @@ namespace Emby.Server.Implementations.Library
/// </summary>
public class CoreResolutionIgnoreRule : IResolverIgnoreRule
{
- private readonly ILibraryManager _libraryManager;
+ private readonly NamingOptions _namingOptions;
private readonly IServerApplicationPaths _serverApplicationPaths;
/// <summary>
/// Initializes a new instance of the <see cref="CoreResolutionIgnoreRule"/> class.
/// </summary>
- /// <param name="libraryManager">The library manager.</param>
+ /// <param name="namingOptions">The naming options.</param>
/// <param name="serverApplicationPaths">The server application paths.</param>
- public CoreResolutionIgnoreRule(ILibraryManager libraryManager, IServerApplicationPaths serverApplicationPaths)
+ public CoreResolutionIgnoreRule(NamingOptions namingOptions, IServerApplicationPaths serverApplicationPaths)
{
- _libraryManager = libraryManager;
+ _namingOptions = namingOptions;
_serverApplicationPaths = serverApplicationPaths;
}
@@ -53,20 +54,10 @@ namespace Emby.Server.Implementations.Library
{
if (parent != null)
{
- // Ignore trailer folders but allow it at the collection level
- if (string.Equals(filename, BaseItem.TrailersFolderName, StringComparison.OrdinalIgnoreCase)
- && !(parent is AggregateFolder)
- && !(parent is UserRootFolder))
- {
- return true;
- }
-
- if (string.Equals(filename, BaseItem.ThemeVideosFolderName, StringComparison.OrdinalIgnoreCase))
- {
- return true;
- }
-
- if (string.Equals(filename, BaseItem.ThemeSongsFolderName, StringComparison.OrdinalIgnoreCase))
+ // Ignore extras folders but allow it at the collection level
+ if (_namingOptions.AllExtrasTypesFolderNames.ContainsKey(filename)
+ && parent is not AggregateFolder
+ && parent is not UserRootFolder)
{
return true;
}
@@ -78,7 +69,7 @@ namespace Emby.Server.Implementations.Library
{
// Don't resolve these into audio files
if (Path.GetFileNameWithoutExtension(filename.AsSpan()).Equals(BaseItem.ThemeSongFileName, StringComparison.Ordinal)
- && _libraryManager.IsAudioFile(filename))
+ && AudioFileParser.IsAudioFile(filename, _namingOptions))
{
return true;
}
diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs
index 132486b4a..464a621cf 100644
--- a/Emby.Server.Implementations/Library/LibraryManager.cs
+++ b/Emby.Server.Implementations/Library/LibraryManager.cs
@@ -11,14 +11,12 @@ using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
-using Emby.Naming.Audio;
using Emby.Naming.Common;
using Emby.Naming.TV;
-using Emby.Naming.Video;
using Emby.Server.Implementations.Library.Resolvers;
using Emby.Server.Implementations.Library.Validators;
using Emby.Server.Implementations.Playlists;
-using Emby.Server.Implementations.ScheduledTasks;
+using Emby.Server.Implementations.ScheduledTasks.Tasks;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
@@ -79,6 +77,8 @@ namespace Emby.Server.Implementations.Library
private readonly IFileSystem _fileSystem;
private readonly IItemRepository _itemRepository;
private readonly IImageProcessor _imageProcessor;
+ private readonly NamingOptions _namingOptions;
+ private readonly ExtraResolver _extraResolver;
/// <summary>
/// The _root folder sync lock.
@@ -88,9 +88,6 @@ namespace Emby.Server.Implementations.Library
private readonly TimeSpan _viewRefreshInterval = TimeSpan.FromHours(24);
- private NamingOptions _namingOptions;
- private string[] _videoFileExtensions;
-
/// <summary>
/// The _root folder.
/// </summary>
@@ -116,6 +113,7 @@ namespace Emby.Server.Implementations.Library
/// <param name="itemRepository">The item repository.</param>
/// <param name="imageProcessor">The image processor.</param>
/// <param name="memoryCache">The memory cache.</param>
+ /// <param name="namingOptions">The naming options.</param>
public LibraryManager(
IServerApplicationHost appHost,
ILogger<LibraryManager> logger,
@@ -130,7 +128,8 @@ namespace Emby.Server.Implementations.Library
IMediaEncoder mediaEncoder,
IItemRepository itemRepository,
IImageProcessor imageProcessor,
- IMemoryCache memoryCache)
+ IMemoryCache memoryCache,
+ NamingOptions namingOptions)
{
_appHost = appHost;
_logger = logger;
@@ -146,6 +145,9 @@ namespace Emby.Server.Implementations.Library
_itemRepository = itemRepository;
_imageProcessor = imageProcessor;
_memoryCache = memoryCache;
+ _namingOptions = namingOptions;
+
+ _extraResolver = new ExtraResolver(namingOptions);
_configurationManager.ConfigurationUpdated += ConfigurationUpdated;
@@ -333,8 +335,7 @@ namespace Emby.Server.Implementations.Library
{
try
{
- var task = BaseItem.ChannelManager.DeleteItem(item);
- Task.WaitAll(task);
+ BaseItem.ChannelManager.DeleteItem(item).GetAwaiter().GetResult();
}
catch (ArgumentException)
{
@@ -492,7 +493,7 @@ namespace Emby.Server.Implementations.Library
}
catch (Exception ex)
{
- _logger.LogError(ex, "Error in {resolver} resolving {path}", resolver.GetType().Name, args.Path);
+ _logger.LogError(ex, "Error in {Resolver} resolving {Path}", resolver.GetType().Name, args.Path);
return null;
}
}
@@ -533,8 +534,8 @@ namespace Emby.Server.Implementations.Library
return key.GetMD5();
}
- public BaseItem ResolvePath(FileSystemMetadata fileInfo, Folder parent = null)
- => ResolvePath(fileInfo, new DirectoryService(_fileSystem), null, parent);
+ public BaseItem ResolvePath(FileSystemMetadata fileInfo, Folder parent = null, IDirectoryService directoryService = null)
+ => ResolvePath(fileInfo, directoryService ?? new DirectoryService(_fileSystem), null, parent);
private BaseItem ResolvePath(
FileSystemMetadata fileInfo,
@@ -647,14 +648,14 @@ namespace Emby.Server.Implementations.Library
/// Determines whether a path should be ignored based on its contents - called after the contents have been read.
/// </summary>
/// <param name="args">The args.</param>
- /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
+ /// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns>
private static bool ShouldResolvePathContents(ItemResolveArgs args)
{
// Ignore any folders containing a file called .ignore
return !args.ContainsFileSystemEntryByName(".ignore");
}
- public IEnumerable<BaseItem> ResolvePaths(IEnumerable<FileSystemMetadata> files, IDirectoryService directoryService, Folder parent, LibraryOptions libraryOptions, string collectionType)
+ public IEnumerable<BaseItem> ResolvePaths(IEnumerable<FileSystemMetadata> files, IDirectoryService directoryService, Folder parent, LibraryOptions libraryOptions, string collectionType = null)
{
return ResolvePaths(files, directoryService, parent, libraryOptions, collectionType, EntityResolvers);
}
@@ -677,7 +678,7 @@ namespace Emby.Server.Implementations.Library
{
var result = resolver.ResolveMultiple(parent, fileList, collectionType, directoryService);
- if (result != null && result.Items.Count > 0)
+ if (result?.Items.Count > 0)
{
var items = new List<BaseItem>();
items.AddRange(result.Items);
@@ -799,7 +800,7 @@ namespace Emby.Server.Implementations.Library
{
var userRootPath = _configurationManager.ApplicationPaths.DefaultUserViewsPath;
- _logger.LogDebug("Creating userRootPath at {path}", userRootPath);
+ _logger.LogDebug("Creating userRootPath at {Path}", userRootPath);
Directory.CreateDirectory(userRootPath);
var newItemId = GetNewItemId(userRootPath, typeof(UserRootFolder));
@@ -810,7 +811,7 @@ namespace Emby.Server.Implementations.Library
}
catch (Exception ex)
{
- _logger.LogError(ex, "Error creating UserRootFolder {path}", newItemId);
+ _logger.LogError(ex, "Error creating UserRootFolder {Path}", newItemId);
}
if (tmpItem == null)
@@ -827,7 +828,7 @@ namespace Emby.Server.Implementations.Library
}
_userRootFolder = tmpItem;
- _logger.LogDebug("Setting userRootFolder: {folder}", _userRootFolder);
+ _logger.LogDebug("Setting userRootFolder: {Folder}", _userRootFolder);
}
}
}
@@ -965,7 +966,7 @@ namespace Emby.Server.Implementations.Library
{
var existing = GetItemList(new InternalItemsQuery
{
- IncludeItemTypes = new[] { nameof(MusicArtist) },
+ IncludeItemTypes = new[] { BaseItemKind.MusicArtist },
Name = name,
DtoOptions = options
}).Cast<MusicArtist>()
@@ -1213,7 +1214,7 @@ namespace Emby.Server.Implementations.Library
}
catch (Exception ex)
{
- _logger.LogError(ex, "Error resolving shortcut file {file}", i);
+ _logger.LogError(ex, "Error resolving shortcut file {File}", i);
return null;
}
})
@@ -1266,7 +1267,7 @@ namespace Emby.Server.Implementations.Library
/// </summary>
/// <param name="id">The id.</param>
/// <returns>BaseItem.</returns>
- /// <exception cref="ArgumentNullException">id</exception>
+ /// <exception cref="ArgumentNullException"><paramref name="id"/> is <c>null</c>.</exception>
public BaseItem GetItemById(Guid id)
{
if (id == Guid.Empty)
@@ -1375,7 +1376,7 @@ namespace Emby.Server.Implementations.Library
return _itemRepository.GetItemIdsList(query);
}
- public QueryResult<(BaseItem, ItemCounts)> GetStudios(InternalItemsQuery query)
+ public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetStudios(InternalItemsQuery query)
{
if (query.User != null)
{
@@ -1386,7 +1387,7 @@ namespace Emby.Server.Implementations.Library
return _itemRepository.GetStudios(query);
}
- public QueryResult<(BaseItem, ItemCounts)> GetGenres(InternalItemsQuery query)
+ public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetGenres(InternalItemsQuery query)
{
if (query.User != null)
{
@@ -1397,7 +1398,7 @@ namespace Emby.Server.Implementations.Library
return _itemRepository.GetGenres(query);
}
- public QueryResult<(BaseItem, ItemCounts)> GetMusicGenres(InternalItemsQuery query)
+ public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetMusicGenres(InternalItemsQuery query)
{
if (query.User != null)
{
@@ -1408,7 +1409,7 @@ namespace Emby.Server.Implementations.Library
return _itemRepository.GetMusicGenres(query);
}
- public QueryResult<(BaseItem, ItemCounts)> GetAllArtists(InternalItemsQuery query)
+ public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetAllArtists(InternalItemsQuery query)
{
if (query.User != null)
{
@@ -1419,7 +1420,7 @@ namespace Emby.Server.Implementations.Library
return _itemRepository.GetAllArtists(query);
}
- public QueryResult<(BaseItem, ItemCounts)> GetArtists(InternalItemsQuery query)
+ public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetArtists(InternalItemsQuery query)
{
if (query.User != null)
{
@@ -1443,7 +1444,7 @@ namespace Emby.Server.Implementations.Library
for (int i = 0; i < len; i++)
{
parents[i] = GetItemById(ancestorIds[i]);
- if (!(parents[i] is ICollectionFolder || parents[i] is UserView))
+ if (parents[i] is not (ICollectionFolder or UserView))
{
return;
}
@@ -1460,7 +1461,7 @@ namespace Emby.Server.Implementations.Library
}
}
- public QueryResult<(BaseItem, ItemCounts)> GetAlbumArtists(InternalItemsQuery query)
+ public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetAlbumArtists(InternalItemsQuery query)
{
if (query.User != null)
{
@@ -1698,7 +1699,7 @@ namespace Emby.Server.Implementations.Library
if (video == null)
{
- _logger.LogError("Intro resolver returned null for {path}.", info.Path);
+ _logger.LogError("Intro resolver returned null for {Path}.", info.Path);
}
else
{
@@ -1717,7 +1718,7 @@ namespace Emby.Server.Implementations.Library
}
catch (Exception ex)
{
- _logger.LogError(ex, "Error resolving path {path}.", info.Path);
+ _logger.LogError(ex, "Error resolving path {Path}.", info.Path);
}
}
else
@@ -1759,7 +1760,7 @@ namespace Emby.Server.Implementations.Library
return orderedItems ?? items;
}
- public IEnumerable<BaseItem> Sort(IEnumerable<BaseItem> items, User user, IEnumerable<ValueTuple<string, SortOrder>> orderBy)
+ public IEnumerable<BaseItem> Sort(IEnumerable<BaseItem> items, User user, IEnumerable<(string OrderBy, SortOrder SortOrder)> orderBy)
{
var isFirst = true;
@@ -2502,16 +2503,6 @@ namespace Emby.Server.Implementations.Library
}
/// <inheritdoc />
- public bool IsVideoFile(string path)
- {
- return VideoResolver.IsVideoFile(path, GetNamingOptions());
- }
-
- /// <inheritdoc />
- public bool IsAudioFile(string path)
- => AudioFileParser.IsAudioFile(path, GetNamingOptions());
-
- /// <inheritdoc />
public int? GetSeasonNumberFromPath(string path)
=> SeasonPathParser.Parse(path, true, true).SeasonNumber;
@@ -2526,7 +2517,7 @@ namespace Emby.Server.Implementations.Library
isAbsoluteNaming = null;
}
- var resolver = new EpisodeResolver(GetNamingOptions());
+ var resolver = new EpisodeResolver(_namingOptions);
var isFolder = episode.VideoType == VideoType.BluRay || episode.VideoType == VideoType.Dvd;
@@ -2683,113 +2674,77 @@ namespace Emby.Server.Implementations.Library
return changed;
}
- /// <inheritdoc />
- public NamingOptions GetNamingOptions()
- {
- if (_namingOptions == null)
- {
- _namingOptions = new NamingOptions();
- _videoFileExtensions = _namingOptions.VideoFileExtensions;
- }
-
- return _namingOptions;
- }
-
public ItemLookupInfo ParseName(string name)
{
- var namingOptions = GetNamingOptions();
+ var namingOptions = _namingOptions;
var result = VideoResolver.CleanDateTime(name, namingOptions);
return new ItemLookupInfo
{
- Name = VideoResolver.TryCleanString(result.Name, namingOptions, out var newName) ? newName.ToString() : result.Name,
+ Name = VideoResolver.TryCleanString(result.Name, namingOptions, out var newName) ? newName : result.Name,
Year = result.Year
};
}
- public IEnumerable<Video> FindTrailers(BaseItem owner, List<FileSystemMetadata> fileSystemChildren, IDirectoryService directoryService)
+ public IEnumerable<BaseItem> FindExtras(BaseItem owner, List<FileSystemMetadata> fileSystemChildren, IDirectoryService directoryService)
{
- var namingOptions = GetNamingOptions();
-
- var files = owner.IsInMixedFolder ? new List<FileSystemMetadata>() : fileSystemChildren.Where(i => i.IsDirectory)
- .Where(i => string.Equals(i.Name, BaseItem.TrailersFolderName, StringComparison.OrdinalIgnoreCase))
- .SelectMany(i => _fileSystem.GetFiles(i.FullName, _videoFileExtensions, false, false))
- .ToList();
-
- var videos = VideoListResolver.Resolve(fileSystemChildren, namingOptions);
-
- var currentVideo = videos.FirstOrDefault(i => string.Equals(owner.Path, i.Files[0].Path, StringComparison.OrdinalIgnoreCase));
-
- if (currentVideo != null)
+ var ownerVideoInfo = VideoResolver.Resolve(owner.Path, owner.IsFolder, _namingOptions);
+ if (ownerVideoInfo == null)
{
- files.AddRange(currentVideo.Extras.Where(i => i.ExtraType == ExtraType.Trailer).Select(i => _fileSystem.GetFileInfo(i.Path)));
+ yield break;
}
- var resolvers = new IItemResolver[]
+ var count = fileSystemChildren.Count;
+ for (var i = 0; i < count; i++)
{
- new GenericVideoResolver<Trailer>(this)
- };
-
- return ResolvePaths(files, directoryService, null, new LibraryOptions(), null, resolvers)
- .OfType<Trailer>()
- .Select(video =>
+ var current = fileSystemChildren[i];
+ if (current.IsDirectory && _namingOptions.AllExtrasTypesFolderNames.ContainsKey(current.Name))
{
- // Try to retrieve it from the db. If we don't find it, use the resolved version
- if (GetItemById(video.Id) is Trailer dbItem)
+ var filesInSubFolder = _fileSystem.GetFiles(current.FullName, null, false, false);
+ foreach (var file in filesInSubFolder)
{
- video = dbItem;
- }
-
- video.ParentId = Guid.Empty;
- video.OwnerId = owner.Id;
- video.ExtraType = ExtraType.Trailer;
- video.TrailerTypes = new[] { TrailerType.LocalTrailer };
-
- return video;
-
- // Sort them so that the list can be easily compared for changes
- }).OrderBy(i => i.Path);
- }
-
- public IEnumerable<Video> FindExtras(BaseItem owner, List<FileSystemMetadata> fileSystemChildren, IDirectoryService directoryService)
- {
- var namingOptions = GetNamingOptions();
-
- var files = owner.IsInMixedFolder ? new List<FileSystemMetadata>() : fileSystemChildren.Where(i => i.IsDirectory)
- .Where(i => BaseItem.AllExtrasTypesFolderNames.ContainsKey(i.Name ?? string.Empty))
- .SelectMany(i => _fileSystem.GetFiles(i.FullName, _videoFileExtensions, false, false))
- .ToList();
-
- var videos = VideoListResolver.Resolve(fileSystemChildren, namingOptions);
-
- var currentVideo = videos.FirstOrDefault(i => string.Equals(owner.Path, i.Files[0].Path, StringComparison.OrdinalIgnoreCase));
-
- if (currentVideo != null)
- {
- files.AddRange(currentVideo.Extras.Where(i => i.ExtraType != ExtraType.Trailer).Select(i => _fileSystem.GetFileInfo(i.Path)));
- }
+ if (!_extraResolver.TryGetExtraTypeForOwner(file.FullName, ownerVideoInfo, out var extraType))
+ {
+ continue;
+ }
- return ResolvePaths(files, directoryService, null, new LibraryOptions(), null)
- .OfType<Video>()
- .Select(video =>
+ var extra = GetExtra(file, extraType.Value);
+ if (extra != null)
+ {
+ yield return extra;
+ }
+ }
+ }
+ else if (!current.IsDirectory && _extraResolver.TryGetExtraTypeForOwner(current.FullName, ownerVideoInfo, out var extraType))
{
- // Try to retrieve it from the db. If we don't find it, use the resolved version
- var dbItem = GetItemById(video.Id) as Video;
-
- if (dbItem != null)
+ var extra = GetExtra(current, extraType.Value);
+ if (extra != null)
{
- video = dbItem;
+ yield return extra;
}
+ }
+ }
- video.ParentId = Guid.Empty;
- video.OwnerId = owner.Id;
-
- SetExtraTypeFromFilename(video);
+ BaseItem GetExtra(FileSystemMetadata file, ExtraType extraType)
+ {
+ var extra = ResolvePath(_fileSystem.GetFileInfo(file.FullName), directoryService, _extraResolver.GetResolversForExtraType(extraType));
+ if (extra is not Video && extra is not Audio)
+ {
+ return null;
+ }
- return video;
+ // Try to retrieve it from the db. If we don't find it, use the resolved version
+ var itemById = GetItemById(extra.Id);
+ if (itemById != null)
+ {
+ extra = itemById;
+ }
- // Sort them so that the list can be easily compared for changes
- }).OrderBy(i => i.Path);
+ extra.ExtraType = extraType;
+ extra.ParentId = Guid.Empty;
+ extra.OwnerId = owner.Id;
+ return extra;
+ }
}
public string GetPathAfterNetworkSubstitution(string path, BaseItem ownerItem)
@@ -2839,15 +2794,6 @@ namespace Emby.Server.Implementations.Library
return path;
}
- private void SetExtraTypeFromFilename(Video item)
- {
- var resolver = new ExtraResolver(GetNamingOptions());
-
- var result = resolver.GetExtraInfo(item.Path);
-
- item.ExtraType = result.ExtraType;
- }
-
public List<PersonInfo> GetPeople(InternalPeopleQuery query)
{
return _itemRepository.GetPeople(query);
@@ -2954,11 +2900,12 @@ namespace Emby.Server.Implementations.Library
var rootFolderPath = _configurationManager.ApplicationPaths.DefaultUserViewsPath;
+ var existingNameCount = 1; // first numbered name will be 2
var virtualFolderPath = Path.Combine(rootFolderPath, name);
while (Directory.Exists(virtualFolderPath))
{
- name += "1";
- virtualFolderPath = Path.Combine(rootFolderPath, name);
+ existingNameCount++;
+ virtualFolderPath = Path.Combine(rootFolderPath, name + " " + existingNameCount);
}
var mediaPathInfos = options.PathInfos;
diff --git a/Emby.Server.Implementations/Library/LiveStreamHelper.cs b/Emby.Server.Implementations/Library/LiveStreamHelper.cs
index 83acd8e9f..20624cc7a 100644
--- a/Emby.Server.Implementations/Library/LiveStreamHelper.cs
+++ b/Emby.Server.Implementations/Library/LiveStreamHelper.cs
@@ -66,11 +66,8 @@ namespace Emby.Server.Implementations.Library
{
var delayMs = mediaSource.AnalyzeDurationMs ?? 0;
delayMs = Math.Max(3000, delayMs);
- if (delayMs > 0)
- {
- _logger.LogInformation("Waiting {0}ms before probing the live stream", delayMs);
- await Task.Delay(delayMs, cancellationToken).ConfigureAwait(false);
- }
+ _logger.LogInformation("Waiting {0}ms before probing the live stream", delayMs);
+ await Task.Delay(delayMs, cancellationToken).ConfigureAwait(false);
}
mediaSource.AnalyzeDurationMs = 3000;
diff --git a/Emby.Server.Implementations/Library/MediaSourceManager.cs b/Emby.Server.Implementations/Library/MediaSourceManager.cs
index 16231c73f..a414e7e16 100644
--- a/Emby.Server.Implementations/Library/MediaSourceManager.cs
+++ b/Emby.Server.Implementations/Library/MediaSourceManager.cs
@@ -13,9 +13,9 @@ using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
+using Jellyfin.Extensions.Json;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
-using Jellyfin.Extensions.Json;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding;
@@ -45,6 +45,7 @@ namespace Emby.Server.Implementations.Library
private readonly IMediaEncoder _mediaEncoder;
private readonly ILocalizationManager _localizationManager;
private readonly IApplicationPaths _appPaths;
+ private readonly IDirectoryService _directoryService;
private readonly ConcurrentDictionary<string, ILiveStream> _openStreams = new ConcurrentDictionary<string, ILiveStream>(StringComparer.OrdinalIgnoreCase);
private readonly SemaphoreSlim _liveStreamSemaphore = new SemaphoreSlim(1, 1);
@@ -61,7 +62,8 @@ namespace Emby.Server.Implementations.Library
ILogger<MediaSourceManager> logger,
IFileSystem fileSystem,
IUserDataManager userDataManager,
- IMediaEncoder mediaEncoder)
+ IMediaEncoder mediaEncoder,
+ IDirectoryService directoryService)
{
_itemRepo = itemRepo;
_userManager = userManager;
@@ -72,6 +74,7 @@ namespace Emby.Server.Implementations.Library
_mediaEncoder = mediaEncoder;
_localizationManager = localizationManager;
_appPaths = applicationPaths;
+ _directoryService = directoryService;
}
public void AddParts(IEnumerable<IMediaSourceProvider> providers)
@@ -106,16 +109,6 @@ namespace Emby.Server.Implementations.Library
return false;
}
- public List<MediaStream> GetMediaStreams(string mediaSourceId)
- {
- var list = GetMediaStreams(new MediaStreamQuery
- {
- ItemId = new Guid(mediaSourceId)
- });
-
- return GetMediaStreamsForItem(list);
- }
-
public List<MediaStream> GetMediaStreams(Guid itemId)
{
var list = GetMediaStreams(new MediaStreamQuery
@@ -161,7 +154,7 @@ namespace Emby.Server.Implementations.Library
if (allowMediaProbe && mediaSources[0].Type != MediaSourceType.Placeholder && !mediaSources[0].MediaStreams.Any(i => i.Type == MediaStreamType.Audio || i.Type == MediaStreamType.Video))
{
await item.RefreshMetadata(
- new MetadataRefreshOptions(new DirectoryService(_fileSystem))
+ new MetadataRefreshOptions(_directoryService)
{
EnableRemoteContentProbe = true,
MetadataRefreshMode = MetadataRefreshMode.FullRefresh
@@ -212,6 +205,7 @@ namespace Emby.Server.Implementations.Library
return SortMediaSources(list);
}
+ /// <inheritdoc />>
public MediaProtocol GetPathProtocol(string path)
{
if (path.StartsWith("Rtsp", StringComparison.OrdinalIgnoreCase))
@@ -258,7 +252,7 @@ namespace Emby.Server.Implementations.Library
{
if (path != null)
{
- if (path.IndexOf(".m3u", StringComparison.OrdinalIgnoreCase) != -1)
+ if (path.Contains(".m3u", StringComparison.OrdinalIgnoreCase))
{
return false;
}
@@ -297,7 +291,7 @@ namespace Emby.Server.Implementations.Library
catch (Exception ex)
{
_logger.LogError(ex, "Error getting media sources");
- return new List<MediaSourceInfo>();
+ return Enumerable.Empty<MediaSourceInfo>();
}
}
@@ -470,12 +464,11 @@ namespace Emby.Server.Implementations.Library
try
{
- var tuple = GetProvider(request.OpenToken);
- var provider = tuple.Item1;
+ var (provider, keyId) = GetProvider(request.OpenToken);
var currentLiveStreams = _openStreams.Values.ToList();
- liveStream = await provider.OpenMediaSource(tuple.Item2, currentLiveStreams, cancellationToken).ConfigureAwait(false);
+ liveStream = await provider.OpenMediaSource(keyId, currentLiveStreams, cancellationToken).ConfigureAwait(false);
mediaSource = liveStream.MediaSource;
@@ -494,14 +487,11 @@ namespace Emby.Server.Implementations.Library
_liveStreamSemaphore.Release();
}
- // TODO: Don't hardcode this
- const bool isAudio = false;
-
try
{
if (mediaSource.MediaStreams.Any(i => i.Index != -1) || !mediaSource.SupportsProbing)
{
- AddMediaInfo(mediaSource, isAudio);
+ AddMediaInfo(mediaSource);
}
else
{
@@ -509,14 +499,14 @@ namespace Emby.Server.Implementations.Library
string cacheKey = request.OpenToken;
await new LiveStreamHelper(_mediaEncoder, _logger, _appPaths)
- .AddMediaInfoWithProbe(mediaSource, isAudio, cacheKey, true, cancellationToken)
+ .AddMediaInfoWithProbe(mediaSource, false, cacheKey, true, cancellationToken)
.ConfigureAwait(false);
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error probing live tv stream");
- AddMediaInfo(mediaSource, isAudio);
+ AddMediaInfo(mediaSource);
}
// TODO: @bond Fix
@@ -536,7 +526,7 @@ namespace Emby.Server.Implementations.Library
return new Tuple<LiveStreamResponse, IDirectStreamProvider>(new LiveStreamResponse(clone), liveStream as IDirectStreamProvider);
}
- private static void AddMediaInfo(MediaSourceInfo mediaSource, bool isAudio)
+ private static void AddMediaInfo(MediaSourceInfo mediaSource)
{
mediaSource.DefaultSubtitleStreamIndex = null;
@@ -838,7 +828,7 @@ namespace Emby.Server.Implementations.Library
}
}
- private (IMediaSourceProvider, string) GetProvider(string key)
+ private (IMediaSourceProvider MediaSourceProvider, string KeyId) GetProvider(string key)
{
if (string.IsNullOrEmpty(key))
{
@@ -855,9 +845,7 @@ namespace Emby.Server.Implementations.Library
return (provider, keyId);
}
- /// <summary>
- /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
- /// </summary>
+ /// <inheritdoc />
public void Dispose()
{
Dispose(true);
diff --git a/Emby.Server.Implementations/Library/MediaStreamSelector.cs b/Emby.Server.Implementations/Library/MediaStreamSelector.cs
index b833122ea..da0c89c13 100644
--- a/Emby.Server.Implementations/Library/MediaStreamSelector.cs
+++ b/Emby.Server.Implementations/Library/MediaStreamSelector.cs
@@ -6,6 +6,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using Jellyfin.Data.Enums;
+using Jellyfin.Extensions;
using MediaBrowser.Model.Entities;
namespace Emby.Server.Implementations.Library
@@ -38,14 +39,11 @@ namespace Emby.Server.Implementations.Library
}
public static int? GetDefaultSubtitleStreamIndex(
- List<MediaStream> streams,
+ IEnumerable<MediaStream> streams,
string[] preferredLanguages,
SubtitlePlaybackMode mode,
string audioTrackLanguage)
{
- streams = GetSortedStreams(streams, MediaStreamType.Subtitle, preferredLanguages)
- .ToList();
-
MediaStream stream = null;
if (mode == SubtitlePlaybackMode.None)
@@ -53,52 +51,48 @@ namespace Emby.Server.Implementations.Library
return null;
}
+ var sortedStreams = streams
+ .Where(i => i.Type == MediaStreamType.Subtitle)
+ .OrderByDescending(x => x.IsExternal)
+ .ThenByDescending(x => x.IsForced && string.Equals(x.Language, audioTrackLanguage, StringComparison.OrdinalIgnoreCase))
+ .ThenByDescending(x => x.IsForced)
+ .ThenByDescending(x => x.IsDefault)
+ .ToList();
+
if (mode == SubtitlePlaybackMode.Default)
{
// Prefer embedded metadata over smart logic
-
- stream = streams.FirstOrDefault(s => s.IsForced && string.Equals(s.Language, audioTrackLanguage, StringComparison.OrdinalIgnoreCase)) ??
- streams.FirstOrDefault(s => s.IsForced) ??
- streams.FirstOrDefault(s => s.IsDefault);
+ stream = sortedStreams.FirstOrDefault(s => s.IsExternal || s.IsForced || s.IsDefault);
// if the audio language is not understood by the user, load their preferred subs, if there are any
- if (stream == null && !preferredLanguages.Contains(audioTrackLanguage, StringComparer.OrdinalIgnoreCase))
+ if (stream == null && !preferredLanguages.Contains(audioTrackLanguage, StringComparison.OrdinalIgnoreCase))
{
- stream = streams.Where(s => !s.IsForced).FirstOrDefault(s => preferredLanguages.Contains(s.Language, StringComparer.OrdinalIgnoreCase));
+ stream = sortedStreams.FirstOrDefault(s => !s.IsForced && preferredLanguages.Contains(s.Language, StringComparison.OrdinalIgnoreCase));
}
}
else if (mode == SubtitlePlaybackMode.Smart)
{
- // Prefer smart logic over embedded metadata
-
// if the audio language is not understood by the user, load their preferred subs, if there are any
- if (!preferredLanguages.Contains(audioTrackLanguage, StringComparer.OrdinalIgnoreCase))
+ if (!preferredLanguages.Contains(audioTrackLanguage, StringComparison.OrdinalIgnoreCase))
{
- stream = streams.Where(s => !s.IsForced).FirstOrDefault(s => preferredLanguages.Contains(s.Language, StringComparer.OrdinalIgnoreCase)) ??
- streams.FirstOrDefault(s => preferredLanguages.Contains(s.Language, StringComparer.OrdinalIgnoreCase));
+ stream = streams.FirstOrDefault(s => !s.IsForced && preferredLanguages.Contains(s.Language, StringComparison.OrdinalIgnoreCase)) ??
+ streams.FirstOrDefault(s => preferredLanguages.Contains(s.Language, StringComparison.OrdinalIgnoreCase));
}
}
else if (mode == SubtitlePlaybackMode.Always)
{
// always load the most suitable full subtitles
- stream = streams.FirstOrDefault(s => !s.IsForced);
+ stream = sortedStreams.FirstOrDefault(s => !s.IsForced);
}
else if (mode == SubtitlePlaybackMode.OnlyForced)
{
// always load the most suitable full subtitles
- stream = streams.FirstOrDefault(s => s.IsForced && string.Equals(s.Language, audioTrackLanguage, StringComparison.OrdinalIgnoreCase)) ??
- streams.FirstOrDefault(s => s.IsForced);
+ stream = sortedStreams.FirstOrDefault(x => x.IsForced);
}
// load forced subs if we have found no suitable full subtitles
- stream ??= streams.FirstOrDefault(s => s.IsForced && string.Equals(s.Language, audioTrackLanguage, StringComparison.OrdinalIgnoreCase));
-
- if (stream != null)
- {
- return stream.Index;
- }
-
- return null;
+ stream ??= sortedStreams.FirstOrDefault(s => s.IsForced && string.Equals(s.Language, audioTrackLanguage, StringComparison.OrdinalIgnoreCase));
+ return stream?.Index;
}
private static IEnumerable<MediaStream> GetSortedStreams(IEnumerable<MediaStream> streams, MediaStreamType type, string[] languagePreferences)
@@ -143,9 +137,9 @@ namespace Emby.Server.Implementations.Library
else if (mode == SubtitlePlaybackMode.Smart)
{
// Prefer smart logic over embedded metadata
- if (!preferredLanguages.Contains(audioTrackLanguage, StringComparer.OrdinalIgnoreCase))
+ if (!preferredLanguages.Contains(audioTrackLanguage, StringComparison.OrdinalIgnoreCase))
{
- filteredStreams = streams.Where(s => !s.IsForced && preferredLanguages.Contains(s.Language, StringComparer.OrdinalIgnoreCase))
+ filteredStreams = streams.Where(s => !s.IsForced && preferredLanguages.Contains(s.Language, StringComparison.OrdinalIgnoreCase))
.ToList();
}
}
diff --git a/Emby.Server.Implementations/Library/MusicManager.cs b/Emby.Server.Implementations/Library/MusicManager.cs
index e2f1fb0ad..d35e74e7b 100644
--- a/Emby.Server.Implementations/Library/MusicManager.cs
+++ b/Emby.Server.Implementations/Library/MusicManager.cs
@@ -52,7 +52,7 @@ namespace Emby.Server.Implementations.Library
var genres = item
.GetRecursiveChildren(user, new InternalItemsQuery(user)
{
- IncludeItemTypes = new[] { nameof(Audio) },
+ IncludeItemTypes = new[] { BaseItemKind.Audio },
DtoOptions = dtoOptions
})
.Cast<Audio>()
@@ -89,7 +89,7 @@ namespace Emby.Server.Implementations.Library
{
return _libraryManager.GetItemList(new InternalItemsQuery(user)
{
- IncludeItemTypes = new[] { nameof(Audio) },
+ IncludeItemTypes = new[] { BaseItemKind.Audio },
GenreIds = genreIds.ToArray(),
@@ -103,7 +103,7 @@ namespace Emby.Server.Implementations.Library
public List<BaseItem> GetInstantMixFromItem(BaseItem item, User user, DtoOptions dtoOptions)
{
- if (item is MusicGenre genre)
+ if (item is MusicGenre)
{
return GetInstantMixFromGenreIds(new[] { item.Id }, user, dtoOptions);
}
diff --git a/Emby.Server.Implementations/Library/PathExtensions.cs b/Emby.Server.Implementations/Library/PathExtensions.cs
index 86b8039fa..64e7d5446 100644
--- a/Emby.Server.Implementations/Library/PathExtensions.cs
+++ b/Emby.Server.Implementations/Library/PathExtensions.cs
@@ -16,7 +16,7 @@ namespace Emby.Server.Implementations.Library
/// <param name="attribute">The attrib.</param>
/// <returns>System.String.</returns>
/// <exception cref="ArgumentException"><paramref name="str" /> or <paramref name="attribute" /> is empty.</exception>
- public static string? GetAttributeValue(this string str, string attribute)
+ public static string? GetAttributeValue(this ReadOnlySpan<char> str, ReadOnlySpan<char> attribute)
{
if (str.Length == 0)
{
@@ -28,17 +28,31 @@ namespace Emby.Server.Implementations.Library
throw new ArgumentException("String can't be empty.", nameof(attribute));
}
- string srch = "[" + attribute + "=";
- int start = str.IndexOf(srch, StringComparison.OrdinalIgnoreCase);
- if (start != -1)
+ var attributeIndex = str.IndexOf(attribute, StringComparison.OrdinalIgnoreCase);
+
+ // Must be at least 3 characters after the attribute =, ], any character.
+ var maxIndex = str.Length - attribute.Length - 3;
+ while (attributeIndex > -1 && attributeIndex < maxIndex)
{
- start += srch.Length;
- int end = str.IndexOf(']', start);
- return str.Substring(start, end - start);
+ var attributeEnd = attributeIndex + attribute.Length;
+ if (attributeIndex > 0
+ && str[attributeIndex - 1] == '['
+ && (str[attributeEnd] == '=' || str[attributeEnd] == '-'))
+ {
+ var closingIndex = str[attributeEnd..].IndexOf(']');
+ // Must be at least 1 character before the closing bracket.
+ if (closingIndex > 1)
+ {
+ return str[(attributeEnd + 1)..(attributeEnd + closingIndex)].Trim().ToString();
+ }
+ }
+
+ str = str[attributeEnd..];
+ attributeIndex = str.IndexOf(attribute, StringComparison.OrdinalIgnoreCase);
}
// for imdbid we also accept pattern matching
- if (string.Equals(attribute, "imdbid", StringComparison.OrdinalIgnoreCase))
+ if (attribute.Equals("imdbid", StringComparison.OrdinalIgnoreCase))
{
var match = ProviderIdParsers.TryFindImdbId(str, out var imdbId);
return match ? imdbId.ToString() : null;
@@ -53,7 +67,7 @@ namespace Emby.Server.Implementations.Library
/// <param name="path">The original path.</param>
/// <param name="subPath">The original sub path.</param>
/// <param name="newSubPath">The new sub path.</param>
- /// <param name="newPath">The result of the sub path replacement</param>
+ /// <param name="newPath">The result of the sub path replacement.</param>
/// <returns>The path after replacing the sub path.</returns>
/// <exception cref="ArgumentNullException"><paramref name="path" />, <paramref name="newSubPath" /> or <paramref name="newSubPath" /> is empty.</exception>
public static bool TryReplaceSubPath(
diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs
index fd9747b4b..7a6aea9c1 100644
--- a/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs
@@ -6,7 +6,10 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
+using Emby.Naming.Audio;
using Emby.Naming.AudioBook;
+using Emby.Naming.Common;
+using Emby.Naming.Video;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
@@ -21,11 +24,11 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
/// </summary>
public class AudioResolver : ItemResolver<MediaBrowser.Controller.Entities.Audio.Audio>, IMultiItemResolver
{
- private readonly ILibraryManager _libraryManager;
+ private readonly NamingOptions _namingOptions;
- public AudioResolver(ILibraryManager libraryManager)
+ public AudioResolver(NamingOptions namingOptions)
{
- _libraryManager = libraryManager;
+ _namingOptions = namingOptions;
}
/// <summary>
@@ -40,7 +43,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
string collectionType,
IDirectoryService directoryService)
{
- var result = ResolveMultipleInternal(parent, files, collectionType, directoryService);
+ var result = ResolveMultipleInternal(parent, files, collectionType);
if (result != null)
{
@@ -56,12 +59,11 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
private MultiItemResolverResult ResolveMultipleInternal(
Folder parent,
List<FileSystemMetadata> files,
- string collectionType,
- IDirectoryService directoryService)
+ string collectionType)
{
if (string.Equals(collectionType, CollectionType.Books, StringComparison.OrdinalIgnoreCase))
{
- return ResolveMultipleAudio<AudioBook>(parent, files, directoryService, false, collectionType, true);
+ return ResolveMultipleAudio(parent, files, true);
}
return null;
@@ -87,14 +89,10 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
return null;
}
- var files = args.FileSystemChildren
- .Where(i => !_libraryManager.IgnoreFile(i, args.Parent))
- .ToList();
-
- return FindAudio<AudioBook>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, false);
+ return FindAudioBook(args, false);
}
- if (_libraryManager.IsAudioFile(args.Path))
+ if (AudioFileParser.IsAudioFile(args.Path, _namingOptions))
{
var extension = Path.GetExtension(args.Path);
@@ -107,7 +105,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
var isMixedCollectionType = string.IsNullOrEmpty(collectionType);
// For conflicting extensions, give priority to videos
- if (isMixedCollectionType && _libraryManager.IsVideoFile(args.Path))
+ if (isMixedCollectionType && VideoResolver.IsVideoFile(args.Path, _namingOptions))
{
return null;
}
@@ -141,29 +139,23 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
return null;
}
- private T FindAudio<T>(ItemResolveArgs args, string path, Folder parent, List<FileSystemMetadata> fileSystemEntries, IDirectoryService directoryService, string collectionType, bool parseName)
- where T : MediaBrowser.Controller.Entities.Audio.Audio, new()
+ private AudioBook FindAudioBook(ItemResolveArgs args, bool parseName)
{
// TODO: Allow GetMultiDiscMovie in here
- const bool supportsMultiVersion = false;
+ var result = ResolveMultipleAudio(args.Parent, args.GetActualFileSystemChildren(), parseName);
- var result = ResolveMultipleAudio<T>(parent, fileSystemEntries, directoryService, supportsMultiVersion, collectionType, parseName) ??
- new MultiItemResolverResult();
-
- if (result.Items.Count == 1)
+ if (result == null || result.Items.Count != 1 || result.Items[0] is not AudioBook item)
{
- // If we were supporting this we'd be checking filesFromOtherItems
- var item = (T)result.Items[0];
- item.IsInMixedFolder = false;
- item.Name = Path.GetFileName(item.ContainingFolderPath);
- return item;
+ return null;
}
- return null;
+ // If we were supporting this we'd be checking filesFromOtherItems
+ item.IsInMixedFolder = false;
+ item.Name = Path.GetFileName(item.ContainingFolderPath);
+ return item;
}
- private MultiItemResolverResult ResolveMultipleAudio<T>(Folder parent, IEnumerable<FileSystemMetadata> fileSystemEntries, IDirectoryService directoryService, bool suppportMultiEditions, string collectionType, bool parseName)
- where T : MediaBrowser.Controller.Entities.Audio.Audio, new()
+ private MultiItemResolverResult ResolveMultipleAudio(Folder parent, IEnumerable<FileSystemMetadata> fileSystemEntries, bool parseName)
{
var files = new List<FileSystemMetadata>();
var items = new List<BaseItem>();
@@ -176,15 +168,13 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
{
leftOver.Add(child);
}
- else if (!IsIgnored(child.Name))
+ else
{
files.Add(child);
}
}
- var namingOptions = ((LibraryManager)_libraryManager).GetNamingOptions();
-
- var resolver = new AudioBookListResolver(namingOptions);
+ var resolver = new AudioBookListResolver(_namingOptions);
var resolverResult = resolver.Resolve(files).ToList();
var result = new MultiItemResolverResult
@@ -210,7 +200,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
var firstMedia = resolvedItem.Files[0];
- var libraryItem = new T
+ var libraryItem = new AudioBook
{
Path = firstMedia.Path,
IsInMixedFolder = isInMixedFolder,
@@ -230,12 +220,12 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
return result;
}
- private bool ContainsFile(List<AudioBookInfo> result, FileSystemMetadata file)
+ private static bool ContainsFile(IEnumerable<AudioBookInfo> result, FileSystemMetadata file)
{
return result.Any(i => ContainsFile(i, file));
}
- private bool ContainsFile(AudioBookInfo result, FileSystemMetadata file)
+ private static bool ContainsFile(AudioBookInfo result, FileSystemMetadata file)
{
return result.Files.Any(i => ContainsFile(i, file)) ||
result.AlternateVersions.Any(i => ContainsFile(i, file)) ||
@@ -246,10 +236,5 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
{
return string.Equals(result.Path, file.FullName, StringComparison.OrdinalIgnoreCase);
}
-
- private static bool IsIgnored(string filename)
- {
- return false;
- }
}
}
diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs
index 8e1eccb10..da00b9cfa 100644
--- a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs
@@ -6,6 +6,7 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Emby.Naming.Audio;
+using Emby.Naming.Common;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
@@ -22,20 +23,17 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
public class MusicAlbumResolver : ItemResolver<MusicAlbum>
{
private readonly ILogger<MusicAlbumResolver> _logger;
- private readonly IFileSystem _fileSystem;
- private readonly ILibraryManager _libraryManager;
+ private readonly NamingOptions _namingOptions;
/// <summary>
/// Initializes a new instance of the <see cref="MusicAlbumResolver"/> class.
/// </summary>
/// <param name="logger">The logger.</param>
- /// <param name="fileSystem">The file system.</param>
- /// <param name="libraryManager">The library manager.</param>
- public MusicAlbumResolver(ILogger<MusicAlbumResolver> logger, IFileSystem fileSystem, ILibraryManager libraryManager)
+ /// <param name="namingOptions">The naming options.</param>
+ public MusicAlbumResolver(ILogger<MusicAlbumResolver> logger, NamingOptions namingOptions)
{
_logger = logger;
- _fileSystem = fileSystem;
- _libraryManager = libraryManager;
+ _namingOptions = namingOptions;
}
/// <summary>
@@ -82,9 +80,12 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
/// <summary>
/// Determine if the supplied file data points to a music album.
/// </summary>
+ /// <param name="path">The path to check.</param>
+ /// <param name="directoryService">The directory service.</param>
+ /// <returns><c>true</c> if the provided path points to a music album, <c>false</c> otherwise.</returns>
public bool IsMusicAlbum(string path, IDirectoryService directoryService)
{
- return ContainsMusic(directoryService.GetFileSystemEntries(path), true, directoryService, _logger, _fileSystem, _libraryManager);
+ return ContainsMusic(directoryService.GetFileSystemEntries(path), true, directoryService);
}
/// <summary>
@@ -98,7 +99,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
if (args.IsDirectory)
{
// if (args.Parent is MusicArtist) return true; // saves us from testing children twice
- if (ContainsMusic(args.FileSystemChildren, true, args.DirectoryService, _logger, _fileSystem, _libraryManager))
+ if (ContainsMusic(args.FileSystemChildren, true, args.DirectoryService))
{
return true;
}
@@ -111,15 +112,12 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
/// Determine if the supplied list contains what we should consider music.
/// </summary>
private bool ContainsMusic(
- IEnumerable<FileSystemMetadata> list,
+ ICollection<FileSystemMetadata> list,
bool allowSubfolders,
- IDirectoryService directoryService,
- ILogger<MusicAlbumResolver> logger,
- IFileSystem fileSystem,
- ILibraryManager libraryManager)
+ IDirectoryService directoryService)
{
// check for audio files before digging down into directories
- var foundAudioFile = list.Any(fileSystemInfo => !fileSystemInfo.IsDirectory && libraryManager.IsAudioFile(fileSystemInfo.FullName));
+ var foundAudioFile = list.Any(fileSystemInfo => !fileSystemInfo.IsDirectory && AudioFileParser.IsAudioFile(fileSystemInfo.FullName, _namingOptions));
if (foundAudioFile)
{
// at least one audio file exists
@@ -134,21 +132,20 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
var discSubfolderCount = 0;
- var namingOptions = ((LibraryManager)_libraryManager).GetNamingOptions();
- var parser = new AlbumParser(namingOptions);
+ var parser = new AlbumParser(_namingOptions);
var directories = list.Where(fileSystemInfo => fileSystemInfo.IsDirectory);
var result = Parallel.ForEach(directories, (fileSystemInfo, state) =>
{
var path = fileSystemInfo.FullName;
- var hasMusic = ContainsMusic(directoryService.GetFileSystemEntries(path), false, directoryService, logger, fileSystem, libraryManager);
+ var hasMusic = ContainsMusic(directoryService.GetFileSystemEntries(path), false, directoryService);
if (hasMusic)
{
if (parser.IsMultiPart(path))
{
- logger.LogDebug("Found multi-disc folder: " + path);
+ _logger.LogDebug("Found multi-disc folder: {Path}", path);
Interlocked.Increment(ref discSubfolderCount);
}
else
diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs
index 3d2ae95d2..210ed0953 100644
--- a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs
@@ -3,12 +3,11 @@
using System;
using System.Linq;
using System.Threading.Tasks;
-using MediaBrowser.Controller.Configuration;
+using Emby.Naming.Common;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Resolvers;
using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.IO;
using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Library.Resolvers.Audio
@@ -19,27 +18,19 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
public class MusicArtistResolver : ItemResolver<MusicArtist>
{
private readonly ILogger<MusicAlbumResolver> _logger;
- private readonly IFileSystem _fileSystem;
- private readonly ILibraryManager _libraryManager;
- private readonly IServerConfigurationManager _config;
+ private NamingOptions _namingOptions;
/// <summary>
/// Initializes a new instance of the <see cref="MusicArtistResolver"/> class.
/// </summary>
/// <param name="logger">The logger for the created <see cref="MusicAlbumResolver"/> instances.</param>
- /// <param name="fileSystem">The file system.</param>
- /// <param name="libraryManager">The library manager.</param>
- /// <param name="config">The configuration manager.</param>
+ /// <param name="namingOptions">The naming options.</param>
public MusicArtistResolver(
ILogger<MusicAlbumResolver> logger,
- IFileSystem fileSystem,
- ILibraryManager libraryManager,
- IServerConfigurationManager config)
+ NamingOptions namingOptions)
{
_logger = logger;
- _fileSystem = fileSystem;
- _libraryManager = libraryManager;
- _config = config;
+ _namingOptions = namingOptions;
}
/// <summary>
@@ -89,7 +80,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
var directoryService = args.DirectoryService;
- var albumResolver = new MusicAlbumResolver(_logger, _fileSystem, _libraryManager);
+ var albumResolver = new MusicAlbumResolver(_logger, _namingOptions);
// If we contain an album assume we are an artist folder
var directories = args.FileSystemChildren.Where(i => i.IsDirectory);
diff --git a/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs
index b102b86cf..9222a9479 100644
--- a/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs
@@ -6,6 +6,7 @@ using System;
using System.IO;
using System.Linq;
using DiscUtils.Udf;
+using Emby.Naming.Common;
using Emby.Naming.Video;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
@@ -21,12 +22,12 @@ namespace Emby.Server.Implementations.Library.Resolvers
public abstract class BaseVideoResolver<T> : MediaBrowser.Controller.Resolvers.ItemResolver<T>
where T : Video, new()
{
- protected BaseVideoResolver(ILibraryManager libraryManager)
+ protected BaseVideoResolver(NamingOptions namingOptions)
{
- LibraryManager = libraryManager;
+ NamingOptions = namingOptions;
}
- protected ILibraryManager LibraryManager { get; }
+ protected NamingOptions NamingOptions { get; }
/// <summary>
/// Resolves the specified args.
@@ -48,120 +49,71 @@ namespace Emby.Server.Implementations.Library.Resolvers
protected virtual TVideoType ResolveVideo<TVideoType>(ItemResolveArgs args, bool parseName)
where TVideoType : Video, new()
{
- var namingOptions = LibraryManager.GetNamingOptions();
+ VideoFileInfo videoInfo = null;
+ VideoType? videoType = null;
// If the path is a file check for a matching extensions
if (args.IsDirectory)
{
- TVideoType video = null;
- VideoFileInfo videoInfo = null;
-
// Loop through each child file/folder and see if we find a video
foreach (var child in args.FileSystemChildren)
{
var filename = child.Name;
-
if (child.IsDirectory)
{
if (IsDvdDirectory(child.FullName, filename, args.DirectoryService))
{
- videoInfo = VideoResolver.ResolveDirectory(args.Path, namingOptions);
-
- if (videoInfo == null)
- {
- return null;
- }
-
- video = new TVideoType
- {
- Path = args.Path,
- VideoType = VideoType.Dvd,
- ProductionYear = videoInfo.Year
- };
- break;
+ videoType = VideoType.Dvd;
}
-
- if (IsBluRayDirectory(filename))
+ else if (IsBluRayDirectory(filename))
{
- videoInfo = VideoResolver.ResolveDirectory(args.Path, namingOptions);
-
- if (videoInfo == null)
- {
- return null;
- }
-
- video = new TVideoType
- {
- Path = args.Path,
- VideoType = VideoType.BluRay,
- ProductionYear = videoInfo.Year
- };
- break;
+ videoType = VideoType.BluRay;
}
}
else if (IsDvdFile(filename))
{
- videoInfo = VideoResolver.ResolveDirectory(args.Path, namingOptions);
-
- if (videoInfo == null)
- {
- return null;
- }
-
- video = new TVideoType
- {
- Path = args.Path,
- VideoType = VideoType.Dvd,
- ProductionYear = videoInfo.Year
- };
- break;
+ videoType = VideoType.Dvd;
}
- }
- if (video != null)
- {
- video.Name = parseName ?
- videoInfo.Name :
- Path.GetFileName(args.Path);
+ if (videoType == null)
+ {
+ continue;
+ }
- Set3DFormat(video, videoInfo);
+ videoInfo = VideoResolver.ResolveDirectory(args.Path, NamingOptions, parseName);
+ break;
}
-
- return video;
}
else
{
- var videoInfo = VideoResolver.Resolve(args.Path, false, namingOptions, false);
-
- if (videoInfo == null)
- {
- return null;
- }
-
- if (LibraryManager.IsVideoFile(args.Path) || videoInfo.IsStub)
- {
- var path = args.Path;
-
- var video = new TVideoType
- {
- Path = path,
- IsInMixedFolder = true,
- ProductionYear = videoInfo.Year
- };
-
- SetVideoType(video, videoInfo);
+ videoInfo = VideoResolver.Resolve(args.Path, false, NamingOptions, parseName);
+ }
- video.Name = parseName ?
- videoInfo.Name :
- Path.GetFileNameWithoutExtension(args.Path);
+ if (videoInfo == null || (!videoInfo.IsStub && !VideoResolver.IsVideoFile(args.Path, NamingOptions)))
+ {
+ return null;
+ }
- Set3DFormat(video, videoInfo);
+ var video = new TVideoType
+ {
+ Name = videoInfo.Name,
+ Path = args.Path,
+ ProductionYear = videoInfo.Year,
+ ExtraType = videoInfo.ExtraType
+ };
- return video;
- }
+ if (videoType.HasValue)
+ {
+ video.VideoType = videoType.Value;
}
+ else
+ {
+ SetVideoType(video, videoInfo);
+ }
+
+ Set3DFormat(video, videoInfo);
- return null;
+ return video;
}
protected void SetVideoType(Video video, VideoFileInfo videoInfo)
@@ -206,8 +158,8 @@ namespace Emby.Server.Implementations.Library.Resolvers
{
// use disc-utils, both DVDs and BDs use UDF filesystem
using (var videoFileStream = File.Open(video.Path, FileMode.Open, FileAccess.Read))
+ using (UdfReader udfReader = new UdfReader(videoFileStream))
{
- UdfReader udfReader = new UdfReader(videoFileStream);
if (udfReader.DirectoryExists("VIDEO_TS"))
{
video.IsoType = IsoType.Dvd;
@@ -267,7 +219,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
protected void Set3DFormat(Video video)
{
- var result = Format3DParser.Parse(video.Path, LibraryManager.GetNamingOptions());
+ var result = Format3DParser.Parse(video.Path, NamingOptions);
Set3DFormat(video, result.Is3D, result.Format3D);
}
@@ -275,6 +227,10 @@ namespace Emby.Server.Implementations.Library.Resolvers
/// <summary>
/// Determines whether [is DVD directory] [the specified directory name].
/// </summary>
+ /// <param name="fullPath">The full path of the directory.</param>
+ /// <param name="directoryName">The name of the directory.</param>
+ /// <param name="directoryService">The directory service.</param>
+ /// <returns><c>true</c> if the provided directory is a DVD directory, <c>false</c> otherwise.</returns>
protected bool IsDvdDirectory(string fullPath, string directoryName, IDirectoryService directoryService)
{
if (!string.Equals(directoryName, "video_ts", StringComparison.OrdinalIgnoreCase))
diff --git a/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs
index 68076730b..8f224f547 100644
--- a/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs
@@ -5,6 +5,7 @@
using System;
using System.IO;
using System.Linq;
+using Jellyfin.Extensions;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Entities;
@@ -32,7 +33,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Books
var extension = Path.GetExtension(args.Path);
- if (extension != null && _validExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
+ if (extension != null && _validExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase))
{
// It's a book
return new Book
@@ -49,13 +50,12 @@ namespace Emby.Server.Implementations.Library.Resolvers.Books
{
var bookFiles = args.FileSystemChildren.Where(f =>
{
- var fileExtension = Path.GetExtension(f.FullName) ??
- string.Empty;
+ var fileExtension = Path.GetExtension(f.FullName)
+ ?? string.Empty;
return _validExtensions.Contains(
fileExtension,
- StringComparer
- .OrdinalIgnoreCase);
+ StringComparer.OrdinalIgnoreCase);
}).ToList();
// Don't return a Book if there is more (or less) than one document in the directory
diff --git a/Emby.Server.Implementations/Library/Resolvers/ExtraResolver.cs b/Emby.Server.Implementations/Library/Resolvers/ExtraResolver.cs
new file mode 100644
index 000000000..3d06ceb5e
--- /dev/null
+++ b/Emby.Server.Implementations/Library/Resolvers/ExtraResolver.cs
@@ -0,0 +1,93 @@
+using System;
+using System.Diagnostics.CodeAnalysis;
+using System.IO;
+using Emby.Naming.Common;
+using Emby.Naming.Video;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Resolvers;
+using MediaBrowser.Model.Entities;
+using static Emby.Naming.Video.ExtraRuleResolver;
+
+namespace Emby.Server.Implementations.Library.Resolvers
+{
+ /// <summary>
+ /// Resolves a Path into a Video or Video subclass.
+ /// </summary>
+ internal class ExtraResolver
+ {
+ private readonly NamingOptions _namingOptions;
+ private readonly IItemResolver[] _trailerResolvers;
+ private readonly IItemResolver[] _videoResolvers;
+
+ /// <summary>
+ /// Initializes an new instance of the <see cref="ExtraResolver"/> class.
+ /// </summary>
+ /// <param name="namingOptions">An instance of <see cref="NamingOptions"/>.</param>
+ public ExtraResolver(NamingOptions namingOptions)
+ {
+ _namingOptions = namingOptions;
+ _trailerResolvers = new IItemResolver[] { new GenericVideoResolver<Trailer>(namingOptions) };
+ _videoResolvers = new IItemResolver[] { new GenericVideoResolver<Video>(namingOptions) };
+ }
+
+ /// <summary>
+ /// Gets the resolvers for the extra type.
+ /// </summary>
+ /// <param name="extraType">The extra type.</param>
+ /// <returns>The resolvers for the extra type.</returns>
+ public IItemResolver[]? GetResolversForExtraType(ExtraType extraType) => extraType switch
+ {
+ ExtraType.Trailer => _trailerResolvers,
+ // For audio we'll have to rely on the AudioResolver, which is a "built-in"
+ ExtraType.ThemeSong => null,
+ _ => _videoResolvers
+ };
+
+ public bool TryGetExtraTypeForOwner(string path, VideoFileInfo ownerVideoFileInfo, [NotNullWhen(true)] out ExtraType? extraType)
+ {
+ var extraResult = GetExtraInfo(path, _namingOptions);
+ if (extraResult.ExtraType == null)
+ {
+ extraType = null;
+ return false;
+ }
+
+ var cleanDateTimeResult = CleanDateTimeParser.Clean(Path.GetFileNameWithoutExtension(path), _namingOptions.CleanDateTimeRegexes);
+ var name = cleanDateTimeResult.Name;
+ var year = cleanDateTimeResult.Year;
+
+ var parentDir = ownerVideoFileInfo.IsDirectory ? ownerVideoFileInfo.Path : Path.GetDirectoryName(ownerVideoFileInfo.Path.AsSpan());
+
+ var trimmedFileNameWithoutExtension = TrimFilenameDelimiters(ownerVideoFileInfo.FileNameWithoutExtension, _namingOptions.VideoFlagDelimiters);
+ var trimmedVideoInfoName = TrimFilenameDelimiters(ownerVideoFileInfo.Name, _namingOptions.VideoFlagDelimiters);
+ var trimmedExtraFileName = TrimFilenameDelimiters(name, _namingOptions.VideoFlagDelimiters);
+
+ // first check filenames
+ bool isValid = StartsWith(trimmedExtraFileName, trimmedFileNameWithoutExtension)
+ || (StartsWith(trimmedExtraFileName, trimmedVideoInfoName) && year == ownerVideoFileInfo.Year);
+
+ if (!isValid)
+ {
+ // When the extra rule type is DirectoryName we must go one level higher to get the "real" dir name
+ var currentParentDir = extraResult.Rule?.RuleType == ExtraRuleType.DirectoryName
+ ? Path.GetDirectoryName(Path.GetDirectoryName(path.AsSpan()))
+ : Path.GetDirectoryName(path.AsSpan());
+
+ isValid = !currentParentDir.IsEmpty && !parentDir.IsEmpty && currentParentDir.Equals(parentDir, StringComparison.OrdinalIgnoreCase);
+ }
+
+ extraType = extraResult.ExtraType;
+ return isValid;
+ }
+
+ private static ReadOnlySpan<char> TrimFilenameDelimiters(ReadOnlySpan<char> name, ReadOnlySpan<char> videoFlagDelimiters)
+ {
+ return name.IsEmpty ? name : name.TrimEnd().TrimEnd(videoFlagDelimiters).TrimEnd();
+ }
+
+ private static bool StartsWith(ReadOnlySpan<char> fileName, ReadOnlySpan<char> baseName)
+ {
+ return !baseName.IsEmpty && fileName.StartsWith(baseName, StringComparison.OrdinalIgnoreCase);
+ }
+ }
+}
diff --git a/Emby.Server.Implementations/Library/Resolvers/FolderResolver.cs b/Emby.Server.Implementations/Library/Resolvers/FolderResolver.cs
index 7aaee017d..db7703cd6 100644
--- a/Emby.Server.Implementations/Library/Resolvers/FolderResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/FolderResolver.cs
@@ -9,7 +9,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
/// <summary>
/// Class FolderResolver.
/// </summary>
- public class FolderResolver : FolderResolver<Folder>
+ public class FolderResolver : GenericFolderResolver<Folder>
{
/// <summary>
/// Gets the priority.
@@ -32,24 +32,4 @@ namespace Emby.Server.Implementations.Library.Resolvers
return null;
}
}
-
- /// <summary>
- /// Class FolderResolver.
- /// </summary>
- /// <typeparam name="TItemType">The type of the T item type.</typeparam>
- public abstract class FolderResolver<TItemType> : ItemResolver<TItemType>
- where TItemType : Folder, new()
- {
- /// <summary>
- /// Sets the initial item values.
- /// </summary>
- /// <param name="item">The item.</param>
- /// <param name="args">The args.</param>
- protected override void SetInitialItemValues(TItemType item, ItemResolveArgs args)
- {
- base.SetInitialItemValues(item, args);
-
- item.IsRoot = args.Parent == null;
- }
- }
}
diff --git a/Emby.Server.Implementations/Library/Resolvers/GenericFolderResolver.cs b/Emby.Server.Implementations/Library/Resolvers/GenericFolderResolver.cs
new file mode 100644
index 000000000..f109a5e9a
--- /dev/null
+++ b/Emby.Server.Implementations/Library/Resolvers/GenericFolderResolver.cs
@@ -0,0 +1,27 @@
+#nullable disable
+
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Library;
+
+namespace Emby.Server.Implementations.Library.Resolvers
+{
+ /// <summary>
+ /// Class FolderResolver.
+ /// </summary>
+ /// <typeparam name="TItemType">The type of the T item type.</typeparam>
+ public abstract class GenericFolderResolver<TItemType> : ItemResolver<TItemType>
+ where TItemType : Folder, new()
+ {
+ /// <summary>
+ /// Sets the initial item values.
+ /// </summary>
+ /// <param name="item">The item.</param>
+ /// <param name="args">The args.</param>
+ protected override void SetInitialItemValues(TItemType item, ItemResolveArgs args)
+ {
+ base.SetInitialItemValues(item, args);
+
+ item.IsRoot = args.Parent == null;
+ }
+ }
+}
diff --git a/Emby.Server.Implementations/Library/Resolvers/GenericVideoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/GenericVideoResolver.cs
index 9599faea4..b8554bd51 100644
--- a/Emby.Server.Implementations/Library/Resolvers/GenericVideoResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/GenericVideoResolver.cs
@@ -1,17 +1,23 @@
-#nullable disable
-
-#pragma warning disable CS1591
+#nullable disable
+using Emby.Naming.Common;
using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Library;
namespace Emby.Server.Implementations.Library.Resolvers
{
+ /// <summary>
+ /// Resolves a Path into an instance of the <see cref="Video"/> class.
+ /// </summary>
+ /// <typeparam name="T">The type of item to resolve.</typeparam>
public class GenericVideoResolver<T> : BaseVideoResolver<T>
where T : Video, new()
{
- public GenericVideoResolver(ILibraryManager libraryManager)
- : base(libraryManager)
+ /// <summary>
+ /// Initializes a new instance of the <see cref="GenericVideoResolver{T}"/> class.
+ /// </summary>
+ /// <param name="namingOptions">The naming options.</param>
+ public GenericVideoResolver(NamingOptions namingOptions)
+ : base(namingOptions)
{
}
}
diff --git a/Emby.Server.Implementations/Library/Resolvers/Movies/BoxSetResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Movies/BoxSetResolver.cs
index 69d71d0d9..6cc04ea81 100644
--- a/Emby.Server.Implementations/Library/Resolvers/Movies/BoxSetResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/Movies/BoxSetResolver.cs
@@ -12,7 +12,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
/// <summary>
/// Class BoxSetResolver.
/// </summary>
- public class BoxSetResolver : FolderResolver<BoxSet>
+ public class BoxSetResolver : GenericFolderResolver<BoxSet>
{
/// <summary>
/// Resolves the specified args.
@@ -65,7 +65,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
private static void SetProviderIdFromPath(BaseItem item)
{
// we need to only look at the name of this actual item (not parents)
- var justName = Path.GetFileName(item.Path);
+ var justName = Path.GetFileName(item.Path.AsSpan());
var id = justName.GetAttributeValue("tmdbid");
diff --git a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs
index 8b55a7744..1a9295dc8 100644
--- a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs
@@ -5,6 +5,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
+using Emby.Naming.Common;
using Emby.Naming.Video;
using Jellyfin.Extensions;
using MediaBrowser.Controller.Drawing;
@@ -24,6 +25,8 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
/// </summary>
public class MovieResolver : BaseVideoResolver<Video>, IMultiItemResolver
{
+ private readonly IImageProcessor _imageProcessor;
+
private string[] _validCollectionTypes = new[]
{
CollectionType.Movies,
@@ -33,15 +36,13 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
CollectionType.Photos
};
- private readonly IImageProcessor _imageProcessor;
-
/// <summary>
/// Initializes a new instance of the <see cref="MovieResolver"/> class.
/// </summary>
- /// <param name="libraryManager">The library manager.</param>
/// <param name="imageProcessor">The image processor.</param>
- public MovieResolver(ILibraryManager libraryManager, IImageProcessor imageProcessor)
- : base(libraryManager)
+ /// <param name="namingOptions">The naming options.</param>
+ public MovieResolver(IImageProcessor imageProcessor, NamingOptions namingOptions)
+ : base(namingOptions)
{
_imageProcessor = imageProcessor;
}
@@ -59,7 +60,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
string collectionType,
IDirectoryService directoryService)
{
- var result = ResolveMultipleInternal(parent, files, collectionType, directoryService);
+ var result = ResolveMultipleInternal(parent, files, collectionType);
if (result != null)
{
@@ -89,26 +90,24 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
return null;
}
- var files = args.FileSystemChildren
- .Where(i => !LibraryManager.IgnoreFile(i, args.Parent))
- .ToList();
+ Video movie = null;
+ var files = args.GetActualFileSystemChildren().ToList();
if (string.Equals(collectionType, CollectionType.MusicVideos, StringComparison.OrdinalIgnoreCase))
{
- return FindMovie<MusicVideo>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, false);
+ movie = FindMovie<MusicVideo>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, false);
}
if (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase))
{
- return FindMovie<Video>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, false);
+ movie = FindMovie<Video>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, false);
}
if (string.IsNullOrEmpty(collectionType))
{
- // Owned items will be caught by the plain video resolver
+ // Owned items will be caught by the video extra resolver
if (args.Parent == null)
{
- // return FindMovie<Video>(args.Path, args.Parent, files, args.DirectoryService, collectionType);
return null;
}
@@ -117,23 +116,22 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
return null;
}
- {
- return FindMovie<Movie>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, true);
- }
+ movie = FindMovie<Movie>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, true);
}
if (string.Equals(collectionType, CollectionType.Movies, StringComparison.OrdinalIgnoreCase))
{
- return FindMovie<Movie>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, true);
+ movie = FindMovie<Movie>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, true);
}
- return null;
+ // ignore extras
+ return movie?.ExtraType == null ? movie : null;
}
- // Handle owned items
+ // Owned items will be caught by the video extra resolver
if (args.Parent == null)
{
- return base.Resolve(args);
+ return null;
}
if (IsInvalid(args.Parent, collectionType))
@@ -168,6 +166,12 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
item = ResolveVideo<Video>(args, false);
}
+ // Ignore extras
+ if (item?.ExtraType != null)
+ {
+ return null;
+ }
+
if (item != null)
{
item.IsInMixedFolder = true;
@@ -179,8 +183,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
private MultiItemResolverResult ResolveMultipleInternal(
Folder parent,
List<FileSystemMetadata> files,
- string collectionType,
- IDirectoryService directoryService)
+ string collectionType)
{
if (IsInvalid(parent, collectionType))
{
@@ -189,13 +192,13 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
if (string.Equals(collectionType, CollectionType.MusicVideos, StringComparison.OrdinalIgnoreCase))
{
- return ResolveVideos<MusicVideo>(parent, files, directoryService, true, collectionType, false);
+ return ResolveVideos<MusicVideo>(parent, files, true, collectionType, false);
}
if (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase) ||
string.Equals(collectionType, CollectionType.Photos, StringComparison.OrdinalIgnoreCase))
{
- return ResolveVideos<Video>(parent, files, directoryService, false, collectionType, false);
+ return ResolveVideos<Video>(parent, files, false, collectionType, false);
}
if (string.IsNullOrEmpty(collectionType))
@@ -203,7 +206,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
// Owned items should just use the plain video type
if (parent == null)
{
- return ResolveVideos<Video>(parent, files, directoryService, false, collectionType, false);
+ return ResolveVideos<Video>(parent, files, false, collectionType, false);
}
if (parent is Series || parent.GetParents().OfType<Series>().Any())
@@ -211,12 +214,12 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
return null;
}
- return ResolveVideos<Movie>(parent, files, directoryService, false, collectionType, true);
+ return ResolveVideos<Movie>(parent, files, false, collectionType, true);
}
if (string.Equals(collectionType, CollectionType.Movies, StringComparison.OrdinalIgnoreCase))
{
- return ResolveVideos<Movie>(parent, files, directoryService, true, collectionType, true);
+ return ResolveVideos<Movie>(parent, files, true, collectionType, true);
}
return null;
@@ -225,21 +228,20 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
private MultiItemResolverResult ResolveVideos<T>(
Folder parent,
IEnumerable<FileSystemMetadata> fileSystemEntries,
- IDirectoryService directoryService,
- bool suppportMultiEditions,
+ bool supportMultiEditions,
string collectionType,
bool parseName)
where T : Video, new()
{
var files = new List<FileSystemMetadata>();
- var videos = new List<BaseItem>();
var leftOver = new List<FileSystemMetadata>();
+ var hasCollectionType = !string.IsNullOrEmpty(collectionType);
// Loop through each child file/folder and see if we find a video
foreach (var child in fileSystemEntries)
{
// This is a hack but currently no better way to resolve a sometimes ambiguous situation
- if (string.IsNullOrEmpty(collectionType))
+ if (!hasCollectionType)
{
if (string.Equals(child.Name, "tvshow.nfo", StringComparison.OrdinalIgnoreCase)
|| string.Equals(child.Name, "season.nfo", StringComparison.OrdinalIgnoreCase))
@@ -258,31 +260,39 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
}
}
- var namingOptions = LibraryManager.GetNamingOptions();
+ var videoInfos = files
+ .Select(i => VideoResolver.Resolve(i.FullName, i.IsDirectory, NamingOptions, parseName))
+ .Where(f => f != null)
+ .ToList();
- var resolverResult = VideoListResolver.Resolve(files, namingOptions, suppportMultiEditions).ToList();
+ var resolverResult = VideoListResolver.Resolve(videoInfos, NamingOptions, supportMultiEditions, parseName);
var result = new MultiItemResolverResult
{
- ExtraFiles = leftOver,
- Items = videos
+ ExtraFiles = leftOver
};
- var isInMixedFolder = resolverResult.Count > 1 || (parent != null && parent.IsTopParent);
+ var isInMixedFolder = resolverResult.Count > 1 || parent?.IsTopParent == true;
foreach (var video in resolverResult)
{
var firstVideo = video.Files[0];
+ var path = firstVideo.Path;
+ if (video.ExtraType != null)
+ {
+ result.ExtraFiles.Add(files.Find(f => string.Equals(f.FullName, path, StringComparison.OrdinalIgnoreCase)));
+ continue;
+ }
+
+ var additionalParts = video.Files.Count > 1 ? video.Files.Skip(1).Select(i => i.Path).ToArray() : Array.Empty<string>();
var videoItem = new T
{
- Path = video.Files[0].Path,
+ Path = path,
IsInMixedFolder = isInMixedFolder,
ProductionYear = video.Year,
- Name = parseName ?
- video.Name :
- Path.GetFileNameWithoutExtension(video.Files[0].Path),
- AdditionalParts = video.Files.Skip(1).Select(i => i.Path).ToArray(),
+ Name = parseName ? video.Name : firstVideo.Name,
+ AdditionalParts = additionalParts,
LocalAlternateVersions = video.AlternateVersions.Select(i => i.Path).ToArray()
};
@@ -300,21 +310,34 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
private static bool IsIgnored(string filename)
{
// Ignore samples
- Match m = Regex.Match(filename, @"\bsample\b", RegexOptions.IgnoreCase);
+ Match m = Regex.Match(filename, @"\bsample\b", RegexOptions.IgnoreCase | RegexOptions.Compiled);
return m.Success;
}
- private bool ContainsFile(List<VideoInfo> result, FileSystemMetadata file)
+ private static bool ContainsFile(IReadOnlyList<VideoInfo> result, FileSystemMetadata file)
{
- return result.Any(i => ContainsFile(i, file));
- }
+ for (var i = 0; i < result.Count; i++)
+ {
+ var current = result[i];
+ for (var j = 0; j < current.Files.Count; j++)
+ {
+ if (ContainsFile(current.Files[j], file))
+ {
+ return true;
+ }
+ }
- private bool ContainsFile(VideoInfo result, FileSystemMetadata file)
- {
- return result.Files.Any(i => ContainsFile(i, file)) ||
- result.AlternateVersions.Any(i => ContainsFile(i, file)) ||
- result.Extras.Any(i => ContainsFile(i, file));
+ for (var j = 0; j < current.AlternateVersions.Count; j++)
+ {
+ if (ContainsFile(current.AlternateVersions[j], file))
+ {
+ return true;
+ }
+ }
+ }
+
+ return false;
}
private static bool ContainsFile(VideoFileInfo result, FileSystemMetadata file)
@@ -343,9 +366,9 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
if (item is Movie || item is MusicVideo)
{
// We need to only look at the name of this actual item (not parents)
- var justName = item.IsInMixedFolder ? Path.GetFileName(item.Path) : Path.GetFileName(item.ContainingFolderPath);
+ var justName = item.IsInMixedFolder ? Path.GetFileName(item.Path.AsSpan()) : Path.GetFileName(item.ContainingFolderPath.AsSpan());
- if (!string.IsNullOrEmpty(justName))
+ if (!justName.IsEmpty)
{
// check for tmdb id
var tmdbid = justName.GetAttributeValue("tmdbid");
@@ -359,7 +382,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
if (!string.IsNullOrEmpty(item.Path))
{
// check for imdb id - we use full media path, as we can assume, that this will match in any use case (wither id in parent dir or in file name)
- var imdbid = item.Path.GetAttributeValue("imdbid");
+ var imdbid = item.Path.AsSpan().GetAttributeValue("imdbid");
if (!string.IsNullOrWhiteSpace(imdbid))
{
@@ -432,13 +455,13 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
// TODO: Allow GetMultiDiscMovie in here
const bool SupportsMultiVersion = true;
- var result = ResolveVideos<T>(parent, fileSystemEntries, directoryService, SupportsMultiVersion, collectionType, parseName) ??
+ var result = ResolveVideos<T>(parent, fileSystemEntries, SupportsMultiVersion, collectionType, parseName) ??
new MultiItemResolverResult();
if (result.Items.Count == 1)
{
var videoPath = result.Items[0].Path;
- var hasPhotos = photos.Any(i => !PhotoResolver.IsOwnedByResolvedMedia(LibraryManager, videoPath, i.Name));
+ var hasPhotos = photos.Any(i => !PhotoResolver.IsOwnedByResolvedMedia(videoPath, i.Name));
if (!hasPhotos)
{
@@ -511,9 +534,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
return null;
}
- var namingOptions = ((LibraryManager)LibraryManager).GetNamingOptions();
-
- var result = new StackResolver(namingOptions).ResolveDirectories(folderPaths).ToList();
+ var result = StackResolver.ResolveDirectories(folderPaths, NamingOptions).ToList();
if (result.Count != 1)
{
diff --git a/Emby.Server.Implementations/Library/Resolvers/PhotoAlbumResolver.cs b/Emby.Server.Implementations/Library/Resolvers/PhotoAlbumResolver.cs
index 534bc80dd..7dd0ab185 100644
--- a/Emby.Server.Implementations/Library/Resolvers/PhotoAlbumResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/PhotoAlbumResolver.cs
@@ -1,6 +1,7 @@
#nullable disable
using System;
+using Emby.Naming.Common;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
@@ -12,20 +13,20 @@ namespace Emby.Server.Implementations.Library.Resolvers
/// <summary>
/// Class PhotoAlbumResolver.
/// </summary>
- public class PhotoAlbumResolver : FolderResolver<PhotoAlbum>
+ public class PhotoAlbumResolver : GenericFolderResolver<PhotoAlbum>
{
private readonly IImageProcessor _imageProcessor;
- private readonly ILibraryManager _libraryManager;
+ private readonly NamingOptions _namingOptions;
/// <summary>
/// Initializes a new instance of the <see cref="PhotoAlbumResolver"/> class.
/// </summary>
/// <param name="imageProcessor">The image processor.</param>
- /// <param name="libraryManager">The library manager.</param>
- public PhotoAlbumResolver(IImageProcessor imageProcessor, ILibraryManager libraryManager)
+ /// <param name="namingOptions">The naming options.</param>
+ public PhotoAlbumResolver(IImageProcessor imageProcessor, NamingOptions namingOptions)
{
_imageProcessor = imageProcessor;
- _libraryManager = libraryManager;
+ _namingOptions = namingOptions;
}
/// <inheritdoc />
@@ -73,7 +74,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
foreach (var siblingFile in files)
{
- if (PhotoResolver.IsOwnedByMedia(_libraryManager, siblingFile.FullName, filename))
+ if (PhotoResolver.IsOwnedByMedia(_namingOptions, siblingFile.FullName, filename))
{
ownedByMedia = true;
break;
diff --git a/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs
index 57bf40e9e..bc2915db6 100644
--- a/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs
@@ -6,6 +6,9 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
+using Emby.Naming.Common;
+using Emby.Naming.Video;
+using Jellyfin.Extensions;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
@@ -16,7 +19,8 @@ namespace Emby.Server.Implementations.Library.Resolvers
public class PhotoResolver : ItemResolver<Photo>
{
private readonly IImageProcessor _imageProcessor;
- private readonly ILibraryManager _libraryManager;
+ private readonly NamingOptions _namingOptions;
+
private static readonly HashSet<string> _ignoreFiles = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
"folder",
@@ -30,10 +34,10 @@ namespace Emby.Server.Implementations.Library.Resolvers
"default"
};
- public PhotoResolver(IImageProcessor imageProcessor, ILibraryManager libraryManager)
+ public PhotoResolver(IImageProcessor imageProcessor, NamingOptions namingOptions)
{
_imageProcessor = imageProcessor;
- _libraryManager = libraryManager;
+ _namingOptions = namingOptions;
}
/// <summary>
@@ -60,7 +64,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
foreach (var file in files)
{
- if (IsOwnedByMedia(_libraryManager, file.FullName, filename))
+ if (IsOwnedByMedia(_namingOptions, file.FullName, filename))
{
return null;
}
@@ -77,17 +81,12 @@ namespace Emby.Server.Implementations.Library.Resolvers
return null;
}
- internal static bool IsOwnedByMedia(ILibraryManager libraryManager, string file, string imageFilename)
+ internal static bool IsOwnedByMedia(NamingOptions namingOptions, string file, string imageFilename)
{
- if (libraryManager.IsVideoFile(file))
- {
- return IsOwnedByResolvedMedia(libraryManager, file, imageFilename);
- }
-
- return false;
+ return VideoResolver.IsVideoFile(file, namingOptions) && IsOwnedByResolvedMedia(file, imageFilename);
}
- internal static bool IsOwnedByResolvedMedia(ILibraryManager libraryManager, string file, string imageFilename)
+ internal static bool IsOwnedByResolvedMedia(string file, string imageFilename)
=> imageFilename.StartsWith(Path.GetFileNameWithoutExtension(file), StringComparison.OrdinalIgnoreCase);
internal static bool IsImageFile(string path, IImageProcessor imageProcessor)
@@ -110,7 +109,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
}
string extension = Path.GetExtension(path).TrimStart('.');
- return imageProcessor.SupportedInputFormats.Contains(extension, StringComparer.OrdinalIgnoreCase);
+ return imageProcessor.SupportedInputFormats.Contains(extension, StringComparison.OrdinalIgnoreCase);
}
}
}
diff --git a/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs b/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs
index 2c4ead719..6b0dfe986 100644
--- a/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs
@@ -5,6 +5,7 @@
using System;
using System.IO;
using System.Linq;
+using Jellyfin.Extensions;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Playlists;
using MediaBrowser.Controller.Resolvers;
@@ -16,7 +17,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
/// <summary>
/// <see cref="IItemResolver"/> for <see cref="Playlist"/> library items.
/// </summary>
- public class PlaylistResolver : FolderResolver<Playlist>
+ public class PlaylistResolver : GenericFolderResolver<Playlist>
{
private string[] _musicPlaylistCollectionTypes =
{
@@ -57,10 +58,10 @@ namespace Emby.Server.Implementations.Library.Resolvers
// Check if this is a music playlist file
// It should have the correct collection type and a supported file extension
- else if (_musicPlaylistCollectionTypes.Contains(args.CollectionType ?? string.Empty, StringComparer.OrdinalIgnoreCase))
+ else if (_musicPlaylistCollectionTypes.Contains(args.CollectionType ?? string.Empty, StringComparison.OrdinalIgnoreCase))
{
var extension = Path.GetExtension(args.Path);
- if (Playlist.SupportedExtensions.Contains(extension ?? string.Empty, StringComparer.OrdinalIgnoreCase))
+ if (Playlist.SupportedExtensions.Contains(extension ?? string.Empty, StringComparison.OrdinalIgnoreCase))
{
return new Playlist
{
diff --git a/Emby.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs b/Emby.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs
index 7b4e14334..6bb999641 100644
--- a/Emby.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs
@@ -13,7 +13,7 @@ using MediaBrowser.Model.IO;
namespace Emby.Server.Implementations.Library.Resolvers
{
- public class SpecialFolderResolver : FolderResolver<Folder>
+ public class SpecialFolderResolver : GenericFolderResolver<Folder>
{
private readonly IFileSystem _fileSystem;
private readonly IServerApplicationPaths _appPaths;
@@ -67,7 +67,6 @@ namespace Emby.Server.Implementations.Library.Resolvers
return args.FileSystemChildren
.Where(i =>
{
-
try
{
return !i.IsDirectory &&
diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs
index cf2d22f4d..be9905647 100644
--- a/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs
@@ -2,7 +2,7 @@
using System;
using System.Linq;
-using MediaBrowser.Controller.Entities;
+using Emby.Naming.Common;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Entities;
@@ -17,9 +17,9 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
/// <summary>
/// Initializes a new instance of the <see cref="EpisodeResolver"/> class.
/// </summary>
- /// <param name="libraryManager">The library manager.</param>
- public EpisodeResolver(ILibraryManager libraryManager)
- : base(libraryManager)
+ /// <param name="namingOptions">The naming options.</param>
+ public EpisodeResolver(NamingOptions namingOptions)
+ : base(namingOptions)
{
}
@@ -44,34 +44,36 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
// If the parent is a Season or Series and the parent is not an extras folder, then this is an Episode if the VideoResolver returns something
// Also handle flat tv folders
- if ((season != null ||
- string.Equals(args.GetCollectionType(), CollectionType.TvShows, StringComparison.OrdinalIgnoreCase) ||
- args.HasParent<Series>())
- && (parent is Series || !BaseItem.AllExtrasTypesFolderNames.ContainsKey(parent.Name)))
+ if (season != null ||
+ string.Equals(args.GetCollectionType(), CollectionType.TvShows, StringComparison.OrdinalIgnoreCase) ||
+ args.HasParent<Series>())
{
var episode = ResolveVideo<Episode>(args, false);
- if (episode != null)
+ // Ignore extras
+ if (episode == null || episode.ExtraType != null)
{
- var series = parent as Series ?? parent.GetParents().OfType<Series>().FirstOrDefault();
+ return null;
+ }
- if (series != null)
- {
- episode.SeriesId = series.Id;
- episode.SeriesName = series.Name;
- }
+ var series = parent as Series ?? parent.GetParents().OfType<Series>().FirstOrDefault();
- if (season != null)
- {
- episode.SeasonId = season.Id;
- episode.SeasonName = season.Name;
- }
+ if (series != null)
+ {
+ episode.SeriesId = series.Id;
+ episode.SeriesName = series.Name;
+ }
- // Assume season 1 if there's no season folder and a season number could not be determined
- if (season == null && !episode.ParentIndexNumber.HasValue && (episode.IndexNumber.HasValue || episode.PremiereDate.HasValue))
- {
- episode.ParentIndexNumber = 1;
- }
+ if (season != null)
+ {
+ episode.SeasonId = season.Id;
+ episode.SeasonName = season.Name;
+ }
+
+ // Assume season 1 if there's no season folder and a season number could not be determined
+ if (season == null && !episode.ParentIndexNumber.HasValue && (episode.IndexNumber.HasValue || episode.PremiereDate.HasValue))
+ {
+ episode.ParentIndexNumber = 1;
}
return episode;
diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs
index 7d707df18..ea4851458 100644
--- a/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs
@@ -1,6 +1,7 @@
#nullable disable
using System.Globalization;
+using Emby.Naming.Common;
using Emby.Naming.TV;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
@@ -12,24 +13,24 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
/// <summary>
/// Class SeasonResolver.
/// </summary>
- public class SeasonResolver : FolderResolver<Season>
+ public class SeasonResolver : GenericFolderResolver<Season>
{
- private readonly ILibraryManager _libraryManager;
private readonly ILocalizationManager _localization;
private readonly ILogger<SeasonResolver> _logger;
+ private readonly NamingOptions _namingOptions;
/// <summary>
/// Initializes a new instance of the <see cref="SeasonResolver"/> class.
/// </summary>
- /// <param name="libraryManager">The library manager.</param>
+ /// <param name="namingOptions">The naming options.</param>
/// <param name="localization">The localization.</param>
/// <param name="logger">The logger.</param>
public SeasonResolver(
- ILibraryManager libraryManager,
+ NamingOptions namingOptions,
ILocalizationManager localization,
ILogger<SeasonResolver> logger)
{
- _libraryManager = libraryManager;
+ _namingOptions = namingOptions;
_localization = localization;
_logger = logger;
}
@@ -43,7 +44,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
{
if (args.Parent is Series series && args.IsDirectory)
{
- var namingOptions = ((LibraryManager)_libraryManager).GetNamingOptions();
+ var namingOptions = _namingOptions;
var path = args.Path;
@@ -65,18 +66,15 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
var episodeInfo = resolver.Resolve(testPath, true);
- if (episodeInfo != null)
+ if (episodeInfo?.EpisodeNumber != null && episodeInfo.SeasonNumber.HasValue)
{
- if (episodeInfo.EpisodeNumber.HasValue && episodeInfo.SeasonNumber.HasValue)
- {
- _logger.LogDebug(
- "Found folder underneath series with episode number: {0}. Season {1}. Episode {2}",
- path,
- episodeInfo.SeasonNumber.Value,
- episodeInfo.EpisodeNumber.Value);
-
- return null;
- }
+ _logger.LogDebug(
+ "Found folder underneath series with episode number: {0}. Season {1}. Episode {2}",
+ path,
+ episodeInfo.SeasonNumber.Value,
+ episodeInfo.EpisodeNumber.Value);
+
+ return null;
}
}
diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs
index 4d8a6494c..f5ac3c665 100644
--- a/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs
@@ -5,7 +5,9 @@
using System;
using System.Collections.Generic;
using System.IO;
+using Emby.Naming.Common;
using Emby.Naming.TV;
+using Emby.Naming.Video;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Resolvers;
@@ -18,20 +20,20 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
/// <summary>
/// Class SeriesResolver.
/// </summary>
- public class SeriesResolver : FolderResolver<Series>
+ public class SeriesResolver : GenericFolderResolver<Series>
{
private readonly ILogger<SeriesResolver> _logger;
- private readonly ILibraryManager _libraryManager;
+ private readonly NamingOptions _namingOptions;
/// <summary>
/// Initializes a new instance of the <see cref="SeriesResolver"/> class.
/// </summary>
/// <param name="logger">The logger.</param>
- /// <param name="libraryManager">The library manager.</param>
- public SeriesResolver(ILogger<SeriesResolver> logger, ILibraryManager libraryManager)
+ /// <param name="namingOptions">The naming options.</param>
+ public SeriesResolver(ILogger<SeriesResolver> logger, NamingOptions namingOptions)
{
_logger = logger;
- _libraryManager = libraryManager;
+ _namingOptions = namingOptions;
}
/// <summary>
@@ -54,16 +56,19 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
return null;
}
+ var seriesInfo = Naming.TV.SeriesResolver.Resolve(_namingOptions, args.Path);
+
var collectionType = args.GetCollectionType();
if (string.Equals(collectionType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase))
{
- var configuredContentType = _libraryManager.GetConfiguredContentType(args.Path);
+ // TODO refactor into separate class or something, this is copied from LibraryManager.GetConfiguredContentType
+ var configuredContentType = args.GetConfiguredContentType();
if (!string.Equals(configuredContentType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase))
{
return new Series
{
Path = args.Path,
- Name = Path.GetFileName(args.Path)
+ Name = seriesInfo.Name
};
}
}
@@ -80,7 +85,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
return new Series
{
Path = args.Path,
- Name = Path.GetFileName(args.Path)
+ Name = seriesInfo.Name
};
}
@@ -89,12 +94,12 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
return null;
}
- if (IsSeriesFolder(args.Path, args.FileSystemChildren, _logger, _libraryManager, false))
+ if (IsSeriesFolder(args.Path, args.FileSystemChildren, false))
{
return new Series
{
Path = args.Path,
- Name = Path.GetFileName(args.Path)
+ Name = seriesInfo.Name
};
}
}
@@ -103,11 +108,9 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
return null;
}
- public static bool IsSeriesFolder(
+ private bool IsSeriesFolder(
string path,
IEnumerable<FileSystemMetadata> fileSystemChildren,
- ILogger<SeriesResolver> logger,
- ILibraryManager libraryManager,
bool isTvContentType)
{
foreach (var child in fileSystemChildren)
@@ -116,21 +119,21 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
{
if (IsSeasonFolder(child.FullName, isTvContentType))
{
- logger.LogDebug("{Path} is a series because of season folder {Dir}.", path, child.FullName);
+ _logger.LogDebug("{Path} is a series because of season folder {Dir}.", path, child.FullName);
return true;
}
}
else
{
string fullName = child.FullName;
- if (libraryManager.IsVideoFile(fullName))
+ if (VideoResolver.IsVideoFile(path, _namingOptions))
{
if (isTvContentType)
{
return true;
}
- var namingOptions = ((LibraryManager)libraryManager).GetNamingOptions();
+ var namingOptions = _namingOptions;
var episodeResolver = new Naming.TV.EpisodeResolver(namingOptions);
@@ -143,7 +146,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
}
}
- logger.LogDebug("{Path} is not a series folder.", path);
+ _logger.LogDebug("{Path} is not a series folder.", path);
return false;
}
@@ -179,13 +182,42 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
/// <param name="path">The path.</param>
private static void SetProviderIdFromPath(Series item, string path)
{
- var justName = Path.GetFileName(path);
+ var justName = Path.GetFileName(path.AsSpan());
+
+ var tvdbId = justName.GetAttributeValue("tvdbid");
+ if (!string.IsNullOrEmpty(tvdbId))
+ {
+ item.SetProviderId(MetadataProvider.Tvdb, tvdbId);
+ }
- var id = justName.GetAttributeValue("tvdbid");
+ var tvmazeId = justName.GetAttributeValue("tvmazeid");
+ if (!string.IsNullOrEmpty(tvmazeId))
+ {
+ item.SetProviderId(MetadataProvider.TvMaze, tvmazeId);
+ }
+
+ var tmdbId = justName.GetAttributeValue("tmdbid");
+ if (!string.IsNullOrEmpty(tmdbId))
+ {
+ item.SetProviderId(MetadataProvider.Tmdb, tmdbId);
+ }
+
+ var anidbId = justName.GetAttributeValue("anidbid");
+ if (!string.IsNullOrEmpty(anidbId))
+ {
+ item.SetProviderId("AniDB", anidbId);
+ }
+
+ var aniListId = justName.GetAttributeValue("anilistid");
+ if (!string.IsNullOrEmpty(aniListId))
+ {
+ item.SetProviderId("AniList", aniListId);
+ }
- if (!string.IsNullOrEmpty(id))
+ var aniSearchId = justName.GetAttributeValue("anisearchid");
+ if (!string.IsNullOrEmpty(aniSearchId))
{
- item.SetProviderId(MetadataProvider.Tvdb, id);
+ item.SetProviderId("AniSearch", aniSearchId);
}
}
}
diff --git a/Emby.Server.Implementations/Library/SearchEngine.cs b/Emby.Server.Implementations/Library/SearchEngine.cs
index 9d0a24a88..55911933a 100644
--- a/Emby.Server.Implementations/Library/SearchEngine.cs
+++ b/Emby.Server.Implementations/Library/SearchEngine.cs
@@ -10,12 +10,9 @@ using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Querying;
using MediaBrowser.Model.Search;
-using Genre = MediaBrowser.Controller.Entities.Genre;
-using Person = MediaBrowser.Controller.Entities.Person;
namespace Emby.Server.Implementations.Library
{
@@ -59,9 +56,9 @@ namespace Emby.Server.Implementations.Library
};
}
- private static void AddIfMissing(List<string> list, string value)
+ private static void AddIfMissing(List<BaseItemKind> list, BaseItemKind value)
{
- if (!list.Contains(value, StringComparer.OrdinalIgnoreCase))
+ if (!list.Contains(value))
{
list.Add(value);
}
@@ -73,7 +70,7 @@ namespace Emby.Server.Implementations.Library
/// <param name="query">The query.</param>
/// <param name="user">The user.</param>
/// <returns>IEnumerable{SearchHintResult}.</returns>
- /// <exception cref="ArgumentNullException">searchTerm</exception>
+ /// <exception cref="ArgumentException"><c>query.SearchTerm</c> is <c>null</c> or empty.</exception>
private List<SearchHintInfo> GetSearchHints(SearchQuery query, User user)
{
var searchTerm = query.SearchTerm;
@@ -86,63 +83,63 @@ namespace Emby.Server.Implementations.Library
searchTerm = searchTerm.Trim().RemoveDiacritics();
var excludeItemTypes = query.ExcludeItemTypes.ToList();
- var includeItemTypes = (query.IncludeItemTypes ?? Array.Empty<string>()).ToList();
+ var includeItemTypes = (query.IncludeItemTypes ?? Array.Empty<BaseItemKind>()).ToList();
- excludeItemTypes.Add(nameof(Year));
- excludeItemTypes.Add(nameof(Folder));
+ excludeItemTypes.Add(BaseItemKind.Year);
+ excludeItemTypes.Add(BaseItemKind.Folder);
- if (query.IncludeGenres && (includeItemTypes.Count == 0 || includeItemTypes.Contains("Genre", StringComparer.OrdinalIgnoreCase)))
+ if (query.IncludeGenres && (includeItemTypes.Count == 0 || includeItemTypes.Contains(BaseItemKind.Genre)))
{
if (!query.IncludeMedia)
{
- AddIfMissing(includeItemTypes, nameof(Genre));
- AddIfMissing(includeItemTypes, nameof(MusicGenre));
+ AddIfMissing(includeItemTypes, BaseItemKind.Genre);
+ AddIfMissing(includeItemTypes, BaseItemKind.MusicGenre);
}
}
else
{
- AddIfMissing(excludeItemTypes, nameof(Genre));
- AddIfMissing(excludeItemTypes, nameof(MusicGenre));
+ AddIfMissing(excludeItemTypes, BaseItemKind.Genre);
+ AddIfMissing(excludeItemTypes, BaseItemKind.MusicGenre);
}
- if (query.IncludePeople && (includeItemTypes.Count == 0 || includeItemTypes.Contains("People", StringComparer.OrdinalIgnoreCase) || includeItemTypes.Contains("Person", StringComparer.OrdinalIgnoreCase)))
+ if (query.IncludePeople && (includeItemTypes.Count == 0 || includeItemTypes.Contains(BaseItemKind.Person)))
{
if (!query.IncludeMedia)
{
- AddIfMissing(includeItemTypes, nameof(Person));
+ AddIfMissing(includeItemTypes, BaseItemKind.Person);
}
}
else
{
- AddIfMissing(excludeItemTypes, nameof(Person));
+ AddIfMissing(excludeItemTypes, BaseItemKind.Person);
}
- if (query.IncludeStudios && (includeItemTypes.Count == 0 || includeItemTypes.Contains("Studio", StringComparer.OrdinalIgnoreCase)))
+ if (query.IncludeStudios && (includeItemTypes.Count == 0 || includeItemTypes.Contains(BaseItemKind.Studio)))
{
if (!query.IncludeMedia)
{
- AddIfMissing(includeItemTypes, nameof(Studio));
+ AddIfMissing(includeItemTypes, BaseItemKind.Studio);
}
}
else
{
- AddIfMissing(excludeItemTypes, nameof(Studio));
+ AddIfMissing(excludeItemTypes, BaseItemKind.Studio);
}
- if (query.IncludeArtists && (includeItemTypes.Count == 0 || includeItemTypes.Contains("MusicArtist", StringComparer.OrdinalIgnoreCase)))
+ if (query.IncludeArtists && (includeItemTypes.Count == 0 || includeItemTypes.Contains(BaseItemKind.MusicArtist)))
{
if (!query.IncludeMedia)
{
- AddIfMissing(includeItemTypes, nameof(MusicArtist));
+ AddIfMissing(includeItemTypes, BaseItemKind.MusicArtist);
}
}
else
{
- AddIfMissing(excludeItemTypes, nameof(MusicArtist));
+ AddIfMissing(excludeItemTypes, BaseItemKind.MusicArtist);
}
- AddIfMissing(excludeItemTypes, nameof(CollectionFolder));
- AddIfMissing(excludeItemTypes, nameof(Folder));
+ AddIfMissing(excludeItemTypes, BaseItemKind.CollectionFolder);
+ AddIfMissing(excludeItemTypes, BaseItemKind.Folder);
var mediaTypes = query.MediaTypes.ToList();
if (includeItemTypes.Count > 0)
@@ -183,7 +180,7 @@ namespace Emby.Server.Implementations.Library
List<BaseItem> mediaItems;
- if (searchQuery.IncludeItemTypes.Length == 1 && string.Equals(searchQuery.IncludeItemTypes[0], "MusicArtist", StringComparison.OrdinalIgnoreCase))
+ if (searchQuery.IncludeItemTypes.Length == 1 && searchQuery.IncludeItemTypes[0] == BaseItemKind.MusicArtist)
{
if (!searchQuery.ParentId.Equals(Guid.Empty))
{
@@ -192,8 +189,8 @@ namespace Emby.Server.Implementations.Library
searchQuery.ParentId = Guid.Empty;
searchQuery.IncludeItemsByName = true;
- searchQuery.IncludeItemTypes = Array.Empty<string>();
- mediaItems = _libraryManager.GetAllArtists(searchQuery).Items.Select(i => i.Item1).ToList();
+ searchQuery.IncludeItemTypes = Array.Empty<BaseItemKind>();
+ mediaItems = _libraryManager.GetAllArtists(searchQuery).Items.Select(i => i.Item).ToList();
}
else
{
diff --git a/Emby.Server.Implementations/Library/UserDataManager.cs b/Emby.Server.Implementations/Library/UserDataManager.cs
index c4e230f21..3810a76c4 100644
--- a/Emby.Server.Implementations/Library/UserDataManager.cs
+++ b/Emby.Server.Implementations/Library/UserDataManager.cs
@@ -25,8 +25,6 @@ namespace Emby.Server.Implementations.Library
/// </summary>
public class UserDataManager : IUserDataManager
{
- public event EventHandler<UserDataSaveEventArgs> UserDataSaved;
-
private readonly ConcurrentDictionary<string, UserItemData> _userData =
new ConcurrentDictionary<string, UserItemData>(StringComparer.OrdinalIgnoreCase);
@@ -44,6 +42,8 @@ namespace Emby.Server.Implementations.Library
_repository = repository;
}
+ public event EventHandler<UserDataSaveEventArgs> UserDataSaved;
+
public void SaveUserData(Guid userId, BaseItem item, UserItemData userData, UserDataSaveReason reason, CancellationToken cancellationToken)
{
var user = _userManager.GetUserById(userId);
@@ -75,7 +75,7 @@ namespace Emby.Server.Implementations.Library
}
var cacheKey = GetCacheKey(userId, item.Id);
- _userData.AddOrUpdate(cacheKey, userData, (k, v) => userData);
+ _userData.AddOrUpdate(cacheKey, userData, (_, _) => userData);
UserDataSaved?.Invoke(this, new UserDataSaveEventArgs
{
@@ -90,10 +90,9 @@ namespace Emby.Server.Implementations.Library
/// <summary>
/// Save the provided user data for the given user. Batch operation. Does not fire any events or update the cache.
/// </summary>
- /// <param name="userId"></param>
- /// <param name="userData"></param>
- /// <param name="cancellationToken"></param>
- /// <returns></returns>
+ /// <param name="userId">The user id.</param>
+ /// <param name="userData">The user item data.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
public void SaveAllUserData(Guid userId, UserItemData[] userData, CancellationToken cancellationToken)
{
var user = _userManager.GetUserById(userId);
@@ -104,8 +103,8 @@ namespace Emby.Server.Implementations.Library
/// <summary>
/// Retrieve all user data for the given user.
/// </summary>
- /// <param name="userId"></param>
- /// <returns></returns>
+ /// <param name="userId">The user id.</param>
+ /// <returns>A <see cref="List{UserItemData}"/> containing all of the user's item data.</returns>
public List<UserItemData> GetAllUserData(Guid userId)
{
var user = _userManager.GetUserById(userId);
@@ -126,7 +125,7 @@ namespace Emby.Server.Implementations.Library
var cacheKey = GetCacheKey(userId, itemId);
- return _userData.GetOrAdd(cacheKey, k => GetUserDataInternal(userId, keys));
+ return _userData.GetOrAdd(cacheKey, _ => GetUserDataInternal(userId, keys));
}
private UserItemData GetUserDataInternal(long internalUserId, List<string> keys)
diff --git a/Emby.Server.Implementations/Library/UserViewManager.cs b/Emby.Server.Implementations/Library/UserViewManager.cs
index e2da672a3..b00bc72e6 100644
--- a/Emby.Server.Implementations/Library/UserViewManager.cs
+++ b/Emby.Server.Implementations/Library/UserViewManager.cs
@@ -8,11 +8,11 @@ using System.Linq;
using System.Threading;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
+using Jellyfin.Extensions;
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Model.Channels;
@@ -20,8 +20,6 @@ using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.Library;
using MediaBrowser.Model.Querying;
-using Genre = MediaBrowser.Controller.Entities.Genre;
-using Person = MediaBrowser.Controller.Entities.Person;
namespace Emby.Server.Implementations.Library
{
@@ -80,7 +78,7 @@ namespace Emby.Server.Implementations.Library
continue;
}
- if (query.PresetViews.Contains(folderViewType ?? string.Empty, StringComparer.OrdinalIgnoreCase))
+ if (query.PresetViews.Contains(folderViewType ?? string.Empty, StringComparison.OrdinalIgnoreCase))
{
list.Add(GetUserView(folder, folderViewType, string.Empty));
}
@@ -175,12 +173,12 @@ namespace Emby.Server.Implementations.Library
string viewType,
string localizationKey,
string sortName,
- Jellyfin.Data.Entities.User user,
+ User user,
string[] presetViews)
{
if (parents.Count == 1 && parents.All(i => string.Equals(i.CollectionType, viewType, StringComparison.OrdinalIgnoreCase)))
{
- if (!presetViews.Contains(viewType, StringComparer.OrdinalIgnoreCase))
+ if (!presetViews.Contains(viewType, StringComparison.OrdinalIgnoreCase))
{
return (Folder)parents[0];
}
@@ -300,11 +298,11 @@ namespace Emby.Server.Implementations.Library
{
if (hasCollectionType.All(i => string.Equals(i.CollectionType, CollectionType.Movies, StringComparison.OrdinalIgnoreCase)))
{
- includeItemTypes = new string[] { "Movie" };
+ includeItemTypes = new[] { BaseItemKind.Movie };
}
else if (hasCollectionType.All(i => string.Equals(i.CollectionType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase)))
{
- includeItemTypes = new string[] { "Episode" };
+ includeItemTypes = new[] { BaseItemKind.Episode };
}
}
}
@@ -341,20 +339,27 @@ namespace Emby.Server.Implementations.Library
mediaTypes = mediaTypes.Distinct().ToList();
}
- var excludeItemTypes = includeItemTypes.Length == 0 && mediaTypes.Count == 0 ? new[]
- {
- nameof(Person),
- nameof(Studio),
- nameof(Year),
- nameof(MusicGenre),
- nameof(Genre)
- } : Array.Empty<string>();
+ var excludeItemTypes = includeItemTypes.Length == 0 && mediaTypes.Count == 0
+ ? new[]
+ {
+ BaseItemKind.Person,
+ BaseItemKind.Studio,
+ BaseItemKind.Year,
+ BaseItemKind.MusicGenre,
+ BaseItemKind.Genre
+ }
+ : Array.Empty<BaseItemKind>();
var query = new InternalItemsQuery(user)
{
IncludeItemTypes = includeItemTypes,
- OrderBy = new[] { (ItemSortBy.DateCreated, SortOrder.Descending) },
- IsFolder = includeItemTypes.Length == 0 ? false : (bool?)null,
+ OrderBy = new[]
+ {
+ (ItemSortBy.DateCreated, SortOrder.Descending),
+ (ItemSortBy.SortName, SortOrder.Descending),
+ (ItemSortBy.ProductionYear, SortOrder.Descending)
+ },
+ IsFolder = includeItemTypes.Length == 0 ? false : null,
ExcludeItemTypes = excludeItemTypes,
IsVirtualItem = false,
Limit = limit * 5,
diff --git a/Emby.Server.Implementations/Library/Validators/ArtistsValidator.cs b/Emby.Server.Implementations/Library/Validators/ArtistsValidator.cs
index f9a3e2c64..7591e8391 100644
--- a/Emby.Server.Implementations/Library/Validators/ArtistsValidator.cs
+++ b/Emby.Server.Implementations/Library/Validators/ArtistsValidator.cs
@@ -3,6 +3,7 @@ using System.Globalization;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
+using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library;
@@ -81,7 +82,7 @@ namespace Emby.Server.Implementations.Library.Validators
var deadEntities = _libraryManager.GetItemList(new InternalItemsQuery
{
- IncludeItemTypes = new[] { nameof(MusicArtist) },
+ IncludeItemTypes = new[] { BaseItemKind.MusicArtist },
IsDeadArtist = true,
IsLocked = false
}).Cast<MusicArtist>().ToList();
@@ -95,10 +96,13 @@ namespace Emby.Server.Implementations.Library.Validators
_logger.LogInformation("Deleting dead {2} {0} {1}.", item.Id.ToString("N", CultureInfo.InvariantCulture), item.Name, item.GetType().Name);
- _libraryManager.DeleteItem(item, new DeleteOptions
- {
- DeleteFileLocation = false
- }, false);
+ _libraryManager.DeleteItem(
+ item,
+ new DeleteOptions
+ {
+ DeleteFileLocation = false
+ },
+ false);
}
progress.Report(100);
diff --git a/Emby.Server.Implementations/Library/Validators/CollectionPostScanTask.cs b/Emby.Server.Implementations/Library/Validators/CollectionPostScanTask.cs
new file mode 100644
index 000000000..88b93a211
--- /dev/null
+++ b/Emby.Server.Implementations/Library/Validators/CollectionPostScanTask.cs
@@ -0,0 +1,156 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Jellyfin.Data.Enums;
+using MediaBrowser.Controller.Collections;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.Movies;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Querying;
+using Microsoft.Extensions.Logging;
+
+namespace Emby.Server.Implementations.Library.Validators
+{
+ /// <summary>
+ /// Class CollectionPostScanTask.
+ /// </summary>
+ public class CollectionPostScanTask : ILibraryPostScanTask
+ {
+ private readonly ILibraryManager _libraryManager;
+ private readonly ICollectionManager _collectionManager;
+ private readonly ILogger<CollectionPostScanTask> _logger;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="CollectionPostScanTask" /> class.
+ /// </summary>
+ /// <param name="libraryManager">The library manager.</param>
+ /// <param name="collectionManager">The collection manager.</param>
+ /// <param name="logger">The logger.</param>
+ public CollectionPostScanTask(
+ ILibraryManager libraryManager,
+ ICollectionManager collectionManager,
+ ILogger<CollectionPostScanTask> logger)
+ {
+ _libraryManager = libraryManager;
+ _collectionManager = collectionManager;
+ _logger = logger;
+ }
+
+ /// <summary>
+ /// Runs the specified progress.
+ /// </summary>
+ /// <param name="progress">The progress.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>Task.</returns>
+ public async Task Run(IProgress<double> progress, CancellationToken cancellationToken)
+ {
+ var collectionNameMoviesMap = new Dictionary<string, HashSet<Guid>>();
+
+ foreach (var library in _libraryManager.RootFolder.Children)
+ {
+ if (!_libraryManager.GetLibraryOptions(library).AutomaticallyAddToCollection)
+ {
+ continue;
+ }
+
+ var startIndex = 0;
+ var pagesize = 1000;
+
+ while (true)
+ {
+ var movies = _libraryManager.GetItemList(new InternalItemsQuery
+ {
+ MediaTypes = new string[] { MediaType.Video },
+ IncludeItemTypes = new[] { BaseItemKind.Movie },
+ IsVirtualItem = false,
+ OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) },
+ Parent = library,
+ StartIndex = startIndex,
+ Limit = pagesize,
+ Recursive = true
+ });
+
+ foreach (var m in movies)
+ {
+ if (m is Movie movie && !string.IsNullOrEmpty(movie.CollectionName))
+ {
+ if (collectionNameMoviesMap.TryGetValue(movie.CollectionName, out var movieList))
+ {
+ movieList.Add(movie.Id);
+ }
+ else
+ {
+ collectionNameMoviesMap[movie.CollectionName] = new HashSet<Guid> { movie.Id };
+ }
+ }
+ }
+
+ if (movies.Count < pagesize)
+ {
+ break;
+ }
+
+ startIndex += pagesize;
+ }
+ }
+
+ var numComplete = 0;
+ var count = collectionNameMoviesMap.Count;
+
+ if (count == 0)
+ {
+ progress.Report(100);
+ return;
+ }
+
+ var boxSets = _libraryManager.GetItemList(new InternalItemsQuery
+ {
+ IncludeItemTypes = new[] { BaseItemKind.BoxSet },
+ CollapseBoxSetItems = false,
+ Recursive = true
+ });
+
+ foreach (var (collectionName, movieIds) in collectionNameMoviesMap)
+ {
+ try
+ {
+ var boxSet = boxSets.FirstOrDefault(b => b?.Name == collectionName) as BoxSet;
+ if (boxSet == null)
+ {
+ // won't automatically create collection if only one movie in it
+ if (movieIds.Count >= 2)
+ {
+ boxSet = await _collectionManager.CreateCollectionAsync(new CollectionCreationOptions
+ {
+ Name = collectionName,
+ IsLocked = true
+ });
+
+ await _collectionManager.AddToCollectionAsync(boxSet.Id, movieIds);
+ }
+ }
+ else
+ {
+ await _collectionManager.AddToCollectionAsync(boxSet.Id, movieIds);
+ }
+
+ numComplete++;
+ double percent = numComplete;
+ percent /= count;
+ percent *= 100;
+
+ progress.Report(percent);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error refreshing {CollectionName} with {@MovieIds}", collectionName, movieIds);
+ }
+ }
+
+ progress.Report(100);
+ }
+ }
+}
diff --git a/Emby.Server.Implementations/Library/Validators/PeopleValidator.cs b/Emby.Server.Implementations/Library/Validators/PeopleValidator.cs
index 8739a9e1b..601aab5b9 100644
--- a/Emby.Server.Implementations/Library/Validators/PeopleValidator.cs
+++ b/Emby.Server.Implementations/Library/Validators/PeopleValidator.cs
@@ -2,6 +2,7 @@ using System;
using System.Globalization;
using System.Threading;
using System.Threading.Tasks;
+using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
@@ -78,7 +79,7 @@ namespace Emby.Server.Implementations.Library.Validators
}
catch (Exception ex)
{
- _logger.LogError(ex, "Error validating IBN entry {person}", person);
+ _logger.LogError(ex, "Error validating IBN entry {Person}", person);
}
// Update progress
@@ -91,7 +92,7 @@ namespace Emby.Server.Implementations.Library.Validators
var deadEntities = _libraryManager.GetItemList(new InternalItemsQuery
{
- IncludeItemTypes = new[] { nameof(Person) },
+ IncludeItemTypes = new[] { BaseItemKind.Person },
IsDeadPerson = true,
IsLocked = false
});
diff --git a/Emby.Server.Implementations/Library/Validators/StudiosValidator.cs b/Emby.Server.Implementations/Library/Validators/StudiosValidator.cs
index 8577d722e..26bc49c1f 100644
--- a/Emby.Server.Implementations/Library/Validators/StudiosValidator.cs
+++ b/Emby.Server.Implementations/Library/Validators/StudiosValidator.cs
@@ -2,6 +2,7 @@ using System;
using System.Globalization;
using System.Threading;
using System.Threading.Tasks;
+using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Persistence;
@@ -80,7 +81,7 @@ namespace Emby.Server.Implementations.Library.Validators
var deadEntities = _libraryManager.GetItemList(new InternalItemsQuery
{
- IncludeItemTypes = new[] { nameof(Studio) },
+ IncludeItemTypes = new[] { BaseItemKind.Studio },
IsDeadStudio = true,
IsLocked = false
});