Merge remote-tracking branch 'origin/master' into hwa

This commit is contained in:
nyanmisaka 2021-12-25 00:33:17 +08:00
commit 728a5988b3
251 changed files with 3204 additions and 3022 deletions

View File

@ -39,6 +39,14 @@ jobs:
vmImage: 'ubuntu-latest'
steps:
- script: echo "##vso[task.setvariable variable=JellyfinVersion]$( awk -F '/' '{ print $NF }' <<<'$(Build.SourceBranch)' | sed 's/^v//' )"
displayName: Set release version (stable)
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v')
- script: './bump-version $(JellyfinVersion)'
displayName: Bump internal version (stable)
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v')
- script: 'docker build -f deployment/Dockerfile.$(BuildConfiguration) -t jellyfin-server-$(BuildConfiguration) deployment'
displayName: 'Build Dockerfile'
@ -80,6 +88,14 @@ jobs:
vmImage: 'ubuntu-latest'
steps:
- script: echo "##vso[task.setvariable variable=JellyfinVersion]$( awk -F '/' '{ print $NF }' <<<'$(Build.SourceBranch)' | sed 's/^v//' )"
displayName: Set release version (stable)
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v')
- script: './bump-version $(JellyfinVersion)'
displayName: Bump internal version (stable)
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v')
- task: DownloadPipelineArtifact@2
displayName: 'Download OpenAPI Spec'
inputs:
@ -127,6 +143,10 @@ jobs:
displayName: Set release version (stable)
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v')
- script: './bump-version $(JellyfinVersion)'
displayName: Bump internal version (stable)
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v')
- task: Docker@2
displayName: 'Push Unstable Image'
condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master')

1
.copr Symbolic link
View File

@ -0,0 +1 @@
fedora

View File

@ -1 +0,0 @@
../fedora/Makefile

View File

@ -150,6 +150,7 @@
- [ianjazz246](https://github.com/ianjazz246)
- [peterspenler](https://github.com/peterspenler)
- [MBR-0001](https://github.com/MBR-0001)
- [jonas-resch](https://github.com/jonas-resch)
# Emby Contributors

View File

@ -18,11 +18,8 @@ using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Playlists;
using MediaBrowser.Controller.TV;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Dto;
@ -30,12 +27,7 @@ using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.Querying;
using Microsoft.Extensions.Logging;
using Book = MediaBrowser.Controller.Entities.Book;
using Episode = MediaBrowser.Controller.Entities.TV.Episode;
using Genre = MediaBrowser.Controller.Entities.Genre;
using Movie = MediaBrowser.Controller.Entities.Movies.Movie;
using MusicAlbum = MediaBrowser.Controller.Entities.Audio.MusicAlbum;
using Series = MediaBrowser.Controller.Entities.TV.Series;
namespace Emby.Dlna.ContentDirectory
{
@ -539,7 +531,7 @@ namespace Emby.Dlna.ContentDirectory
User = user,
Recursive = true,
IsMissing = false,
ExcludeItemTypes = new[] { nameof(Book) },
ExcludeItemTypes = new[] { BaseItemKind.Book },
IsFolder = isFolder,
MediaTypes = mediaTypes,
DtoOptions = GetDtoOptions()
@ -619,7 +611,7 @@ namespace Emby.Dlna.ContentDirectory
Limit = limit,
StartIndex = startIndex,
IsVirtualItem = false,
ExcludeItemTypes = new[] { nameof(Book) },
ExcludeItemTypes = new[] { BaseItemKind.Book },
IsPlaceHolder = false,
DtoOptions = GetDtoOptions(),
OrderBy = GetOrderBy(sort, folder.IsPreSorted)
@ -644,7 +636,7 @@ namespace Emby.Dlna.ContentDirectory
{
StartIndex = startIndex,
Limit = limit,
IncludeItemTypes = new[] { nameof(LiveTvChannel) },
IncludeItemTypes = new[] { BaseItemKind.LiveTvChannel },
OrderBy = GetOrderBy(sort, false)
};
@ -675,23 +667,23 @@ namespace Emby.Dlna.ContentDirectory
switch (stubType)
{
case StubType.Latest:
return GetLatest(item, query, nameof(Audio));
return GetLatest(item, query, BaseItemKind.Audio);
case StubType.Playlists:
return GetMusicPlaylists(query);
case StubType.Albums:
return GetChildrenOfItem(item, query, nameof(MusicAlbum));
return GetChildrenOfItem(item, query, BaseItemKind.MusicAlbum);
case StubType.Artists:
return GetMusicArtists(item, query);
case StubType.AlbumArtists:
return GetMusicAlbumArtists(item, query);
case StubType.FavoriteAlbums:
return GetChildrenOfItem(item, query, nameof(MusicAlbum), true);
return GetChildrenOfItem(item, query, BaseItemKind.MusicAlbum, true);
case StubType.FavoriteArtists:
return GetFavoriteArtists(item, query);
case StubType.FavoriteSongs:
return GetChildrenOfItem(item, query, nameof(Audio), true);
return GetChildrenOfItem(item, query, BaseItemKind.Audio, true);
case StubType.Songs:
return GetChildrenOfItem(item, query, nameof(Audio));
return GetChildrenOfItem(item, query, BaseItemKind.Audio);
case StubType.Genres:
return GetMusicGenres(item, query);
}
@ -746,13 +738,13 @@ namespace Emby.Dlna.ContentDirectory
case StubType.ContinueWatching:
return GetMovieContinueWatching(item, query);
case StubType.Latest:
return GetLatest(item, query, nameof(Movie));
return GetLatest(item, query, BaseItemKind.Movie);
case StubType.Movies:
return GetChildrenOfItem(item, query, nameof(Movie));
return GetChildrenOfItem(item, query, BaseItemKind.Movie);
case StubType.Collections:
return GetMovieCollections(query);
case StubType.Favorites:
return GetChildrenOfItem(item, query, nameof(Movie), true);
return GetChildrenOfItem(item, query, BaseItemKind.Movie, true);
case StubType.Genres:
return GetGenres(item, query);
}
@ -831,13 +823,13 @@ namespace Emby.Dlna.ContentDirectory
case StubType.NextUp:
return GetNextUp(item, query);
case StubType.Latest:
return GetLatest(item, query, nameof(Episode));
return GetLatest(item, query, BaseItemKind.Episode);
case StubType.Series:
return GetChildrenOfItem(item, query, nameof(Series));
return GetChildrenOfItem(item, query, BaseItemKind.Series);
case StubType.FavoriteSeries:
return GetChildrenOfItem(item, query, nameof(Series), true);
return GetChildrenOfItem(item, query, BaseItemKind.Series, true);
case StubType.FavoriteEpisodes:
return GetChildrenOfItem(item, query, nameof(Episode), true);
return GetChildrenOfItem(item, query, BaseItemKind.Episode, true);
case StubType.Genres:
return GetGenres(item, query);
}
@ -898,7 +890,7 @@ namespace Emby.Dlna.ContentDirectory
private QueryResult<ServerItem> GetMovieCollections(InternalItemsQuery query)
{
query.Recursive = true;
query.IncludeItemTypes = new[] { nameof(BoxSet) };
query.IncludeItemTypes = new[] { BaseItemKind.BoxSet };
var result = _libraryManager.GetItemsResult(query);
@ -913,7 +905,7 @@ namespace Emby.Dlna.ContentDirectory
/// <param name="itemType">The item type.</param>
/// <param name="isFavorite">A value indicating whether to only fetch favorite items.</param>
/// <returns>The <see cref="QueryResult{ServerItem}"/>.</returns>
private QueryResult<ServerItem> GetChildrenOfItem(BaseItem parent, InternalItemsQuery query, string itemType, bool isFavorite = false)
private QueryResult<ServerItem> GetChildrenOfItem(BaseItem parent, InternalItemsQuery query, BaseItemKind itemType, bool isFavorite = false)
{
query.Recursive = true;
query.Parent = parent;
@ -1013,7 +1005,7 @@ namespace Emby.Dlna.ContentDirectory
private QueryResult<ServerItem> GetMusicPlaylists(InternalItemsQuery query)
{
query.Parent = null;
query.IncludeItemTypes = new[] { nameof(Playlist) };
query.IncludeItemTypes = new[] { BaseItemKind.Playlist };
query.Recursive = true;
var result = _libraryManager.GetItemsResult(query);
@ -1052,7 +1044,7 @@ namespace Emby.Dlna.ContentDirectory
/// <param name="query">The <see cref="InternalItemsQuery"/>.</param>
/// <param name="itemType">The item type.</param>
/// <returns>The <see cref="QueryResult{ServerItem}"/>.</returns>
private QueryResult<ServerItem> GetLatest(BaseItem parent, InternalItemsQuery query, string itemType)
private QueryResult<ServerItem> GetLatest(BaseItem parent, InternalItemsQuery query, BaseItemKind itemType)
{
query.OrderBy = Array.Empty<(string, SortOrder)>();
@ -1086,7 +1078,7 @@ namespace Emby.Dlna.ContentDirectory
{
Recursive = true,
ArtistIds = new[] { item.Id },
IncludeItemTypes = new[] { nameof(MusicAlbum) },
IncludeItemTypes = new[] { BaseItemKind.MusicAlbum },
Limit = limit,
StartIndex = startIndex,
DtoOptions = GetDtoOptions(),
@ -1115,8 +1107,8 @@ namespace Emby.Dlna.ContentDirectory
GenreIds = new[] { item.Id },
IncludeItemTypes = new[]
{
nameof(Movie),
nameof(Series)
BaseItemKind.Movie,
BaseItemKind.Series
},
Limit = limit,
StartIndex = startIndex,
@ -1144,7 +1136,7 @@ namespace Emby.Dlna.ContentDirectory
{
Recursive = true,
GenreIds = new[] { item.Id },
IncludeItemTypes = new[] { nameof(MusicAlbum) },
IncludeItemTypes = new[] { BaseItemKind.MusicAlbum },
Limit = limit,
StartIndex = startIndex,
DtoOptions = GetDtoOptions(),

View File

@ -5,7 +5,6 @@ using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.Json;
using System.Text.RegularExpressions;
using System.Threading.Tasks;

View File

@ -124,7 +124,7 @@ namespace Emby.Dlna.Main
config);
Current = this;
var netConfig = config.GetConfiguration<NetworkConfiguration>("network");
var netConfig = config.GetConfiguration<NetworkConfiguration>(NetworkConfigurationStore.StoreKey);
_disabled = appHost.ListenWithHttps && netConfig.RequireHttps;
if (_disabled && _config.GetDlnaConfiguration().EnableServer)

View File

@ -14,6 +14,7 @@ namespace Emby.Naming.AudioBook
public class AudioBookListResolver
{
private readonly NamingOptions _options;
private readonly AudioBookResolver _audioBookResolver;
/// <summary>
/// Initializes a new instance of the <see cref="AudioBookListResolver"/> class.
@ -22,6 +23,7 @@ namespace Emby.Naming.AudioBook
public AudioBookListResolver(NamingOptions options)
{
_options = options;
_audioBookResolver = new AudioBookResolver(_options);
}
/// <summary>
@ -31,21 +33,19 @@ namespace Emby.Naming.AudioBook
/// <returns>Returns IEnumerable of <see cref="AudioBookInfo"/>.</returns>
public IEnumerable<AudioBookInfo> Resolve(IEnumerable<FileSystemMetadata> files)
{
var audioBookResolver = new AudioBookResolver(_options);
// File with empty fullname will be sorted out here.
var audiobookFileInfos = files
.Select(i => audioBookResolver.Resolve(i.FullName))
.Select(i => _audioBookResolver.Resolve(i.FullName))
.OfType<AudioBookFileInfo>()
.ToList();
var stackResult = new StackResolver(_options)
.ResolveAudioBooks(audiobookFileInfos);
var stackResult = StackResolver.ResolveAudioBooks(audiobookFileInfos);
foreach (var stack in stackResult)
{
var stackFiles = stack.Files
.Select(i => audioBookResolver.Resolve(i))
.Select(i => _audioBookResolver.Resolve(i))
.OfType<AudioBookFileInfo>()
.ToList();

View File

@ -1,7 +1,7 @@
using System;
using System.IO;
using System.Linq;
using Emby.Naming.Common;
using Jellyfin.Extensions;
namespace Emby.Naming.AudioBook
{
@ -37,7 +37,7 @@ namespace Emby.Naming.AudioBook
var extension = Path.GetExtension(path);
// Check supported extensions
if (!_options.AudioFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
if (!_options.AudioFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase))
{
return null;
}

View File

@ -1,6 +1,7 @@
#pragma warning disable CA1819
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using Emby.Naming.Video;
@ -124,11 +125,11 @@ namespace Emby.Naming.Common
token: "DSR")
};
VideoFileStackingExpressions = new[]
VideoFileStackingRules = new[]
{
"(?<title>.*?)(?<volume>[ _.-]*(?:cd|dvd|p(?:ar)?t|dis[ck])[ _.-]*[0-9]+)(?<ignore>.*?)(?<extension>\\.[^.]+)$",
"(?<title>.*?)(?<volume>[ _.-]*(?:cd|dvd|p(?:ar)?t|dis[ck])[ _.-]*[a-d])(?<ignore>.*?)(?<extension>\\.[^.]+)$",
"(?<title>.*?)(?<volume>[ ._-]*[a-d])(?<ignore>.*?)(?<extension>\\.[^.]+)$"
new FileStackRule(@"^(?<filename>.*?)(?:(?<=[\]\)\}])|[ _.-]+)[\(\[]?(?<parttype>cd|dvd|part|pt|dis[ck])[ _.-]*(?<number>[0-9]+)[\)\]]?(?:\.[^.]+)?$", true),
new FileStackRule(@"^(?<filename>.*?)(?:(?<=[\]\)\}])|[ _.-]+)[\(\[]?(?<parttype>cd|dvd|part|pt|dis[ck])[ _.-]*(?<number>[a-d])[\)\]]?(?:\.[^.]+)?$", false),
new FileStackRule(@"^(?<filename>.*?)(?:(?<=[\]\)\}])|[ _.-]?)(?<number>[a-d])(?:\.[^.]+)?$", false)
};
CleanDateTimes = new[]
@ -403,6 +404,12 @@ namespace Emby.Naming.Common
VideoExtraRules = new[]
{
new ExtraRule(
ExtraType.Trailer,
ExtraRuleType.DirectoryName,
"trailers",
MediaType.Video),
new ExtraRule(
ExtraType.Trailer,
ExtraRuleType.Filename,
@ -469,6 +476,12 @@ namespace Emby.Naming.Common
"theme",
MediaType.Audio),
new ExtraRule(
ExtraType.ThemeSong,
ExtraRuleType.DirectoryName,
"theme-music",
MediaType.Audio),
new ExtraRule(
ExtraType.Scene,
ExtraRuleType.Suffix,
@ -563,7 +576,7 @@ namespace Emby.Naming.Common
ExtraType.Unknown,
ExtraRuleType.DirectoryName,
"extras",
MediaType.Video),
MediaType.Video)
};
Format3DRules = new[]
@ -675,9 +688,29 @@ namespace Emby.Naming.Common
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToArray();
AllExtrasTypesFolderNames = new Dictionary<string, ExtraType>(StringComparer.OrdinalIgnoreCase)
{
["trailers"] = ExtraType.Trailer,
["theme-music"] = ExtraType.ThemeSong,
["backdrops"] = ExtraType.ThemeVideo,
["extras"] = ExtraType.Unknown,
["behind the scenes"] = ExtraType.BehindTheScenes,
["deleted scenes"] = ExtraType.DeletedScene,
["interviews"] = ExtraType.Interview,
["scenes"] = ExtraType.Scene,
["samples"] = ExtraType.Sample,
["shorts"] = ExtraType.Clip,
["featurettes"] = ExtraType.Clip
};
Compile();
}
/// <summary>
/// Gets or sets the folder name to extra types mapping.
/// </summary>
public Dictionary<string, ExtraType> AllExtrasTypesFolderNames { get; set; }
/// <summary>
/// Gets or sets list of audio file extensions.
/// </summary>
@ -759,9 +792,9 @@ namespace Emby.Naming.Common
public Format3DRule[] Format3DRules { get; set; }
/// <summary>
/// Gets or sets list of raw video file-stacking expressions strings.
/// Gets the file stacking rules.
/// </summary>
public string[] VideoFileStackingExpressions { get; set; }
public FileStackRule[] VideoFileStackingRules { get; }
/// <summary>
/// Gets or sets list of raw clean DateTimes regular expressions strings.
@ -783,11 +816,6 @@ namespace Emby.Naming.Common
/// </summary>
public ExtraRule[] VideoExtraRules { get; set; }
/// <summary>
/// Gets list of video file-stack regular expressions.
/// </summary>
public Regex[] VideoFileStackingRegexes { get; private set; } = Array.Empty<Regex>();
/// <summary>
/// Gets list of clean datetime regular expressions.
/// </summary>
@ -813,7 +841,6 @@ namespace Emby.Naming.Common
/// </summary>
public void Compile()
{
VideoFileStackingRegexes = VideoFileStackingExpressions.Select(Compile).ToArray();
CleanDateTimeRegexes = CleanDateTimes.Select(Compile).ToArray();
CleanStringRegexes = CleanStrings.Select(Compile).ToArray();
EpisodeWithoutSeasonRegexes = EpisodeWithoutSeasonExpressions.Select(Compile).ToArray();

View File

@ -2,6 +2,7 @@ using System;
using System.IO;
using System.Linq;
using Emby.Naming.Common;
using Jellyfin.Extensions;
namespace Emby.Naming.Subtitles
{
@ -34,7 +35,7 @@ namespace Emby.Naming.Subtitles
}
var extension = Path.GetExtension(path);
if (!_options.SubtitleFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
if (!_options.SubtitleFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase))
{
return null;
}
@ -42,11 +43,11 @@ namespace Emby.Naming.Subtitles
var flags = GetFlags(path);
var info = new SubtitleInfo(
path,
_options.SubtitleDefaultFlags.Any(i => flags.Contains(i, StringComparer.OrdinalIgnoreCase)),
_options.SubtitleForcedFlags.Any(i => flags.Contains(i, StringComparer.OrdinalIgnoreCase)));
_options.SubtitleDefaultFlags.Any(i => flags.Contains(i, StringComparison.OrdinalIgnoreCase)),
_options.SubtitleForcedFlags.Any(i => flags.Contains(i, StringComparison.OrdinalIgnoreCase)));
var parts = flags.Where(i => !_options.SubtitleDefaultFlags.Contains(i, StringComparer.OrdinalIgnoreCase)
&& !_options.SubtitleForcedFlags.Contains(i, StringComparer.OrdinalIgnoreCase))
var parts = flags.Where(i => !_options.SubtitleDefaultFlags.Contains(i, StringComparison.OrdinalIgnoreCase)
&& !_options.SubtitleForcedFlags.Contains(i, StringComparison.OrdinalIgnoreCase))
.ToList();
// Should have a name, language and file extension

View File

@ -1,8 +1,8 @@
using System;
using System.IO;
using System.Linq;
using Emby.Naming.Common;
using Emby.Naming.Video;
using Jellyfin.Extensions;
namespace Emby.Naming.TV
{
@ -48,7 +48,7 @@ namespace Emby.Naming.TV
{
var extension = Path.GetExtension(path);
// Check supported extensions
if (!_options.VideoFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
if (!_options.VideoFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase))
{
// It's not supported. Check stub extensions
if (!StubResolver.TryResolveFile(path, _options, out stubType))

View File

@ -1,4 +1,3 @@
using System.Globalization;
using Emby.Naming.Common;
namespace Emby.Naming.TV

View File

@ -1,4 +1,3 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Text.RegularExpressions;

View File

@ -1,5 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using Emby.Naming.Audio;
using Emby.Naming.Common;
@ -9,45 +11,27 @@ namespace Emby.Naming.Video
/// <summary>
/// Resolve if file is extra for video.
/// </summary>
public class ExtraResolver
public static class ExtraResolver
{
private static readonly char[] _digits = new[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' };
private readonly NamingOptions _options;
/// <summary>
/// Initializes a new instance of the <see cref="ExtraResolver"/> class.
/// </summary>
/// <param name="options"><see cref="NamingOptions"/> object containing VideoExtraRules and passed to <see cref="AudioFileParser"/> and <see cref="VideoResolver"/>.</param>
public ExtraResolver(NamingOptions options)
{
_options = options;
}
private static readonly char[] _digits = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' };
/// <summary>
/// Attempts to resolve if file is extra.
/// </summary>
/// <param name="path">Path to file.</param>
/// <param name="namingOptions">The naming options.</param>
/// <returns>Returns <see cref="ExtraResult"/> object.</returns>
public ExtraResult GetExtraInfo(string path)
public static ExtraResult GetExtraInfo(string path, NamingOptions namingOptions)
{
var result = new ExtraResult();
for (var i = 0; i < _options.VideoExtraRules.Length; i++)
for (var i = 0; i < namingOptions.VideoExtraRules.Length; i++)
{
var rule = _options.VideoExtraRules[i];
if (rule.MediaType == MediaType.Audio)
var rule = namingOptions.VideoExtraRules[i];
if ((rule.MediaType == MediaType.Audio && !AudioFileParser.IsAudioFile(path, namingOptions))
|| (rule.MediaType == MediaType.Video && !VideoResolver.IsVideoFile(path, namingOptions)))
{
if (!AudioFileParser.IsAudioFile(path, _options))
{
continue;
}
}
else if (rule.MediaType == MediaType.Video)
{
if (!VideoResolver.IsVideoFile(path, _options))
{
continue;
}
continue;
}
var pathSpan = path.AsSpan();
@ -76,9 +60,9 @@ namespace Emby.Naming.Video
{
var filename = Path.GetFileName(path);
var regex = new Regex(rule.Token, RegexOptions.IgnoreCase);
var isMatch = Regex.IsMatch(filename, rule.Token, RegexOptions.IgnoreCase | RegexOptions.Compiled);
if (regex.IsMatch(filename))
if (isMatch)
{
result.ExtraType = rule.ExtraType;
result.Rule = rule;
@ -102,5 +86,66 @@ namespace Emby.Naming.Video
return result;
}
/// <summary>
/// Finds extras matching the video info.
/// </summary>
/// <param name="files">The list of file video infos.</param>
/// <param name="videoInfo">The video to compare against.</param>
/// <param name="videoFlagDelimiters">The video flag delimiters.</param>
/// <returns>A list of video extras for [videoInfo].</returns>
public static IReadOnlyList<VideoFileInfo> GetExtras(IReadOnlyList<VideoInfo> files, VideoFileInfo videoInfo, ReadOnlySpan<char> videoFlagDelimiters)
{
var parentDir = videoInfo.IsDirectory ? videoInfo.Path : Path.GetDirectoryName(videoInfo.Path.AsSpan());
var trimmedFileNameWithoutExtension = TrimFilenameDelimiters(videoInfo.FileNameWithoutExtension, videoFlagDelimiters);
var trimmedVideoInfoName = TrimFilenameDelimiters(videoInfo.Name, videoFlagDelimiters);
var result = new List<VideoFileInfo>();
for (var pos = files.Count - 1; pos >= 0; pos--)
{
var current = files[pos];
// ignore non-extras and multi-file (can this happen?)
if (current.ExtraType == null || current.Files.Count > 1)
{
continue;
}
var currentFile = current.Files[0];
var trimmedCurrentFileName = TrimFilenameDelimiters(currentFile.Name, videoFlagDelimiters);
// first check filenames
bool isValid = StartsWith(trimmedCurrentFileName, trimmedFileNameWithoutExtension)
|| (StartsWith(trimmedCurrentFileName, trimmedVideoInfoName) && currentFile.Year == videoInfo.Year);
// then by directory
if (!isValid)
{
// When the extra rule type is DirectoryName we must go one level higher to get the "real" dir name
var currentParentDir = currentFile.ExtraRule?.RuleType == ExtraRuleType.DirectoryName
? Path.GetDirectoryName(Path.GetDirectoryName(currentFile.Path.AsSpan()))
: Path.GetDirectoryName(currentFile.Path.AsSpan());
isValid = !currentParentDir.IsEmpty && !parentDir.IsEmpty && currentParentDir.Equals(parentDir, StringComparison.OrdinalIgnoreCase);
}
if (isValid)
{
result.Add(currentFile);
}
}
return result.OrderBy(r => r.Path).ToArray();
}
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);
}
}
}

View File

@ -1,6 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Jellyfin.Extensions;
namespace Emby.Naming.Video
{
@ -12,25 +12,30 @@ namespace Emby.Naming.Video
/// <summary>
/// Initializes a new instance of the <see cref="FileStack"/> class.
/// </summary>
public FileStack()
/// <param name="name">The stack name.</param>
/// <param name="isDirectory">Whether the stack files are directories.</param>
/// <param name="files">The stack files.</param>
public FileStack(string name, bool isDirectory, IReadOnlyList<string> files)
{
Files = new List<string>();
Name = name;
IsDirectoryStack = isDirectory;
Files = files;
}
/// <summary>
/// Gets or sets name of file stack.
/// Gets the name of file stack.
/// </summary>
public string Name { get; set; } = string.Empty;
public string Name { get; }
/// <summary>
/// Gets or sets list of paths in stack.
/// Gets the list of paths in stack.
/// </summary>
public List<string> Files { get; set; }
public IReadOnlyList<string> Files { get; }
/// <summary>
/// Gets or sets a value indicating whether stack is directory stack.
/// Gets a value indicating whether stack is directory stack.
/// </summary>
public bool IsDirectoryStack { get; set; }
public bool IsDirectoryStack { get; }
/// <summary>
/// Helper function to determine if path is in the stack.
@ -40,12 +45,12 @@ namespace Emby.Naming.Video
/// <returns>True if file is in the stack.</returns>
public bool ContainsFile(string file, bool isDirectory)
{
if (IsDirectoryStack == isDirectory)
if (string.IsNullOrEmpty(file))
{
return Files.Contains(file, StringComparer.OrdinalIgnoreCase);
return false;
}
return false;
return IsDirectoryStack == isDirectory && Files.Contains(file, StringComparison.OrdinalIgnoreCase);
}
}
}

View File

@ -0,0 +1,48 @@
using System.Diagnostics.CodeAnalysis;
using System.Text.RegularExpressions;
namespace Emby.Naming.Video;
/// <summary>
/// Regex based rule for file stacking (eg. disc1, disc2).
/// </summary>
public class FileStackRule
{
private readonly Regex _tokenRegex;
/// <summary>
/// Initializes a new instance of the <see cref="FileStackRule"/> class.
/// </summary>
/// <param name="token">Token.</param>
/// <param name="isNumerical">Whether the file stack rule uses numerical or alphabetical numbering.</param>
public FileStackRule(string token, bool isNumerical)
{
_tokenRegex = new Regex(token, RegexOptions.IgnoreCase);
IsNumerical = isNumerical;
}
/// <summary>
/// Gets a value indicating whether the rule uses numerical or alphabetical numbering.
/// </summary>
public bool IsNumerical { get; }
/// <summary>
/// Match the input against the rule regex.
/// </summary>
/// <param name="input">The input.</param>
/// <param name="result">The part type and number or <c>null</c>.</param>
/// <returns>A value indicating whether the input matched the rule.</returns>
public bool Match(string input, [NotNullWhen(true)] out (string StackName, string PartType, string PartNumber)? result)
{
result = null;
var match = _tokenRegex.Match(input);
if (!match.Success)
{
return false;
}
var partType = match.Groups["parttype"].Success ? match.Groups["parttype"].Value : "unknown";
result = (match.Groups["filename"].Value, partType, match.Groups["number"].Value);
return true;
}
}

View File

@ -2,7 +2,6 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using Emby.Naming.AudioBook;
using Emby.Naming.Common;
using MediaBrowser.Model.IO;
@ -12,37 +11,28 @@ namespace Emby.Naming.Video
/// <summary>
/// Resolve <see cref="FileStack"/> from list of paths.
/// </summary>
public class StackResolver
public static class StackResolver
{
private readonly NamingOptions _options;
/// <summary>
/// Initializes a new instance of the <see cref="StackResolver"/> class.
/// </summary>
/// <param name="options"><see cref="NamingOptions"/> object containing VideoFileStackingRegexes and passes options to <see cref="VideoResolver"/>.</param>
public StackResolver(NamingOptions options)
{
_options = options;
}
/// <summary>
/// Resolves only directories from paths.
/// </summary>
/// <param name="files">List of paths.</param>
/// <param name="namingOptions">The naming options.</param>
/// <returns>Enumerable <see cref="FileStack"/> of directories.</returns>
public IEnumerable<FileStack> ResolveDirectories(IEnumerable<string> files)
public static IEnumerable<FileStack> ResolveDirectories(IEnumerable<string> files, NamingOptions namingOptions)
{
return Resolve(files.Select(i => new FileSystemMetadata { FullName = i, IsDirectory = true }));
return Resolve(files.Select(i => new FileSystemMetadata { FullName = i, IsDirectory = true }), namingOptions);
}
/// <summary>
/// Resolves only files from paths.
/// </summary>
/// <param name="files">List of paths.</param>
/// <param name="namingOptions">The naming options.</param>
/// <returns>Enumerable <see cref="FileStack"/> of files.</returns>
public IEnumerable<FileStack> ResolveFiles(IEnumerable<string> files)
public static IEnumerable<FileStack> ResolveFiles(IEnumerable<string> files, NamingOptions namingOptions)
{
return Resolve(files.Select(i => new FileSystemMetadata { FullName = i, IsDirectory = false }));
return Resolve(files.Select(i => new FileSystemMetadata { FullName = i, IsDirectory = false }), namingOptions);
}
/// <summary>
@ -50,7 +40,7 @@ namespace Emby.Naming.Video
/// </summary>
/// <param name="files">List of paths.</param>
/// <returns>Enumerable <see cref="FileStack"/> of directories.</returns>
public IEnumerable<FileStack> ResolveAudioBooks(IEnumerable<AudioBookFileInfo> files)
public static IEnumerable<FileStack> ResolveAudioBooks(IEnumerable<AudioBookFileInfo> files)
{
var groupedDirectoryFiles = files.GroupBy(file => Path.GetDirectoryName(file.Path));
@ -60,19 +50,13 @@ namespace Emby.Naming.Video
{
foreach (var file in directory)
{
var stack = new FileStack { Name = Path.GetFileNameWithoutExtension(file.Path), IsDirectoryStack = false };
stack.Files.Add(file.Path);
var stack = new FileStack(Path.GetFileNameWithoutExtension(file.Path), false, new[] { file.Path });
yield return stack;
}
}
else
{
var stack = new FileStack { Name = Path.GetFileName(directory.Key), IsDirectoryStack = false };
foreach (var file in directory)
{
stack.Files.Add(file.Path);
}
var stack = new FileStack(Path.GetFileName(directory.Key), false, directory.Select(f => f.Path).ToArray());
yield return stack;
}
}
@ -82,158 +66,91 @@ namespace Emby.Naming.Video
/// Resolves videos from paths.
/// </summary>
/// <param name="files">List of paths.</param>
/// <param name="namingOptions">The naming options.</param>
/// <returns>Enumerable <see cref="FileStack"/> of videos.</returns>
public IEnumerable<FileStack> Resolve(IEnumerable<FileSystemMetadata> files)
public static IEnumerable<FileStack> Resolve(IEnumerable<FileSystemMetadata> files, NamingOptions namingOptions)
{
var list = files
.Where(i => i.IsDirectory || VideoResolver.IsVideoFile(i.FullName, _options) || VideoResolver.IsStubFile(i.FullName, _options))
.OrderBy(i => i.FullName)
.ToList();
var potentialFiles = files
.Where(i => i.IsDirectory || VideoResolver.IsVideoFile(i.FullName, namingOptions) || VideoResolver.IsStubFile(i.FullName, namingOptions))
.OrderBy(i => i.FullName);
var expressions = _options.VideoFileStackingRegexes;
for (var i = 0; i < list.Count; i++)
var potentialStacks = new Dictionary<string, StackMetadata>();
foreach (var file in potentialFiles)
{
var offset = 0;
var file1 = list[i];
var expressionIndex = 0;
while (expressionIndex < expressions.Length)
var name = file.Name;
if (string.IsNullOrEmpty(name))
{
var exp = expressions[expressionIndex];
var stack = new FileStack();
name = Path.GetFileName(file.FullName);
}
// (Title)(Volume)(Ignore)(Extension)
var match1 = FindMatch(file1, exp, offset);
if (match1.Success)
for (var i = 0; i < namingOptions.VideoFileStackingRules.Length; i++)
{
var rule = namingOptions.VideoFileStackingRules[i];
if (!rule.Match(name, out var stackParsingResult))
{
var title1 = match1.Groups["title"].Value;
var volume1 = match1.Groups["volume"].Value;
var ignore1 = match1.Groups["ignore"].Value;
var extension1 = match1.Groups["extension"].Value;
continue;
}
var j = i + 1;
while (j < list.Count)
var stackName = stackParsingResult.Value.StackName;
var partNumber = stackParsingResult.Value.PartNumber;
var partType = stackParsingResult.Value.PartType;
if (!potentialStacks.TryGetValue(stackName, out var stackResult))
{
stackResult = new StackMetadata(file.IsDirectory, rule.IsNumerical, partType);
potentialStacks[stackName] = stackResult;
}
if (stackResult.Parts.Count > 0)
{
if (stackResult.IsDirectory != file.IsDirectory
|| !string.Equals(partType, stackResult.PartType, StringComparison.OrdinalIgnoreCase)
|| stackResult.ContainsPart(partNumber))
{
var file2 = list[j];
if (file1.IsDirectory != file2.IsDirectory)
{
j++;
continue;
}
// (Title)(Volume)(Ignore)(Extension)
var match2 = FindMatch(file2, exp, offset);
if (match2.Success)
{
var title2 = match2.Groups[1].Value;
var volume2 = match2.Groups[2].Value;
var ignore2 = match2.Groups[3].Value;
var extension2 = match2.Groups[4].Value;
if (string.Equals(title1, title2, StringComparison.OrdinalIgnoreCase))
{
if (!string.Equals(volume1, volume2, StringComparison.OrdinalIgnoreCase))
{
if (string.Equals(ignore1, ignore2, StringComparison.OrdinalIgnoreCase)
&& string.Equals(extension1, extension2, StringComparison.OrdinalIgnoreCase))
{
if (stack.Files.Count == 0)
{
stack.Name = title1 + ignore1;
stack.IsDirectoryStack = file1.IsDirectory;
stack.Files.Add(file1.FullName);
}
stack.Files.Add(file2.FullName);
}
else
{
// Sequel
offset = 0;
expressionIndex++;
break;
}
}
else if (!string.Equals(ignore1, ignore2, StringComparison.OrdinalIgnoreCase))
{
// False positive, try again with offset
offset = match1.Groups[3].Index;
break;
}
else
{
// Extension mismatch
offset = 0;
expressionIndex++;
break;
}
}
else
{
// Title mismatch
offset = 0;
expressionIndex++;
break;
}
}
else
{
// No match 2, next expression
offset = 0;
expressionIndex++;
break;
}
j++;
continue;
}
if (j == list.Count)
if (rule.IsNumerical != stackResult.IsNumerical)
{
expressionIndex = expressions.Length;
break;
}
}
else
{
// No match 1
offset = 0;
expressionIndex++;
}
if (stack.Files.Count > 1)
{
yield return stack;
i += stack.Files.Count - 1;
break;
}
stackResult.Parts.Add(partNumber, file);
break;
}
}
}
private static string GetRegexInput(FileSystemMetadata file)
{
// For directories, dummy up an extension otherwise the expressions will fail
var input = !file.IsDirectory
? file.FullName
: file.FullName + ".mkv";
return Path.GetFileName(input);
}
private static Match FindMatch(FileSystemMetadata input, Regex regex, int offset)
{
var regexInput = GetRegexInput(input);
if (offset < 0 || offset >= regexInput.Length)
foreach (var (fileName, stack) in potentialStacks)
{
return Match.Empty;
if (stack.Parts.Count < 2)
{
continue;
}
yield return new FileStack(fileName, stack.IsDirectory, stack.Parts.Select(kv => kv.Value.FullName).ToArray());
}
}
private class StackMetadata
{
public StackMetadata(bool isDirectory, bool isNumerical, string partType)
{
Parts = new Dictionary<string, FileSystemMetadata>(StringComparer.OrdinalIgnoreCase);
IsDirectory = isDirectory;
IsNumerical = isNumerical;
PartType = partType;
}
return regex.Match(regexInput, offset);
public Dictionary<string, FileSystemMetadata> Parts { get; }
public bool IsDirectory { get; }
public bool IsNumerical { get; }
public string PartType { get; }
public bool ContainsPart(string partNumber) => Parts.ContainsKey(partNumber);
}
}
}

View File

@ -1,7 +1,7 @@
using System;
using System.IO;
using System.Linq;
using Emby.Naming.Common;
using Jellyfin.Extensions;
namespace Emby.Naming.Video
{
@ -28,7 +28,7 @@ namespace Emby.Naming.Video
var extension = Path.GetExtension(path);
if (!options.StubFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
if (!options.StubFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase))
{
return false;
}

View File

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using MediaBrowser.Model.Entities;
namespace Emby.Naming.Video
{
@ -17,7 +18,6 @@ namespace Emby.Naming.Video
Name = name;
Files = Array.Empty<VideoFileInfo>();
Extras = Array.Empty<VideoFileInfo>();
AlternateVersions = Array.Empty<VideoFileInfo>();
}
@ -39,16 +39,15 @@ namespace Emby.Naming.Video
/// <value>The files.</value>
public IReadOnlyList<VideoFileInfo> Files { get; set; }
/// <summary>
/// Gets or sets the extras.
/// </summary>
/// <value>The extras.</value>
public IReadOnlyList<VideoFileInfo> Extras { get; set; }
/// <summary>
/// Gets or sets the alternate versions.
/// </summary>
/// <value>The alternate versions.</value>
public IReadOnlyList<VideoFileInfo> AlternateVersions { get; set; }
/// <summary>
/// Gets or sets the extra type.
/// </summary>
public ExtraType? ExtraType { get; set; }
}
}

View File

@ -4,7 +4,6 @@ using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using Emby.Naming.Common;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
namespace Emby.Naming.Video
@ -17,29 +16,38 @@ namespace Emby.Naming.Video
/// <summary>
/// Resolves alternative versions and extras from list of video files.
/// </summary>
/// <param name="files">List of related video files.</param>
/// <param name="videoInfos">List of related video files.</param>
/// <param name="namingOptions">The naming options.</param>
/// <param name="supportMultiVersion">Indication we should consider multi-versions of content.</param>
/// <param name="parseName">Whether to parse the name or use the filename.</param>
/// <returns>Returns enumerable of <see cref="VideoInfo"/> which groups files together when related.</returns>
public static IEnumerable<VideoInfo> Resolve(IEnumerable<FileSystemMetadata> files, NamingOptions namingOptions, bool supportMultiVersion = true)
public static IReadOnlyList<VideoInfo> Resolve(IReadOnlyList<VideoFileInfo> videoInfos, NamingOptions namingOptions, bool supportMultiVersion = true, bool parseName = true)
{
var videoInfos = files
.Select(i => VideoResolver.Resolve(i.FullName, i.IsDirectory, namingOptions))
.OfType<VideoFileInfo>()
.ToList();
// Filter out all extras, otherwise they could cause stacks to not be resolved
// See the unit test TestStackedWithTrailer
var nonExtras = videoInfos
.Where(i => i.ExtraType == null)
.Select(i => new FileSystemMetadata { FullName = i.Path, IsDirectory = i.IsDirectory });
var stackResult = new StackResolver(namingOptions)
.Resolve(nonExtras).ToList();
var stackResult = StackResolver.Resolve(nonExtras, namingOptions).ToList();
var remainingFiles = videoInfos
.Where(i => !stackResult.Any(s => i.Path != null && s.ContainsFile(i.Path, i.IsDirectory)))
.ToList();
var remainingFiles = new List<VideoFileInfo>();
var standaloneMedia = new List<VideoFileInfo>();
for (var i = 0; i < videoInfos.Count; i++)
{
var current = videoInfos[i];
if (stackResult.Any(s => s.ContainsFile(current.Path, current.IsDirectory)))
{
continue;
}
remainingFiles.Add(current);
if (current.ExtraType == null)
{
standaloneMedia.Add(current);
}
}
var list = new List<VideoInfo>();
@ -47,27 +55,15 @@ namespace Emby.Naming.Video
{
var info = new VideoInfo(stack.Name)
{
Files = stack.Files.Select(i => VideoResolver.Resolve(i, stack.IsDirectoryStack, namingOptions))
Files = stack.Files.Select(i => VideoResolver.Resolve(i, stack.IsDirectoryStack, namingOptions, parseName))
.OfType<VideoFileInfo>()
.ToList()
};
info.Year = info.Files[0].Year;
var extras = ExtractExtras(remainingFiles, stack.Name, Path.GetFileNameWithoutExtension(stack.Files[0].AsSpan()), namingOptions.VideoFlagDelimiters);
if (extras.Count > 0)
{
info.Extras = extras;
}
list.Add(info);
}
var standaloneMedia = remainingFiles
.Where(i => i.ExtraType == null)
.ToList();
foreach (var media in standaloneMedia)
{
var info = new VideoInfo(media.Name) { Files = new[] { media } };
@ -75,10 +71,6 @@ namespace Emby.Naming.Video
info.Year = info.Files[0].Year;
remainingFiles.Remove(media);
var extras = ExtractExtras(remainingFiles, media.FileNameWithoutExtension, namingOptions.VideoFlagDelimiters);
info.Extras = extras;
list.Add(info);
}
@ -87,58 +79,12 @@ namespace Emby.Naming.Video
list = GetVideosGroupedByVersion(list, namingOptions);
}
// If there's only one resolved video, use the folder name as well to find extras
if (list.Count == 1)
{
var info = list[0];
var videoPath = list[0].Files[0].Path;
var parentPath = Path.GetDirectoryName(videoPath.AsSpan());
if (!parentPath.IsEmpty)
{
var folderName = Path.GetFileName(parentPath);
if (!folderName.IsEmpty)
{
var extras = ExtractExtras(remainingFiles, folderName, namingOptions.VideoFlagDelimiters);
extras.AddRange(info.Extras);
info.Extras = extras;
}
}
// Add the extras that are just based on file name as well
var extrasByFileName = remainingFiles
.Where(i => i.ExtraRule != null && i.ExtraRule.RuleType == ExtraRuleType.Filename)
.ToList();
remainingFiles = remainingFiles
.Except(extrasByFileName)
.ToList();
extrasByFileName.AddRange(info.Extras);
info.Extras = extrasByFileName;
}
// If there's only one video, accept all trailers
// Be lenient because people use all kinds of mishmash conventions with trailers.
if (list.Count == 1)
{
var trailers = remainingFiles
.Where(i => i.ExtraType == ExtraType.Trailer)
.ToList();
trailers.AddRange(list[0].Extras);
list[0].Extras = trailers;
remainingFiles = remainingFiles
.Except(trailers)
.ToList();
}
// Whatever files are left, just add them
list.AddRange(remainingFiles.Select(i => new VideoInfo(i.Name)
{
Files = new[] { i },
Year = i.Year
Year = i.Year,
ExtraType = i.ExtraType
}));
return list;
@ -162,6 +108,11 @@ namespace Emby.Naming.Video
for (var i = 0; i < videos.Count; i++)
{
var video = videos[i];
if (video.ExtraType != null)
{
continue;
}
if (!IsEligibleForMultiVersion(folderName, video.Files[0].Path, namingOptions))
{
return videos;
@ -178,17 +129,14 @@ namespace Emby.Naming.Video
var alternateVersionsLen = videos.Count - 1;
var alternateVersions = new VideoFileInfo[alternateVersionsLen];
var extras = new List<VideoFileInfo>(list[0].Extras);
for (int i = 0; i < alternateVersionsLen; i++)
{
var video = videos[i + 1];
alternateVersions[i] = video.Files[0];
extras.AddRange(video.Extras);
}
list[0].AlternateVersions = alternateVersions;
list[0].Name = folderName.ToString();
list[0].Extras = extras;
return list;
}
@ -230,7 +178,7 @@ namespace Emby.Naming.Video
var tmpTestFilename = testFilename.ToString();
if (CleanStringParser.TryClean(tmpTestFilename, namingOptions.CleanStringRegexes, out var cleanName))
{
tmpTestFilename = cleanName.Trim().ToString();
tmpTestFilename = cleanName.Trim();
}
// The CleanStringParser should have removed common keywords etc.
@ -238,67 +186,5 @@ namespace Emby.Naming.Video
|| testFilename[0] == '-'
|| Regex.IsMatch(tmpTestFilename, @"^\[([^]]*)\]", RegexOptions.Compiled);
}
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, ReadOnlySpan<char> trimmedBaseName)
{
if (baseName.IsEmpty)
{
return false;
}
return fileName.StartsWith(baseName, StringComparison.OrdinalIgnoreCase)
|| (!trimmedBaseName.IsEmpty && fileName.StartsWith(trimmedBaseName, StringComparison.OrdinalIgnoreCase));
}
/// <summary>
/// Finds similar filenames to that of [baseName] and removes any matches from [remainingFiles].
/// </summary>
/// <param name="remainingFiles">The list of remaining filenames.</param>
/// <param name="baseName">The base name to use for the comparison.</param>
/// <param name="videoFlagDelimiters">The video flag delimiters.</param>
/// <returns>A list of video extras for [baseName].</returns>
private static List<VideoFileInfo> ExtractExtras(IList<VideoFileInfo> remainingFiles, ReadOnlySpan<char> baseName, ReadOnlySpan<char> videoFlagDelimiters)
{
return ExtractExtras(remainingFiles, baseName, ReadOnlySpan<char>.Empty, videoFlagDelimiters);
}
/// <summary>
/// Finds similar filenames to that of [firstBaseName] and [secondBaseName] and removes any matches from [remainingFiles].
/// </summary>
/// <param name="remainingFiles">The list of remaining filenames.</param>
/// <param name="firstBaseName">The first base name to use for the comparison.</param>
/// <param name="secondBaseName">The second base name to use for the comparison.</param>
/// <param name="videoFlagDelimiters">The video flag delimiters.</param>
/// <returns>A list of video extras for [firstBaseName] and [secondBaseName].</returns>
private static List<VideoFileInfo> ExtractExtras(IList<VideoFileInfo> remainingFiles, ReadOnlySpan<char> firstBaseName, ReadOnlySpan<char> secondBaseName, ReadOnlySpan<char> videoFlagDelimiters)
{
var trimmedFirstBaseName = TrimFilenameDelimiters(firstBaseName, videoFlagDelimiters);
var trimmedSecondBaseName = TrimFilenameDelimiters(secondBaseName, videoFlagDelimiters);
var result = new List<VideoFileInfo>();
for (var pos = remainingFiles.Count - 1; pos >= 0; pos--)
{
var file = remainingFiles[pos];
if (file.ExtraType == null)
{
continue;
}
var filename = file.FileNameWithoutExtension;
if (StartsWith(filename, firstBaseName, trimmedFirstBaseName)
|| StartsWith(filename, secondBaseName, trimmedSecondBaseName))
{
result.Add(file);
remainingFiles.RemoveAt(pos);
}
}
return result;
}
}
}

View File

@ -16,10 +16,11 @@ namespace Emby.Naming.Video
/// </summary>
/// <param name="path">The path.</param>
/// <param name="namingOptions">The naming options.</param>
/// <param name="parseName">Whether to parse the name or use the filename.</param>
/// <returns>VideoFileInfo.</returns>
public static VideoFileInfo? ResolveDirectory(string? path, NamingOptions namingOptions)
public static VideoFileInfo? ResolveDirectory(string? path, NamingOptions namingOptions, bool parseName = true)
{
return Resolve(path, true, namingOptions);
return Resolve(path, true, namingOptions, parseName);
}
/// <summary>
@ -74,7 +75,7 @@ namespace Emby.Naming.Video
var format3DResult = Format3DParser.Parse(path, namingOptions);
var extraResult = new ExtraResolver(namingOptions).GetExtraInfo(path);
var extraResult = ExtraResolver.GetExtraInfo(path, namingOptions);
var name = Path.GetFileNameWithoutExtension(path);

View File

@ -5,6 +5,7 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Data.Events;
using Jellyfin.Extensions;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Entities;
@ -104,7 +105,7 @@ namespace Emby.Notifications
var type = entry.Type;
if (string.IsNullOrEmpty(type) || !_coreNotificationTypes.Contains(type, StringComparer.OrdinalIgnoreCase))
if (string.IsNullOrEmpty(type) || !_coreNotificationTypes.Contains(type, StringComparison.OrdinalIgnoreCase))
{
return;
}

View File

@ -3,6 +3,7 @@ using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Extensions;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
@ -60,7 +61,7 @@ namespace Emby.Photos
item.SetImagePath(ImageType.Primary, item.Path);
// Examples: https://github.com/mono/taglib-sharp/blob/a5f6949a53d09ce63ee7495580d6802921a21f14/tests/fixtures/TagLib.Tests.Images/NullOrientationTest.cs
if (_includeExtensions.Contains(Path.GetExtension(item.Path), StringComparer.OrdinalIgnoreCase))
if (_includeExtensions.Contains(Path.GetExtension(item.Path), StringComparison.OrdinalIgnoreCase))
{
try
{

View File

@ -301,7 +301,7 @@ namespace Emby.Server.Implementations.AppBase
{
return _configurations.GetOrAdd(
key,
(k, configurationManager) =>
static (k, configurationManager) =>
{
var file = configurationManager.GetConfigurationFile(k);

View File

@ -1,6 +1,5 @@
using System;
using System.IO;
using System.Linq;
using MediaBrowser.Model.Serialization;
namespace Emby.Server.Implementations.AppBase

View File

@ -313,22 +313,6 @@ namespace Emby.Server.Implementations
? Environment.MachineName
: ConfigurationManager.Configuration.ServerName;
/// <summary>
/// Temporary function to migration network settings out of system.xml and into network.xml.
/// TODO: remove at the point when a fixed migration path has been decided upon.
/// </summary>
private void MigrateNetworkConfiguration()
{
string path = Path.Combine(ConfigurationManager.CommonApplicationPaths.ConfigurationDirectoryPath, "network.xml");
if (!File.Exists(path))
{
var networkSettings = new NetworkConfiguration();
ClassMigrationHelper.CopyProperties(ConfigurationManager.Configuration, networkSettings);
_xmlSerializer.SerializeToFile(networkSettings, path);
Logger.LogDebug("Successfully migrated network settings.");
}
}
public string ExpandVirtualPath(string path)
{
var appPaths = ApplicationPaths;
@ -513,8 +497,6 @@ namespace Emby.Server.Implementations
ConfigurationManager.AddParts(GetExports<IConfigurationFactory>());
// Have to migrate settings here as migration subsystem not yet initialised.
MigrateNetworkConfiguration();
NetManager = new NetworkManager(ConfigurationManager, LoggerFactory.CreateLogger<NetworkManager>());
// Initialize runtime stat collection

View File

@ -1,11 +1,8 @@
using System.IO;
using MediaBrowser.Model.IO;
using SharpCompress.Archives.SevenZip;
using SharpCompress.Archives.Tar;
using SharpCompress.Common;
using SharpCompress.Readers;
using SharpCompress.Readers.GZip;
using SharpCompress.Readers.Zip;
namespace Emby.Server.Implementations.Archiving
{
@ -14,55 +11,6 @@ namespace Emby.Server.Implementations.Archiving
/// </summary>
public class ZipClient : IZipClient
{
/// <summary>
/// Extracts all.
/// </summary>
/// <param name="sourceFile">The source file.</param>
/// <param name="targetPath">The target path.</param>
/// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
public void ExtractAll(string sourceFile, string targetPath, bool overwriteExistingFiles)
{
using var fileStream = File.OpenRead(sourceFile);
ExtractAll(fileStream, targetPath, overwriteExistingFiles);
}
/// <summary>
/// Extracts all.
/// </summary>
/// <param name="source">The source.</param>
/// <param name="targetPath">The target path.</param>
/// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
public void ExtractAll(Stream source, string targetPath, bool overwriteExistingFiles)
{
using var reader = ReaderFactory.Open(source);
var options = new ExtractionOptions
{
ExtractFullPath = true
};
if (overwriteExistingFiles)
{
options.Overwrite = true;
}
Directory.CreateDirectory(targetPath);
reader.WriteAllToDirectory(targetPath, options);
}
/// <inheritdoc />
public void ExtractAllFromZip(Stream source, string targetPath, bool overwriteExistingFiles)
{
using var reader = ZipReader.Open(source);
var options = new ExtractionOptions
{
ExtractFullPath = true,
Overwrite = overwriteExistingFiles
};
Directory.CreateDirectory(targetPath);
reader.WriteAllToDirectory(targetPath, options);
}
/// <inheritdoc />
public void ExtractAllFromGz(Stream source, string targetPath, bool overwriteExistingFiles)
{
@ -94,69 +42,5 @@ namespace Emby.Server.Implementations.Archiving
reader.WriteEntryToFile(Path.Combine(targetPath, filename));
}
}
/// <summary>
/// Extracts all from7z.
/// </summary>
/// <param name="sourceFile">The source file.</param>
/// <param name="targetPath">The target path.</param>
/// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
public void ExtractAllFrom7z(string sourceFile, string targetPath, bool overwriteExistingFiles)
{
using var fileStream = File.OpenRead(sourceFile);
ExtractAllFrom7z(fileStream, targetPath, overwriteExistingFiles);
}
/// <summary>
/// Extracts all from7z.
/// </summary>
/// <param name="source">The source.</param>
/// <param name="targetPath">The target path.</param>
/// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
public void ExtractAllFrom7z(Stream source, string targetPath, bool overwriteExistingFiles)
{
using var archive = SevenZipArchive.Open(source);
using var reader = archive.ExtractAllEntries();
var options = new ExtractionOptions
{
ExtractFullPath = true,
Overwrite = overwriteExistingFiles
};
Directory.CreateDirectory(targetPath);
reader.WriteAllToDirectory(targetPath, options);
}
/// <summary>
/// Extracts all from tar.
/// </summary>
/// <param name="sourceFile">The source file.</param>
/// <param name="targetPath">The target path.</param>
/// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
public void ExtractAllFromTar(string sourceFile, string targetPath, bool overwriteExistingFiles)
{
using var fileStream = File.OpenRead(sourceFile);
ExtractAllFromTar(fileStream, targetPath, overwriteExistingFiles);
}
/// <summary>
/// Extracts all from tar.
/// </summary>
/// <param name="source">The source.</param>
/// <param name="targetPath">The target path.</param>
/// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
public void ExtractAllFromTar(Stream source, string targetPath, bool overwriteExistingFiles)
{
using var archive = TarArchive.Open(source);
using var reader = archive.ExtractAllEntries();
var options = new ExtractionOptions
{
ExtractFullPath = true,
Overwrite = overwriteExistingFiles
};
Directory.CreateDirectory(targetPath);
reader.WriteAllToDirectory(targetPath, options);
}
}
}

View File

@ -10,6 +10,7 @@ using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
using Jellyfin.Extensions.Json;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Progress;
@ -179,7 +180,7 @@ namespace Emby.Server.Implementations.Channels
try
{
return (GetChannelProvider(i) is IHasFolderAttributes hasAttributes
&& hasAttributes.Attributes.Contains("Recordings", StringComparer.OrdinalIgnoreCase)) == val;
&& hasAttributes.Attributes.Contains("Recordings", StringComparison.OrdinalIgnoreCase)) == val;
}
catch
{
@ -541,7 +542,7 @@ namespace Emby.Server.Implementations.Channels
return _libraryManager.GetItemIds(
new InternalItemsQuery
{
IncludeItemTypes = new[] { nameof(Channel) },
IncludeItemTypes = new[] { BaseItemKind.Channel },
OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) }
}).Select(i => GetChannelFeatures(i)).ToArray();
}
@ -1135,7 +1136,7 @@ namespace Emby.Server.Implementations.Channels
if (!info.IsLiveStream)
{
if (item.Tags.Contains("livestream", StringComparer.OrdinalIgnoreCase))
if (item.Tags.Contains("livestream", StringComparison.OrdinalIgnoreCase))
{
item.Tags = item.Tags.Except(new[] { "livestream" }, StringComparer.OrdinalIgnoreCase).ToArray();
_logger.LogDebug("Forcing update due to Tags {0}", item.Name);
@ -1144,7 +1145,7 @@ namespace Emby.Server.Implementations.Channels
}
else
{
if (!item.Tags.Contains("livestream", StringComparer.OrdinalIgnoreCase))
if (!item.Tags.Contains("livestream", StringComparison.OrdinalIgnoreCase))
{
item.Tags = item.Tags.Concat(new[] { "livestream" }).ToArray();
_logger.LogDebug("Forcing update due to Tags {0}", item.Name);

View File

@ -2,6 +2,7 @@ using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
@ -51,7 +52,7 @@ namespace Emby.Server.Implementations.Channels
var uninstalledChannels = _libraryManager.GetItemList(new InternalItemsQuery
{
IncludeItemTypes = new[] { nameof(Channel) },
IncludeItemTypes = new[] { BaseItemKind.Channel },
ExcludeItemIds = installedChannelIds.ToArray()
});

View File

@ -4,8 +4,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Jellyfin.Extensions;
using Microsoft.Extensions.Logging;
using SQLitePCL.pretty;
@ -194,7 +194,7 @@ namespace Emby.Server.Implementations.Data
protected void AddColumn(IDatabaseConnection connection, string table, string columnName, string type, List<string> existingColumnNames)
{
if (existingColumnNames.Contains(columnName, StringComparer.OrdinalIgnoreCase))
if (existingColumnNames.Contains(columnName, StringComparison.OrdinalIgnoreCase))
{
return;
}

View File

@ -62,7 +62,7 @@ namespace Emby.Server.Implementations.Data
private readonly ItemFields[] _allItemFields = Enum.GetValues<ItemFields>();
private static readonly string[] _retriveItemColumns =
private static readonly string[] _retrieveItemColumns =
{
"type",
"data",
@ -133,7 +133,7 @@ namespace Emby.Server.Implementations.Data
"OwnerId"
};
private static readonly string _retriveItemColumnsSelectQuery = $"select {string.Join(',', _retriveItemColumns)} from TypedBaseItems where guid = @guid";
private static readonly string _retrieveItemColumnsSelectQuery = $"select {string.Join(',', _retrieveItemColumns)} from TypedBaseItems where guid = @guid";
private static readonly string[] _mediaStreamSaveColumns =
{
@ -196,57 +196,56 @@ namespace Emby.Server.Implementations.Data
private static readonly string _mediaAttachmentInsertPrefix;
private static readonly HashSet<string> _programTypes = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
private static readonly BaseItemKind[] _programTypes = new[]
{
"Program",
"TvChannel",
"LiveTvProgram",
"LiveTvTvChannel"
BaseItemKind.Program,
BaseItemKind.TvChannel,
BaseItemKind.LiveTvProgram,
BaseItemKind.LiveTvChannel
};
private static readonly HashSet<string> _programExcludeParentTypes = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
private static readonly BaseItemKind[] _programExcludeParentTypes = new[]
{
"Series",
"Season",
"MusicAlbum",
"MusicArtist",
"PhotoAlbum"
BaseItemKind.Series,
BaseItemKind.Season,
BaseItemKind.MusicAlbum,
BaseItemKind.MusicArtist,
BaseItemKind.PhotoAlbum
};
private static readonly HashSet<string> _serviceTypes = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
private static readonly BaseItemKind[] _serviceTypes = new[]
{
"TvChannel",
"LiveTvTvChannel"
BaseItemKind.TvChannel,
BaseItemKind.LiveTvChannel
};
private static readonly HashSet<string> _startDateTypes = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
private static readonly BaseItemKind[] _startDateTypes = new[]
{
"Program",
"LiveTvProgram"
BaseItemKind.Program,
BaseItemKind.LiveTvProgram
};
private static readonly HashSet<string> _seriesTypes = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
private static readonly BaseItemKind[] _seriesTypes = new[]
{
"Book",
"AudioBook",
"Episode",
"Season"
BaseItemKind.Book,
BaseItemKind.AudioBook,
BaseItemKind.Episode,
BaseItemKind.Season
};
private static readonly HashSet<string> _artistExcludeParentTypes = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
private static readonly BaseItemKind[] _artistExcludeParentTypes = new[]
{
"Series",
"Season",
"PhotoAlbum"
BaseItemKind.Series,
BaseItemKind.Season,
BaseItemKind.PhotoAlbum
};
private static readonly HashSet<string> _artistsTypes = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
private static readonly BaseItemKind[] _artistsTypes = new[]
{
"Audio",
"MusicAlbum",
"MusicVideo",
"AudioBook",
"AudioPodcast"
BaseItemKind.Audio,
BaseItemKind.MusicAlbum,
BaseItemKind.MusicVideo,
BaseItemKind.AudioBook
};
private static readonly Type[] _knownTypes =
@ -285,6 +284,43 @@ namespace Emby.Server.Implementations.Data
private readonly Dictionary<string, string> _types = GetTypeMapDictionary();
private static readonly Dictionary<BaseItemKind, string> _baseItemKindNames = new()
{
{ BaseItemKind.AggregateFolder, typeof(AggregateFolder).FullName },
{ BaseItemKind.Audio, typeof(Audio).FullName },
{ BaseItemKind.AudioBook, typeof(AudioBook).FullName },
{ BaseItemKind.BasePluginFolder, typeof(BasePluginFolder).FullName },
{ BaseItemKind.Book, typeof(Book).FullName },
{ BaseItemKind.BoxSet, typeof(BoxSet).FullName },
{ BaseItemKind.Channel, typeof(Channel).FullName },
{ BaseItemKind.CollectionFolder, typeof(CollectionFolder).FullName },
{ BaseItemKind.Episode, typeof(Episode).FullName },
{ BaseItemKind.Folder, typeof(Folder).FullName },
{ BaseItemKind.Genre, typeof(Genre).FullName },
{ BaseItemKind.Movie, typeof(Movie).FullName },
{ BaseItemKind.LiveTvChannel, typeof(LiveTvChannel).FullName },
{ BaseItemKind.LiveTvProgram, typeof(LiveTvProgram).FullName },
{ BaseItemKind.MusicAlbum, typeof(MusicAlbum).FullName },
{ BaseItemKind.MusicArtist, typeof(MusicArtist).FullName },
{ BaseItemKind.MusicGenre, typeof(MusicGenre).FullName },
{ BaseItemKind.MusicVideo, typeof(MusicVideo).FullName },
{ BaseItemKind.Person, typeof(Person).FullName },
{ BaseItemKind.Photo, typeof(Photo).FullName },
{ BaseItemKind.PhotoAlbum, typeof(PhotoAlbum).FullName },
{ BaseItemKind.Playlist, typeof(Playlist).FullName },
{ BaseItemKind.PlaylistsFolder, typeof(PlaylistsFolder).FullName },
{ BaseItemKind.Season, typeof(Season).FullName },
{ BaseItemKind.Series, typeof(Series).FullName },
{ BaseItemKind.Studio, typeof(Studio).FullName },
{ BaseItemKind.Trailer, typeof(Trailer).FullName },
{ BaseItemKind.TvChannel, typeof(LiveTvChannel).FullName },
{ BaseItemKind.TvProgram, typeof(LiveTvProgram).FullName },
{ BaseItemKind.UserRootFolder, typeof(UserRootFolder).FullName },
{ BaseItemKind.UserView, typeof(UserView).FullName },
{ BaseItemKind.Video, typeof(Video).FullName },
{ BaseItemKind.Year, typeof(Year).FullName }
};
static SqliteItemRepository()
{
var queryPrefixText = new StringBuilder();
@ -1320,7 +1356,7 @@ namespace Emby.Server.Implementations.Data
using (var connection = GetConnection(true))
{
using (var statement = PrepareStatement(connection, _retriveItemColumnsSelectQuery))
using (var statement = PrepareStatement(connection, _retrieveItemColumnsSelectQuery))
{
statement.TryBind("@guid", id);
@ -2212,7 +2248,7 @@ namespace Emby.Server.Implementations.Data
private bool HasProgramAttributes(InternalItemsQuery query)
{
if (_programExcludeParentTypes.Contains(query.ParentType))
if (query.ParentType != null && _programExcludeParentTypes.Contains(query.ParentType.Value))
{
return false;
}
@ -2227,7 +2263,7 @@ namespace Emby.Server.Implementations.Data
private bool HasServiceName(InternalItemsQuery query)
{
if (_programExcludeParentTypes.Contains(query.ParentType))
if (query.ParentType != null && _programExcludeParentTypes.Contains(query.ParentType.Value))
{
return false;
}
@ -2242,7 +2278,7 @@ namespace Emby.Server.Implementations.Data
private bool HasStartDate(InternalItemsQuery query)
{
if (_programExcludeParentTypes.Contains(query.ParentType))
if (query.ParentType != null && _programExcludeParentTypes.Contains(query.ParentType.Value))
{
return false;
}
@ -2262,7 +2298,7 @@ namespace Emby.Server.Implementations.Data
return true;
}
return query.IncludeItemTypes.Contains("Episode", StringComparer.OrdinalIgnoreCase);
return query.IncludeItemTypes.Contains(BaseItemKind.Episode);
}
private bool HasTrailerTypes(InternalItemsQuery query)
@ -2272,12 +2308,12 @@ namespace Emby.Server.Implementations.Data
return true;
}
return query.IncludeItemTypes.Contains("Trailer", StringComparer.OrdinalIgnoreCase);
return query.IncludeItemTypes.Contains(BaseItemKind.Trailer);
}
private bool HasArtistFields(InternalItemsQuery query)
{
if (_artistExcludeParentTypes.Contains(query.ParentType))
if (query.ParentType != null && _artistExcludeParentTypes.Contains(query.ParentType.Value))
{
return false;
}
@ -2292,7 +2328,7 @@ namespace Emby.Server.Implementations.Data
private bool HasSeriesFields(InternalItemsQuery query)
{
if (string.Equals(query.ParentType, "PhotoAlbum", StringComparison.OrdinalIgnoreCase))
if (query.ParentType == BaseItemKind.PhotoAlbum)
{
return false;
}
@ -2630,7 +2666,7 @@ namespace Emby.Server.Implementations.Data
query.Limit = query.Limit.Value + 4;
}
var columns = _retriveItemColumns.ToList();
var columns = _retrieveItemColumns.ToList();
SetFinalColumnsToSelect(query, columns);
var commandTextBuilder = new StringBuilder("select ", 1024)
.AppendJoin(',', columns)
@ -2821,7 +2857,7 @@ namespace Emby.Server.Implementations.Data
query.Limit = query.Limit.Value + 4;
}
var columns = _retriveItemColumns.ToList();
var columns = _retrieveItemColumns.ToList();
SetFinalColumnsToSelect(query, columns);
var commandTextBuilder = new StringBuilder("select ", 512)
.AppendJoin(',', columns)
@ -3487,8 +3523,8 @@ namespace Emby.Server.Implementations.Data
if (query.IsMovie == true)
{
if (query.IncludeItemTypes.Length == 0
|| query.IncludeItemTypes.Contains(nameof(Movie))
|| query.IncludeItemTypes.Contains(nameof(Trailer)))
|| query.IncludeItemTypes.Contains(BaseItemKind.Movie)
|| query.IncludeItemTypes.Contains(BaseItemKind.Trailer))
{
whereClauses.Add("(IsMovie is null OR IsMovie=@IsMovie)");
}
@ -3563,31 +3599,81 @@ namespace Emby.Server.Implementations.Data
statement?.TryBind("@IsFolder", query.IsFolder);
}
var includeTypes = query.IncludeItemTypes.Select(MapIncludeItemTypes).Where(x => x != null).ToArray();
var includeTypes = query.IncludeItemTypes;
// Only specify excluded types if no included types are specified
if (includeTypes.Length == 0)
if (query.IncludeItemTypes.Length == 0)
{
var excludeTypes = query.ExcludeItemTypes.Select(MapIncludeItemTypes).Where(x => x != null).ToArray();
var excludeTypes = query.ExcludeItemTypes;
if (excludeTypes.Length == 1)
{
whereClauses.Add("type<>@type");
statement?.TryBind("@type", excludeTypes[0]);
if (_baseItemKindNames.TryGetValue(excludeTypes[0], out var excludeTypeName))
{
whereClauses.Add("type<>@type");
statement?.TryBind("@type", excludeTypeName);
}
else
{
Logger.LogWarning("Undefined BaseItemKind to Type mapping: {BaseItemKind}", excludeTypes[0]);
}
}
else if (excludeTypes.Length > 1)
{
var inClause = string.Join(',', excludeTypes.Select(i => "'" + i + "'"));
whereClauses.Add($"type not in ({inClause})");
var whereBuilder = new StringBuilder("type not in (");
foreach (var excludeType in excludeTypes)
{
if (_baseItemKindNames.TryGetValue(excludeType, out var baseItemKindName))
{
whereBuilder
.Append('\'')
.Append(baseItemKindName)
.Append("',");
}
else
{
Logger.LogWarning("Undefined BaseItemKind to Type mapping: {BaseItemKind}", excludeType);
}
}
// Remove trailing comma.
whereBuilder.Length--;
whereBuilder.Append(')');
whereClauses.Add(whereBuilder.ToString());
}
}
else if (includeTypes.Length == 1)
{
whereClauses.Add("type=@type");
statement?.TryBind("@type", includeTypes[0]);
if (_baseItemKindNames.TryGetValue(includeTypes[0], out var includeTypeName))
{
whereClauses.Add("type=@type");
statement?.TryBind("@type", includeTypeName);
}
else
{
Logger.LogWarning("Undefined BaseItemKind to Type mapping: {BaseItemKind}", includeTypes[0]);
}
}
else if (includeTypes.Length > 1)
{
var inClause = string.Join(',', includeTypes.Select(i => "'" + i + "'"));
whereClauses.Add($"type in ({inClause})");
var whereBuilder = new StringBuilder("type in (");
foreach (var includeType in includeTypes)
{
if (_baseItemKindNames.TryGetValue(includeType, out var baseItemKindName))
{
whereBuilder
.Append('\'')
.Append(baseItemKindName)
.Append("',");
}
else
{
Logger.LogWarning("Undefined BaseItemKind to Type mapping: {BaseItemKind}", includeType);
}
}
// Remove trailing comma.
whereBuilder.Length--;
whereBuilder.Append(')');
whereClauses.Add(whereBuilder.ToString());
}
if (query.ChannelIds.Count == 1)
@ -3911,7 +3997,7 @@ namespace Emby.Server.Implementations.Data
if (query.IsPlayed.HasValue)
{
// We should probably figure this out for all folders, but for right now, this is the only place where we need it
if (query.IncludeItemTypes.Length == 1 && string.Equals(query.IncludeItemTypes[0], nameof(Series), StringComparison.OrdinalIgnoreCase))
if (query.IncludeItemTypes.Length == 1 && query.IncludeItemTypes[0] == BaseItemKind.Series)
{
if (query.IsPlayed.Value)
{
@ -4761,27 +4847,27 @@ namespace Emby.Server.Implementations.Data
{
var list = new List<string>();
if (IsTypeInQuery(nameof(Person), query))
if (IsTypeInQuery(BaseItemKind.Person, query))
{
list.Add(typeof(Person).FullName);
}
if (IsTypeInQuery(nameof(Genre), query))
if (IsTypeInQuery(BaseItemKind.Genre, query))
{
list.Add(typeof(Genre).FullName);
}
if (IsTypeInQuery(nameof(MusicGenre), query))
if (IsTypeInQuery(BaseItemKind.MusicGenre, query))
{
list.Add(typeof(MusicGenre).FullName);
}
if (IsTypeInQuery(nameof(MusicArtist), query))
if (IsTypeInQuery(BaseItemKind.MusicArtist, query))
{
list.Add(typeof(MusicArtist).FullName);
}
if (IsTypeInQuery(nameof(Studio), query))
if (IsTypeInQuery(BaseItemKind.Studio, query))
{
list.Add(typeof(Studio).FullName);
}
@ -4789,14 +4875,14 @@ namespace Emby.Server.Implementations.Data
return list;
}
private bool IsTypeInQuery(string type, InternalItemsQuery query)
private bool IsTypeInQuery(BaseItemKind type, InternalItemsQuery query)
{
if (query.ExcludeItemTypes.Contains(type, StringComparer.OrdinalIgnoreCase))
if (query.ExcludeItemTypes.Contains(type))
{
return false;
}
return query.IncludeItemTypes.Length == 0 || query.IncludeItemTypes.Contains(type, StringComparer.OrdinalIgnoreCase);
return query.IncludeItemTypes.Length == 0 || query.IncludeItemTypes.Contains(type);
}
private string GetCleanValue(string value)
@ -4836,12 +4922,12 @@ namespace Emby.Server.Implementations.Data
return true;
}
if (query.IncludeItemTypes.Contains(nameof(Episode), StringComparer.OrdinalIgnoreCase)
|| query.IncludeItemTypes.Contains(nameof(Video), StringComparer.OrdinalIgnoreCase)
|| query.IncludeItemTypes.Contains(nameof(Movie), StringComparer.OrdinalIgnoreCase)
|| query.IncludeItemTypes.Contains(nameof(MusicVideo), StringComparer.OrdinalIgnoreCase)
|| query.IncludeItemTypes.Contains(nameof(Series), StringComparer.OrdinalIgnoreCase)
|| query.IncludeItemTypes.Contains(nameof(Season), StringComparer.OrdinalIgnoreCase))
if (query.IncludeItemTypes.Contains(BaseItemKind.Episode)
|| query.IncludeItemTypes.Contains(BaseItemKind.Video)
|| query.IncludeItemTypes.Contains(BaseItemKind.Movie)
|| query.IncludeItemTypes.Contains(BaseItemKind.MusicVideo)
|| query.IncludeItemTypes.Contains(BaseItemKind.Series)
|| query.IncludeItemTypes.Contains(BaseItemKind.Season))
{
return true;
}
@ -4890,22 +4976,6 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
return dict;
}
private string MapIncludeItemTypes(string value)
{
if (_types.TryGetValue(value, out string result))
{
return result;
}
if (IsValidType(value))
{
return value;
}
Logger.LogWarning("Unknown item type: {ItemType}", value);
return null;
}
public void DeleteItem(Guid id)
{
if (id == Guid.Empty)
@ -5351,7 +5421,7 @@ AND Type = @InternalPersonType)");
stringBuilder.Clear();
}
List<string> columns = _retriveItemColumns.ToList();
List<string> columns = _retrieveItemColumns.ToList();
// Unfortunately we need to add it to columns to ensure the order of the columns in the select
if (!string.IsNullOrEmpty(itemCountColumns))
{
@ -5569,7 +5639,7 @@ AND Type = @InternalPersonType)");
return result;
}
private static ItemCounts GetItemCounts(IReadOnlyList<ResultSetValue> reader, int countStartColumn, string[] typesToCount)
private static ItemCounts GetItemCounts(IReadOnlyList<ResultSetValue> reader, int countStartColumn, BaseItemKind[] typesToCount)
{
var counts = new ItemCounts();

View File

@ -7,9 +7,9 @@ using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
using MediaBrowser.Common;
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Drawing;
@ -294,7 +294,7 @@ namespace Emby.Server.Implementations.Dto
path = path.TrimStart('.');
}
if (!string.IsNullOrEmpty(path) && containers.Contains(path, StringComparer.OrdinalIgnoreCase))
if (!string.IsNullOrEmpty(path) && containers.Contains(path, StringComparison.OrdinalIgnoreCase))
{
fileExtensionContainer = path;
}
@ -470,7 +470,7 @@ namespace Emby.Server.Implementations.Dto
{
var parentAlbumIds = _libraryManager.GetItemIds(new InternalItemsQuery
{
IncludeItemTypes = new[] { nameof(MusicAlbum) },
IncludeItemTypes = new[] { BaseItemKind.MusicAlbum },
Name = item.Album,
Limit = 1
});
@ -1404,44 +1404,27 @@ namespace Emby.Server.Implementations.Dto
return null;
}
ImageDimensions size;
var defaultAspectRatio = item.GetDefaultPrimaryImageAspectRatio();
if (defaultAspectRatio > 0)
{
return defaultAspectRatio;
}
if (!imageInfo.IsLocalFile)
{
return null;
return item.GetDefaultPrimaryImageAspectRatio();
}
try
{
size = _imageProcessor.GetImageDimensions(item, imageInfo);
if (size.Width <= 0 || size.Height <= 0)
var size = _imageProcessor.GetImageDimensions(item, imageInfo);
var width = size.Width;
var height = size.Height;
if (width > 0 && height > 0)
{
return null;
return (double)width / height;
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to determine primary image aspect ratio for {0}", imageInfo.Path);
return null;
_logger.LogError(ex, "Failed to determine primary image aspect ratio for {ImagePath}", imageInfo.Path);
}
var width = size.Width;
var height = size.Height;
if (width <= 0 || height <= 0)
{
return null;
}
return (double)width / height;
return item.GetDefaultPrimaryImageAspectRatio();
}
}
}

View File

@ -29,7 +29,7 @@
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="6.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.1" />
<PackageReference Include="Mono.Nat" Version="3.0.2" />
<PackageReference Include="prometheus-net.DotNetRuntime" Version="4.2.2" />
<PackageReference Include="sharpcompress" Version="0.30.1" />

View File

@ -13,7 +13,6 @@ using Jellyfin.Networking.Configuration;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Plugins;
using MediaBrowser.Model.Dlna;
using Microsoft.Extensions.Logging;
using Mono.Nat;

View File

@ -2,7 +2,6 @@
using System.Threading.Tasks;
using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Authentication;
using MediaBrowser.Controller.Net;
using Microsoft.AspNetCore.Http;

View File

@ -180,7 +180,7 @@ namespace Emby.Server.Implementations.HttpServer
}
WebSocketMessage<object>? stub;
long bytesConsumed = 0;
long bytesConsumed;
try
{
stub = DeserializeWebSocketMessage(buffer, out bytesConsumed);

View File

@ -28,35 +28,35 @@ namespace Emby.Server.Implementations.Images
var view = (CollectionFolder)item;
var viewType = view.CollectionType;
string[] includeItemTypes;
BaseItemKind[] includeItemTypes;
if (string.Equals(viewType, CollectionType.Movies, StringComparison.Ordinal))
{
includeItemTypes = new string[] { "Movie" };
includeItemTypes = new[] { BaseItemKind.Movie };
}
else if (string.Equals(viewType, CollectionType.TvShows, StringComparison.Ordinal))
{
includeItemTypes = new string[] { "Series" };
includeItemTypes = new[] { BaseItemKind.Series };
}
else if (string.Equals(viewType, CollectionType.Music, StringComparison.Ordinal))
{
includeItemTypes = new string[] { "MusicAlbum" };
includeItemTypes = new[] { BaseItemKind.MusicAlbum };
}
else if (string.Equals(viewType, CollectionType.Books, StringComparison.Ordinal))
{
includeItemTypes = new string[] { "Book", "AudioBook" };
includeItemTypes = new[] { BaseItemKind.Book, BaseItemKind.AudioBook };
}
else if (string.Equals(viewType, CollectionType.BoxSets, StringComparison.Ordinal))
{
includeItemTypes = new string[] { "BoxSet" };
includeItemTypes = new[] { BaseItemKind.BoxSet };
}
else if (string.Equals(viewType, CollectionType.HomeVideos, StringComparison.Ordinal) || string.Equals(viewType, CollectionType.Photos, StringComparison.Ordinal))
{
includeItemTypes = new string[] { "Video", "Photo" };
includeItemTypes = new[] { BaseItemKind.Video, BaseItemKind.Photo };
}
else
{
includeItemTypes = new string[] { "Video", "Audio", "Photo", "Movie", "Series" };
includeItemTypes = new[] { BaseItemKind.Video, BaseItemKind.Audio, BaseItemKind.Photo, BaseItemKind.Movie, BaseItemKind.Series };
}
var recursive = !string.Equals(CollectionType.Playlists, viewType, StringComparison.OrdinalIgnoreCase);

View File

@ -6,6 +6,8 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Dto;
@ -34,14 +36,14 @@ namespace Emby.Server.Implementations.Images
var view = (UserView)item;
var isUsingCollectionStrip = IsUsingCollectionStrip(view);
var recursive = isUsingCollectionStrip && !new[] { CollectionType.BoxSets, CollectionType.Playlists }.Contains(view.ViewType ?? string.Empty, StringComparer.OrdinalIgnoreCase);
var recursive = isUsingCollectionStrip && !new[] { CollectionType.BoxSets, CollectionType.Playlists }.Contains(view.ViewType ?? string.Empty, StringComparison.OrdinalIgnoreCase);
var result = view.GetItemList(new InternalItemsQuery
{
User = view.UserId.HasValue ? _userManager.GetUserById(view.UserId.Value) : null,
CollapseBoxSetItems = false,
Recursive = recursive,
ExcludeItemTypes = new[] { "UserView", "CollectionFolder", "Person" },
ExcludeItemTypes = new[] { BaseItemKind.UserView, BaseItemKind.CollectionFolder, BaseItemKind.Person },
DtoOptions = new DtoOptions(false)
});

View File

@ -8,8 +8,6 @@ using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
@ -43,7 +41,7 @@ namespace Emby.Server.Implementations.Images
return _libraryManager.GetItemList(new InternalItemsQuery
{
Genres = new[] { item.Name },
IncludeItemTypes = new[] { nameof(Series), nameof(Movie) },
IncludeItemTypes = new[] { BaseItemKind.Series, BaseItemKind.Movie },
OrderBy = new[] { (ItemSortBy.Random, SortOrder.Ascending) },
Limit = 4,
Recursive = true,

View File

@ -44,9 +44,9 @@ namespace Emby.Server.Implementations.Images
Genres = new[] { item.Name },
IncludeItemTypes = new[]
{
nameof(MusicAlbum),
nameof(MusicVideo),
nameof(Audio)
BaseItemKind.MusicAlbum,
BaseItemKind.MusicVideo,
BaseItemKind.Audio
},
OrderBy = new[] { (ItemSortBy.Random, SortOrder.Ascending) },
Limit = 4,

View File

@ -54,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;
}

View File

@ -11,11 +11,9 @@ 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;
@ -533,8 +531,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,
@ -654,7 +652,7 @@ namespace Emby.Server.Implementations.Library
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 +675,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);
@ -965,7 +963,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>()
@ -2685,89 +2683,105 @@ namespace Emby.Server.Implementations.Library
};
}
public IEnumerable<Video> FindTrailers(BaseItem owner, List<FileSystemMetadata> fileSystemChildren, IDirectoryService directoryService)
public IEnumerable<BaseItem> FindExtras(BaseItem owner, List<FileSystemMetadata> fileSystemChildren, IDirectoryService directoryService)
{
var namingOptions = _namingOptions;
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, namingOptions.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;
var files = new List<VideoFileInfo>();
var nonVideoFiles = new List<FileSystemMetadata>();
for (var i = 0; i < count; i++)
{
new GenericVideoResolver<Trailer>(_namingOptions)
};
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, _namingOptions.VideoFileExtensions, false, false);
foreach (var file in filesInSubFolder)
{
video = dbItem;
var videoInfo = VideoResolver.Resolve(file.FullName, file.IsDirectory, _namingOptions);
if (videoInfo == null)
{
nonVideoFiles.Add(file);
continue;
}
files.Add(videoInfo);
}
}
else if (!current.IsDirectory)
{
var videoInfo = VideoResolver.Resolve(current.FullName, current.IsDirectory, _namingOptions);
if (videoInfo == null)
{
nonVideoFiles.Add(current);
continue;
}
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 = _namingOptions;
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, namingOptions.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)));
files.Add(videoInfo);
}
}
return ResolvePaths(files, directoryService, null, new LibraryOptions(), null)
.OfType<Video>()
.Select(video =>
if (files.Count == 0)
{
yield break;
}
var videos = VideoListResolver.Resolve(files, _namingOptions);
// owner video info cannot be null as that implies it has no path
var extras = ExtraResolver.GetExtras(videos, ownerVideoInfo, _namingOptions.VideoFlagDelimiters);
for (var i = 0; i < extras.Count; i++)
{
var currentExtra = extras[i];
var resolved = ResolvePath(_fileSystem.GetFileInfo(currentExtra.Path), null, directoryService);
if (resolved is not Video video)
{
// Try to retrieve it from the db. If we don't find it, use the resolved version
var dbItem = GetItemById(video.Id) as Video;
continue;
}
if (dbItem != null)
{
video = dbItem;
}
// Try to retrieve it from the db. If we don't find it, use the resolved version
if (GetItemById(resolved.Id) is Video dbItem)
{
video = dbItem;
}
video.ParentId = Guid.Empty;
video.OwnerId = owner.Id;
video.ExtraType = currentExtra.ExtraType;
video.ParentId = Guid.Empty;
video.OwnerId = owner.Id;
yield return video;
}
SetExtraTypeFromFilename(video);
// TODO: theme songs must be handled "manually" (but should we?) since they aren't video files
for (var i = 0; i < nonVideoFiles.Count; i++)
{
var current = nonVideoFiles[i];
var extraInfo = ExtraResolver.GetExtraInfo(current.FullName, _namingOptions);
if (extraInfo.ExtraType != ExtraType.ThemeSong)
{
continue;
}
return video;
var resolved = ResolvePath(current, null, directoryService);
if (resolved is not Audio themeSong)
{
continue;
}
// Sort them so that the list can be easily compared for changes
}).OrderBy(i => i.Path);
// Try to retrieve it from the db. If we don't find it, use the resolved version
if (GetItemById(themeSong.Id) is Audio dbItem)
{
themeSong = dbItem;
}
themeSong.ExtraType = ExtraType.ThemeSong;
themeSong.OwnerId = owner.Id;
themeSong.ParentId = Guid.Empty;
yield return themeSong;
}
}
public string GetPathAfterNetworkSubstitution(string path, BaseItem ownerItem)
@ -2817,15 +2831,6 @@ namespace Emby.Server.Implementations.Library
return path;
}
private void SetExtraTypeFromFilename(Video item)
{
var resolver = new ExtraResolver(_namingOptions);
var result = resolver.GetExtraInfo(item.Path);
item.ExtraType = result.ExtraType;
}
public List<PersonInfo> GetPeople(InternalPeopleQuery query)
{
return _itemRepository.GetPeople(query);
@ -2932,11 +2937,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;

View File

@ -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
@ -64,18 +65,18 @@ namespace Emby.Server.Implementations.Library
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 = sortedStreams.FirstOrDefault(s => !s.IsForced && preferredLanguages.Contains(s.Language, StringComparer.OrdinalIgnoreCase));
stream = sortedStreams.FirstOrDefault(s => !s.IsForced && preferredLanguages.Contains(s.Language, StringComparison.OrdinalIgnoreCase));
}
}
else if (mode == SubtitlePlaybackMode.Smart)
{
// 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.FirstOrDefault(s => !s.IsForced && 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)
@ -136,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();
}
}

View File

@ -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(),

View File

@ -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] == '=')
{
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;

View File

@ -4,12 +4,10 @@ using System;
using System.Linq;
using System.Threading.Tasks;
using Emby.Naming.Common;
using MediaBrowser.Controller.Configuration;
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

View File

@ -49,120 +49,71 @@ namespace Emby.Server.Implementations.Library.Resolvers
protected virtual TVideoType ResolveVideo<TVideoType>(ItemResolveArgs args, bool parseName)
where TVideoType : Video, new()
{
var namingOptions = NamingOptions;
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 (videoType == null)
{
continue;
}
videoInfo = VideoResolver.ResolveDirectory(args.Path, NamingOptions, parseName);
break;
}
if (video != null)
{
video.Name = parseName ?
videoInfo.Name :
Path.GetFileName(args.Path);
Set3DFormat(video, videoInfo);
}
return video;
}
else
{
var videoInfo = VideoResolver.Resolve(args.Path, false, namingOptions, false);
if (videoInfo == null)
{
return null;
}
if (VideoResolver.IsVideoFile(args.Path, NamingOptions) || videoInfo.IsStub)
{
var path = args.Path;
var video = new TVideoType
{
Path = path,
IsInMixedFolder = true,
ProductionYear = videoInfo.Year
};
SetVideoType(video, videoInfo);
video.Name = parseName ?
videoInfo.Name :
Path.GetFileNameWithoutExtension(args.Path);
Set3DFormat(video, videoInfo);
return video;
}
videoInfo = VideoResolver.Resolve(args.Path, false, NamingOptions, parseName);
}
return null;
if (videoInfo == null || (!videoInfo.IsStub && !VideoResolver.IsVideoFile(args.Path, NamingOptions)))
{
return null;
}
var video = new TVideoType
{
Name = videoInfo.Name,
Path = args.Path,
ProductionYear = videoInfo.Year,
ExtraType = videoInfo.ExtraType
};
if (videoType.HasValue)
{
video.VideoType = videoType.Value;
}
else
{
SetVideoType(video, videoInfo);
}
Set3DFormat(video, videoInfo);
return video;
}
protected void SetVideoType(Video video, VideoFileInfo videoInfo)
@ -207,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;

View File

@ -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

View File

@ -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");

View File

@ -26,7 +26,6 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
public class MovieResolver : BaseVideoResolver<Video>, IMultiItemResolver
{
private readonly IImageProcessor _imageProcessor;
private readonly StackResolver _stackResolver;
private string[] _validCollectionTypes = new[]
{
@ -46,7 +45,6 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
: base(namingOptions)
{
_imageProcessor = imageProcessor;
_stackResolver = new StackResolver(NamingOptions);
}
/// <summary>
@ -62,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)
{
@ -92,16 +90,17 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
return null;
}
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))
@ -118,17 +117,16 @@ 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
@ -169,6 +167,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;
@ -180,8 +184,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))
{
@ -190,13 +193,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))
@ -204,7 +207,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())
@ -212,12 +215,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;
@ -226,21 +229,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))
@ -259,29 +261,39 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
}
}
var resolverResult = VideoListResolver.Resolve(files, NamingOptions, suppportMultiEditions).ToList();
var videoInfos = files
.Select(i => VideoResolver.Resolve(i.FullName, i.IsDirectory, NamingOptions, parseName))
.Where(f => f != null)
.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()
};
@ -299,21 +311,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)
@ -342,9 +367,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");
@ -358,7 +383,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))
{
@ -431,7 +456,7 @@ 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)
@ -510,7 +535,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
return null;
}
var result = _stackResolver.ResolveDirectories(folderPaths).ToList();
var result = StackResolver.ResolveDirectories(folderPaths, NamingOptions).ToList();
if (result.Count != 1)
{

View File

@ -8,6 +8,7 @@ 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;
@ -109,7 +110,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);
}
}
}

View File

@ -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;
@ -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
{

View File

@ -3,7 +3,6 @@
using System;
using System.Linq;
using Emby.Naming.Common;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Entities;
@ -45,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;

View File

@ -5,12 +5,9 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Emby.Naming.Common;
using Emby.Naming.TV;
using Emby.Naming.Video;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Resolvers;
@ -185,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 id = justName.GetAttributeValue("tvdbid");
if (!string.IsNullOrEmpty(id))
var tvdbId = justName.GetAttributeValue("tvdbid");
if (!string.IsNullOrEmpty(tvdbId))
{
item.SetProviderId(MetadataProvider.Tvdb, id);
item.SetProviderId(MetadataProvider.Tvdb, 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);
}
var aniSearchId = justName.GetAttributeValue("anisearchid");
if (!string.IsNullOrEmpty(aniSearchId))
{
item.SetProviderId("AniSearch", aniSearchId);
}
}
}

View File

@ -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);
}
@ -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,7 +189,7 @@ namespace Emby.Server.Implementations.Library
searchQuery.ParentId = Guid.Empty;
searchQuery.IncludeItemsByName = true;
searchQuery.IncludeItemTypes = Array.Empty<string>();
searchQuery.IncludeItemTypes = Array.Empty<BaseItemKind>();
mediaItems = _libraryManager.GetAllArtists(searchQuery).Items.Select(i => i.Item1).ToList();
}
else

View File

@ -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));
}
@ -180,7 +178,7 @@ namespace Emby.Server.Implementations.Library
{
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 };
}
}
}
@ -344,13 +342,13 @@ namespace Emby.Server.Implementations.Library
var excludeItemTypes = includeItemTypes.Length == 0 && mediaTypes.Count == 0
? new[]
{
nameof(Person),
nameof(Studio),
nameof(Year),
nameof(MusicGenre),
nameof(Genre)
BaseItemKind.Person,
BaseItemKind.Studio,
BaseItemKind.Year,
BaseItemKind.MusicGenre,
BaseItemKind.Genre
}
: Array.Empty<string>();
: Array.Empty<BaseItemKind>();
var query = new InternalItemsQuery(user)
{

View File

@ -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();

View File

@ -64,7 +64,7 @@ namespace Emby.Server.Implementations.Library.Validators
var movies = _libraryManager.GetItemList(new InternalItemsQuery
{
MediaTypes = new string[] { MediaType.Video },
IncludeItemTypes = new[] { nameof(Movie) },
IncludeItemTypes = new[] { BaseItemKind.Movie },
IsVirtualItem = false,
OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) },
Parent = library,
@ -108,7 +108,7 @@ namespace Emby.Server.Implementations.Library.Validators
var boxSets = _libraryManager.GetItemList(new InternalItemsQuery
{
IncludeItemTypes = new[] { nameof(BoxSet) },
IncludeItemTypes = new[] { BaseItemKind.BoxSet },
CollapseBoxSetItems = false,
Recursive = true
});

View File

@ -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;
@ -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
});

View File

@ -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
});

View File

@ -17,6 +17,7 @@ using System.Xml;
using Emby.Server.Implementations.Library;
using Jellyfin.Data.Enums;
using Jellyfin.Data.Events;
using Jellyfin.Extensions;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Progress;
@ -227,7 +228,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
foreach (var virtualFolder in virtualFolders)
{
if (!virtualFolder.Locations.Contains(path, StringComparer.OrdinalIgnoreCase))
if (!virtualFolder.Locations.Contains(path, StringComparison.OrdinalIgnoreCase))
{
continue;
}
@ -891,7 +892,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
throw new ArgumentNullException(nameof(tunerHostId));
}
return info.EnabledTuners.Contains(tunerHostId, StringComparer.OrdinalIgnoreCase);
return info.EnabledTuners.Contains(tunerHostId, StringComparison.OrdinalIgnoreCase);
}
public async Task<IEnumerable<ProgramInfo>> GetProgramsAsync(string channelId, DateTime startDateUtc, DateTime endDateUtc, CancellationToken cancellationToken)
@ -1778,7 +1779,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
var program = string.IsNullOrWhiteSpace(timer.ProgramId) ? null : _libraryManager.GetItemList(new InternalItemsQuery
{
IncludeItemTypes = new[] { nameof(LiveTvProgram) },
IncludeItemTypes = new[] { BaseItemKind.LiveTvProgram },
Limit = 1,
ExternalId = timer.ProgramId,
DtoOptions = new DtoOptions(true)
@ -2137,7 +2138,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
var query = new InternalItemsQuery
{
IncludeItemTypes = new string[] { nameof(LiveTvProgram) },
IncludeItemTypes = new[] { BaseItemKind.LiveTvProgram },
Limit = 1,
DtoOptions = new DtoOptions(true)
{
@ -2332,7 +2333,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
var deletes = _timerProvider.GetAll()
.Where(i => string.Equals(i.SeriesTimerId, seriesTimer.Id, StringComparison.OrdinalIgnoreCase))
.Where(i => !allTimerIds.Contains(i.Id, StringComparer.OrdinalIgnoreCase) && i.StartDate > DateTime.UtcNow)
.Where(i => !allTimerIds.Contains(i.Id, StringComparison.OrdinalIgnoreCase) && i.StartDate > DateTime.UtcNow)
.Where(i => deleteStatuses.Contains(i.Status))
.ToList();
@ -2352,7 +2353,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
var query = new InternalItemsQuery
{
IncludeItemTypes = new string[] { nameof(LiveTvProgram) },
IncludeItemTypes = new[] { BaseItemKind.LiveTvProgram },
ExternalSeriesId = seriesTimer.SeriesId,
DtoOptions = new DtoOptions(true)
{
@ -2387,7 +2388,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
channel = _libraryManager.GetItemList(
new InternalItemsQuery
{
IncludeItemTypes = new string[] { nameof(LiveTvChannel) },
IncludeItemTypes = new[] { BaseItemKind.LiveTvChannel },
ItemIds = new[] { parent.ChannelId },
DtoOptions = new DtoOptions()
}).FirstOrDefault() as LiveTvChannel;
@ -2446,7 +2447,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
channel = _libraryManager.GetItemList(
new InternalItemsQuery
{
IncludeItemTypes = new string[] { nameof(LiveTvChannel) },
IncludeItemTypes = new[] { BaseItemKind.LiveTvChannel },
ItemIds = new[] { programInfo.ChannelId },
DtoOptions = new DtoOptions()
}).FirstOrDefault() as LiveTvChannel;
@ -2511,7 +2512,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
var seriesIds = _libraryManager.GetItemIds(
new InternalItemsQuery
{
IncludeItemTypes = new[] { nameof(Series) },
IncludeItemTypes = new[] { BaseItemKind.Series },
Name = program.Name
}).ToArray();
@ -2524,7 +2525,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
var result = _libraryManager.GetItemIds(new InternalItemsQuery
{
IncludeItemTypes = new[] { nameof(Episode) },
IncludeItemTypes = new[] { BaseItemKind.Episode },
ParentIndexNumber = program.SeasonNumber.Value,
IndexNumber = program.EpisodeNumber.Value,
AncestorIds = seriesIds,
@ -2621,7 +2622,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
if (newDevicesOnly)
{
discoveredDevices = discoveredDevices.Where(d => !configuredDeviceIds.Contains(d.DeviceId, StringComparer.OrdinalIgnoreCase))
discoveredDevices = discoveredDevices.Where(d => !configuredDeviceIds.Contains(d.DeviceId, StringComparison.OrdinalIgnoreCase))
.ToList();
}

View File

@ -9,7 +9,7 @@ using System.Globalization;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Net.Mime;
using System.Security.Cryptography;
using System.Text;
@ -101,11 +101,10 @@ namespace Emby.Server.Implementations.LiveTv.Listings
}
};
var requestString = JsonSerializer.Serialize(requestList, _jsonOptions);
_logger.LogDebug("Request string for schedules is: {RequestString}", requestString);
_logger.LogDebug("Request string for schedules is: {@RequestString}", requestList);
using var options = new HttpRequestMessage(HttpMethod.Post, ApiUrl + "/schedules");
options.Content = new StringContent(requestString, Encoding.UTF8, MediaTypeNames.Application.Json);
options.Content = JsonContent.Create(requestList, options: _jsonOptions);
options.Headers.TryAddWithoutValidation("token", token);
using var response = await Send(options, true, info, cancellationToken).ConfigureAwait(false);
await using var responseStream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
@ -121,8 +120,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
programRequestOptions.Headers.TryAddWithoutValidation("token", token);
var programIds = dailySchedules.SelectMany(d => d.Programs.Select(s => s.ProgramId)).Distinct();
programRequestOptions.Content = new ByteArrayContent(JsonSerializer.SerializeToUtf8Bytes(programIds, _jsonOptions));
programRequestOptions.Content.Headers.ContentType = MediaTypeHeaderValue.Parse(MediaTypeNames.Application.Json);
programRequestOptions.Content = JsonContent.Create(programIds, options: _jsonOptions);
using var innerResponse = await Send(programRequestOptions, true, info, cancellationToken).ConfigureAwait(false);
await using var innerResponseStream = await innerResponse.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
@ -243,19 +241,19 @@ namespace Emby.Server.Implementations.LiveTv.Listings
if (programInfo.AudioProperties.Count != 0)
{
if (programInfo.AudioProperties.Contains("atmos", StringComparer.OrdinalIgnoreCase))
if (programInfo.AudioProperties.Contains("atmos", StringComparison.OrdinalIgnoreCase))
{
audioType = ProgramAudio.Atmos;
}
else if (programInfo.AudioProperties.Contains("dd 5.1", StringComparer.OrdinalIgnoreCase))
else if (programInfo.AudioProperties.Contains("dd 5.1", StringComparison.OrdinalIgnoreCase))
{
audioType = ProgramAudio.DolbyDigital;
}
else if (programInfo.AudioProperties.Contains("dd", StringComparer.OrdinalIgnoreCase))
else if (programInfo.AudioProperties.Contains("dd", StringComparison.OrdinalIgnoreCase))
{
audioType = ProgramAudio.DolbyDigital;
}
else if (programInfo.AudioProperties.Contains("stereo", StringComparer.OrdinalIgnoreCase))
else if (programInfo.AudioProperties.Contains("stereo", StringComparison.OrdinalIgnoreCase))
{
audioType = ProgramAudio.Stereo;
}
@ -317,8 +315,8 @@ namespace Emby.Server.Implementations.LiveTv.Listings
if (programInfo.VideoProperties != null)
{
info.IsHD = programInfo.VideoProperties.Contains("hdtv", StringComparer.OrdinalIgnoreCase);
info.Is3D = programInfo.VideoProperties.Contains("3d", StringComparer.OrdinalIgnoreCase);
info.IsHD = programInfo.VideoProperties.Contains("hdtv", StringComparison.OrdinalIgnoreCase);
info.Is3D = programInfo.VideoProperties.Contains("3d", StringComparison.OrdinalIgnoreCase);
}
if (details.ContentRating != null && details.ContentRating.Count > 0)
@ -327,7 +325,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
.Replace("--", "-", StringComparison.Ordinal);
var invalid = new[] { "N/A", "Approved", "Not Rated", "Passed" };
if (invalid.Contains(info.OfficialRating, StringComparer.OrdinalIgnoreCase))
if (invalid.Contains(info.OfficialRating, StringComparison.OrdinalIgnoreCase))
{
info.OfficialRating = null;
}
@ -389,9 +387,9 @@ namespace Emby.Server.Implementations.LiveTv.Listings
if (details.Genres != null)
{
info.Genres = details.Genres.Where(g => !string.IsNullOrWhiteSpace(g)).ToList();
info.IsNews = details.Genres.Contains("news", StringComparer.OrdinalIgnoreCase);
info.IsNews = details.Genres.Contains("news", StringComparison.OrdinalIgnoreCase);
if (info.Genres.Contains("children", StringComparer.OrdinalIgnoreCase))
if (info.Genres.Contains("children", StringComparison.OrdinalIgnoreCase))
{
info.IsKids = true;
}

View File

@ -190,10 +190,10 @@ namespace Emby.Server.Implementations.LiveTv.Listings
IsSeries = program.Episode != null,
IsRepeat = program.IsPreviouslyShown && !program.IsNew,
IsPremiere = program.Premiere != null,
IsKids = program.Categories.Any(c => info.KidsCategories.Contains(c, StringComparer.OrdinalIgnoreCase)),
IsMovie = program.Categories.Any(c => info.MovieCategories.Contains(c, StringComparer.OrdinalIgnoreCase)),
IsNews = program.Categories.Any(c => info.NewsCategories.Contains(c, StringComparer.OrdinalIgnoreCase)),
IsSports = program.Categories.Any(c => info.SportsCategories.Contains(c, StringComparer.OrdinalIgnoreCase)),
IsKids = program.Categories.Any(c => info.KidsCategories.Contains(c, StringComparison.OrdinalIgnoreCase)),
IsMovie = program.Categories.Any(c => info.MovieCategories.Contains(c, StringComparison.OrdinalIgnoreCase)),
IsNews = program.Categories.Any(c => info.NewsCategories.Contains(c, StringComparison.OrdinalIgnoreCase)),
IsSports = program.Categories.Any(c => info.SportsCategories.Contains(c, StringComparison.OrdinalIgnoreCase)),
ImageUrl = program.Icon != null && !string.IsNullOrEmpty(program.Icon.Source) ? program.Icon.Source : null,
HasImage = program.Icon != null && !string.IsNullOrEmpty(program.Icon.Source),
OfficialRating = program.Rating != null && !string.IsNullOrEmpty(program.Rating.Value) ? program.Rating.Value : null,

View File

@ -7,12 +7,12 @@ using System.Globalization;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Data.Enums;
using MediaBrowser.Common;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Model.Dto;
@ -161,7 +161,7 @@ namespace Emby.Server.Implementations.LiveTv
{
var librarySeries = _libraryManager.GetItemList(new InternalItemsQuery
{
IncludeItemTypes = new string[] { nameof(Series) },
IncludeItemTypes = new[] { BaseItemKind.Series },
Name = seriesName,
Limit = 1,
ImageTypes = new ImageType[] { ImageType.Thumb },
@ -204,7 +204,7 @@ namespace Emby.Server.Implementations.LiveTv
var program = _libraryManager.GetItemList(new InternalItemsQuery
{
IncludeItemTypes = new string[] { nameof(LiveTvProgram) },
IncludeItemTypes = new[] { BaseItemKind.LiveTvProgram },
ExternalSeriesId = programSeriesId,
Limit = 1,
ImageTypes = new ImageType[] { ImageType.Primary },
@ -255,7 +255,7 @@ namespace Emby.Server.Implementations.LiveTv
{
var librarySeries = _libraryManager.GetItemList(new InternalItemsQuery
{
IncludeItemTypes = new string[] { nameof(Series) },
IncludeItemTypes = new[] { BaseItemKind.Series },
Name = seriesName,
Limit = 1,
ImageTypes = new ImageType[] { ImageType.Thumb },
@ -298,7 +298,7 @@ namespace Emby.Server.Implementations.LiveTv
var program = _libraryManager.GetItemList(new InternalItemsQuery
{
IncludeItemTypes = new string[] { nameof(Series) },
IncludeItemTypes = new[] { BaseItemKind.Series },
Name = seriesName,
Limit = 1,
ImageTypes = new ImageType[] { ImageType.Primary },
@ -309,7 +309,7 @@ namespace Emby.Server.Implementations.LiveTv
{
program = _libraryManager.GetItemList(new InternalItemsQuery
{
IncludeItemTypes = new string[] { nameof(LiveTvProgram) },
IncludeItemTypes = new[] { BaseItemKind.LiveTvProgram },
ExternalSeriesId = programSeriesId,
Limit = 1,
ImageTypes = new ImageType[] { ImageType.Primary },

View File

@ -33,8 +33,6 @@ using MediaBrowser.Model.LiveTv;
using MediaBrowser.Model.Querying;
using MediaBrowser.Model.Tasks;
using Microsoft.Extensions.Logging;
using Episode = MediaBrowser.Controller.Entities.TV.Episode;
using Movie = MediaBrowser.Controller.Entities.Movies.Movie;
namespace Emby.Server.Implementations.LiveTv
{
@ -191,7 +189,7 @@ namespace Emby.Server.Implementations.LiveTv
IsKids = query.IsKids,
IsSports = query.IsSports,
IsSeries = query.IsSeries,
IncludeItemTypes = new[] { nameof(LiveTvChannel) },
IncludeItemTypes = new[] { BaseItemKind.LiveTvChannel },
TopParentIds = new[] { topFolder.Id },
IsFavorite = query.IsFavorite,
IsLiked = query.IsLiked,
@ -810,7 +808,7 @@ namespace Emby.Server.Implementations.LiveTv
var internalQuery = new InternalItemsQuery(user)
{
IncludeItemTypes = new[] { nameof(LiveTvProgram) },
IncludeItemTypes = new[] { BaseItemKind.LiveTvProgram },
MinEndDate = query.MinEndDate,
MinStartDate = query.MinStartDate,
MaxEndDate = query.MaxEndDate,
@ -874,7 +872,7 @@ namespace Emby.Server.Implementations.LiveTv
var internalQuery = new InternalItemsQuery(user)
{
IncludeItemTypes = new[] { nameof(LiveTvProgram) },
IncludeItemTypes = new[] { BaseItemKind.LiveTvProgram },
IsAiring = query.IsAiring,
HasAired = query.HasAired,
IsNews = query.IsNews,
@ -1085,8 +1083,8 @@ namespace Emby.Server.Implementations.LiveTv
if (cleanDatabase)
{
CleanDatabaseInternal(newChannelIdList.ToArray(), new[] { nameof(LiveTvChannel) }, progress, cancellationToken);
CleanDatabaseInternal(newProgramIdList.ToArray(), new[] { nameof(LiveTvProgram) }, progress, cancellationToken);
CleanDatabaseInternal(newChannelIdList.ToArray(), new[] { BaseItemKind.LiveTvChannel }, progress, cancellationToken);
CleanDatabaseInternal(newProgramIdList.ToArray(), new[] { BaseItemKind.LiveTvProgram }, progress, cancellationToken);
}
var coreService = _services.OfType<EmbyTV.EmbyTV>().FirstOrDefault();
@ -1177,7 +1175,7 @@ namespace Emby.Server.Implementations.LiveTv
var existingPrograms = _libraryManager.GetItemList(new InternalItemsQuery
{
IncludeItemTypes = new string[] { nameof(LiveTvProgram) },
IncludeItemTypes = new[] { BaseItemKind.LiveTvProgram },
ChannelIds = new Guid[] { currentChannel.Id },
DtoOptions = new DtoOptions(true)
}).Cast<LiveTvProgram>().ToDictionary(i => i.Id);
@ -1261,7 +1259,7 @@ namespace Emby.Server.Implementations.LiveTv
return new Tuple<List<Guid>, List<Guid>>(channels, programs);
}
private void CleanDatabaseInternal(Guid[] currentIdList, string[] validTypes, IProgress<double> progress, CancellationToken cancellationToken)
private void CleanDatabaseInternal(Guid[] currentIdList, BaseItemKind[] validTypes, IProgress<double> progress, CancellationToken cancellationToken)
{
var list = _itemRepo.GetItemIdsList(new InternalItemsQuery
{
@ -1328,25 +1326,25 @@ namespace Emby.Server.Implementations.LiveTv
.Select(i => i.Id)
.ToList();
var excludeItemTypes = new List<string>();
var excludeItemTypes = new List<BaseItemKind>();
if (folderIds.Count == 0)
{
return new QueryResult<BaseItem>();
}
var includeItemTypes = new List<string>();
var includeItemTypes = new List<BaseItemKind>();
var genres = new List<string>();
if (query.IsMovie.HasValue)
{
if (query.IsMovie.Value)
{
includeItemTypes.Add(nameof(Movie));
includeItemTypes.Add(BaseItemKind.Movie);
}
else
{
excludeItemTypes.Add(nameof(Movie));
excludeItemTypes.Add(BaseItemKind.Movie);
}
}
@ -1354,11 +1352,11 @@ namespace Emby.Server.Implementations.LiveTv
{
if (query.IsSeries.Value)
{
includeItemTypes.Add(nameof(Episode));
includeItemTypes.Add(BaseItemKind.Episode);
}
else
{
excludeItemTypes.Add(nameof(Episode));
excludeItemTypes.Add(BaseItemKind.Episode);
}
}
@ -1878,7 +1876,7 @@ namespace Emby.Server.Implementations.LiveTv
var programs = options.AddCurrentProgram ? _libraryManager.GetItemList(new InternalItemsQuery(user)
{
IncludeItemTypes = new[] { nameof(LiveTvProgram) },
IncludeItemTypes = new[] { BaseItemKind.LiveTvProgram },
ChannelIds = channelIds,
MaxStartDate = now,
MinEndDate = now,

View File

@ -3,7 +3,6 @@
#pragma warning disable CS1591
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
@ -85,7 +84,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
Logger.LogInformation("Opening HDHR UDP Live stream from {Host}", uri.Host);
var remoteAddress = IPAddress.Parse(uri.Host);
IPAddress localAddress = null;
IPAddress localAddress;
using (var tcpClient = new TcpClient())
{
try

View File

@ -10,6 +10,7 @@ using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Extensions;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller;
@ -119,7 +120,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
{
var extension = Path.GetExtension(mediaSource.Path) ?? string.Empty;
if (!_disallowedSharedStreamExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
if (!_disallowedSharedStreamExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase))
{
return new SharedHttpStream(mediaSource, tunerHost, streamId, FileSystem, _httpClientFactory, Logger, Config, _appHost, _streamHelper);
}

View File

@ -11,11 +11,11 @@
"Collections": "التجميعات",
"DeviceOfflineWithName": "قُطِع الاتصال ب{0}",
"DeviceOnlineWithName": "{0} متصل",
"FailedLoginAttemptWithUserName": "عملية تسجيل الدخول فشلت من {0}",
"FailedLoginAttemptWithUserName": "محاولة تسجيل الدخول فشلت من {0}",
"Favorites": "مفضلات",
"Folders": "المجلدات",
"Genres": "التضنيفات",
"HeaderAlbumArtists": "ألبوم الفنان",
"HeaderAlbumArtists": "فناني الألبوم",
"HeaderContinueWatching": "استمر بالمشاهدة",
"HeaderFavoriteAlbums": "الألبومات المفضلة",
"HeaderFavoriteArtists": "الفنانون المفضلون",

View File

@ -10,5 +10,49 @@
"AuthenticationSucceededWithUserName": "{0} wedii ddilysun llwyddiannus",
"Artists": "Artistiaid",
"AppDeviceValues": "Ap: {0}, Dyfais: {1}",
"Albums": "Albwmau"
"Albums": "Albwmau",
"Genres": "Genres",
"Folders": "Ffolderi",
"Favorites": "Ffefrynnau",
"LabelRunningTimeValue": "Amser rhedeg: {0}",
"TaskOptimizeDatabase": "Cronfa ddata Optimeiddio",
"TaskRefreshChannels": "Adnewyddu Sianeli",
"TaskRefreshPeople": "Adnewyddu Pobl",
"TasksChannelsCategory": "Sianeli Internet",
"VersionNumber": "Fersiwn {0}",
"ScheduledTaskStartedWithName": "{0} wedi dechrau",
"ScheduledTaskFailedWithName": "{0} wedi methu",
"ProviderValue": "Darparwr: {0}",
"NotificationOptionInstallationFailed": "Fethu Gosod",
"NameSeasonUnknown": "Tymor Anhysbys",
"NameSeasonNumber": "Tymor {0}",
"MusicVideos": "Fideos Cerddoriaeth",
"MixedContent": "Cynnwys amrywiol",
"HomeVideos": "Fideos Cartref",
"HeaderNextUp": "Nesaf i Fyny",
"HeaderFavoriteArtists": "Ffefryn Artistiaid",
"HeaderFavoriteAlbums": "Ffefryn Albwmau",
"HeaderContinueWatching": "Parhewch i Weithio",
"TasksApplicationCategory": "Rhaglen",
"TasksLibraryCategory": "Llyfrgell",
"TasksMaintenanceCategory": "Cynnal a Chadw",
"System": "System",
"Plugin": "Ategyn",
"Music": "Cerddoriaeth",
"Latest": "Diweddaraf",
"Inherit": "Etifeddu",
"Forced": "Orfodi",
"Application": "Rhaglen",
"HeaderAlbumArtists": "Artistiaid albwm",
"Sync": "Cysoni",
"Songs": "Caneuon",
"Shows": "Rhaglenni",
"Playlists": "Rhestri Chwarae",
"Photos": "Lluniau",
"ValueSpecialEpisodeName": "Arbennig - {0}",
"Movies": "Ffilmiau",
"Undefined": "Heb ddiffiniad",
"TvShows": "Rhaglenni teledu",
"HeaderLiveTV": "Teledu Byw",
"User": "Defnyddiwr"
}

View File

@ -104,7 +104,7 @@
"TaskRefreshChannelsDescription": "Refreŝigas informon pri interretaj kanaloj.",
"TaskDownloadMissingSubtitles": "Elŝuti mankantajn subtekstojn",
"TaskCleanTranscode": "Malplenigi Transkodadan Katalogon",
"TaskRefreshChapterImages": "Eltiri Ĉapitraj Bildojn",
"TaskRefreshChapterImages": "Eltiri Ĉapitrajn Bildojn",
"TaskCleanCache": "Malplenigi Staplan Katalogon",
"TaskCleanActivityLog": "Malplenigi Aktivecan Ĵurnalon",
"PluginUpdatedWithName": "{0} estis ĝisdatigita",

View File

@ -15,8 +15,8 @@
"Favorites": "Favoritos",
"Folders": "Carpetas",
"Genres": "Géneros",
"HeaderAlbumArtists": "Artista del álbum",
"HeaderContinueWatching": "Continuar viendo",
"HeaderAlbumArtists": "Artistas del álbum",
"HeaderContinueWatching": "Seguir viendo",
"HeaderFavoriteAlbums": "Álbumes favoritos",
"HeaderFavoriteArtists": "Artistas favoritos",
"HeaderFavoriteEpisodes": "Episodios favoritos",

View File

@ -15,7 +15,7 @@
"HeaderFavoriteEpisodes": "Episodios favoritos",
"HeaderFavoriteShows": "Programas favoritos",
"HeaderContinueWatching": "Continuar viendo",
"HeaderAlbumArtists": "Artistas del álbum",
"HeaderAlbumArtists": "Artistas de álbum",
"Genres": "Géneros",
"Folders": "Carpetas",
"Favorites": "Favoritos",
@ -29,7 +29,7 @@
"TaskRefreshChannelsDescription": "Actualiza la información de canales de Internet.",
"TaskRefreshChannels": "Actualizar canales",
"TaskCleanTranscodeDescription": "Elimina archivos transcodificados que tengan más de un día.",
"TaskCleanTranscode": "Limpiar directorio de transcodificado",
"TaskCleanTranscode": "Limpiar el directorio de transcodificaciones",
"TaskUpdatePluginsDescription": "Descarga e instala actualizaciones para complementos que están configurados para actualizarse automáticamente.",
"TaskUpdatePlugins": "Actualizar complementos",
"TaskRefreshPeopleDescription": "Actualiza metadatos de actores y directores en tu biblioteca de medios.",
@ -91,7 +91,7 @@
"NameSeasonUnknown": "Temporada desconocida",
"NameSeasonNumber": "Temporada {0}",
"NameInstallFailed": "Falló la instalación de {0}",
"MusicVideos": "Videos Musicales",
"MusicVideos": "Videos musicales",
"Music": "Música",
"MixedContent": "Contenido mezclado",
"MessageServerConfigurationUpdated": "Se ha actualizado la configuración del servidor",
@ -105,7 +105,7 @@
"Inherit": "Heredar",
"HomeVideos": "Videos caseros",
"HeaderRecordingGroups": "Grupos de grabación",
"FailedLoginAttemptWithUserName": "Intento fallido de inicio de sesión desde {0}",
"FailedLoginAttemptWithUserName": "Intento de inicio de sesión fallido desde {0}",
"DeviceOnlineWithName": "{0} está conectado",
"DeviceOfflineWithName": "{0} se ha desconectado",
"ChapterNameValue": "Capítulo {0}",
@ -114,10 +114,10 @@
"Application": "Aplicación",
"AppDeviceValues": "App: {0}, Dispositivo: {1}",
"TaskCleanActivityLogDescription": "Elimina las entradas del registro de actividad anteriores al periodo configurado.",
"TaskCleanActivityLog": "Limpiar Registro de Actividades",
"TaskCleanActivityLog": "Limpiar registro de actividades",
"Undefined": "Sin definir",
"Forced": "Forzado",
"Default": "Por defecto",
"TaskOptimizeDatabaseDescription": "Compacta la base de datos y restaura el espacio libre. Ejecutar esta tarea después de actualizar las librerías o realizar otros cambios que impliquen modificar las bases de datos puede mejorar la performance.",
"TaskOptimizeDatabase": "Optimización de base de datos"
"TaskOptimizeDatabaseDescription": "Compacta la base de datos y libera espacio. Ejecutar esta tarea después de escanear la biblioteca o hacer otros cambios que impliquen modificaciones en la base de datos puede mejorar el rendimiento.",
"TaskOptimizeDatabase": "Optimizar base de datos"
}

View File

@ -102,7 +102,7 @@
"Forced": "Sunnitud",
"Folders": "Kaustad",
"Favorites": "Lemmikud",
"FailedLoginAttemptWithUserName": "Ebaõnnestunud sisselogimiskatse kasutajalt {0}",
"FailedLoginAttemptWithUserName": "{0} - sisselogimine nurjus",
"DeviceOnlineWithName": "{0} on ühendatud",
"DeviceOfflineWithName": "{0} katkestas ühenduse",
"Default": "Vaikimisi",

View File

@ -6,7 +6,7 @@
"AuthenticationSucceededWithUserName": "{0} با موفقیت تایید اعتبار شد",
"Books": "کتاب‌ها",
"CameraImageUploadedFrom": "یک عکس جدید از دوربین ارسال شده است {0}",
"Channels": "کانالها",
"Channels": "کانالها",
"ChapterNameValue": "قسمت {0}",
"Collections": "مجموعه‌ها",
"DeviceOfflineWithName": "ارتباط {0} قطع شد",
@ -37,7 +37,7 @@
"MessageNamedServerConfigurationUpdatedWithValue": "پکربندی بخش {0} سرور بروزرسانی شد",
"MessageServerConfigurationUpdated": "پیکربندی سرور بروزرسانی شد",
"MixedContent": "محتوای مخلوط",
"Movies": "فیلمها",
"Movies": "فیلم ها",
"Music": "موسیقی",
"MusicVideos": "موزیک ویدیوها",
"NameInstallFailed": "{0} نصب با مشکل مواجه شد",
@ -118,5 +118,6 @@
"Default": "پیشفرض",
"TaskCleanActivityLogDescription": "ورودی‌های قدیمی‌تر از سن تنظیم شده در سیاهه فعالیت را حذف می‌کند.",
"TaskCleanActivityLog": "پاکسازی سیاهه فعالیت",
"Undefined": "تعریف نشده"
"Undefined": "تعریف نشده",
"TaskOptimizeDatabase": "بهینه سازی پایگاه داده"
}

View File

@ -117,5 +117,7 @@
"TaskCleanActivityLogDescription": "Tanggalin ang mga tala ng aktibidad na mas luma sa nakatakda na edad.",
"Default": "Default",
"Undefined": "Hindi tiyak",
"Forced": "Sapilitan"
"Forced": "Sapilitan",
"TaskOptimizeDatabaseDescription": "Iko-compact ang database at ita-truncate ang free space. Ang pagpapatakbo ng gawaing ito pagkatapos ng pag-scan sa library o paggawa ng iba pang mga pagbabago na nagpapahiwatig ng mga pagbabago sa database ay maaaring magpa-improve ng performance.",
"TaskOptimizeDatabase": "I-optimize ang database"
}

View File

@ -15,7 +15,7 @@
"Favorites": "Favoriti",
"Folders": "Mape",
"Genres": "Žanrovi",
"HeaderAlbumArtists": "Album od izvođača",
"HeaderAlbumArtists": "Izvođači albuma",
"HeaderContinueWatching": "Nastavi gledati",
"HeaderFavoriteAlbums": "Omiljeni albumi",
"HeaderFavoriteArtists": "Omiljeni izvođači",

View File

@ -11,12 +11,12 @@
"Collections": "コレクション",
"DeviceOfflineWithName": "{0} が切断されました",
"DeviceOnlineWithName": "{0} が接続されました",
"FailedLoginAttemptWithUserName": "ログインを試行しましたが {0}によって失敗しました",
"FailedLoginAttemptWithUserName": "ログインを試行しましたが {0} によって失敗しました",
"Favorites": "お気に入り",
"Folders": "フォルダー",
"Genres": "ジャンル",
"HeaderAlbumArtists": "アーティストのアルバム",
"HeaderContinueWatching": "視聴を続ける",
"HeaderAlbumArtists": "アルバムアーティスト",
"HeaderContinueWatching": "続きを見る",
"HeaderFavoriteAlbums": "お気に入りのアルバム",
"HeaderFavoriteArtists": "お気に入りのアーティスト",
"HeaderFavoriteEpisodes": "お気に入りのエピソード",

View File

@ -1,7 +1,7 @@
{
"Albums": "Albumai",
"AppDeviceValues": "Programa: {0}, Įrenginys: {1}",
"Application": "Programa",
"Application": "Programėlė",
"Artists": "Atlikėjai",
"AuthenticationSucceededWithUserName": "{0} sėkmingai autentifikuota",
"Books": "Knygos",

View File

@ -5,7 +5,7 @@
"PluginUninstalledWithName": "{0} беше успешно деинсталирано",
"PluginInstalledWithName": "{0} беше успешно инсталирано",
"Plugin": "Додатоци",
"Playlists": "Листи",
"Playlists": "Плејлисти",
"Photos": "Слики",
"NotificationOptionVideoPlaybackStopped": "Видео стопирано",
"NotificationOptionVideoPlayback": "Видео пуштено",
@ -97,5 +97,8 @@
"TasksChannelsCategory": "Интернет Канали",
"TasksApplicationCategory": "Апликација",
"TasksLibraryCategory": "Библиотека",
"TasksMaintenanceCategory": "Одржување"
"TasksMaintenanceCategory": "Одржување",
"Undefined": "Недефинирано",
"Forced": "Принудно",
"Default": "Зададено"
}

View File

@ -9,5 +9,6 @@
"Genres": "Төрөл зүйл",
"Favorites": "Дуртай",
"Collections": "Багц",
"Artists": "Жүжигчин"
"Artists": "Зураачуд",
"Albums": "Цомгууд"
}

View File

@ -2,7 +2,7 @@
"Albums": "Album-album",
"AppDeviceValues": "Apl: {0}, Peranti: {1}",
"Application": "Aplikasi",
"Artists": "Artis",
"Artists": "Artis-artis",
"AuthenticationSucceededWithUserName": "{0} berjaya disahkan",
"Books": "Buku-buku",
"CameraImageUploadedFrom": "Gambar baharu telah dimuat naik melalui {0}",
@ -37,9 +37,9 @@
"MessageNamedServerConfigurationUpdatedWithValue": "Konfigurasi pelayan di bahagian {0} telah dikemas kini",
"MessageServerConfigurationUpdated": "Konfigurasi pelayan telah dikemas kini",
"MixedContent": "Kandungan campuran",
"Movies": "Filem",
"Movies": "Filem-filem",
"Music": "Muzik",
"MusicVideos": "Muzik video",
"MusicVideos": "Video muzik",
"NameInstallFailed": "{0} pemasangan gagal",
"NameSeasonNumber": "Musim {0}",
"NameSeasonUnknown": "Musim Tidak Diketahui",
@ -53,43 +53,43 @@
"NotificationOptionNewLibraryContent": "Kandungan baru telah ditambah",
"NotificationOptionPluginError": "Kegagalan plugin",
"NotificationOptionPluginInstalled": "Plugin telah dipasang",
"NotificationOptionPluginUninstalled": "Plugin uninstalled",
"NotificationOptionPluginUpdateInstalled": "Plugin update installed",
"NotificationOptionServerRestartRequired": "Server restart required",
"NotificationOptionTaskFailed": "Scheduled task failure",
"NotificationOptionUserLockedOut": "User locked out",
"NotificationOptionVideoPlayback": "Video playback started",
"NotificationOptionPluginUninstalled": "Plugin telah dinyahpasang",
"NotificationOptionPluginUpdateInstalled": "Kemaskini plugin telah dipasang",
"NotificationOptionServerRestartRequired": "",
"NotificationOptionTaskFailed": "Kegagalan tugas berjadual",
"NotificationOptionUserLockedOut": "Pengguna telah dikunci",
"NotificationOptionVideoPlayback": "Ulangmain video bermula",
"NotificationOptionVideoPlaybackStopped": "Ulangmain video dihentikan",
"Photos": "Gambar-gambar",
"Playlists": "Senarai main",
"Plugin": "Plugin",
"PluginInstalledWithName": "{0} was installed",
"PluginUninstalledWithName": "{0} was uninstalled",
"PluginUpdatedWithName": "{0} was updated",
"ProviderValue": "Provider: {0}",
"PluginInstalledWithName": "{0} telah dipasang",
"PluginUninstalledWithName": "{0} telah dinyahpasang",
"PluginUpdatedWithName": "{0} telah dikemaskini",
"ProviderValue": "Pembekal: {0}",
"ScheduledTaskFailedWithName": "{0} gagal",
"ScheduledTaskStartedWithName": "{0} bermula",
"ServerNameNeedsToBeRestarted": "{0} needs to be restarted",
"Shows": "Series",
"ServerNameNeedsToBeRestarted": "{0} perlu di ulangmula",
"Shows": "Tayangan",
"Songs": "Lagu-lagu",
"StartupEmbyServerIsLoading": "Pelayan Jellyfin sedang dimuatkan. Sila cuba sebentar lagi.",
"SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}",
"SubtitleDownloadFailureFromForItem": "Muat turun sarikata gagal dari {0} untuk {1}",
"Sync": "Sync",
"Sync": "Segerak",
"System": "Sistem",
"TvShows": "TV Shows",
"User": "User",
"UserCreatedWithName": "User {0} has been created",
"UserDeletedWithName": "User {0} has been deleted",
"UserDownloadingItemWithValues": "{0} is downloading {1}",
"TvShows": "Tayangan TV",
"User": "Pengguna",
"UserCreatedWithName": "Pengguna {0} telah diwujudkan",
"UserDeletedWithName": "Pengguna {0} telah dipadamkan",
"UserDownloadingItemWithValues": "{0} sedang memuat turun {1}",
"UserLockedOutWithName": "Pengguna {0} telah dikunci",
"UserOfflineFromDevice": "{0} telah terputus dari {1}",
"UserOnlineFromDevice": "{0} berada dalam talian dari {1}",
"UserPasswordChangedWithName": "Kata laluan telah ditukar bagi pengguna {0}",
"UserPolicyUpdatedWithName": "Dasar pengguna telah dikemas kini untuk {0}",
"UserStartedPlayingItemWithValues": "{0} is playing {1} on {2}",
"UserStoppedPlayingItemWithValues": "{0} has finished playing {1} on {2}",
"ValueHasBeenAddedToLibrary": "{0} has been added to your media library",
"UserStartedPlayingItemWithValues": "{0} sedang dimainkan {1} pada {2}",
"UserStoppedPlayingItemWithValues": "{0} telah tamat dimainkan {1} pada {2}",
"ValueHasBeenAddedToLibrary": "{0} telah ditambah ke media library anda",
"ValueSpecialEpisodeName": "Khas - {0}",
"VersionNumber": "Versi {0}",
"TaskCleanActivityLog": "Log Aktiviti Bersih",

View File

@ -69,7 +69,7 @@
"UserDeletedWithName": "प्रयोगकर्ता {0} हटाइएको छ",
"UserCreatedWithName": "प्रयोगकर्ता {0} सिर्जना गरिएको छ",
"User": "प्रयोगकर्ता",
"PluginInstalledWithName": "",
"PluginInstalledWithName": "{0} सभएको थियो",
"StartupEmbyServerIsLoading": "Jellyfin सर्भर लोड हुँदैछ। कृपया छिट्टै फेरि प्रयास गर्नुहोस्।",
"Songs": "गीतहरू",
"Shows": "शोहरू",

View File

@ -15,7 +15,7 @@
"Favorites": "Favorieten",
"Folders": "Mappen",
"Genres": "Genres",
"HeaderAlbumArtists": "Artiests Album",
"HeaderAlbumArtists": "Album Artiesten",
"HeaderContinueWatching": "Kijken hervatten",
"HeaderFavoriteAlbums": "Favoriete albums",
"HeaderFavoriteArtists": "Favoriete artiesten",

View File

@ -24,7 +24,7 @@
"TasksLibraryCategory": "ਲਾਇਬ੍ਰੇਰੀ",
"TasksMaintenanceCategory": "ਰੱਖ-ਰਖਾਅ",
"VersionNumber": "ਵਰਜਨ {0}",
"ValueSpecialEpisodeName": "ਵਿਸ਼ੇਸ਼ - {0}",
"ValueSpecialEpisodeName": "ਖਾਸ - {0}",
"ValueHasBeenAddedToLibrary": "{0} ਤੁਹਾਡੀ ਮੀਡੀਆ ਲਾਇਬ੍ਰੇਰੀ ਵਿੱਚ ਸ਼ਾਮਲ ਕੀਤਾ ਗਿਆ ਹੈ",
"UserStoppedPlayingItemWithValues": "{0} ਨੇ {2} 'ਤੇ {1} ਖੇਡਣਾ ਪੂਰਾ ਕਰ ਲਿਆ ਹੈ",
"UserStartedPlayingItemWithValues": "{0} {2} 'ਤੇ {1} ਖੇਡ ਰਿਹਾ ਹੈ",
@ -43,8 +43,8 @@
"Sync": "ਸਿੰਕ",
"SubtitleDownloadFailureFromForItem": "ਉਪਸਿਰਲੇਖ {1} ਲਈ {0} ਤੋਂ ਡਾ toਨਲੋਡ ਕਰਨ ਵਿੱਚ ਅਸਫਲ ਰਹੇ",
"StartupEmbyServerIsLoading": "ਜੈਲੀਫਿਨ ਸਰਵਰ ਲੋਡ ਹੋ ਰਿਹਾ ਹੈ. ਕਿਰਪਾ ਕਰਕੇ ਜਲਦੀ ਹੀ ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ.",
"Songs": "ਗਾਣੇ",
"Shows": "ਸ਼ੋਅਜ਼",
"Songs": "ਗਾਣੇ",
"Shows": "ਸ਼ੋਅ",
"ServerNameNeedsToBeRestarted": "{0} ਮੁੜ ਚਾਲੂ ਕਰਨ ਦੀ ਲੋੜ ਹੈ",
"ScheduledTaskStartedWithName": "{0} ਸ਼ੁਰੂ ਹੋਇਆ",
"ScheduledTaskFailedWithName": "{0} ਅਸਫਲ",
@ -53,7 +53,7 @@
"PluginUninstalledWithName": "{0} ਅਣਇੰਸਟੌਲ ਕੀਤਾ ਗਿਆ ਸੀ",
"PluginInstalledWithName": "{0} ਲਗਾਇਆ ਗਿਆ ਸੀ",
"Plugin": "ਪਲੱਗਇਨ",
"Playlists": "ਪਲੇਲਿਸਟਸ",
"Playlists": "ਪਲੇਸੂਚੀਆਂ",
"Photos": "ਫੋਟੋਆਂ",
"NotificationOptionVideoPlaybackStopped": "ਵੀਡੀਓ ਪਲੇਬੈਕ ਰੋਕਿਆ ਗਿਆ",
"NotificationOptionVideoPlayback": "ਵੀਡੀਓ ਪਲੇਬੈਕ ਸ਼ੁਰੂ ਹੋਇਆ",
@ -102,13 +102,13 @@
"HeaderAlbumArtists": "ਐਲਬਮ ਕਲਾਕਾਰ",
"Genres": "ਸ਼ੈਲੀਆਂ",
"Forced": "ਮਜਬੂਰ",
"Folders": "ਫੋਲਡਰ",
"Folders": "ਫੋਲਡਰ",
"Favorites": "ਮਨਪਸੰਦ",
"FailedLoginAttemptWithUserName": "ਤੋਂ ਲਾਗਇਨ ਕੋਸ਼ਿਸ਼ ਫੇਲ ਹੋਈ {0}",
"DeviceOnlineWithName": "{0} ਜੁੜਿਆ ਹੋਇਆ ਹੈ",
"DeviceOfflineWithName": "{0} ਡਿਸਕਨੈਕਟ ਹੋ ਗਿਆ ਹੈ",
"Default": "ਮੂਲ",
"Collections": "ਸੰਗ੍ਰਹਿ",
"Default": "ਡਿਫੌਲਟ",
"Collections": "ਸੰਗ੍ਰਹਿ",
"ChapterNameValue": "ਅਧਿਆਇ {0}",
"Channels": "ਚੈਨਲ",
"CameraImageUploadedFrom": "ਤੋਂ ਇੱਕ ਨਵਾਂ ਕੈਮਰਾ ਚਿੱਤਰ ਅਪਲੋਡ ਕੀਤਾ ਗਿਆ ਹੈ {0}",

View File

@ -15,7 +15,7 @@
"Favorites": "Ulubione",
"Folders": "Foldery",
"Genres": "Gatunki",
"HeaderAlbumArtists": "Album artysty",
"HeaderAlbumArtists": "Wykonawcy albumów",
"HeaderContinueWatching": "Kontynuuj odtwarzanie",
"HeaderFavoriteAlbums": "Ulubione albumy",
"HeaderFavoriteArtists": "Ulubieni wykonawcy",
@ -47,7 +47,7 @@
"NotificationOptionApplicationUpdateAvailable": "Dostępna aktualizacja aplikacji",
"NotificationOptionApplicationUpdateInstalled": "Zaktualizowano aplikację",
"NotificationOptionAudioPlayback": "Rozpoczęto odtwarzanie muzyki",
"NotificationOptionAudioPlaybackStopped": "Odtwarzane dźwięku zatrzymane",
"NotificationOptionAudioPlaybackStopped": "Odtwarzanie dźwięku zatrzymane",
"NotificationOptionCameraImageUploaded": "Przekazano obraz z urządzenia przenośnego",
"NotificationOptionInstallationFailed": "Nieudana instalacja",
"NotificationOptionNewLibraryContent": "Dodano nową zawartość",
@ -98,7 +98,7 @@
"TaskRefreshChannels": "Odśwież kanały",
"TaskCleanTranscodeDescription": "Usuwa transkodowane pliki starsze niż 1 dzień.",
"TaskCleanTranscode": "Wyczyść folder transkodowania",
"TaskUpdatePluginsDescription": "Pobiera i instaluje aktualizacje dla pluginów które są skonfigurowane do automatycznej aktualizacji.",
"TaskUpdatePluginsDescription": "Pobiera i instaluje aktualizacje dla pluginów, które są skonfigurowane do automatycznej aktualizacji.",
"TaskUpdatePlugins": "Aktualizuj pluginy",
"TaskRefreshPeopleDescription": "Odświeża metadane o aktorów i reżyserów w Twojej bibliotece mediów.",
"TaskRefreshPeople": "Odśwież obsadę",

View File

@ -1,5 +1,7 @@
{
"Books": "Libros",
"AuthenticationSucceededWithUserName": "{0} autentificado correctamente",
"Artists": "Artistas"
"Artists": "Artistas",
"Songs": "Shantees",
"Albums": "Ships"
}

View File

@ -11,7 +11,7 @@
"HeaderFavoriteEpisodes": "Episódios Favoritos",
"HeaderFavoriteShows": "Séries Favoritas",
"HeaderContinueWatching": "Continuar assistindo",
"HeaderAlbumArtists": "Discos do Artista",
"HeaderAlbumArtists": "Artistas do Álbum",
"Genres": "Gêneros",
"Folders": "Diretórios",
"Favorites": "Favoritos",

View File

@ -74,7 +74,7 @@
"HeaderFavoriteArtists": "Artiști Favoriți",
"HeaderFavoriteAlbums": "Albume Favorite",
"HeaderContinueWatching": "Vizionează în continuare",
"HeaderAlbumArtists": "Album Artiști",
"HeaderAlbumArtists": "Albume Artiști",
"Genres": "Genuri",
"Folders": "Dosare",
"Favorites": "Favorite",

View File

@ -74,7 +74,7 @@
"NameSeasonUnknown": "Sezon i panjohur",
"NameSeasonNumber": "Sezoni {0}",
"NameInstallFailed": "Instalimi i {0} dështoi",
"MusicVideos": "Video muzikore",
"MusicVideos": "Video Muzikore",
"Music": "Muzikë",
"Movies": "Filmat",
"MixedContent": "Përmbajtje e përzier",
@ -96,7 +96,7 @@
"HeaderFavoriteArtists": "Artistët e preferuar",
"HeaderFavoriteAlbums": "Albumet e preferuar",
"HeaderContinueWatching": "Vazhdo të shikosh",
"HeaderAlbumArtists": "Artistët e Albumeve",
"HeaderAlbumArtists": "Artistët e albumeve",
"Genres": "Zhanret",
"Folders": "Skedarët",
"Favorites": "Të preferuarat",

View File

@ -50,7 +50,7 @@
"NameSeasonUnknown": "Непозната сезона",
"NameSeasonNumber": "Сезона {0}",
"NameInstallFailed": "Инсталација {0} није успела",
"MusicVideos": "Музички спотови",
"MusicVideos": "Музички видео",
"Music": "Музика",
"Movies": "Филмови",
"MixedContent": "Мешовит садржај",
@ -64,7 +64,7 @@
"ItemRemovedWithName": "{0} уклоњено из библиотеке",
"ItemAddedWithName": "{0} додато у библиотеку",
"Inherit": "Наследи",
"HomeVideos": "Кућни видео",
"HomeVideos": "Кућни Видео",
"HeaderRecordingGroups": "Групе снимања",
"HeaderNextUp": "Следи",
"HeaderLiveTV": "ТВ уживо",
@ -117,5 +117,6 @@
"TaskCleanActivityLog": "Очисти историју активности",
"Undefined": "Недефинисано",
"Forced": "Принудно",
"Default": "Подразумевано"
"Default": "Подразумевано",
"TaskOptimizeDatabase": "Оптимизуј датабазу"
}

View File

@ -15,7 +15,7 @@
"Favorites": "Favoriter",
"Folders": "Mappar",
"Genres": "Genrer",
"HeaderAlbumArtists": "Artistens album",
"HeaderAlbumArtists": "Albumsartister",
"HeaderContinueWatching": "Fortsätt kolla",
"HeaderFavoriteAlbums": "Favoritalbum",
"HeaderFavoriteArtists": "Favoritartister",
@ -96,8 +96,8 @@
"TaskDownloadMissingSubtitles": "Ladda ned saknade undertexter",
"TaskRefreshChannelsDescription": "Uppdaterar information för internetkanaler.",
"TaskRefreshChannels": "Uppdatera kanaler",
"TaskCleanTranscodeDescription": "Raderar transkodningsfiler som är mer än en dag gamla.",
"TaskCleanTranscode": "Töm transkodningskatalog",
"TaskCleanTranscodeDescription": "Raderar omkodningsfiler som är mer än en dag gamla.",
"TaskCleanTranscode": "Töm omkodningskatalog",
"TaskUpdatePluginsDescription": "Laddar ned och installerar uppdateringar till insticksprogram som är konfigurerade att uppdateras automatiskt.",
"TaskUpdatePlugins": "Uppdatera insticksprogram",
"TaskRefreshPeopleDescription": "Uppdaterar metadata för skådespelare och regissörer i ditt mediabibliotek.",

View File

@ -21,7 +21,7 @@
"Inherit": "மரபுரிமையாகப் பெறு",
"HeaderRecordingGroups": "பதிவு குழுக்கள்",
"Folders": "கோப்புறைகள்",
"FailedLoginAttemptWithUserName": "{0} இல் இருந்து உள்நுழைவு முயற்சி தோல்வியடைந்தது",
"FailedLoginAttemptWithUserName": "{0} இன் உள்நுழைவு முயற்சி தோல்வியடைந்தது",
"DeviceOnlineWithName": "{0} இணைக்கப்பட்டது",
"DeviceOfflineWithName": "{0} துண்டிக்கப்பட்டது",
"Collections": "தொகுப்புகள்",
@ -85,7 +85,7 @@
"HeaderFavoriteArtists": "பிடித்த கலைஞர்கள்",
"HeaderFavoriteAlbums": "பிடித்த ஆல்பங்கள்",
"HeaderContinueWatching": "தொடர்ந்து பார்",
"HeaderAlbumArtists": "இசைக் கலைஞர்கள்",
"HeaderAlbumArtists": "கலைஞரின் ஆல்பம்",
"Genres": "வகைகள்",
"Favorites": "பிடித்தவை",
"ChapterNameValue": "அத்தியாயம் {0}",

View File

@ -1 +1,23 @@
{}
{
"ValueSpecialEpisodeName": "ప్రత్యేక - {0}",
"Sync": "సమకాలీకరించు",
"Songs": "పాటలు",
"Shows": "ప్రదర్శనలు",
"Playlists": "ప్లేజాబితాలు",
"Photos": "ఫోటోలు",
"MusicVideos": "మ్యూజిక్ వీడియోలు",
"Music": "సంగీతం",
"Movies": "సినిమాలు",
"HeaderContinueWatching": "చూడటం కొనసాగించండి",
"HeaderAlbumArtists": "ఆల్బమ్ కళాకారులు",
"Genres": "శైలులు",
"Forced": "బలవంతంగా",
"Folders": "ఫోల్డర్లు",
"Favorites": "ఇష్టమైనవి",
"Default": "డిఫాల్ట్",
"Collections": "సేకరణలు",
"Channels": "ఛానెల్‌లు",
"Books": "పుస్తకాలు",
"Artists": "కళాకారులు",
"Albums": "ఆల్బమ్‌లు"
}

View File

@ -3,7 +3,7 @@
"Favorites": "Yêu Thích",
"Folders": "Thư Mục",
"Genres": "Thể Loại",
"HeaderAlbumArtists": "Album Nghệ sĩ",
"HeaderAlbumArtists": "Album nghệ sĩ",
"HeaderContinueWatching": "Xem Tiếp",
"HeaderLiveTV": "TV Trực Tiếp",
"Movies": "Phim",
@ -103,7 +103,7 @@
"HeaderFavoriteEpisodes": "Tập Phim Yêu Thích",
"HeaderFavoriteArtists": "Nghệ Sĩ Yêu Thích",
"HeaderFavoriteAlbums": "Album Ưa Thích",
"FailedLoginAttemptWithUserName": "Nỗ lực đăng nhập thất bại từ {0}",
"FailedLoginAttemptWithUserName": "Đăng nhập không thành công thử từ {0}",
"DeviceOnlineWithName": "{0} đã kết nối",
"DeviceOfflineWithName": "{0} đã ngắt kết nối",
"ChapterNameValue": "Phân Cảnh {0}",

View File

@ -11,7 +11,7 @@
"Collections": "合集",
"DeviceOfflineWithName": "{0} 已断开",
"DeviceOnlineWithName": "{0} 已连接",
"FailedLoginAttemptWithUserName": "来自 {0} 的失败登入",
"FailedLoginAttemptWithUserName": "从 {0} 尝试登录失败",
"Favorites": "我的最爱",
"Folders": "文件夹",
"Genres": "风格",

View File

@ -1 +1,29 @@
{}
{
"TasksApplicationCategory": "Ukusetshenziswa",
"TasksLibraryCategory": "Umtapo",
"TasksMaintenanceCategory": "Ukunakekela",
"User": "Umsebenzisi",
"Undefined": "Akuchaziwe",
"System": "Isistimu",
"Sync": "Vumelanisa",
"Songs": "Amaculo",
"Shows": "Izinhlelo",
"Plugin": "Isijobelelo",
"Playlists": "Izinhla Zokudlalayo",
"Photos": "Izithombe",
"Music": "Umculo",
"Movies": "Amamuvi",
"Latest": "lwakamuva",
"Inherit": "Ngefa",
"Forced": "Kuphoqiwe",
"Application": "Ukusetshenziswa",
"Genres": "Izinhlobo",
"Folders": "Izikhwama",
"Favorites": "Izintandokazi",
"Default": "Okumisiwe",
"Collections": "Amaqoqo",
"Channels": "Amashaneli",
"Books": "Izincwadi",
"Artists": "Abadlali",
"Albums": "Ama-albhamu"
}

View File

@ -310,7 +310,7 @@ namespace Emby.Server.Implementations.Localization
return _dictionaries.GetOrAdd(
culture,
(key, localizationManager) => localizationManager.GetDictionary(Prefix, key, DefaultCulture + ".json").GetAwaiter().GetResult(),
static (key, localizationManager) => localizationManager.GetDictionary(Prefix, key, DefaultCulture + ".json").GetAwaiter().GetResult(),
this);
}

View File

@ -9,6 +9,7 @@ using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Extensions;
using MediaBrowser.Controller.Chapters;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
@ -120,7 +121,7 @@ namespace Emby.Server.Implementations.MediaEncoder
var path = GetChapterImagePath(video, chapter.StartPositionTicks);
if (!currentImages.Contains(path, StringComparer.OrdinalIgnoreCase))
if (!currentImages.Contains(path, StringComparison.OrdinalIgnoreCase))
{
if (extractImages)
{
@ -219,7 +220,7 @@ namespace Emby.Server.Implementations.MediaEncoder
{
var deadImages = images
.Except(chapters.Select(i => i.ImagePath).Where(i => !string.IsNullOrEmpty(i)), StringComparer.OrdinalIgnoreCase)
.Where(i => BaseItem.SupportedImageExtensions.Contains(Path.GetExtension(i), StringComparer.OrdinalIgnoreCase))
.Where(i => BaseItem.SupportedImageExtensions.Contains(Path.GetExtension(i), StringComparison.OrdinalIgnoreCase))
.ToList();
foreach (var image in deadImages)

View File

@ -4,6 +4,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Text.Json.Serialization;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Playlists;
using MediaBrowser.Model.Querying;
@ -45,7 +46,7 @@ namespace Emby.Server.Implementations.Playlists
}
query.Recursive = true;
query.IncludeItemTypes = new[] { "Playlist" };
query.IncludeItemTypes = new[] { BaseItemKind.Playlist };
query.Parent = null;
return LibraryManager.GetItemsResult(query);
}

Some files were not shown because too many files have changed in this diff Show More