diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
index c5f8b4ba40..1fe2553858 100644
--- a/CONTRIBUTORS.md
+++ b/CONTRIBUTORS.md
@@ -213,3 +213,4 @@
- [SvenVandenbrande](https://github.com/SvenVandenbrande)
- [olsh](https://github.com/olsh)
- [lbenini](https://github.com/lbenini)
+ - [gnuyent](https://github.com/gnuyent)
diff --git a/Dockerfile b/Dockerfile
index 4e2d06b82a..0859fdc4c8 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -8,15 +8,7 @@ RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-
&& npm ci --no-audit --unsafe-perm \
&& mv dist /dist
-FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION} as builder
-WORKDIR /repo
-COPY . .
-ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
-# because of changes in docker and systemd we need to not build in parallel at the moment
-# see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting
-RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="/jellyfin" --self-contained --runtime linux-x64 "-p:DebugSymbols=false;DebugType=none"
-
-FROM debian:buster-slim
+FROM debian:buster-slim as app
# https://askubuntu.com/questions/972516/debian-frontend-environment-variable
ARG DEBIAN_FRONTEND="noninteractive"
@@ -25,9 +17,6 @@ ARG APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE=DontWarn
# https://github.com/NVIDIA/nvidia-docker/wiki/Installation-(Native-GPU-Support)
ENV NVIDIA_DRIVER_CAPABILITIES="compute,video,utility"
-COPY --from=builder /jellyfin /jellyfin
-COPY --from=web-builder /dist /jellyfin/jellyfin-web
-
# https://github.com/intel/compute-runtime/releases
ARG GMMLIB_VERSION=20.3.2
ARG IGC_VERSION=1.0.5435
@@ -73,6 +62,19 @@ ENV LC_ALL en_US.UTF-8
ENV LANG en_US.UTF-8
ENV LANGUAGE en_US:en
+FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION} as builder
+WORKDIR /repo
+COPY . .
+ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
+# because of changes in docker and systemd we need to not build in parallel at the moment
+# see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting
+RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="/jellyfin" --self-contained --runtime linux-x64 "-p:DebugSymbols=false;DebugType=none"
+
+FROM app
+
+COPY --from=builder /jellyfin /jellyfin
+COPY --from=web-builder /dist /jellyfin/jellyfin-web
+
EXPOSE 8096
VOLUME /cache /config /media
ENTRYPOINT ["./jellyfin/jellyfin", \
diff --git a/Dockerfile.arm b/Dockerfile.arm
index 25a0de7db6..cc0c79c94f 100644
--- a/Dockerfile.arm
+++ b/Dockerfile.arm
@@ -13,19 +13,8 @@ RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-
&& npm ci --no-audit --unsafe-perm \
&& mv dist /dist
-
-FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION} as builder
-WORKDIR /repo
-COPY . .
-ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
-# Discard objs - may cause failures if exists
-RUN find . -type d -name obj | xargs -r rm -r
-# Build
-RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm "-p:DebugSymbols=false;DebugType=none"
-
-
FROM multiarch/qemu-user-static:x86_64-arm as qemu
-FROM arm32v7/debian:buster-slim
+FROM arm32v7/debian:buster-slim as app
# https://askubuntu.com/questions/972516/debian-frontend-environment-variable
ARG DEBIAN_FRONTEND="noninteractive"
@@ -61,14 +50,25 @@ RUN apt-get update \
&& chmod 777 /cache /config /media \
&& sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && locale-gen
-COPY --from=builder /jellyfin /jellyfin
-COPY --from=web-builder /dist /jellyfin/jellyfin-web
-
ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1
ENV LC_ALL en_US.UTF-8
ENV LANG en_US.UTF-8
ENV LANGUAGE en_US:en
+FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION} as builder
+WORKDIR /repo
+COPY . .
+ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
+# Discard objs - may cause failures if exists
+RUN find . -type d -name obj | xargs -r rm -r
+# Build
+RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm "-p:DebugSymbols=false;DebugType=none"
+
+FROM app
+
+COPY --from=builder /jellyfin /jellyfin
+COPY --from=web-builder /dist /jellyfin/jellyfin-web
+
EXPOSE 8096
VOLUME /cache /config /media
ENTRYPOINT ["./jellyfin/jellyfin", \
diff --git a/Dockerfile.arm64 b/Dockerfile.arm64
index c9f19c5a39..64367a32da 100644
--- a/Dockerfile.arm64
+++ b/Dockerfile.arm64
@@ -13,18 +13,8 @@ RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-
&& npm ci --no-audit --unsafe-perm \
&& mv dist /dist
-
-FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION} as builder
-WORKDIR /repo
-COPY . .
-ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
-# Discard objs - may cause failures if exists
-RUN find . -type d -name obj | xargs -r rm -r
-# Build
-RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm64 "-p:DebugSymbols=false;DebugType=none"
-
FROM multiarch/qemu-user-static:x86_64-aarch64 as qemu
-FROM arm64v8/debian:buster-slim
+FROM arm64v8/debian:buster-slim as app
# https://askubuntu.com/questions/972516/debian-frontend-environment-variable
ARG DEBIAN_FRONTEND="noninteractive"
@@ -50,14 +40,25 @@ RUN apt-get update && apt-get install --no-install-recommends --no-install-sugge
&& chmod 777 /cache /config /media \
&& sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && locale-gen
-COPY --from=builder /jellyfin /jellyfin
-COPY --from=web-builder /dist /jellyfin/jellyfin-web
-
ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1
ENV LC_ALL en_US.UTF-8
ENV LANG en_US.UTF-8
ENV LANGUAGE en_US:en
+FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION} as builder
+WORKDIR /repo
+COPY . .
+ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
+# Discard objs - may cause failures if exists
+RUN find . -type d -name obj | xargs -r rm -r
+# Build
+RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm64 "-p:DebugSymbols=false;DebugType=none"
+
+FROM app
+
+COPY --from=builder /jellyfin /jellyfin
+COPY --from=web-builder /dist /jellyfin/jellyfin-web
+
EXPOSE 8096
VOLUME /cache /config /media
ENTRYPOINT ["./jellyfin/jellyfin", \
diff --git a/Emby.Dlna/ContentDirectory/ControlHandler.cs b/Emby.Dlna/ContentDirectory/ControlHandler.cs
index 27c5b22680..ac336e5dcc 100644
--- a/Emby.Dlna/ContentDirectory/ControlHandler.cs
+++ b/Emby.Dlna/ContentDirectory/ControlHandler.cs
@@ -288,21 +288,14 @@ namespace Emby.Dlna.ContentDirectory
/// The xml feature list.
private static string WriteFeatureListXml()
{
- // TODO: clean this up
- var builder = new StringBuilder();
-
- builder.Append("");
- builder.Append("");
-
- builder.Append("");
- builder.Append("");
- builder.Append("");
- builder.Append("");
- builder.Append("");
-
- builder.Append("");
-
- return builder.ToString();
+ return ""
+ + ""
+ + ""
+ + ""
+ + ""
+ + ""
+ + ""
+ + "";
}
///
diff --git a/Emby.Dlna/DlnaManager.cs b/Emby.Dlna/DlnaManager.cs
index b08f7590d4..af70793ccf 100644
--- a/Emby.Dlna/DlnaManager.cs
+++ b/Emby.Dlna/DlnaManager.cs
@@ -1,7 +1,4 @@
-#nullable disable
-
#pragma warning disable CS1591
-
using System;
using System.Collections.Generic;
using System.Globalization;
@@ -96,12 +93,14 @@ namespace Emby.Dlna
}
}
+ ///
public DeviceProfile GetDefaultProfile()
{
return new DefaultProfile();
}
- public DeviceProfile GetProfile(DeviceIdentification deviceInfo)
+ ///
+ public DeviceProfile? GetProfile(DeviceIdentification deviceInfo)
{
if (deviceInfo == null)
{
@@ -111,13 +110,13 @@ namespace Emby.Dlna
var profile = GetProfiles()
.FirstOrDefault(i => i.Identification != null && IsMatch(deviceInfo, i.Identification));
- if (profile != null)
+ if (profile == null)
{
- _logger.LogDebug("Found matching device profile: {ProfileName}", profile.Name);
+ LogUnmatchedProfile(deviceInfo);
}
else
{
- LogUnmatchedProfile(deviceInfo);
+ _logger.LogDebug("Found matching device profile: {ProfileName}", profile.Name);
}
return profile;
@@ -187,7 +186,8 @@ namespace Emby.Dlna
}
}
- public DeviceProfile GetProfile(IHeaderDictionary headers)
+ ///
+ public DeviceProfile? GetProfile(IHeaderDictionary headers)
{
if (headers == null)
{
@@ -195,15 +195,13 @@ namespace Emby.Dlna
}
var profile = GetProfiles().FirstOrDefault(i => i.Identification != null && IsMatch(headers, i.Identification));
-
- if (profile != null)
+ if (profile == null)
{
- _logger.LogDebug("Found matching device profile: {0}", profile.Name);
+ _logger.LogDebug("No matching device profile found. {@Headers}", headers);
}
else
{
- var headerString = string.Join(", ", headers.Select(i => string.Format(CultureInfo.InvariantCulture, "{0}={1}", i.Key, i.Value)));
- _logger.LogDebug("No matching device profile found. {0}", headerString);
+ _logger.LogDebug("Found matching device profile: {0}", profile.Name);
}
return profile;
@@ -253,19 +251,19 @@ namespace Emby.Dlna
return xmlFies
.Select(i => ParseProfileFile(i, type))
.Where(i => i != null)
- .ToList();
+ .ToList()!; // We just filtered out all the nulls
}
catch (IOException)
{
- return new List();
+ return Array.Empty();
}
}
- private DeviceProfile ParseProfileFile(string path, DeviceProfileType type)
+ private DeviceProfile? ParseProfileFile(string path, DeviceProfileType type)
{
lock (_profiles)
{
- if (_profiles.TryGetValue(path, out Tuple profileTuple))
+ if (_profiles.TryGetValue(path, out Tuple? profileTuple))
{
return profileTuple.Item2;
}
@@ -293,7 +291,8 @@ namespace Emby.Dlna
}
}
- public DeviceProfile GetProfile(string id)
+ ///
+ public DeviceProfile? GetProfile(string id)
{
if (string.IsNullOrEmpty(id))
{
@@ -322,6 +321,7 @@ namespace Emby.Dlna
}
}
+ ///
public IEnumerable GetProfileInfos()
{
return GetProfileInfosInternal().Select(i => i.Info);
@@ -329,17 +329,14 @@ namespace Emby.Dlna
private InternalProfileInfo GetInternalProfileInfo(FileSystemMetadata file, DeviceProfileType type)
{
- return new InternalProfileInfo
- {
- Path = file.FullName,
-
- Info = new DeviceProfileInfo
+ return new InternalProfileInfo(
+ new DeviceProfileInfo
{
Id = file.FullName.ToLowerInvariant().GetMD5().ToString("N", CultureInfo.InvariantCulture),
Name = _fileSystem.GetFileNameWithoutExtension(file),
Type = type
- }
- };
+ },
+ file.FullName);
}
private async Task ExtractSystemProfilesAsync()
@@ -359,7 +356,8 @@ namespace Emby.Dlna
systemProfilesPath,
Path.GetFileName(name.AsSpan()).Slice(namespaceName.Length));
- using (var stream = _assembly.GetManifestResourceStream(name))
+ // The stream should exist as we just got its name from GetManifestResourceNames
+ using (var stream = _assembly.GetManifestResourceStream(name)!)
{
var fileInfo = _fileSystem.GetFileInfo(path);
@@ -380,6 +378,7 @@ namespace Emby.Dlna
Directory.CreateDirectory(UserProfilesPath);
}
+ ///
public void DeleteProfile(string id)
{
var info = GetProfileInfosInternal().First(i => string.Equals(id, i.Info.Id, StringComparison.OrdinalIgnoreCase));
@@ -397,6 +396,7 @@ namespace Emby.Dlna
}
}
+ ///
public void CreateProfile(DeviceProfile profile)
{
profile = ReserializeProfile(profile);
@@ -412,6 +412,7 @@ namespace Emby.Dlna
SaveProfile(profile, path, DeviceProfileType.User);
}
+ ///
public void UpdateProfile(DeviceProfile profile)
{
profile = ReserializeProfile(profile);
@@ -470,9 +471,11 @@ namespace Emby.Dlna
var json = JsonSerializer.Serialize(profile, _jsonOptions);
- return JsonSerializer.Deserialize(json, _jsonOptions);
+ // Output can't be null if the input isn't null
+ return JsonSerializer.Deserialize(json, _jsonOptions)!;
}
+ ///
public string GetServerDescriptionXml(IHeaderDictionary headers, string serverUuId, string serverAddress)
{
var profile = GetDefaultProfile();
@@ -482,6 +485,7 @@ namespace Emby.Dlna
return new DescriptionXmlBuilder(profile, serverUuId, serverAddress, _appHost.FriendlyName, serverId).GetXml();
}
+ ///
public ImageStream GetIcon(string filename)
{
var format = filename.EndsWith(".png", StringComparison.OrdinalIgnoreCase)
@@ -499,9 +503,15 @@ namespace Emby.Dlna
private class InternalProfileInfo
{
- internal DeviceProfileInfo Info { get; set; }
+ internal InternalProfileInfo(DeviceProfileInfo info, string path)
+ {
+ Info = info;
+ Path = path;
+ }
+
+ internal DeviceProfileInfo Info { get; }
- internal string Path { get; set; }
+ internal string Path { get; }
}
}
diff --git a/Emby.Naming/AudioBook/AudioBookInfo.cs b/Emby.Naming/AudioBook/AudioBookInfo.cs
index 15702ff2ca..acd8905af6 100644
--- a/Emby.Naming/AudioBook/AudioBookInfo.cs
+++ b/Emby.Naming/AudioBook/AudioBookInfo.cs
@@ -15,7 +15,7 @@ namespace Emby.Naming.AudioBook
/// List of files composing the actual audiobook.
/// List of extra files.
/// Alternative version of files.
- public AudioBookInfo(string name, int? year, List files, List extras, List alternateVersions)
+ public AudioBookInfo(string name, int? year, IReadOnlyList files, IReadOnlyList extras, IReadOnlyList alternateVersions)
{
Name = name;
Year = year;
@@ -39,18 +39,18 @@ namespace Emby.Naming.AudioBook
/// Gets or sets the files.
///
/// The files.
- public List Files { get; set; }
+ public IReadOnlyList Files { get; set; }
///
/// Gets or sets the extras.
///
/// The extras.
- public List Extras { get; set; }
+ public IReadOnlyList Extras { get; set; }
///
/// Gets or sets the alternate versions.
///
/// The alternate versions.
- public List AlternateVersions { get; set; }
+ public IReadOnlyList AlternateVersions { get; set; }
}
}
diff --git a/Emby.Naming/AudioBook/AudioBookListResolver.cs b/Emby.Naming/AudioBook/AudioBookListResolver.cs
index ca53228903..1e4a8d2edc 100644
--- a/Emby.Naming/AudioBook/AudioBookListResolver.cs
+++ b/Emby.Naming/AudioBook/AudioBookListResolver.cs
@@ -87,7 +87,7 @@ namespace Emby.Naming.AudioBook
foreach (var audioFile in group)
{
var name = Path.GetFileNameWithoutExtension(audioFile.Path);
- if (name.Equals("audiobook") ||
+ if (name.Equals("audiobook", StringComparison.OrdinalIgnoreCase) ||
name.Contains(nameParserResult.Name, StringComparison.OrdinalIgnoreCase) ||
name.Contains(nameWithReplacedDots, StringComparison.OrdinalIgnoreCase))
{
diff --git a/Emby.Naming/Common/NamingOptions.cs b/Emby.Naming/Common/NamingOptions.cs
index 5f125eb4f1..915ce42cc9 100644
--- a/Emby.Naming/Common/NamingOptions.cs
+++ b/Emby.Naming/Common/NamingOptions.cs
@@ -284,7 +284,7 @@ namespace Emby.Naming.Common
// Not a Kodi rule as well, but below rule also causes false positives for triple-digit episode names
// [bar] Foo - 1 [baz] special case of below expression to prevent false positives with digits in the series name
- new EpisodeExpression(@".*?(\[.*?\])+.*?(?[\w\s]+?)[\s_]*-[\s_]*(?[0-9]+).*$")
+ new EpisodeExpression(@".*[\\\/]?.*?(\[.*?\])+.*?(?[-\w\s]+?)[\s_]*-[\s_]*(?[0-9]+).*$")
{
IsNamed = true
},
diff --git a/Emby.Naming/Emby.Naming.csproj b/Emby.Naming/Emby.Naming.csproj
index db1b8ac9d7..07d879e96a 100644
--- a/Emby.Naming/Emby.Naming.csproj
+++ b/Emby.Naming/Emby.Naming.csproj
@@ -1,4 +1,4 @@
-
+
@@ -49,7 +49,4 @@
-
-
-
diff --git a/Emby.Naming/Video/ExtraResolver.cs b/Emby.Naming/Video/ExtraResolver.cs
index 1fade985be..a32af002cc 100644
--- a/Emby.Naming/Video/ExtraResolver.cs
+++ b/Emby.Naming/Video/ExtraResolver.cs
@@ -1,6 +1,5 @@
using System;
using System.IO;
-using System.Linq;
using System.Text.RegularExpressions;
using Emby.Naming.Audio;
using Emby.Naming.Common;
diff --git a/Emby.Naming/Video/VideoListResolver.cs b/Emby.Naming/Video/VideoListResolver.cs
index 7da2dcd7a5..ed7d511a39 100644
--- a/Emby.Naming/Video/VideoListResolver.cs
+++ b/Emby.Naming/Video/VideoListResolver.cs
@@ -21,7 +21,7 @@ namespace Emby.Naming.Video
/// The naming options.
/// Indication we should consider multi-versions of content.
/// Returns enumerable of which groups files together when related.
- public static IEnumerable Resolve(List files, NamingOptions namingOptions, bool supportMultiVersion = true)
+ public static IEnumerable Resolve(IEnumerable files, NamingOptions namingOptions, bool supportMultiVersion = true)
{
var videoInfos = files
.Select(i => VideoResolver.Resolve(i.FullName, i.IsDirectory, namingOptions))
diff --git a/Emby.Server.Implementations/Channels/ChannelManager.cs b/Emby.Server.Implementations/Channels/ChannelManager.cs
index 093607dd5e..aa54510a71 100644
--- a/Emby.Server.Implementations/Channels/ChannelManager.cs
+++ b/Emby.Server.Implementations/Channels/ChannelManager.cs
@@ -880,7 +880,7 @@ namespace Emby.Server.Implementations.Channels
}
}
- private async Task CacheResponse(object result, string path)
+ private async Task CacheResponse(ChannelItemResult result, string path)
{
try
{
diff --git a/Emby.Server.Implementations/Collections/CollectionManager.cs b/Emby.Server.Implementations/Collections/CollectionManager.cs
index 08acd17672..8270c2e84c 100644
--- a/Emby.Server.Implementations/Collections/CollectionManager.cs
+++ b/Emby.Server.Implementations/Collections/CollectionManager.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
using System;
using System.Collections.Generic;
using System.IO;
@@ -63,13 +61,13 @@ namespace Emby.Server.Implementations.Collections
}
///
- public event EventHandler CollectionCreated;
+ public event EventHandler? CollectionCreated;
///
- public event EventHandler ItemsAddedToCollection;
+ public event EventHandler? ItemsAddedToCollection;
///
- public event EventHandler ItemsRemovedFromCollection;
+ public event EventHandler? ItemsRemovedFromCollection;
private IEnumerable FindFolders(string path)
{
@@ -80,7 +78,7 @@ namespace Emby.Server.Implementations.Collections
.Where(i => _fileSystem.AreEqual(path, i.Path) || _fileSystem.ContainsSubPath(i.Path, path));
}
- internal async Task EnsureLibraryFolder(string path, bool createIfNeeded)
+ internal async Task EnsureLibraryFolder(string path, bool createIfNeeded)
{
var existingFolder = FindFolders(path).FirstOrDefault();
if (existingFolder != null)
@@ -114,7 +112,7 @@ namespace Emby.Server.Implementations.Collections
return Path.Combine(_appPaths.DataPath, "collections");
}
- private Task GetCollectionsFolder(bool createIfNeeded)
+ private Task GetCollectionsFolder(bool createIfNeeded)
{
return EnsureLibraryFolder(GetCollectionsFolderPath(), createIfNeeded);
}
@@ -203,8 +201,7 @@ namespace Emby.Server.Implementations.Collections
private async Task AddToCollectionAsync(Guid collectionId, IEnumerable ids, bool fireEvent, MetadataRefreshOptions refreshOptions)
{
- var collection = _libraryManager.GetItemById(collectionId) as BoxSet;
- if (collection == null)
+ if (_libraryManager.GetItemById(collectionId) is not BoxSet collection)
{
throw new ArgumentException("No collection exists with the supplied Id");
}
@@ -256,9 +253,7 @@ namespace Emby.Server.Implementations.Collections
///
public async Task RemoveFromCollectionAsync(Guid collectionId, IEnumerable itemIds)
{
- var collection = _libraryManager.GetItemById(collectionId) as BoxSet;
-
- if (collection == null)
+ if (_libraryManager.GetItemById(collectionId) is not BoxSet collection)
{
throw new ArgumentException("No collection exists with the supplied Id");
}
@@ -312,11 +307,7 @@ namespace Emby.Server.Implementations.Collections
foreach (var item in items)
{
- if (item is not ISupportsBoxSetGrouping)
- {
- results[item.Id] = item;
- }
- else
+ if (item is ISupportsBoxSetGrouping)
{
var itemId = item.Id;
@@ -340,6 +331,7 @@ namespace Emby.Server.Implementations.Collections
}
var alreadyInResults = false;
+
// this is kind of a performance hack because only Video has alternate versions that should be in a box set?
if (item is Video video)
{
@@ -355,11 +347,13 @@ namespace Emby.Server.Implementations.Collections
}
}
- if (!alreadyInResults)
+ if (alreadyInResults)
{
- results[itemId] = item;
+ continue;
}
}
+
+ results[item.Id] = item;
}
return results.Values;
diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj
index 4c9e058212..e0f841d529 100644
--- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj
+++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj
@@ -28,7 +28,7 @@
-
+
diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs
index 488614609a..b2625a68c9 100644
--- a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs
+++ b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs
@@ -141,7 +141,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
}
// Temporary. TODO - allow clients to specify that the token has been shared with a casting device
- var allowTokenInfoUpdate = authInfo.Client == null || authInfo.Client.IndexOf("chromecast", StringComparison.OrdinalIgnoreCase) == -1;
+ var allowTokenInfoUpdate = authInfo.Client == null || !authInfo.Client.Contains("chromecast", StringComparison.OrdinalIgnoreCase);
if (string.IsNullOrWhiteSpace(authInfo.Device))
{
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs
index c9657f6057..16ff98a7d8 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs
@@ -13,7 +13,6 @@ using System.Threading.Tasks;
using Jellyfin.Extensions;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net;
-using MediaBrowser.Controller;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Model.LiveTv;
using Microsoft.Extensions.Logging;
@@ -44,22 +43,29 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
public async Task GetListingsStream(TunerHostInfo info, CancellationToken cancellationToken)
{
- if (info.Url.StartsWith("http", StringComparison.OrdinalIgnoreCase))
+ if (info == null)
{
- using var requestMessage = new HttpRequestMessage(HttpMethod.Get, info.Url);
- if (!string.IsNullOrEmpty(info.UserAgent))
- {
- requestMessage.Headers.UserAgent.TryParseAdd(info.UserAgent);
- }
+ throw new ArgumentNullException(nameof(info));
+ }
+
+ if (!info.Url.StartsWith("http", StringComparison.OrdinalIgnoreCase))
+ {
+ return File.OpenRead(info.Url);
+ }
- var response = await _httpClientFactory.CreateClient(NamedClient.Default)
- .SendAsync(requestMessage, cancellationToken)
- .ConfigureAwait(false);
- response.EnsureSuccessStatusCode();
- return await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
+ using var requestMessage = new HttpRequestMessage(HttpMethod.Get, info.Url);
+ if (!string.IsNullOrEmpty(info.UserAgent))
+ {
+ requestMessage.Headers.UserAgent.TryParseAdd(info.UserAgent);
}
- return File.OpenRead(info.Url);
+ // Set HttpCompletionOption.ResponseHeadersRead to prevent timeouts on larger files
+ var response = await _httpClientFactory.CreateClient(NamedClient.Default)
+ .SendAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead, cancellationToken)
+ .ConfigureAwait(false);
+ response.EnsureSuccessStatusCode();
+
+ return await response.Content.ReadAsStreamAsync(cancellationToken);
}
private async Task> GetChannelsAsync(TextReader reader, string channelIdPrefix, string tunerHostId)
@@ -83,7 +89,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
if (trimmedLine.StartsWith(ExtInfPrefix, StringComparison.OrdinalIgnoreCase))
{
extInf = trimmedLine.Substring(ExtInfPrefix.Length).Trim();
- _logger.LogInformation("Found m3u channel: {0}", extInf);
}
else if (!string.IsNullOrWhiteSpace(extInf) && !trimmedLine.StartsWith('#'))
{
@@ -99,6 +104,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
channel.Path = trimmedLine;
channels.Add(channel);
+ _logger.LogInformation("Parsed channel: {ChannelName}", channel.Name);
extInf = string.Empty;
}
}
diff --git a/Emby.Server.Implementations/Localization/Core/af.json b/Emby.Server.Implementations/Localization/Core/af.json
index 4f21c66bc4..18f17dda95 100644
--- a/Emby.Server.Implementations/Localization/Core/af.json
+++ b/Emby.Server.Implementations/Localization/Core/af.json
@@ -2,24 +2,24 @@
"Artists": "Kunstenare",
"Channels": "Kanale",
"Folders": "Lêergidse",
- "Favorites": "Gunstellinge",
+ "Favorites": "Gunstelinge",
"HeaderFavoriteShows": "Gunsteling Vertonings",
"ValueSpecialEpisodeName": "Spesiale - {0}",
- "HeaderAlbumArtists": "Album Kunstenaars",
+ "HeaderAlbumArtists": "Kunstenaars se Album",
"Books": "Boeke",
"HeaderNextUp": "Volgende",
"Movies": "Flieks",
"Shows": "Televisie Reekse",
"HeaderContinueWatching": "Kyk Verder",
"HeaderFavoriteEpisodes": "Gunsteling Episodes",
- "Photos": "Fotos",
+ "Photos": "Foto's",
"Playlists": "Snitlyste",
"HeaderFavoriteArtists": "Gunsteling Kunstenaars",
"HeaderFavoriteAlbums": "Gunsteling Albums",
"Sync": "Sinkroniseer",
"HeaderFavoriteSongs": "Gunsteling Liedjies",
"Songs": "Liedjies",
- "DeviceOnlineWithName": "{0} gekoppel is",
+ "DeviceOnlineWithName": "{0} is gekoppel",
"DeviceOfflineWithName": "{0} is ontkoppel",
"Collections": "Versamelings",
"Inherit": "Ontvang",
@@ -71,7 +71,7 @@
"NameSeasonUnknown": "Seisoen Onbekend",
"NameSeasonNumber": "Seisoen {0}",
"NameInstallFailed": "{0} installering het misluk",
- "MusicVideos": "Musiek videos",
+ "MusicVideos": "Musiek Videos",
"Music": "Musiek",
"MixedContent": "Gemengde inhoud",
"MessageServerConfigurationUpdated": "Bediener konfigurasie is opgedateer",
@@ -79,15 +79,15 @@
"MessageApplicationUpdatedTo": "Jellyfin Bediener is opgedateer na {0}",
"MessageApplicationUpdated": "Jellyfin Bediener is opgedateer",
"Latest": "Nuutste",
- "LabelRunningTimeValue": "Lopende tyd: {0}",
+ "LabelRunningTimeValue": "Werktyd: {0}",
"LabelIpAddressValue": "IP adres: {0}",
"ItemRemovedWithName": "{0} is uit versameling verwyder",
- "ItemAddedWithName": "{0} is in die versameling",
- "HomeVideos": "Tuis opnames",
+ "ItemAddedWithName": "{0} is by die versameling gevoeg",
+ "HomeVideos": "Tuis Videos",
"HeaderRecordingGroups": "Groep Opnames",
"Genres": "Genres",
"FailedLoginAttemptWithUserName": "Mislukte aansluiting van {0}",
- "ChapterNameValue": "Hoofstuk",
+ "ChapterNameValue": "Hoofstuk {0}",
"CameraImageUploadedFrom": "'n Nuwe kamera photo opgelaai van {0}",
"AuthenticationSucceededWithUserName": "{0} suksesvol geverifieer",
"Albums": "Albums",
@@ -117,5 +117,7 @@
"Forced": "Geforseer",
"Default": "Oorspronklik",
"TaskCleanActivityLogDescription": "Verwyder aktiwiteitsaantekeninge ouer as die opgestelde ouderdom.",
- "TaskCleanActivityLog": "Maak Aktiwiteitsaantekeninge Skoon"
+ "TaskCleanActivityLog": "Maak Aktiwiteitsaantekeninge Skoon",
+ "TaskOptimizeDatabaseDescription": "Komprimeer databasis en verkort vrye ruimte. As hierdie taak uitgevoer word nadat die media versameling geskandeer is of ander veranderings aangebring is wat databasisaanpassings impliseer, kan dit die prestasie verbeter.",
+ "TaskOptimizeDatabase": "Optimaliseer databasis"
}
diff --git a/Emby.Server.Implementations/Localization/Core/ca.json b/Emby.Server.Implementations/Localization/Core/ca.json
index 1b612dc716..7715daa7ce 100644
--- a/Emby.Server.Implementations/Localization/Core/ca.json
+++ b/Emby.Server.Implementations/Localization/Core/ca.json
@@ -5,7 +5,7 @@
"Artists": "Artistes",
"AuthenticationSucceededWithUserName": "{0} s'ha autenticat correctament",
"Books": "Llibres",
- "CameraImageUploadedFrom": "Una nova imatge de la càmera ha estat pujada des de {0}",
+ "CameraImageUploadedFrom": "S'ha pujat una nova imatge des de la camera desde {0}",
"Channels": "Canals",
"ChapterNameValue": "Capítol {0}",
"Collections": "Col·leccions",
diff --git a/Emby.Server.Implementations/Localization/Core/el.json b/Emby.Server.Implementations/Localization/Core/el.json
index 23d45b4737..697063f262 100644
--- a/Emby.Server.Implementations/Localization/Core/el.json
+++ b/Emby.Server.Implementations/Localization/Core/el.json
@@ -1,5 +1,5 @@
{
- "Albums": "Άλμπουμς",
+ "Albums": "Άλμπουμ",
"AppDeviceValues": "Εφαρμογή: {0}, Συσκευή: {1}",
"Application": "Εφαρμογή",
"Artists": "Καλλιτέχνες",
@@ -15,7 +15,7 @@
"Favorites": "Αγαπημένα",
"Folders": "Φάκελοι",
"Genres": "Είδη",
- "HeaderAlbumArtists": "Καλλιτέχνες του Άλμπουμ",
+ "HeaderAlbumArtists": "Άλμπουμ Καλλιτέχνη",
"HeaderContinueWatching": "Συνεχίστε την παρακολούθηση",
"HeaderFavoriteAlbums": "Αγαπημένα Άλμπουμ",
"HeaderFavoriteArtists": "Αγαπημένοι Καλλιτέχνες",
@@ -39,7 +39,7 @@
"MixedContent": "Ανάμεικτο Περιεχόμενο",
"Movies": "Ταινίες",
"Music": "Μουσική",
- "MusicVideos": "Μουσικά βίντεο",
+ "MusicVideos": "Μουσικά Βίντεο",
"NameInstallFailed": "{0} η εγκατάσταση απέτυχε",
"NameSeasonNumber": "Κύκλος {0}",
"NameSeasonUnknown": "Άγνωστος Κύκλος",
@@ -62,7 +62,7 @@
"NotificationOptionVideoPlaybackStopped": "Η αναπαραγωγή βίντεο σταμάτησε",
"Photos": "Φωτογραφίες",
"Playlists": "Λίστες αναπαραγωγής",
- "Plugin": "Plugin",
+ "Plugin": "Πρόσθετο",
"PluginInstalledWithName": "{0} εγκαταστήθηκε",
"PluginUninstalledWithName": "{0} έχει απεγκατασταθεί",
"PluginUpdatedWithName": "{0} έχει αναβαθμιστεί",
@@ -118,5 +118,7 @@
"TaskCleanActivityLog": "Καθαρό Αρχείο Καταγραφής Δραστηριοτήτων",
"Undefined": "Απροσδιόριστο",
"Forced": "Εξαναγκασμένο",
- "Default": "Προεπιλογή"
+ "Default": "Προεπιλογή",
+ "TaskOptimizeDatabaseDescription": "Συμπιέζει τη βάση δεδομένων και δημιουργεί ελεύθερο χώρο. Η εκτέλεση αυτής της εργασίας μετά τη σάρωση της βιβλιοθήκης ή την πραγματοποίηση άλλων αλλαγών που συνεπάγονται τροποποιήσεις της βάσης δεδομένων μπορεί να βελτιώσει την απόδοση.",
+ "TaskOptimizeDatabase": "Βελτιστοποίηση βάσης δεδομένων"
}
diff --git a/Emby.Server.Implementations/Localization/Core/en-US.json b/Emby.Server.Implementations/Localization/Core/en-US.json
index 65964f6d9f..ca127cdb86 100644
--- a/Emby.Server.Implementations/Localization/Core/en-US.json
+++ b/Emby.Server.Implementations/Localization/Core/en-US.json
@@ -17,7 +17,7 @@
"Folders": "Folders",
"Forced": "Forced",
"Genres": "Genres",
- "HeaderAlbumArtists": "Album Artists",
+ "HeaderAlbumArtists": "Artist's Album",
"HeaderContinueWatching": "Continue Watching",
"HeaderFavoriteAlbums": "Favorite Albums",
"HeaderFavoriteArtists": "Favorite Artists",
@@ -27,7 +27,7 @@
"HeaderLiveTV": "Live TV",
"HeaderNextUp": "Next Up",
"HeaderRecordingGroups": "Recording Groups",
- "HomeVideos": "Home videos",
+ "HomeVideos": "Home Videos",
"Inherit": "Inherit",
"ItemAddedWithName": "{0} was added to the library",
"ItemRemovedWithName": "{0} was removed from the library",
@@ -41,7 +41,7 @@
"MixedContent": "Mixed content",
"Movies": "Movies",
"Music": "Music",
- "MusicVideos": "Music videos",
+ "MusicVideos": "Music Videos",
"NameInstallFailed": "{0} installation failed",
"NameSeasonNumber": "Season {0}",
"NameSeasonUnknown": "Season Unknown",
diff --git a/Emby.Server.Implementations/Localization/Core/es-MX.json b/Emby.Server.Implementations/Localization/Core/es-MX.json
index 5d7ed243f4..432814dac1 100644
--- a/Emby.Server.Implementations/Localization/Core/es-MX.json
+++ b/Emby.Server.Implementations/Localization/Core/es-MX.json
@@ -15,7 +15,7 @@
"Favorites": "Favoritos",
"Folders": "Carpetas",
"Genres": "Géneros",
- "HeaderAlbumArtists": "Artistas del álbum",
+ "HeaderAlbumArtists": "Artistas del Álbum",
"HeaderContinueWatching": "Continuar viendo",
"HeaderFavoriteAlbums": "Álbumes favoritos",
"HeaderFavoriteArtists": "Artistas favoritos",
@@ -25,7 +25,7 @@
"HeaderLiveTV": "TV en vivo",
"HeaderNextUp": "A continuación",
"HeaderRecordingGroups": "Grupos de grabación",
- "HomeVideos": "Videos caseros",
+ "HomeVideos": "Videos Caseros",
"Inherit": "Heredar",
"ItemAddedWithName": "{0} fue agregado a la biblioteca",
"ItemRemovedWithName": "{0} fue removido de la biblioteca",
@@ -39,7 +39,7 @@
"MixedContent": "Contenido mezclado",
"Movies": "Películas",
"Music": "Música",
- "MusicVideos": "Videos musicales",
+ "MusicVideos": "Videos Musicales",
"NameInstallFailed": "Falló la instalación de {0}",
"NameSeasonNumber": "Temporada {0}",
"NameSeasonUnknown": "Temporada desconocida",
@@ -49,7 +49,7 @@
"NotificationOptionAudioPlayback": "Reproducción de audio iniciada",
"NotificationOptionAudioPlaybackStopped": "Reproducción de audio detenida",
"NotificationOptionCameraImageUploaded": "Imagen de la cámara subida",
- "NotificationOptionInstallationFailed": "Falla de instalación",
+ "NotificationOptionInstallationFailed": "Fallo en la instalación",
"NotificationOptionNewLibraryContent": "Nuevo contenido agregado",
"NotificationOptionPluginError": "Falla de complemento",
"NotificationOptionPluginInstalled": "Complemento instalado",
@@ -69,7 +69,7 @@
"ProviderValue": "Proveedor: {0}",
"ScheduledTaskFailedWithName": "{0} falló",
"ScheduledTaskStartedWithName": "{0} iniciado",
- "ServerNameNeedsToBeRestarted": "{0} debe ser reiniciado",
+ "ServerNameNeedsToBeRestarted": "{0} necesita ser reiniciado",
"Shows": "Programas",
"Songs": "Canciones",
"StartupEmbyServerIsLoading": "El servidor Jellyfin está cargando. Por favor, intente de nuevo pronto.",
@@ -94,9 +94,9 @@
"VersionNumber": "Versión {0}",
"TaskDownloadMissingSubtitlesDescription": "Busca subtítulos faltantes en Internet basándose en la configuración de metadatos.",
"TaskDownloadMissingSubtitles": "Descargar subtítulos faltantes",
- "TaskRefreshChannelsDescription": "Actualiza la información de canales de Internet.",
+ "TaskRefreshChannelsDescription": "Actualiza la información de los canales de Internet.",
"TaskRefreshChannels": "Actualizar canales",
- "TaskCleanTranscodeDescription": "Elimina archivos transcodificados que tengan más de un día.",
+ "TaskCleanTranscodeDescription": "Elimina archivos transcodificados que tengan más de un día de antigüedad.",
"TaskCleanTranscode": "Limpiar directorio de transcodificado",
"TaskUpdatePluginsDescription": "Descarga e instala actualizaciones para complementos que están configurados para actualizarse automáticamente.",
"TaskUpdatePlugins": "Actualizar complementos",
@@ -118,5 +118,7 @@
"TaskCleanActivityLog": "Limpiar registro de actividades",
"Undefined": "Sin definir",
"Forced": "Forzado",
- "Default": "Predeterminado"
+ "Default": "Predeterminado",
+ "TaskOptimizeDatabase": "Optimizar base de datos",
+ "TaskOptimizeDatabaseDescription": "Compacta la base de datos y trunca el espacio libre. Puede mejorar el rendimiento si se realiza esta tarea después de escanear la biblioteca o después de realizar otros cambios que impliquen modificar la base de datos."
}
diff --git a/Emby.Server.Implementations/Localization/Core/es.json b/Emby.Server.Implementations/Localization/Core/es.json
index 7d42182b0c..d3d9d27038 100644
--- a/Emby.Server.Implementations/Localization/Core/es.json
+++ b/Emby.Server.Implementations/Localization/Core/es.json
@@ -15,7 +15,7 @@
"Favorites": "Favoritos",
"Folders": "Carpetas",
"Genres": "Géneros",
- "HeaderAlbumArtists": "Artistas del álbum",
+ "HeaderAlbumArtists": "Artista del álbum",
"HeaderContinueWatching": "Continuar viendo",
"HeaderFavoriteAlbums": "Álbumes favoritos",
"HeaderFavoriteArtists": "Artistas favoritos",
diff --git a/Emby.Server.Implementations/Localization/Core/hu.json b/Emby.Server.Implementations/Localization/Core/hu.json
index 255d5427a6..85ab1511a4 100644
--- a/Emby.Server.Implementations/Localization/Core/hu.json
+++ b/Emby.Server.Implementations/Localization/Core/hu.json
@@ -15,7 +15,7 @@
"Favorites": "Kedvencek",
"Folders": "Könyvtárak",
"Genres": "Műfajok",
- "HeaderAlbumArtists": "Album előadók",
+ "HeaderAlbumArtists": "Előadó albumai",
"HeaderContinueWatching": "Megtekintés folytatása",
"HeaderFavoriteAlbums": "Kedvenc albumok",
"HeaderFavoriteArtists": "Kedvenc előadók",
diff --git a/Emby.Server.Implementations/Localization/Core/it.json b/Emby.Server.Implementations/Localization/Core/it.json
index 8b753400ef..5e28cf09fb 100644
--- a/Emby.Server.Implementations/Localization/Core/it.json
+++ b/Emby.Server.Implementations/Localization/Core/it.json
@@ -15,7 +15,7 @@
"Favorites": "Preferiti",
"Folders": "Cartelle",
"Genres": "Generi",
- "HeaderAlbumArtists": "Artisti degli Album",
+ "HeaderAlbumArtists": "Artisti dell'Album",
"HeaderContinueWatching": "Continua a guardare",
"HeaderFavoriteAlbums": "Album Preferiti",
"HeaderFavoriteArtists": "Artisti Preferiti",
@@ -25,7 +25,7 @@
"HeaderLiveTV": "Diretta TV",
"HeaderNextUp": "Prossimo",
"HeaderRecordingGroups": "Gruppi di Registrazione",
- "HomeVideos": "Video personali",
+ "HomeVideos": "Video Personali",
"Inherit": "Eredita",
"ItemAddedWithName": "{0} è stato aggiunto alla libreria",
"ItemRemovedWithName": "{0} è stato rimosso dalla libreria",
@@ -39,7 +39,7 @@
"MixedContent": "Contenuto misto",
"Movies": "Film",
"Music": "Musica",
- "MusicVideos": "Video musicali",
+ "MusicVideos": "Video Musicali",
"NameInstallFailed": "{0} installazione fallita",
"NameSeasonNumber": "Stagione {0}",
"NameSeasonUnknown": "Stagione sconosciuta",
@@ -70,7 +70,7 @@
"ScheduledTaskFailedWithName": "{0} fallito",
"ScheduledTaskStartedWithName": "{0} avviati",
"ServerNameNeedsToBeRestarted": "{0} deve essere riavviato",
- "Shows": "Programmi",
+ "Shows": "Serie TV",
"Songs": "Canzoni",
"StartupEmbyServerIsLoading": "Jellyfin server si sta avviando. Per favore riprova più tardi.",
"SubtitleDownloadFailureForItem": "Impossibile scaricare i sottotitoli per {0}",
diff --git a/Emby.Server.Implementations/Localization/Core/kk.json b/Emby.Server.Implementations/Localization/Core/kk.json
index 1b4a18deb5..d28564a7c6 100644
--- a/Emby.Server.Implementations/Localization/Core/kk.json
+++ b/Emby.Server.Implementations/Localization/Core/kk.json
@@ -15,7 +15,7 @@
"Favorites": "Tañdaulylar",
"Folders": "Qaltalar",
"Genres": "Janrlar",
- "HeaderAlbumArtists": "Älbom oryndauşylary",
+ "HeaderAlbumArtists": "Oryndauşynyñ älbomy",
"HeaderContinueWatching": "Qaraudy jalğastyru",
"HeaderFavoriteAlbums": "Tañdauly älbomdar",
"HeaderFavoriteArtists": "Tañdauly oryndauşylar",
diff --git a/Emby.Server.Implementations/Localization/Core/ml.json b/Emby.Server.Implementations/Localization/Core/ml.json
index 435f9b6305..09ef349136 100644
--- a/Emby.Server.Implementations/Localization/Core/ml.json
+++ b/Emby.Server.Implementations/Localization/Core/ml.json
@@ -103,7 +103,7 @@
"ValueSpecialEpisodeName": "പ്രത്യേക - {0}",
"Collections": "ശേഖരങ്ങൾ",
"Folders": "ഫോൾഡറുകൾ",
- "HeaderAlbumArtists": "ആൽബം ആർട്ടിസ്റ്റുകൾ",
+ "HeaderAlbumArtists": "കലാകാരന്റെ ആൽബം",
"Sync": "സമന്വയിപ്പിക്കുക",
"Movies": "സിനിമകൾ",
"Photos": "ഫോട്ടോകൾ",
diff --git a/Emby.Server.Implementations/Localization/Core/pr.json b/Emby.Server.Implementations/Localization/Core/pr.json
new file mode 100644
index 0000000000..0967ef424b
--- /dev/null
+++ b/Emby.Server.Implementations/Localization/Core/pr.json
@@ -0,0 +1 @@
+{}
diff --git a/Emby.Server.Implementations/Localization/Core/ru.json b/Emby.Server.Implementations/Localization/Core/ru.json
index 248f06c4b2..cd016b51b4 100644
--- a/Emby.Server.Implementations/Localization/Core/ru.json
+++ b/Emby.Server.Implementations/Localization/Core/ru.json
@@ -25,7 +25,7 @@
"HeaderLiveTV": "Эфир",
"HeaderNextUp": "Очередное",
"HeaderRecordingGroups": "Группы записей",
- "HomeVideos": "Домашнее видео",
+ "HomeVideos": "Домашние видео",
"Inherit": "Наследуемое",
"ItemAddedWithName": "{0} - добавлено в медиатеку",
"ItemRemovedWithName": "{0} - изъято из медиатеки",
diff --git a/Emby.Server.Implementations/Localization/Core/sk.json b/Emby.Server.Implementations/Localization/Core/sk.json
index 37da7d5ab3..ad90bd8134 100644
--- a/Emby.Server.Implementations/Localization/Core/sk.json
+++ b/Emby.Server.Implementations/Localization/Core/sk.json
@@ -39,7 +39,7 @@
"MixedContent": "Zmiešaný obsah",
"Movies": "Filmy",
"Music": "Hudba",
- "MusicVideos": "Hudobné videoklipy",
+ "MusicVideos": "Hudobné videá",
"NameInstallFailed": "Inštalácia {0} zlyhala",
"NameSeasonNumber": "Séria {0}",
"NameSeasonUnknown": "Neznáma séria",
diff --git a/Emby.Server.Implementations/Localization/Core/sv.json b/Emby.Server.Implementations/Localization/Core/sv.json
index d992bf79b1..88b182f8d9 100644
--- a/Emby.Server.Implementations/Localization/Core/sv.json
+++ b/Emby.Server.Implementations/Localization/Core/sv.json
@@ -118,5 +118,6 @@
"TaskCleanActivityLog": "Rensa Aktivitets Logg",
"Undefined": "odefinierad",
"Forced": "Tvingad",
- "Default": "Standard"
+ "Default": "Standard",
+ "TaskOptimizeDatabase": "Optimera databasen"
}
diff --git a/Emby.Server.Implementations/Localization/Core/tr.json b/Emby.Server.Implementations/Localization/Core/tr.json
index c6b904045b..771c91d59f 100644
--- a/Emby.Server.Implementations/Localization/Core/tr.json
+++ b/Emby.Server.Implementations/Localization/Core/tr.json
@@ -43,7 +43,7 @@
"NameInstallFailed": "{0} kurulumu başarısız",
"NameSeasonNumber": "Sezon {0}",
"NameSeasonUnknown": "Bilinmeyen Sezon",
- "NewVersionIsAvailable": "Jellyfin Sunucusunun yeni bir versiyonu indirmek için hazır.",
+ "NewVersionIsAvailable": "Jellyfin Sunucusunun yeni bir sürümü indirmek için hazır.",
"NotificationOptionApplicationUpdateAvailable": "Uygulama güncellemesi mevcut",
"NotificationOptionApplicationUpdateInstalled": "Uygulama güncellemesi yüklendi",
"NotificationOptionAudioPlayback": "Ses çalma başladı",
@@ -75,7 +75,7 @@
"StartupEmbyServerIsLoading": "Jellyfin Sunucusu yükleniyor. Lütfen kısa süre sonra tekrar deneyin.",
"SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}",
"SubtitleDownloadFailureFromForItem": "{1} için alt yazılar {0} 'dan indirilemedi",
- "Sync": "Eşitle",
+ "Sync": "Eşzamanlama",
"System": "Sistem",
"TvShows": "Diziler",
"User": "Kullanıcı",
@@ -89,34 +89,36 @@
"UserPolicyUpdatedWithName": "Kullanıcı politikası {0} için güncellendi",
"UserStartedPlayingItemWithValues": "{0}, {2} cihazında {1} izliyor",
"UserStoppedPlayingItemWithValues": "{0}, {2} cihazında {1} izlemeyi bitirdi",
- "ValueHasBeenAddedToLibrary": "Medya kitaplığınıza {0} eklendi",
+ "ValueHasBeenAddedToLibrary": "Medya kütüphanenize {0} eklendi",
"ValueSpecialEpisodeName": "Özel - {0}",
- "VersionNumber": "Versiyon {0}",
+ "VersionNumber": "Sürüm {0}",
"TaskCleanCache": "Geçici dosya klasörünü temizle",
"TasksChannelsCategory": "İnternet kanalları",
"TasksApplicationCategory": "Uygulama",
"TasksLibraryCategory": "Kütüphane",
- "TasksMaintenanceCategory": "Onarım",
+ "TasksMaintenanceCategory": "Bakım",
"TaskRefreshPeopleDescription": "Medya kütüphanenizdeki videoların oyuncu ve yönetmen bilgilerini günceller.",
"TaskDownloadMissingSubtitlesDescription": "Metadata ayarlarını baz alarak eksik altyazıları internette arar.",
"TaskDownloadMissingSubtitles": "Eksik altyazıları indir",
"TaskRefreshChannelsDescription": "Internet kanal bilgilerini yenile.",
"TaskRefreshChannels": "Kanalları Yenile",
- "TaskCleanTranscodeDescription": "Bir günü dolmuş dönüştürme bilgisi içeren dosyaları siler.",
+ "TaskCleanTranscodeDescription": "Bir günden daha eski dönüştürme dosyalarını siler.",
"TaskCleanTranscode": "Dönüşüm Dizinini Temizle",
"TaskUpdatePluginsDescription": "Otomatik güncellenmeye ayarlanmış eklentilerin güncellemelerini indirir ve kurar.",
"TaskUpdatePlugins": "Eklentileri Güncelle",
"TaskRefreshPeople": "Kullanıcıları Yenile",
- "TaskCleanLogsDescription": "{0} günden eski log dosyalarını siler.",
- "TaskCleanLogs": "Log Dizinini Temizle",
- "TaskRefreshLibraryDescription": "Medya kütüphanenize eklenen yeni dosyaları arar ve bilgileri yeniler.",
+ "TaskCleanLogsDescription": "{0} günden eski günlük dosyalarını siler.",
+ "TaskCleanLogs": "Günlük Dizinini Temizle",
+ "TaskRefreshLibraryDescription": "Medya kütüphanenize eklenen yeni dosyaları arar ve ortam bilgilerini yeniler.",
"TaskRefreshLibrary": "Medya Kütüphanesini Tara",
"TaskRefreshChapterImagesDescription": "Sahnelere ayrılmış videolar için küçük resimler oluştur.",
"TaskRefreshChapterImages": "Bölüm Resimlerini Çıkar",
"TaskCleanCacheDescription": "Sistem tarafından artık ihtiyaç duyulmayan önbellek dosyalarını siler.",
- "TaskCleanActivityLog": "İşlem Günlüğünü Temizle",
- "TaskCleanActivityLogDescription": "Belirtilen sureden daha eski etkinlik log kayıtları silindi.",
+ "TaskCleanActivityLog": "Etkinlik Günlüğünü Temizle",
+ "TaskCleanActivityLogDescription": "Yapılandırılan tarihten daha eski olan etkinlik günlüğü girişlerini siler.",
"Undefined": "Bilinmeyen",
"Default": "Varsayılan",
- "Forced": "Zorla"
+ "Forced": "Zorla",
+ "TaskOptimizeDatabaseDescription": "Veritabanını sıkıştırır ve boş alanı keser. Kitaplığı taradıktan sonra veya veritabanında değişiklik anlamına gelen diğer işlemleri yaptıktan sonra bu görevi çalıştırmak performansı artırabilir.",
+ "TaskOptimizeDatabase": "Veritabanını optimize et"
}
diff --git a/Emby.Server.Implementations/Localization/Core/vi.json b/Emby.Server.Implementations/Localization/Core/vi.json
index 20ab1dd7db..3d69e418b9 100644
--- a/Emby.Server.Implementations/Localization/Core/vi.json
+++ b/Emby.Server.Implementations/Localization/Core/vi.json
@@ -3,7 +3,7 @@
"Favorites": "Yêu Thích",
"Folders": "Thư Mục",
"Genres": "Thể Loại",
- "HeaderAlbumArtists": "Tuyển Tập Nghệ sĩ",
+ "HeaderAlbumArtists": "Album Nghệ sĩ",
"HeaderContinueWatching": "Xem Tiếp",
"HeaderLiveTV": "TV Trực Tiếp",
"Movies": "Phim",
@@ -82,7 +82,7 @@
"NameSeasonUnknown": "Không Rõ Mùa",
"NameSeasonNumber": "Phần {0}",
"NameInstallFailed": "{0} cài đặt thất bại",
- "MusicVideos": "Video Nhạc",
+ "MusicVideos": "Videos Nhạc",
"Music": "Nhạc",
"MixedContent": "Nội dung hỗn hợp",
"MessageServerConfigurationUpdated": "Cấu hình máy chủ đã được cập nhật",
diff --git a/Emby.Server.Implementations/Localization/LocalizationManager.cs b/Emby.Server.Implementations/Localization/LocalizationManager.cs
index a9e3bfdb0e..03919197e2 100644
--- a/Emby.Server.Implementations/Localization/LocalizationManager.cs
+++ b/Emby.Server.Implementations/Localization/LocalizationManager.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
@@ -23,6 +21,9 @@ namespace Emby.Server.Implementations.Localization
public class LocalizationManager : ILocalizationManager
{
private const string DefaultCulture = "en-US";
+ private const string RatingsPath = "Emby.Server.Implementations.Localization.Ratings.";
+ private const string CulturesPath = "Emby.Server.Implementations.Localization.iso6392.txt";
+ private const string CountriesPath = "Emby.Server.Implementations.Localization.countries.json";
private static readonly Assembly _assembly = typeof(LocalizationManager).Assembly;
private static readonly string[] _unratedValues = { "n/a", "unrated", "not rated" };
@@ -35,10 +36,10 @@ namespace Emby.Server.Implementations.Localization
private readonly ConcurrentDictionary> _dictionaries =
new ConcurrentDictionary>(StringComparer.OrdinalIgnoreCase);
- private List _cultures;
-
private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
+ private List _cultures = new List();
+
///
/// Initializes a new instance of the class.
///
@@ -58,43 +59,39 @@ namespace Emby.Server.Implementations.Localization
/// .
public async Task LoadAll()
{
- const string RatingsResource = "Emby.Server.Implementations.Localization.Ratings.";
-
// Extract from the assembly
foreach (var resource in _assembly.GetManifestResourceNames())
{
- if (!resource.StartsWith(RatingsResource, StringComparison.Ordinal))
+ if (!resource.StartsWith(RatingsPath, StringComparison.Ordinal))
{
continue;
}
- string countryCode = resource.Substring(RatingsResource.Length, 2);
+ string countryCode = resource.Substring(RatingsPath.Length, 2);
var dict = new Dictionary(StringComparer.OrdinalIgnoreCase);
- using (var str = _assembly.GetManifestResourceStream(resource))
- using (var reader = new StreamReader(str))
+ await using var stream = _assembly.GetManifestResourceStream(resource);
+ using var reader = new StreamReader(stream!); // shouldn't be null here, we just got the resource path from Assembly.GetManifestResourceNames()
+ await foreach (var line in reader.ReadAllLinesAsync().ConfigureAwait(false))
{
- await foreach (var line in reader.ReadAllLinesAsync().ConfigureAwait(false))
+ if (string.IsNullOrWhiteSpace(line))
{
- if (string.IsNullOrWhiteSpace(line))
- {
- continue;
- }
-
- string[] parts = line.Split(',');
- if (parts.Length == 2
- && int.TryParse(parts[1], NumberStyles.Integer, CultureInfo.InvariantCulture, out var value))
- {
- var name = parts[0];
- dict.Add(name, new ParentalRating(name, value));
- }
+ continue;
+ }
+
+ string[] parts = line.Split(',');
+ if (parts.Length == 2
+ && int.TryParse(parts[1], NumberStyles.Integer, CultureInfo.InvariantCulture, out var value))
+ {
+ var name = parts[0];
+ dict.Add(name, new ParentalRating(name, value));
+ }
#if DEBUG
- else
- {
- _logger.LogWarning("Malformed line in ratings file for country {CountryCode}", countryCode);
- }
-#endif
+ else
+ {
+ _logger.LogWarning("Malformed line in ratings file for country {CountryCode}", countryCode);
}
+#endif
}
_allParentalRatings[countryCode] = dict;
@@ -114,52 +111,49 @@ namespace Emby.Server.Implementations.Localization
{
List list = new List();
- const string ResourcePath = "Emby.Server.Implementations.Localization.iso6392.txt";
-
- using (var stream = _assembly.GetManifestResourceStream(ResourcePath))
- using (var reader = new StreamReader(stream))
+ await using var stream = _assembly.GetManifestResourceStream(CulturesPath)
+ ?? throw new InvalidOperationException($"Invalid resource path: '{CulturesPath}'");
+ using var reader = new StreamReader(stream);
+ await foreach (var line in reader.ReadAllLinesAsync().ConfigureAwait(false))
{
- await foreach (var line in reader.ReadAllLinesAsync().ConfigureAwait(false))
+ if (string.IsNullOrWhiteSpace(line))
{
- if (string.IsNullOrWhiteSpace(line))
+ continue;
+ }
+
+ var parts = line.Split('|');
+
+ if (parts.Length == 5)
+ {
+ string name = parts[3];
+ if (string.IsNullOrWhiteSpace(name))
{
continue;
}
- var parts = line.Split('|');
+ string twoCharName = parts[2];
+ if (string.IsNullOrWhiteSpace(twoCharName))
+ {
+ continue;
+ }
- if (parts.Length == 5)
+ string[] threeletterNames;
+ if (string.IsNullOrWhiteSpace(parts[1]))
{
- string name = parts[3];
- if (string.IsNullOrWhiteSpace(name))
- {
- continue;
- }
-
- string twoCharName = parts[2];
- if (string.IsNullOrWhiteSpace(twoCharName))
- {
- continue;
- }
-
- string[] threeletterNames;
- if (string.IsNullOrWhiteSpace(parts[1]))
- {
- threeletterNames = new[] { parts[0] };
- }
- else
- {
- threeletterNames = new[] { parts[0], parts[1] };
- }
-
- list.Add(new CultureDto
- {
- DisplayName = name,
- Name = name,
- ThreeLetterISOLanguageNames = threeletterNames,
- TwoLetterISOLanguageName = twoCharName
- });
+ threeletterNames = new[] { parts[0] };
}
+ else
+ {
+ threeletterNames = new[] { parts[0], parts[1] };
+ }
+
+ list.Add(new CultureDto
+ {
+ DisplayName = name,
+ Name = name,
+ ThreeLetterISOLanguageNames = threeletterNames,
+ TwoLetterISOLanguageName = twoCharName
+ });
}
}
@@ -167,7 +161,7 @@ namespace Emby.Server.Implementations.Localization
}
///
- public CultureDto FindLanguageInfo(string language)
+ public CultureDto? FindLanguageInfo(string language)
{
// TODO language should ideally be a ReadOnlySpan but moq cannot mock ref structs
for (var i = 0; i < _cultures.Count; i++)
@@ -188,9 +182,10 @@ namespace Emby.Server.Implementations.Localization
///
public IEnumerable GetCountries()
{
- using StreamReader reader = new StreamReader(_assembly.GetManifestResourceStream("Emby.Server.Implementations.Localization.countries.json"));
-
- return JsonSerializer.Deserialize>(reader.ReadToEnd(), _jsonOptions);
+ using StreamReader reader = new StreamReader(
+ _assembly.GetManifestResourceStream(CountriesPath) ?? throw new InvalidOperationException($"Invalid resource path: '{CountriesPath}'"));
+ return JsonSerializer.Deserialize>(reader.ReadToEnd(), _jsonOptions)
+ ?? throw new InvalidOperationException($"Resource contains invalid data: '{CountriesPath}'");
}
///
@@ -210,7 +205,9 @@ namespace Emby.Server.Implementations.Localization
countryCode = "us";
}
- return GetRatings(countryCode) ?? GetRatings("us");
+ return GetRatings(countryCode)
+ ?? GetRatings("us")
+ ?? throw new InvalidOperationException($"Invalid resource path: '{CountriesPath}'");
}
///
@@ -218,7 +215,7 @@ namespace Emby.Server.Implementations.Localization
///
/// The country code.
/// The ratings.
- private Dictionary GetRatings(string countryCode)
+ private Dictionary? GetRatings(string countryCode)
{
_allParentalRatings.TryGetValue(countryCode, out var value);
@@ -243,7 +240,7 @@ namespace Emby.Server.Implementations.Localization
var ratingsDictionary = GetParentalRatingsDictionary();
- if (ratingsDictionary.TryGetValue(rating, out ParentalRating value))
+ if (ratingsDictionary.TryGetValue(rating, out ParentalRating? value))
{
return value.Value;
}
@@ -273,20 +270,6 @@ namespace Emby.Server.Implementations.Localization
return null;
}
- ///
- public bool HasUnicodeCategory(string value, UnicodeCategory category)
- {
- foreach (var chr in value)
- {
- if (char.GetUnicodeCategory(chr) == category)
- {
- return true;
- }
- }
-
- return false;
- }
-
///
public string GetLocalizedString(string phrase)
{
@@ -350,22 +333,23 @@ namespace Emby.Server.Implementations.Localization
private async Task CopyInto(IDictionary dictionary, string resourcePath)
{
- using (var stream = _assembly.GetManifestResourceStream(resourcePath))
+ await using var stream = _assembly.GetManifestResourceStream(resourcePath);
+ // If a Culture doesn't have a translation the stream will be null and it defaults to en-us further up the chain
+ if (stream == null)
{
- // If a Culture doesn't have a translation the stream will be null and it defaults to en-us further up the chain
- if (stream != null)
- {
- var dict = await JsonSerializer.DeserializeAsync>(stream, _jsonOptions).ConfigureAwait(false);
+ _logger.LogError("Missing translation/culture resource: {ResourcePath}", resourcePath);
+ return;
+ }
- foreach (var key in dict.Keys)
- {
- dictionary[key] = dict[key];
- }
- }
- else
- {
- _logger.LogError("Missing translation/culture resource: {ResourcePath}", resourcePath);
- }
+ var dict = await JsonSerializer.DeserializeAsync>(stream, _jsonOptions).ConfigureAwait(false);
+ if (dict == null)
+ {
+ throw new InvalidOperationException($"Resource contains invalid data: '{stream}'");
+ }
+
+ foreach (var key in dict.Keys)
+ {
+ dictionary[key] = dict[key];
}
}
diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs
index baeb86a221..b764a139cb 100644
--- a/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs
+++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs
@@ -55,9 +55,19 @@ namespace Emby.Server.Implementations.ScheduledTasks
_localization = localization;
}
- ///
- /// Creates the triggers that define when the task will run.
- ///
+ ///
+ public string Name => _localization.GetLocalizedString("TaskRefreshChapterImages");
+
+ ///
+ public string Description => _localization.GetLocalizedString("TaskRefreshChapterImagesDescription");
+
+ ///
+ public string Category => _localization.GetLocalizedString("TasksLibraryCategory");
+
+ ///
+ public string Key => "RefreshChapterImages";
+
+ ///
public IEnumerable GetDefaultTriggers()
{
return new[]
@@ -162,26 +172,5 @@ namespace Emby.Server.Implementations.ScheduledTasks
}
}
}
-
- ///
- public string Name => _localization.GetLocalizedString("TaskRefreshChapterImages");
-
- ///
- public string Description => _localization.GetLocalizedString("TaskRefreshChapterImagesDescription");
-
- ///
- public string Category => _localization.GetLocalizedString("TasksLibraryCategory");
-
- ///
- public string Key => "RefreshChapterImages";
-
- ///
- public bool IsHidden => false;
-
- ///
- public bool IsEnabled => true;
-
- ///
- public bool IsLogged => true;
}
}
diff --git a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs
index c295af7eb3..05fa5b1350 100644
--- a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs
+++ b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs
@@ -380,7 +380,7 @@ namespace Jellyfin.Api.Helpers
private void DeleteHlsPartialStreamFiles(string outputFilePath)
{
var directory = Path.GetDirectoryName(outputFilePath)
- ?? throw new ArgumentException("Path can't be a root directory.", nameof(outputFilePath));
+ ?? throw new ArgumentException("Path can't be a root directory.", nameof(outputFilePath));
var name = Path.GetFileNameWithoutExtension(outputFilePath);
@@ -444,6 +444,10 @@ namespace Jellyfin.Api.Helpers
{
var audioCodec = state.ActualOutputAudioCodec;
var videoCodec = state.ActualOutputVideoCodec;
+ var hardwareAccelerationTypeString = _serverConfigurationManager.GetEncodingOptions().HardwareAccelerationType;
+ HardwareEncodingType? hardwareAccelerationType = string.IsNullOrEmpty(hardwareAccelerationTypeString)
+ ? null
+ : (HardwareEncodingType)Enum.Parse(typeof(HardwareEncodingType), hardwareAccelerationTypeString, true);
_sessionManager.ReportTranscodingInfo(deviceId, new TranscodingInfo
{
@@ -458,6 +462,7 @@ namespace Jellyfin.Api.Helpers
AudioChannels = state.OutputAudioChannels,
IsAudioDirect = EncodingHelper.IsCopyCodec(state.OutputAudioCodec),
IsVideoDirect = EncodingHelper.IsCopyCodec(state.OutputVideoCodec),
+ HardwareAccelerationType = hardwareAccelerationType,
TranscodeReasons = state.TranscodeReasons
});
}
@@ -759,8 +764,8 @@ namespace Jellyfin.Api.Helpers
if (state.MediaSource.RequiresOpening && string.IsNullOrWhiteSpace(state.Request.LiveStreamId))
{
var liveStreamResponse = await _mediaSourceManager.OpenLiveStream(
- new LiveStreamRequest { OpenToken = state.MediaSource.OpenToken },
- cancellationTokenSource.Token)
+ new LiveStreamRequest { OpenToken = state.MediaSource.OpenToken },
+ cancellationTokenSource.Token)
.ConfigureAwait(false);
var encodingOptions = _serverConfigurationManager.GetEncodingOptions();
diff --git a/Jellyfin.Api/Jellyfin.Api.csproj b/Jellyfin.Api/Jellyfin.Api.csproj
index a527282d15..669925198b 100644
--- a/Jellyfin.Api/Jellyfin.Api.csproj
+++ b/Jellyfin.Api/Jellyfin.Api.csproj
@@ -14,7 +14,7 @@
-
+
diff --git a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj
index 728f9021dc..a75b285936 100644
--- a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj
+++ b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj
@@ -19,13 +19,13 @@
-
-
-
+
+
+ allruntime; build; native; contentfiles; analyzers; buildtransitive
-
+ allruntime; build; native; contentfiles; analyzers; buildtransitive
diff --git a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
index 15dc438561..f19e87aba5 100644
--- a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
+++ b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
@@ -303,7 +303,7 @@ namespace Jellyfin.Server.Extensions
{
description.TryGetMethodInfo(out MethodInfo methodInfo);
// Attribute name, method name, none.
- return description?.ActionDescriptor?.AttributeRouteInfo?.Name
+ return description?.ActionDescriptor.AttributeRouteInfo?.Name
?? methodInfo?.Name
?? null;
});
@@ -341,7 +341,7 @@ namespace Jellyfin.Server.Extensions
{
foreach (var address in host.GetAddresses())
{
- AddIpAddress(config, options, addr.Address, addr.PrefixLength);
+ AddIpAddress(config, options, address, address.AddressFamily == AddressFamily.InterNetwork ? 32 : 128);
}
}
}
@@ -397,7 +397,7 @@ namespace Jellyfin.Server.Extensions
Type = "object",
Properties = typeof(ImageType).GetEnumNames().ToDictionary(
name => name,
- name => new OpenApiSchema
+ _ => new OpenApiSchema
{
Type = "object",
AdditionalProperties = new OpenApiSchema
diff --git a/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj
index 49529b7944..ea64663bd7 100644
--- a/Jellyfin.Server/Jellyfin.Server.csproj
+++ b/Jellyfin.Server/Jellyfin.Server.csproj
@@ -33,13 +33,13 @@
-
-
+
+
-
+
diff --git a/Jellyfin.Server/Middleware/BaseUrlRedirectionMiddleware.cs b/Jellyfin.Server/Middleware/BaseUrlRedirectionMiddleware.cs
index 2eef223e52..3e5982eedf 100644
--- a/Jellyfin.Server/Middleware/BaseUrlRedirectionMiddleware.cs
+++ b/Jellyfin.Server/Middleware/BaseUrlRedirectionMiddleware.cs
@@ -58,9 +58,12 @@ namespace Jellyfin.Server.Middleware
return;
}
- if (!startsWithBaseUrl)
+ if (!startsWithBaseUrl
+ || localPath.Length == baseUrlPrefix.Length
+ // Local path is /baseUrl/
+ || (localPath.Length == baseUrlPrefix.Length + 1 && localPath[^1] == '/'))
{
- // Always redirect back to the default path if the base prefix is invalid or missing
+ // Always redirect back to the default path if the base prefix is invalid, missing, or is the full path.
_logger.LogDebug("Normalizing an URL at {LocalPath}", localPath);
httpContext.Response.Redirect(baseUrlPrefix + "/" + _configuration[ConfigurationExtensions.DefaultRedirectKey]);
return;
diff --git a/Jellyfin.Server/Middleware/ExceptionMiddleware.cs b/Jellyfin.Server/Middleware/ExceptionMiddleware.cs
index f6c76e4d9e..db7877c31e 100644
--- a/Jellyfin.Server/Middleware/ExceptionMiddleware.cs
+++ b/Jellyfin.Server/Middleware/ExceptionMiddleware.cs
@@ -137,11 +137,6 @@ namespace Jellyfin.Server.Middleware
private string NormalizeExceptionMessage(string msg)
{
- if (msg == null)
- {
- return string.Empty;
- }
-
// Strip any information we don't want to reveal
return msg.Replace(
_configuration.ApplicationPaths.ProgramSystemPath,
diff --git a/Jellyfin.Server/Migrations/MigrationRunner.cs b/Jellyfin.Server/Migrations/MigrationRunner.cs
index cf938ab8cd..0af5cfd619 100644
--- a/Jellyfin.Server/Migrations/MigrationRunner.cs
+++ b/Jellyfin.Server/Migrations/MigrationRunner.cs
@@ -40,7 +40,7 @@ namespace Jellyfin.Server.Migrations
.Select(m => ActivatorUtilities.CreateInstance(host.ServiceProvider, m))
.OfType()
.ToArray();
- var migrationOptions = ((IConfigurationManager)host.ConfigurationManager).GetConfiguration(MigrationsListStore.StoreKey);
+ var migrationOptions = host.ConfigurationManager.GetConfiguration(MigrationsListStore.StoreKey);
if (!host.ConfigurationManager.Configuration.IsStartupWizardCompleted && migrationOptions.Applied.Count == 0)
{
diff --git a/Jellyfin.Server/Migrations/Routines/MigrateActivityLogDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateActivityLogDb.cs
index 6048160c63..9e22978aee 100644
--- a/Jellyfin.Server/Migrations/Routines/MigrateActivityLogDb.cs
+++ b/Jellyfin.Server/Migrations/Routines/MigrateActivityLogDb.cs
@@ -92,7 +92,7 @@ namespace Jellyfin.Server.Migrations.Routines
if (entry[6].SQLiteType != SQLiteType.Null && !Guid.TryParse(entry[6].ToString(), out guid))
{
// This is not a valid Guid, see if it is an internal ID from an old Emby schema
- _logger.LogWarning("Invalid Guid in UserId column: ", entry[6].ToString());
+ _logger.LogWarning("Invalid Guid in UserId column: {Guid}", entry[6].ToString());
using var statement = userDbConnection.PrepareStatement("SELECT guid FROM LocalUsersv2 WHERE Id=@Id");
statement.TryBind("@Id", entry[6].ToString());
diff --git a/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs
index e25d291226..6ff59626de 100644
--- a/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs
+++ b/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
-using System.Globalization;
using System.IO;
using System.Linq;
using System.Text.Json;
diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs
index 934372a94d..7018d537fd 100644
--- a/Jellyfin.Server/Program.cs
+++ b/Jellyfin.Server/Program.cs
@@ -5,7 +5,6 @@ using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
-using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
@@ -121,11 +120,11 @@ namespace Jellyfin.Server
// Log uncaught exceptions to the logging instead of std error
AppDomain.CurrentDomain.UnhandledException -= UnhandledExceptionToConsole;
- AppDomain.CurrentDomain.UnhandledException += (sender, e)
+ AppDomain.CurrentDomain.UnhandledException += (_, e)
=> _logger.LogCritical((Exception)e.ExceptionObject, "Unhandled Exception");
// Intercept Ctrl+C and Ctrl+Break
- Console.CancelKeyPress += (sender, e) =>
+ Console.CancelKeyPress += (_, e) =>
{
if (_tokenSource.IsCancellationRequested)
{
@@ -139,7 +138,7 @@ namespace Jellyfin.Server
};
// Register a SIGTERM handler
- AppDomain.CurrentDomain.ProcessExit += (sender, e) =>
+ AppDomain.CurrentDomain.ProcessExit += (_, _) =>
{
if (_tokenSource.IsCancellationRequested)
{
@@ -180,7 +179,7 @@ namespace Jellyfin.Server
"The server is expected to host the web client, but the provided content directory is either " +
$"invalid or empty: {webContentPath}. If you do not want to host the web client with the " +
"server, you may set the '--nowebclient' command line flag, or set" +
- $"'{MediaBrowser.Controller.Extensions.ConfigurationExtensions.HostWebClientKey}=false' in your config settings.");
+ $"'{ConfigurationExtensions.HostWebClientKey}=false' in your config settings.");
}
}
@@ -543,7 +542,7 @@ namespace Jellyfin.Server
// Get a stream of the resource contents
// NOTE: The .csproj name is used instead of the assembly name in the resource path
const string ResourcePath = "Jellyfin.Server.Resources.Configuration.logging.json";
- await using Stream? resource = typeof(Program).Assembly.GetManifestResourceStream(ResourcePath)
+ await using Stream resource = typeof(Program).Assembly.GetManifestResourceStream(ResourcePath)
?? throw new InvalidOperationException($"Invalid resource path: '{ResourcePath}'");
// Copy the resource contents to the expected file path for the config file
diff --git a/MediaBrowser.Common/Extensions/ProcessExtensions.cs b/MediaBrowser.Common/Extensions/ProcessExtensions.cs
index c747871222..08e01bfd65 100644
--- a/MediaBrowser.Common/Extensions/ProcessExtensions.cs
+++ b/MediaBrowser.Common/Extensions/ProcessExtensions.cs
@@ -40,7 +40,7 @@ namespace MediaBrowser.Common.Extensions
// Add an event handler for the process exit event
var tcs = new TaskCompletionSource();
- process.Exited += (sender, args) => tcs.TrySetResult(true);
+ process.Exited += (_, _) => tcs.TrySetResult(true);
// Return immediately if the process has already exited
if (process.HasExitedSafe())
diff --git a/MediaBrowser.Common/Net/IPHost.cs b/MediaBrowser.Common/Net/IPHost.cs
index d78d7def2b..1f125f2b1d 100644
--- a/MediaBrowser.Common/Net/IPHost.cs
+++ b/MediaBrowser.Common/Net/IPHost.cs
@@ -4,7 +4,6 @@ using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text.RegularExpressions;
-using System.Threading.Tasks;
namespace MediaBrowser.Common.Net
{
@@ -196,7 +195,7 @@ namespace MediaBrowser.Common.Net
return res;
}
- throw new InvalidCastException("Host does not contain a valid value. {host}");
+ throw new InvalidCastException($"Host does not contain a valid value. {host}");
}
///
@@ -221,7 +220,7 @@ namespace MediaBrowser.Common.Net
return res;
}
- throw new InvalidCastException("Host does not contain a valid value. {host}");
+ throw new InvalidCastException($"Host does not contain a valid value. {host}");
}
///
@@ -349,7 +348,7 @@ namespace MediaBrowser.Common.Net
}
}
- output = output[0..^1];
+ output = output[..^1];
if (moreThanOne)
{
@@ -400,7 +399,7 @@ namespace MediaBrowser.Common.Net
if ((_addresses.Length == 0 && !Resolved) || (DateTime.UtcNow > _lastResolved.Value.AddMinutes(Timeout)))
{
_lastResolved = DateTime.UtcNow;
- ResolveHostInternal().GetAwaiter().GetResult();
+ ResolveHostInternal();
Resolved = true;
}
@@ -410,30 +409,31 @@ namespace MediaBrowser.Common.Net
///
/// Task that looks up a Host name and returns its IP addresses.
///
- /// A representing the asynchronous operation.
- private async Task ResolveHostInternal()
+ private void ResolveHostInternal()
{
- if (!string.IsNullOrEmpty(HostName))
+ var hostName = HostName;
+ if (string.IsNullOrEmpty(hostName))
{
- // Resolves the host name - so save a DNS lookup.
- if (string.Equals(HostName, "localhost", StringComparison.OrdinalIgnoreCase))
+ return;
+ }
+
+ // Resolves the host name - so save a DNS lookup.
+ if (string.Equals(hostName, "localhost", StringComparison.OrdinalIgnoreCase))
+ {
+ _addresses = new IPAddress[] { IPAddress.Loopback, IPAddress.IPv6Loopback };
+ return;
+ }
+
+ if (Uri.CheckHostName(hostName) == UriHostNameType.Dns)
+ {
+ try
{
- _addresses = new IPAddress[] { IPAddress.Loopback, IPAddress.IPv6Loopback };
- return;
+ _addresses = Dns.GetHostEntry(hostName).AddressList;
}
-
- if (Uri.CheckHostName(HostName).Equals(UriHostNameType.Dns))
+ catch (SocketException ex)
{
- try
- {
- IPHostEntry ip = await Dns.GetHostEntryAsync(HostName).ConfigureAwait(false);
- _addresses = ip.AddressList;
- }
- catch (SocketException ex)
- {
- // Log and then ignore socket errors, as the result value will just be an empty array.
- Debug.WriteLine("GetHostEntryAsync failed with {Message}.", ex.Message);
- }
+ // Log and then ignore socket errors, as the result value will just be an empty array.
+ Debug.WriteLine("GetHostAddresses failed with {Message}.", ex.Message);
}
}
}
diff --git a/MediaBrowser.Common/Plugins/BasePluginOfT.cs b/MediaBrowser.Common/Plugins/BasePluginOfT.cs
index 8a6d28e0f6..afda83a7c5 100644
--- a/MediaBrowser.Common/Plugins/BasePluginOfT.cs
+++ b/MediaBrowser.Common/Plugins/BasePluginOfT.cs
@@ -47,10 +47,10 @@ namespace MediaBrowser.Common.Plugins
var assemblyFilePath = assembly.Location;
var dataFolderPath = Path.Combine(ApplicationPaths.PluginsPath, Path.GetFileNameWithoutExtension(assemblyFilePath));
- if (!Directory.Exists(dataFolderPath) && Version != null)
+ if (Version != null && !Directory.Exists(dataFolderPath))
{
// Try again with the version number appended to the folder name.
- dataFolderPath = dataFolderPath + "_" + Version.ToString();
+ dataFolderPath += "_" + Version.ToString();
}
SetAttributes(assemblyFilePath, dataFolderPath, assemblyName.Version);
diff --git a/MediaBrowser.Common/Providers/ProviderIdParsers.cs b/MediaBrowser.Common/Providers/ProviderIdParsers.cs
index 33d09ed385..487b5a6d29 100644
--- a/MediaBrowser.Common/Providers/ProviderIdParsers.cs
+++ b/MediaBrowser.Common/Providers/ProviderIdParsers.cs
@@ -18,7 +18,7 @@ namespace MediaBrowser.Common.Providers
/// The text to parse.
/// The parsed IMDb id.
/// True if parsing was successful, false otherwise.
- public static bool TryFindImdbId(ReadOnlySpan text, [NotNullWhen(true)] out ReadOnlySpan imdbId)
+ public static bool TryFindImdbId(ReadOnlySpan text, out ReadOnlySpan imdbId)
{
// imdb id is at least 9 chars (tt + 7 numbers)
while (text.Length >= 2 + ImdbMinNumbers)
@@ -62,7 +62,7 @@ namespace MediaBrowser.Common.Providers
/// The text with the url to parse.
/// The parsed TMDb id.
/// True if parsing was successful, false otherwise.
- public static bool TryFindTmdbMovieId(ReadOnlySpan text, [NotNullWhen(true)] out ReadOnlySpan tmdbId)
+ public static bool TryFindTmdbMovieId(ReadOnlySpan text, out ReadOnlySpan tmdbId)
=> TryFindProviderId(text, "themoviedb.org/movie/", out tmdbId);
///
@@ -71,7 +71,7 @@ namespace MediaBrowser.Common.Providers
/// The text with the url to parse.
/// The parsed TMDb id.
/// True if parsing was successful, false otherwise.
- public static bool TryFindTmdbSeriesId(ReadOnlySpan text, [NotNullWhen(true)] out ReadOnlySpan tmdbId)
+ public static bool TryFindTmdbSeriesId(ReadOnlySpan text, out ReadOnlySpan tmdbId)
=> TryFindProviderId(text, "themoviedb.org/tv/", out tmdbId);
///
@@ -80,7 +80,7 @@ namespace MediaBrowser.Common.Providers
/// The text with the url to parse.
/// The parsed TVDb id.
/// True if parsing was successful, false otherwise.
- public static bool TryFindTvdbId(ReadOnlySpan text, [NotNullWhen(true)] out ReadOnlySpan tvdbId)
+ public static bool TryFindTvdbId(ReadOnlySpan text, out ReadOnlySpan tvdbId)
=> TryFindProviderId(text, "thetvdb.com/?tab=series&id=", out tvdbId);
private static bool TryFindProviderId(ReadOnlySpan text, ReadOnlySpan searchString, [NotNullWhen(true)] out ReadOnlySpan providerId)
diff --git a/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs b/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs
index 97f40b5372..abfdb41d80 100644
--- a/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs
+++ b/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs
@@ -1,6 +1,5 @@
-#nullable disable
-
using System;
+using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading;
using Jellyfin.Extensions;
@@ -16,7 +15,7 @@ namespace MediaBrowser.Controller.BaseItemManager
{
private readonly IServerConfigurationManager _serverConfigurationManager;
- private int _metadataRefreshConcurrency = 0;
+ private int _metadataRefreshConcurrency;
///
/// Initializes a new instance of the class.
@@ -101,7 +100,7 @@ namespace MediaBrowser.Controller.BaseItemManager
/// Called when the configuration is updated.
/// It will refresh the metadata throttler if the relevant config changed.
///
- private void OnConfigurationUpdated(object sender, EventArgs e)
+ private void OnConfigurationUpdated(object? sender, EventArgs e)
{
int newMetadataRefreshConcurrency = GetMetadataRefreshConcurrency();
if (_metadataRefreshConcurrency != newMetadataRefreshConcurrency)
@@ -114,6 +113,7 @@ namespace MediaBrowser.Controller.BaseItemManager
///
/// Creates the metadata refresh throttler.
///
+ [MemberNotNull(nameof(MetadataRefreshThrottler))]
private void SetupMetadataThrottler()
{
MetadataRefreshThrottler = new SemaphoreSlim(_metadataRefreshConcurrency);
diff --git a/MediaBrowser.Controller/BaseItemManager/IBaseItemManager.cs b/MediaBrowser.Controller/BaseItemManager/IBaseItemManager.cs
index b2b36c040b..e18994214e 100644
--- a/MediaBrowser.Controller/BaseItemManager/IBaseItemManager.cs
+++ b/MediaBrowser.Controller/BaseItemManager/IBaseItemManager.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
using System.Threading;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.Configuration;
@@ -34,4 +32,4 @@ namespace MediaBrowser.Controller.BaseItemManager
/// true if image fetcher is enabled, else false.
bool IsImageFetcherEnabled(BaseItem baseItem, LibraryOptions libraryOptions, string name);
}
-}
\ No newline at end of file
+}
diff --git a/MediaBrowser.Controller/Channels/ChannelItemResult.cs b/MediaBrowser.Controller/Channels/ChannelItemResult.cs
index 7a0addd9f9..ca7721991d 100644
--- a/MediaBrowser.Controller/Channels/ChannelItemResult.cs
+++ b/MediaBrowser.Controller/Channels/ChannelItemResult.cs
@@ -1,7 +1,6 @@
-#nullable disable
-
-#pragma warning disable CA1002, CA2227, CS1591
+#pragma warning disable CS1591
+using System;
using System.Collections.Generic;
namespace MediaBrowser.Controller.Channels
@@ -10,10 +9,10 @@ namespace MediaBrowser.Controller.Channels
{
public ChannelItemResult()
{
- Items = new List();
+ Items = Array.Empty();
}
- public List Items { get; set; }
+ public IReadOnlyList Items { get; set; }
public int? TotalRecordCount { get; set; }
}
diff --git a/MediaBrowser.Controller/Collections/CollectionCreationOptions.cs b/MediaBrowser.Controller/Collections/CollectionCreationOptions.cs
index 76ad335c59..30f5f4efa2 100644
--- a/MediaBrowser.Controller/Collections/CollectionCreationOptions.cs
+++ b/MediaBrowser.Controller/Collections/CollectionCreationOptions.cs
@@ -1,6 +1,6 @@
#nullable disable
-#pragma warning disable CA2227, CS1591
+#pragma warning disable CS1591
using System;
using System.Collections.Generic;
diff --git a/MediaBrowser.Controller/Collections/CollectionModifiedEventArgs.cs b/MediaBrowser.Controller/Collections/CollectionModifiedEventArgs.cs
index 8155cf3dbd..e538fa4b3f 100644
--- a/MediaBrowser.Controller/Collections/CollectionModifiedEventArgs.cs
+++ b/MediaBrowser.Controller/Collections/CollectionModifiedEventArgs.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
#pragma warning disable CS1591
using System;
diff --git a/MediaBrowser.Controller/Collections/ICollectionManager.cs b/MediaBrowser.Controller/Collections/ICollectionManager.cs
index 49cc39f047..b8c33ee5a0 100644
--- a/MediaBrowser.Controller/Collections/ICollectionManager.cs
+++ b/MediaBrowser.Controller/Collections/ICollectionManager.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
#pragma warning disable CS1591
using System;
@@ -16,17 +14,17 @@ namespace MediaBrowser.Controller.Collections
///
/// Occurs when [collection created].
///
- event EventHandler CollectionCreated;
+ event EventHandler? CollectionCreated;
///
/// Occurs when [items added to collection].
///
- event EventHandler ItemsAddedToCollection;
+ event EventHandler? ItemsAddedToCollection;
///
/// Occurs when [items removed from collection].
///
- event EventHandler ItemsRemovedFromCollection;
+ event EventHandler? ItemsRemovedFromCollection;
///
/// Creates the collection.
diff --git a/MediaBrowser.Controller/Configuration/IServerConfigurationManager.cs b/MediaBrowser.Controller/Configuration/IServerConfigurationManager.cs
index 44e2c45dd4..43ad04dbac 100644
--- a/MediaBrowser.Controller/Configuration/IServerConfigurationManager.cs
+++ b/MediaBrowser.Controller/Configuration/IServerConfigurationManager.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
using MediaBrowser.Common.Configuration;
using MediaBrowser.Model.Configuration;
diff --git a/MediaBrowser.Controller/Dlna/IDlnaManager.cs b/MediaBrowser.Controller/Dlna/IDlnaManager.cs
index b51dc255ce..a64919700d 100644
--- a/MediaBrowser.Controller/Dlna/IDlnaManager.cs
+++ b/MediaBrowser.Controller/Dlna/IDlnaManager.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
#pragma warning disable CS1591
using System.Collections.Generic;
@@ -22,7 +20,7 @@ namespace MediaBrowser.Controller.Dlna
///
/// The headers.
/// DeviceProfile.
- DeviceProfile GetProfile(IHeaderDictionary headers);
+ DeviceProfile? GetProfile(IHeaderDictionary headers);
///
/// Gets the default profile.
@@ -53,14 +51,14 @@ namespace MediaBrowser.Controller.Dlna
///
/// The identifier.
/// DeviceProfile.
- DeviceProfile GetProfile(string id);
+ DeviceProfile? GetProfile(string id);
///
/// Gets the profile.
///
/// The device information.
/// DeviceProfile.
- DeviceProfile GetProfile(DeviceIdentification deviceInfo);
+ DeviceProfile? GetProfile(DeviceIdentification deviceInfo);
///
/// Gets the server description XML.
diff --git a/MediaBrowser.Controller/Entities/AggregateFolder.cs b/MediaBrowser.Controller/Entities/AggregateFolder.cs
index fe1bc62aba..9589f52452 100644
--- a/MediaBrowser.Controller/Entities/AggregateFolder.cs
+++ b/MediaBrowser.Controller/Entities/AggregateFolder.cs
@@ -1,6 +1,6 @@
#nullable disable
-#pragma warning disable CS1591
+#pragma warning disable CA1819, CS1591
using System;
using System.Collections.Concurrent;
@@ -18,52 +18,51 @@ namespace MediaBrowser.Controller.Entities
{
///
/// Specialized folder that can have items added to it's children by external entities.
- /// Used for our RootFolder so plug-ins can add items.
+ /// Used for our RootFolder so plugins can add items.
///
public class AggregateFolder : Folder
{
+ private readonly object _childIdsLock = new object();
+
+ ///
+ /// The _virtual children.
+ ///
+ private readonly ConcurrentBag _virtualChildren = new ConcurrentBag();
private bool _requiresRefresh;
+ private Guid[] _childrenIds = null;
public AggregateFolder()
{
PhysicalLocationsList = Array.Empty();
}
- [JsonIgnore]
- public override bool IsPhysicalRoot => true;
-
- public override bool CanDelete()
- {
- return false;
- }
-
- [JsonIgnore]
- public override bool SupportsPlayedStatus => false;
-
- ///
- /// The _virtual children.
- ///
- private readonly ConcurrentBag _virtualChildren = new ConcurrentBag();
-
///
/// Gets the virtual children.
///
/// The virtual children.
public ConcurrentBag VirtualChildren => _virtualChildren;
+ [JsonIgnore]
+ public override bool IsPhysicalRoot => true;
+
+ [JsonIgnore]
+ public override bool SupportsPlayedStatus => false;
+
[JsonIgnore]
public override string[] PhysicalLocations => PhysicalLocationsList;
public string[] PhysicalLocationsList { get; set; }
+ public override bool CanDelete()
+ {
+ return false;
+ }
+
protected override FileSystemMetadata[] GetFileSystemChildren(IDirectoryService directoryService)
{
return CreateResolveArgs(directoryService, true).FileSystemChildren;
}
- private Guid[] _childrenIds = null;
- private readonly object _childIdsLock = new object();
-
protected override List LoadChildren()
{
lock (_childIdsLock)
@@ -169,7 +168,7 @@ namespace MediaBrowser.Controller.Entities
/// Adds the virtual child.
///
/// The child.
- ///
+ /// Throws if child is null.
public void AddVirtualChild(BaseItem child)
{
if (child == null)
diff --git a/MediaBrowser.Controller/Entities/Audio/Audio.cs b/MediaBrowser.Controller/Entities/Audio/Audio.cs
index 576ab67a22..7bf1219ec2 100644
--- a/MediaBrowser.Controller/Entities/Audio/Audio.cs
+++ b/MediaBrowser.Controller/Entities/Audio/Audio.cs
@@ -1,6 +1,6 @@
#nullable disable
-#pragma warning disable CS1591
+#pragma warning disable CA1002, CA1724, CA1826, CS1591
using System;
using System.Collections.Generic;
@@ -25,6 +25,12 @@ namespace MediaBrowser.Controller.Entities.Audio
IHasLookupInfo,
IHasMediaSources
{
+ public Audio()
+ {
+ Artists = Array.Empty();
+ AlbumArtists = Array.Empty();
+ }
+
///
[JsonIgnore]
public IReadOnlyList Artists { get; set; }
@@ -33,17 +39,6 @@ namespace MediaBrowser.Controller.Entities.Audio
[JsonIgnore]
public IReadOnlyList AlbumArtists { get; set; }
- public Audio()
- {
- Artists = Array.Empty();
- AlbumArtists = Array.Empty();
- }
-
- public override double GetDefaultPrimaryImageAspectRatio()
- {
- return 1;
- }
-
[JsonIgnore]
public override bool SupportsPlayedStatus => true;
@@ -62,11 +57,6 @@ namespace MediaBrowser.Controller.Entities.Audio
[JsonIgnore]
public override Folder LatestItemsIndexContainer => AlbumEntity;
- public override bool CanDownload()
- {
- return IsFileProtocol;
- }
-
[JsonIgnore]
public MusicAlbum AlbumEntity => FindParent();
@@ -77,6 +67,16 @@ namespace MediaBrowser.Controller.Entities.Audio
[JsonIgnore]
public override string MediaType => Model.Entities.MediaType.Audio;
+ public override double GetDefaultPrimaryImageAspectRatio()
+ {
+ return 1;
+ }
+
+ public override bool CanDownload()
+ {
+ return IsFileProtocol;
+ }
+
///
/// Creates the name of the sort.
///
diff --git a/MediaBrowser.Controller/Entities/Audio/IHasMusicGenres.cs b/MediaBrowser.Controller/Entities/Audio/IHasMusicGenres.cs
index db60c3071d..c2dae5a2dc 100644
--- a/MediaBrowser.Controller/Entities/Audio/IHasMusicGenres.cs
+++ b/MediaBrowser.Controller/Entities/Audio/IHasMusicGenres.cs
@@ -1,6 +1,6 @@
#nullable disable
-#pragma warning disable CS1591
+#pragma warning disable CA1819, CS1591
namespace MediaBrowser.Controller.Entities.Audio
{
diff --git a/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs b/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs
index 610bce4f5f..03d1f33043 100644
--- a/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs
+++ b/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs
@@ -1,6 +1,6 @@
#nullable disable
-#pragma warning disable CS1591
+#pragma warning disable CA1721, CA1826, CS1591
using System;
using System.Collections.Generic;
@@ -23,18 +23,18 @@ namespace MediaBrowser.Controller.Entities.Audio
///
public class MusicAlbum : Folder, IHasAlbumArtist, IHasArtist, IHasMusicGenres, IHasLookupInfo, IMetadataContainer
{
- ///
- public IReadOnlyList AlbumArtists { get; set; }
-
- ///
- public IReadOnlyList Artists { get; set; }
-
public MusicAlbum()
{
Artists = Array.Empty();
AlbumArtists = Array.Empty();
}
+ ///
+ public IReadOnlyList AlbumArtists { get; set; }
+
+ ///
+ public IReadOnlyList Artists { get; set; }
+
[JsonIgnore]
public override bool SupportsAddingToPlaylist => true;
@@ -44,6 +44,25 @@ namespace MediaBrowser.Controller.Entities.Audio
[JsonIgnore]
public MusicArtist MusicArtist => GetMusicArtist(new DtoOptions(true));
+ [JsonIgnore]
+ public override bool SupportsPlayedStatus => false;
+
+ [JsonIgnore]
+ public override bool SupportsCumulativeRunTimeTicks => true;
+
+ [JsonIgnore]
+ public string AlbumArtist => AlbumArtists.FirstOrDefault();
+
+ [JsonIgnore]
+ public override bool SupportsPeople => false;
+
+ ///
+ /// Gets the tracks.
+ ///
+ /// The tracks.
+ [JsonIgnore]
+ public IEnumerable
public class MusicGenre : BaseItem, IItemByName
{
- public override List GetUserDataKeys()
- {
- var list = base.GetUserDataKeys();
-
- list.Insert(0, GetType().Name + "-" + (Name ?? string.Empty).RemoveDiacritics());
- return list;
- }
-
- public override string CreatePresentationUniqueKey()
- {
- return GetUserDataKeys()[0];
- }
-
[JsonIgnore]
public override bool SupportsAddingToPlaylist => true;
@@ -45,6 +32,22 @@ namespace MediaBrowser.Controller.Entities.Audio
[JsonIgnore]
public override string ContainingFolderPath => Path;
+ [JsonIgnore]
+ public override bool SupportsPeople => false;
+
+ public override List GetUserDataKeys()
+ {
+ var list = base.GetUserDataKeys();
+
+ list.Insert(0, GetType().Name + "-" + (Name ?? string.Empty).RemoveDiacritics());
+ return list;
+ }
+
+ public override string CreatePresentationUniqueKey()
+ {
+ return GetUserDataKeys()[0];
+ }
+
public override double GetDefaultPrimaryImageAspectRatio()
{
return 1;
@@ -60,9 +63,6 @@ namespace MediaBrowser.Controller.Entities.Audio
return true;
}
- [JsonIgnore]
- public override bool SupportsPeople => false;
-
public IList GetTaggedItems(InternalItemsQuery query)
{
query.GenreIds = new[] { Id };
@@ -106,6 +106,8 @@ namespace MediaBrowser.Controller.Entities.Audio
///
/// This is called before any metadata refresh and returns true or false indicating if changes were made.
///
+ /// Option to replace metadata.
+ /// True if metadata changed.
public override bool BeforeMetadataRefresh(bool replaceAllMetadata)
{
var hasChanges = base.BeforeMetadataRefresh(replaceAllMetadata);
diff --git a/MediaBrowser.Controller/Entities/AudioBook.cs b/MediaBrowser.Controller/Entities/AudioBook.cs
index 4052846228..782481fbcd 100644
--- a/MediaBrowser.Controller/Entities/AudioBook.cs
+++ b/MediaBrowser.Controller/Entities/AudioBook.cs
@@ -1,6 +1,6 @@
#nullable disable
-#pragma warning disable CS1591
+#pragma warning disable CA1724, CS1591
using System;
using System.Text.Json.Serialization;
diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs
index 23b97f70c6..067fecd878 100644
--- a/MediaBrowser.Controller/Entities/BaseItem.cs
+++ b/MediaBrowser.Controller/Entities/BaseItem.cs
@@ -1,6 +1,6 @@
#nullable disable
-#pragma warning disable CS1591
+#pragma warning disable CS1591, SA1401
using System;
using System.Collections.Generic;
@@ -40,6 +40,22 @@ namespace MediaBrowser.Controller.Entities
///
public abstract class BaseItem : IHasProviderIds, IHasLookupInfo, IEquatable
{
+ ///
+ /// The trailer folder name.
+ ///
+ public const string TrailerFolderName = "trailers";
+ public const string ThemeSongsFolderName = "theme-music";
+ public const string ThemeSongFilename = "theme";
+ public const string ThemeVideosFolderName = "backdrops";
+ public const string ExtrasFolderName = "extras";
+ public const string BehindTheScenesFolderName = "behind the scenes";
+ public const string DeletedScenesFolderName = "deleted scenes";
+ public const string InterviewFolderName = "interviews";
+ public const string SceneFolderName = "scenes";
+ public const string SampleFolderName = "samples";
+ public const string ShortsFolderName = "shorts";
+ public const string FeaturettesFolderName = "featurettes";
+
///
/// The supported image extensions.
///
@@ -61,38 +77,21 @@ namespace MediaBrowser.Controller.Entities
".ttml"
};
- protected BaseItem()
- {
- Tags = Array.Empty();
- Genres = Array.Empty();
- Studios = Array.Empty();
- ProviderIds = new Dictionary(StringComparer.OrdinalIgnoreCase);
- LockedFields = Array.Empty();
- ImageInfos = Array.Empty();
- ProductionLocations = Array.Empty();
- RemoteTrailers = Array.Empty();
- ExtraIds = Array.Empty();
- }
-
- public static readonly char[] SlugReplaceChars = { '?', '/', '&' };
- public static char SlugChar = '-';
-
///
- /// The trailer folder name.
+ /// Extra types that should be counted and displayed as "Special Features" in the UI.
///
- public const string TrailerFolderName = "trailers";
- public const string ThemeSongsFolderName = "theme-music";
- public const string ThemeSongFilename = "theme";
- public const string ThemeVideosFolderName = "backdrops";
- public const string ExtrasFolderName = "extras";
- public const string BehindTheScenesFolderName = "behind the scenes";
- public const string DeletedScenesFolderName = "deleted scenes";
- public const string InterviewFolderName = "interviews";
- public const string SceneFolderName = "scenes";
- public const string SampleFolderName = "samples";
- public const string ShortsFolderName = "shorts";
- public const string FeaturettesFolderName = "featurettes";
+ public static readonly IReadOnlyCollection DisplayExtraTypes = new HashSet
+ {
+ Model.Entities.ExtraType.Unknown,
+ Model.Entities.ExtraType.BehindTheScenes,
+ Model.Entities.ExtraType.Clip,
+ Model.Entities.ExtraType.DeletedScene,
+ Model.Entities.ExtraType.Interview,
+ Model.Entities.ExtraType.Sample,
+ Model.Entities.ExtraType.Scene
+ };
+ public static readonly char[] SlugReplaceChars = { '?', '/', '&' };
public static readonly string[] AllExtrasTypesFolderNames =
{
ExtrasFolderName,
@@ -105,6 +104,29 @@ namespace MediaBrowser.Controller.Entities
FeaturettesFolderName
};
+ private string _sortName;
+ private Guid[] _themeSongIds;
+ private Guid[] _themeVideoIds;
+
+ private string _forcedSortName;
+
+ private string _name;
+
+ public static char SlugChar = '-';
+
+ protected BaseItem()
+ {
+ Tags = Array.Empty();
+ Genres = Array.Empty();
+ Studios = Array.Empty();
+ ProviderIds = new Dictionary(StringComparer.OrdinalIgnoreCase);
+ LockedFields = Array.Empty();
+ ImageInfos = Array.Empty();
+ ProductionLocations = Array.Empty();
+ RemoteTrailers = Array.Empty();
+ ExtraIds = Array.Empty();
+ }
+
[JsonIgnore]
public Guid[] ThemeSongIds
{
@@ -194,8 +216,6 @@ namespace MediaBrowser.Controller.Entities
[JsonIgnore]
public virtual bool SupportsRemoteImageDownloading => true;
- private string _name;
-
///
/// Gets or sets the name.
///
@@ -328,12 +348,6 @@ namespace MediaBrowser.Controller.Entities
[JsonIgnore]
public virtual bool IsHidden => false;
- public BaseItem GetOwner()
- {
- var ownerId = OwnerId;
- return ownerId.Equals(Guid.Empty) ? null : LibraryManager.GetItemById(ownerId);
- }
-
///
/// Gets the type of the location.
///
@@ -379,13 +393,6 @@ namespace MediaBrowser.Controller.Entities
}
}
- public bool IsPathProtocol(MediaProtocol protocol)
- {
- var current = PathProtocol;
-
- return current.HasValue && current.Value == protocol;
- }
-
[JsonIgnore]
public bool IsFileProtocol => IsPathProtocol(MediaProtocol.File);
@@ -423,35 +430,17 @@ namespace MediaBrowser.Controller.Entities
[JsonIgnore]
public virtual bool EnableAlphaNumericSorting => true;
- private List> GetSortChunks(string s1)
- {
- var list = new List>();
-
- int thisMarker = 0;
-
- while (thisMarker < s1.Length)
- {
- char thisCh = s1[thisMarker];
+ public virtual bool IsHD => Height >= 720;
- var thisChunk = new StringBuilder();
- bool isNumeric = char.IsDigit(thisCh);
+ public bool IsShortcut { get; set; }
- while (thisMarker < s1.Length && char.IsDigit(thisCh) == isNumeric)
- {
- thisChunk.Append(thisCh);
- thisMarker++;
+ public string ShortcutPath { get; set; }
- if (thisMarker < s1.Length)
- {
- thisCh = s1[thisMarker];
- }
- }
+ public int Width { get; set; }
- list.Add(new Tuple(thisChunk, isNumeric));
- }
+ public int Height { get; set; }
- return list;
- }
+ public Guid[] ExtraIds { get; set; }
///
/// Gets the primary image path.
@@ -463,72 +452,6 @@ namespace MediaBrowser.Controller.Entities
[JsonIgnore]
public string PrimaryImagePath => this.GetImagePath(ImageType.Primary);
- public virtual bool CanDelete()
- {
- if (SourceType == SourceType.Channel)
- {
- return ChannelManager.CanDelete(this);
- }
-
- return IsFileProtocol;
- }
-
- public virtual bool IsAuthorizedToDelete(User user, List allCollectionFolders)
- {
- if (user.HasPermission(PermissionKind.EnableContentDeletion))
- {
- return true;
- }
-
- var allowed = user.GetPreferenceValues(PreferenceKind.EnableContentDeletionFromFolders);
-
- if (SourceType == SourceType.Channel)
- {
- return allowed.Contains(ChannelId);
- }
- else
- {
- var collectionFolders = LibraryManager.GetCollectionFolders(this, allCollectionFolders);
-
- foreach (var folder in collectionFolders)
- {
- if (allowed.Contains(folder.Id))
- {
- return true;
- }
- }
- }
-
- return false;
- }
-
- public bool CanDelete(User user, List allCollectionFolders)
- {
- return CanDelete() && IsAuthorizedToDelete(user, allCollectionFolders);
- }
-
- public bool CanDelete(User user)
- {
- var allCollectionFolders = LibraryManager.GetUserRootFolder().Children.OfType().ToList();
-
- return CanDelete(user, allCollectionFolders);
- }
-
- public virtual bool CanDownload()
- {
- return false;
- }
-
- public virtual bool IsAuthorizedToDownload(User user)
- {
- return user.HasPermission(PermissionKind.EnableContentDownloading);
- }
-
- public bool CanDownload(User user)
- {
- return CanDownload() && IsAuthorizedToDownload(user);
- }
-
///
/// Gets or sets the date created.
///
@@ -548,38 +471,6 @@ namespace MediaBrowser.Controller.Entities
[JsonIgnore]
public DateTime DateLastRefreshed { get; set; }
- ///
- /// Gets or sets the logger.
- ///
- public static ILogger Logger { get; set; }
-
- public static ILibraryManager LibraryManager { get; set; }
-
- public static IServerConfigurationManager ConfigurationManager { get; set; }
-
- public static IProviderManager ProviderManager { get; set; }
-
- public static ILocalizationManager LocalizationManager { get; set; }
-
- public static IItemRepository ItemRepository { get; set; }
-
- public static IFileSystem FileSystem { get; set; }
-
- public static IUserDataManager UserDataManager { get; set; }
-
- public static IChannelManager ChannelManager { get; set; }
-
- public static IMediaSourceManager MediaSourceManager { get; set; }
-
- ///
- /// Returns a that represents this instance.
- ///
- /// A that represents this instance.
- public override string ToString()
- {
- return Name;
- }
-
[JsonIgnore]
public bool IsLocked { get; set; }
@@ -611,211 +502,87 @@ namespace MediaBrowser.Controller.Entities
}
}
- private string _forcedSortName;
-
- ///
- /// Gets or sets the name of the forced sort.
- ///
- /// The name of the forced sort.
[JsonIgnore]
- public string ForcedSortName
+ public bool EnableMediaSourceDisplay
{
- get => _forcedSortName;
- set
+ get
{
- _forcedSortName = value;
- _sortName = null;
+ if (SourceType == SourceType.Channel)
+ {
+ return ChannelManager.EnableMediaSourceDisplay(this);
+ }
+
+ return true;
}
}
- private string _sortName;
- private Guid[] _themeSongIds;
- private Guid[] _themeVideoIds;
+ [JsonIgnore]
+ public Guid ParentId { get; set; }
///
- /// Gets or sets the name of the sort.
+ /// Gets or sets the logger.
///
- /// The name of the sort.
- [JsonIgnore]
- public string SortName
- {
- get
- {
- if (_sortName == null)
- {
- if (!string.IsNullOrEmpty(ForcedSortName))
- {
- // Need the ToLower because that's what CreateSortName does
- _sortName = ModifySortChunks(ForcedSortName).ToLowerInvariant();
- }
- else
- {
- _sortName = CreateSortName();
- }
- }
-
- return _sortName;
- }
-
- set => _sortName = value;
- }
-
- public string GetInternalMetadataPath()
- {
- var basePath = ConfigurationManager.ApplicationPaths.InternalMetadataPath;
-
- return GetInternalMetadataPath(basePath);
- }
-
- protected virtual string GetInternalMetadataPath(string basePath)
- {
- if (SourceType == SourceType.Channel)
- {
- return System.IO.Path.Join(basePath, "channels", ChannelId.ToString("N", CultureInfo.InvariantCulture), Id.ToString("N", CultureInfo.InvariantCulture));
- }
-
- ReadOnlySpan idString = Id.ToString("N", CultureInfo.InvariantCulture);
-
- return System.IO.Path.Join(basePath, "library", idString.Slice(0, 2), idString);
- }
-
- ///
- /// Creates the name of the sort.
- ///
- /// System.String.
- protected virtual string CreateSortName()
- {
- if (Name == null)
- {
- return null; // some items may not have name filled in properly
- }
-
- if (!EnableAlphaNumericSorting)
- {
- return Name.TrimStart();
- }
-
- var sortable = Name.Trim().ToLowerInvariant();
-
- foreach (var removeChar in ConfigurationManager.Configuration.SortRemoveCharacters)
- {
- sortable = sortable.Replace(removeChar, string.Empty, StringComparison.Ordinal);
- }
-
- foreach (var replaceChar in ConfigurationManager.Configuration.SortReplaceCharacters)
- {
- sortable = sortable.Replace(replaceChar, " ", StringComparison.Ordinal);
- }
-
- foreach (var search in ConfigurationManager.Configuration.SortRemoveWords)
- {
- // Remove from beginning if a space follows
- if (sortable.StartsWith(search + " ", StringComparison.Ordinal))
- {
- sortable = sortable.Remove(0, search.Length + 1);
- }
-
- // Remove from middle if surrounded by spaces
- sortable = sortable.Replace(" " + search + " ", " ", StringComparison.Ordinal);
-
- // Remove from end if followed by a space
- if (sortable.EndsWith(" " + search, StringComparison.Ordinal))
- {
- sortable = sortable.Remove(sortable.Length - (search.Length + 1));
- }
- }
+ public static ILogger Logger { get; set; }
- return ModifySortChunks(sortable);
- }
+ public static ILibraryManager LibraryManager { get; set; }
- private string ModifySortChunks(string name)
- {
- var chunks = GetSortChunks(name);
+ public static IServerConfigurationManager ConfigurationManager { get; set; }
- var builder = new StringBuilder();
+ public static IProviderManager ProviderManager { get; set; }
- foreach (var chunk in chunks)
- {
- var chunkBuilder = chunk.Item1;
+ public static ILocalizationManager LocalizationManager { get; set; }
- // This chunk is numeric
- if (chunk.Item2)
- {
- while (chunkBuilder.Length < 10)
- {
- chunkBuilder.Insert(0, '0');
- }
- }
+ public static IItemRepository ItemRepository { get; set; }
- builder.Append(chunkBuilder);
- }
+ public static IFileSystem FileSystem { get; set; }
- // logger.LogDebug("ModifySortChunks Start: {0} End: {1}", name, builder.ToString());
- return builder.ToString().RemoveDiacritics();
- }
+ public static IUserDataManager UserDataManager { get; set; }
- [JsonIgnore]
- public bool EnableMediaSourceDisplay
- {
- get
- {
- if (SourceType == SourceType.Channel)
- {
- return ChannelManager.EnableMediaSourceDisplay(this);
- }
+ public static IChannelManager ChannelManager { get; set; }
- return true;
- }
- }
+ public static IMediaSourceManager MediaSourceManager { get; set; }
+ ///
+ /// Gets or sets the name of the forced sort.
+ ///
+ /// The name of the forced sort.
[JsonIgnore]
- public Guid ParentId { get; set; }
-
- public void SetParent(Folder parent)
- {
- ParentId = parent == null ? Guid.Empty : parent.Id;
- }
-
- public BaseItem GetParent()
- {
- var parentId = ParentId;
- if (!parentId.Equals(Guid.Empty))
- {
- return LibraryManager.GetItemById(parentId);
- }
-
- return null;
- }
-
- public IEnumerable GetParents()
+ public string ForcedSortName
{
- var parent = GetParent();
-
- while (parent != null)
+ get => _forcedSortName;
+ set
{
- yield return parent;
-
- parent = parent.GetParent();
+ _forcedSortName = value;
+ _sortName = null;
}
}
///
- /// Finds a parent of a given type.
+ /// Gets or sets the name of the sort.
///
- ///
- /// ``0.
- public T FindParent()
- where T : Folder
+ /// The name of the sort.
+ [JsonIgnore]
+ public string SortName
{
- foreach (var parent in GetParents())
+ get
{
- if (parent is T item)
+ if (_sortName == null)
{
- return item;
+ if (!string.IsNullOrEmpty(ForcedSortName))
+ {
+ // Need the ToLower because that's what CreateSortName does
+ _sortName = ModifySortChunks(ForcedSortName).ToLowerInvariant();
+ }
+ else
+ {
+ _sortName = CreateSortName();
+ }
}
+
+ return _sortName;
}
- return null;
+ set => _sortName = value;
}
[JsonIgnore]
@@ -948,56 +715,399 @@ namespace MediaBrowser.Controller.Entities
[JsonIgnore]
public int? IndexNumber { get; set; }
- ///
- /// Gets or sets the parent index number. For an episode this could be the season number, or for a song this could be the disc number.
- ///
- /// The parent index number.
- [JsonIgnore]
- public int? ParentIndexNumber { get; set; }
+ ///
+ /// Gets or sets the parent index number. For an episode this could be the season number, or for a song this could be the disc number.
+ ///
+ /// The parent index number.
+ [JsonIgnore]
+ public int? ParentIndexNumber { get; set; }
+
+ [JsonIgnore]
+ public virtual bool HasLocalAlternateVersions => false;
+
+ [JsonIgnore]
+ public string OfficialRatingForComparison
+ {
+ get
+ {
+ var officialRating = OfficialRating;
+ if (!string.IsNullOrEmpty(officialRating))
+ {
+ return officialRating;
+ }
+
+ var parent = DisplayParent;
+ if (parent != null)
+ {
+ return parent.OfficialRatingForComparison;
+ }
+
+ return null;
+ }
+ }
+
+ [JsonIgnore]
+ public string CustomRatingForComparison
+ {
+ get
+ {
+ var customRating = CustomRating;
+ if (!string.IsNullOrEmpty(customRating))
+ {
+ return customRating;
+ }
+
+ var parent = DisplayParent;
+ if (parent != null)
+ {
+ return parent.CustomRatingForComparison;
+ }
+
+ return null;
+ }
+ }
+
+ ///
+ /// Gets or sets the provider ids.
+ ///
+ /// The provider ids.
+ [JsonIgnore]
+ public Dictionary ProviderIds { get; set; }
+
+ [JsonIgnore]
+ public virtual Folder LatestItemsIndexContainer => null;
+
+ [JsonIgnore]
+ public string PresentationUniqueKey { get; set; }
+
+ [JsonIgnore]
+ public virtual bool EnableRememberingTrackSelections => true;
+
+ [JsonIgnore]
+ public virtual bool IsTopParent
+ {
+ get
+ {
+ if (this is BasePluginFolder || this is Channel)
+ {
+ return true;
+ }
+
+ if (this is IHasCollectionType view)
+ {
+ if (string.Equals(view.CollectionType, CollectionType.LiveTv, StringComparison.OrdinalIgnoreCase))
+ {
+ return true;
+ }
+ }
+
+ if (GetParent() is AggregateFolder)
+ {
+ return true;
+ }
+
+ return false;
+ }
+ }
+
+ [JsonIgnore]
+ public virtual bool SupportsAncestors => true;
+
+ [JsonIgnore]
+ public virtual bool StopRefreshIfLocalMetadataFound => true;
+
+ [JsonIgnore]
+ protected virtual bool SupportsOwnedItems => !ParentId.Equals(Guid.Empty) && IsFileProtocol;
+
+ [JsonIgnore]
+ public virtual bool SupportsPeople => false;
+
+ [JsonIgnore]
+ public virtual bool SupportsThemeMedia => false;
+
+ [JsonIgnore]
+ public virtual bool SupportsInheritedParentImages => false;
+
+ ///
+ /// Gets a value indicating whether this instance is folder.
+ ///
+ /// true if this instance is folder; otherwise, false.
+ [JsonIgnore]
+ public virtual bool IsFolder => false;
+
+ [JsonIgnore]
+ public virtual bool IsDisplayedAsFolder => false;
+
+ ///
+ /// Gets or sets the remote trailers.
+ ///
+ /// The remote trailers.
+ public IReadOnlyList RemoteTrailers { get; set; }
+
+ public virtual bool SupportsExternalTransfer => false;
+
+ public virtual double GetDefaultPrimaryImageAspectRatio()
+ {
+ return 0;
+ }
+
+ public virtual string CreatePresentationUniqueKey()
+ {
+ return Id.ToString("N", CultureInfo.InvariantCulture);
+ }
+
+ public bool IsPathProtocol(MediaProtocol protocol)
+ {
+ var current = PathProtocol;
+
+ return current.HasValue && current.Value == protocol;
+ }
+
+ private List> GetSortChunks(string s1)
+ {
+ var list = new List>();
+
+ int thisMarker = 0;
+
+ while (thisMarker < s1.Length)
+ {
+ char thisCh = s1[thisMarker];
+
+ var thisChunk = new StringBuilder();
+ bool isNumeric = char.IsDigit(thisCh);
+
+ while (thisMarker < s1.Length && char.IsDigit(thisCh) == isNumeric)
+ {
+ thisChunk.Append(thisCh);
+ thisMarker++;
+
+ if (thisMarker < s1.Length)
+ {
+ thisCh = s1[thisMarker];
+ }
+ }
+
+ list.Add(new Tuple(thisChunk, isNumeric));
+ }
+
+ return list;
+ }
+
+ public virtual bool CanDelete()
+ {
+ if (SourceType == SourceType.Channel)
+ {
+ return ChannelManager.CanDelete(this);
+ }
+
+ return IsFileProtocol;
+ }
+
+ public virtual bool IsAuthorizedToDelete(User user, List allCollectionFolders)
+ {
+ if (user.HasPermission(PermissionKind.EnableContentDeletion))
+ {
+ return true;
+ }
+
+ var allowed = user.GetPreferenceValues(PreferenceKind.EnableContentDeletionFromFolders);
+
+ if (SourceType == SourceType.Channel)
+ {
+ return allowed.Contains(ChannelId);
+ }
+ else
+ {
+ var collectionFolders = LibraryManager.GetCollectionFolders(this, allCollectionFolders);
+
+ foreach (var folder in collectionFolders)
+ {
+ if (allowed.Contains(folder.Id))
+ {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ public BaseItem GetOwner()
+ {
+ var ownerId = OwnerId;
+ return ownerId.Equals(Guid.Empty) ? null : LibraryManager.GetItemById(ownerId);
+ }
+
+ public bool CanDelete(User user, List allCollectionFolders)
+ {
+ return CanDelete() && IsAuthorizedToDelete(user, allCollectionFolders);
+ }
+
+ public bool CanDelete(User user)
+ {
+ var allCollectionFolders = LibraryManager.GetUserRootFolder().Children.OfType().ToList();
+
+ return CanDelete(user, allCollectionFolders);
+ }
+
+ public virtual bool CanDownload()
+ {
+ return false;
+ }
+
+ public virtual bool IsAuthorizedToDownload(User user)
+ {
+ return user.HasPermission(PermissionKind.EnableContentDownloading);
+ }
+
+ public bool CanDownload(User user)
+ {
+ return CanDownload() && IsAuthorizedToDownload(user);
+ }
+
+ ///
+ /// Returns a that represents this instance.
+ ///
+ /// A that represents this instance.
+ public override string ToString()
+ {
+ return Name;
+ }
+
+ public string GetInternalMetadataPath()
+ {
+ var basePath = ConfigurationManager.ApplicationPaths.InternalMetadataPath;
+
+ return GetInternalMetadataPath(basePath);
+ }
+
+ protected virtual string GetInternalMetadataPath(string basePath)
+ {
+ if (SourceType == SourceType.Channel)
+ {
+ return System.IO.Path.Join(basePath, "channels", ChannelId.ToString("N", CultureInfo.InvariantCulture), Id.ToString("N", CultureInfo.InvariantCulture));
+ }
+
+ ReadOnlySpan idString = Id.ToString("N", CultureInfo.InvariantCulture);
+
+ return System.IO.Path.Join(basePath, "library", idString.Slice(0, 2), idString);
+ }
+
+ ///
+ /// Creates the name of the sort.
+ ///
+ /// System.String.
+ protected virtual string CreateSortName()
+ {
+ if (Name == null)
+ {
+ return null; // some items may not have name filled in properly
+ }
+
+ if (!EnableAlphaNumericSorting)
+ {
+ return Name.TrimStart();
+ }
+
+ var sortable = Name.Trim().ToLowerInvariant();
+
+ foreach (var removeChar in ConfigurationManager.Configuration.SortRemoveCharacters)
+ {
+ sortable = sortable.Replace(removeChar, string.Empty, StringComparison.Ordinal);
+ }
+
+ foreach (var replaceChar in ConfigurationManager.Configuration.SortReplaceCharacters)
+ {
+ sortable = sortable.Replace(replaceChar, " ", StringComparison.Ordinal);
+ }
+
+ foreach (var search in ConfigurationManager.Configuration.SortRemoveWords)
+ {
+ // Remove from beginning if a space follows
+ if (sortable.StartsWith(search + " ", StringComparison.Ordinal))
+ {
+ sortable = sortable.Remove(0, search.Length + 1);
+ }
+
+ // Remove from middle if surrounded by spaces
+ sortable = sortable.Replace(" " + search + " ", " ", StringComparison.Ordinal);
+
+ // Remove from end if followed by a space
+ if (sortable.EndsWith(" " + search, StringComparison.Ordinal))
+ {
+ sortable = sortable.Remove(sortable.Length - (search.Length + 1));
+ }
+ }
+
+ return ModifySortChunks(sortable);
+ }
+
+ private string ModifySortChunks(string name)
+ {
+ var chunks = GetSortChunks(name);
+
+ var builder = new StringBuilder();
+
+ foreach (var chunk in chunks)
+ {
+ var chunkBuilder = chunk.Item1;
+
+ // This chunk is numeric
+ if (chunk.Item2)
+ {
+ while (chunkBuilder.Length < 10)
+ {
+ chunkBuilder.Insert(0, '0');
+ }
+ }
+
+ builder.Append(chunkBuilder);
+ }
- [JsonIgnore]
- public virtual bool HasLocalAlternateVersions => false;
+ // logger.LogDebug("ModifySortChunks Start: {0} End: {1}", name, builder.ToString());
+ return builder.ToString().RemoveDiacritics();
+ }
- [JsonIgnore]
- public string OfficialRatingForComparison
+ public BaseItem GetParent()
{
- get
+ var parentId = ParentId;
+ if (!parentId.Equals(Guid.Empty))
{
- var officialRating = OfficialRating;
- if (!string.IsNullOrEmpty(officialRating))
- {
- return officialRating;
- }
+ return LibraryManager.GetItemById(parentId);
+ }
- var parent = DisplayParent;
- if (parent != null)
- {
- return parent.OfficialRatingForComparison;
- }
+ return null;
+ }
- return null;
+ public IEnumerable GetParents()
+ {
+ var parent = GetParent();
+
+ while (parent != null)
+ {
+ yield return parent;
+
+ parent = parent.GetParent();
}
}
- [JsonIgnore]
- public string CustomRatingForComparison
+ ///
+ /// Finds a parent of a given type.
+ ///
+ /// Type of parent.
+ /// ``0.
+ public T FindParent()
+ where T : Folder
{
- get
+ foreach (var parent in GetParents())
{
- var customRating = CustomRating;
- if (!string.IsNullOrEmpty(customRating))
- {
- return customRating;
- }
-
- var parent = DisplayParent;
- if (parent != null)
+ if (parent is T item)
{
- return parent.CustomRatingForComparison;
+ return item;
}
-
- return null;
}
+
+ return null;
}
///
@@ -1405,14 +1515,46 @@ namespace MediaBrowser.Controller.Entities
}
}
- [JsonIgnore]
- protected virtual bool SupportsOwnedItems => !ParentId.Equals(Guid.Empty) && IsFileProtocol;
+ protected bool IsVisibleStandaloneInternal(User user, bool checkFolders)
+ {
+ if (!IsVisible(user))
+ {
+ return false;
+ }
- [JsonIgnore]
- public virtual bool SupportsPeople => false;
+ if (GetParents().Any(i => !i.IsVisible(user)))
+ {
+ return false;
+ }
- [JsonIgnore]
- public virtual bool SupportsThemeMedia => false;
+ if (checkFolders)
+ {
+ var topParent = GetParents().LastOrDefault() ?? this;
+
+ if (string.IsNullOrEmpty(topParent.Path))
+ {
+ return true;
+ }
+
+ var itemCollectionFolders = LibraryManager.GetCollectionFolders(this).Select(i => i.Id).ToList();
+
+ if (itemCollectionFolders.Count > 0)
+ {
+ var userCollectionFolders = LibraryManager.GetUserRootFolder().GetChildren(user, true).Select(i => i.Id).ToList();
+ if (!itemCollectionFolders.Any(userCollectionFolders.Contains))
+ {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ public void SetParent(Folder parent)
+ {
+ ParentId = parent == null ? Guid.Empty : parent.Id;
+ }
///
/// Refreshes owned items such as trailers, theme videos, special features, etc.
@@ -1609,29 +1751,6 @@ namespace MediaBrowser.Controller.Entities
return themeSongsChanged;
}
- ///
- /// Gets or sets the provider ids.
- ///
- /// The provider ids.
- [JsonIgnore]
- public Dictionary ProviderIds { get; set; }
-
- [JsonIgnore]
- public virtual Folder LatestItemsIndexContainer => null;
-
- public virtual double GetDefaultPrimaryImageAspectRatio()
- {
- return 0;
- }
-
- public virtual string CreatePresentationUniqueKey()
- {
- return Id.ToString("N", CultureInfo.InvariantCulture);
- }
-
- [JsonIgnore]
- public string PresentationUniqueKey { get; set; }
-
public string GetPresentationUniqueKey()
{
return PresentationUniqueKey ?? CreatePresentationUniqueKey();
@@ -1929,55 +2048,6 @@ namespace MediaBrowser.Controller.Entities
return IsVisibleStandaloneInternal(user, true);
}
- [JsonIgnore]
- public virtual bool SupportsInheritedParentImages => false;
-
- protected bool IsVisibleStandaloneInternal(User user, bool checkFolders)
- {
- if (!IsVisible(user))
- {
- return false;
- }
-
- if (GetParents().Any(i => !i.IsVisible(user)))
- {
- return false;
- }
-
- if (checkFolders)
- {
- var topParent = GetParents().LastOrDefault() ?? this;
-
- if (string.IsNullOrEmpty(topParent.Path))
- {
- return true;
- }
-
- var itemCollectionFolders = LibraryManager.GetCollectionFolders(this).Select(i => i.Id).ToList();
-
- if (itemCollectionFolders.Count > 0)
- {
- var userCollectionFolders = LibraryManager.GetUserRootFolder().GetChildren(user, true).Select(i => i.Id).ToList();
- if (!itemCollectionFolders.Any(userCollectionFolders.Contains))
- {
- return false;
- }
- }
- }
-
- return true;
- }
-
- ///
- /// Gets a value indicating whether this instance is folder.
- ///
- /// true if this instance is folder; otherwise, false.
- [JsonIgnore]
- public virtual bool IsFolder => false;
-
- [JsonIgnore]
- public virtual bool IsDisplayedAsFolder => false;
-
public virtual string GetClientTypeName()
{
if (IsFolder && SourceType == SourceType.Channel && !(this is Channel))
@@ -2066,14 +2136,11 @@ namespace MediaBrowser.Controller.Entities
return null;
}
- [JsonIgnore]
- public virtual bool EnableRememberingTrackSelections => true;
-
///
/// Adds a studio to the item.
///
/// The name.
- ///
+ /// Throws if name is null.
public void AddStudio(string name)
{
if (string.IsNullOrEmpty(name))
@@ -2109,7 +2176,7 @@ namespace MediaBrowser.Controller.Entities
/// Adds a genre to the item.
///
/// The name.
- ///
+ /// Throwns if name is null.
public void AddGenre(string name)
{
if (string.IsNullOrEmpty(name))
@@ -2132,8 +2199,7 @@ namespace MediaBrowser.Controller.Entities
/// The user.
/// The date played.
/// if set to true [reset position].
- /// Task.
- ///
+ /// Throws if user is null.
public virtual void MarkPlayed(
User user,
DateTime? datePlayed,
@@ -2170,8 +2236,7 @@ namespace MediaBrowser.Controller.Entities
/// Marks the unplayed.
///
/// The user.
- /// Task.
- ///
+ /// Throws if user is null.
public virtual void MarkUnplayed(User user)
{
if (user == null)
@@ -2271,6 +2336,7 @@ namespace MediaBrowser.Controller.Entities
///
/// The type.
/// The index.
+ /// A task.
public async Task DeleteImageAsync(ImageType type, int index)
{
var info = GetImageInfo(type, index);
@@ -2308,6 +2374,8 @@ namespace MediaBrowser.Controller.Entities
///
/// Validates that images within the item are still on the filesystem.
///
+ /// The directory service to use.
+ /// true if the images validate, false if not.
public bool ValidateImages(IDirectoryService directoryService)
{
var allFiles = ImageInfos
@@ -2335,7 +2403,6 @@ namespace MediaBrowser.Controller.Entities
/// Type of the image.
/// Index of the image.
/// System.String.
- ///
/// Item is null.
public string GetImagePath(ImageType imageType, int imageIndex)
=> GetImageInfo(imageType, imageIndex)?.Path;
@@ -2821,39 +2888,6 @@ namespace MediaBrowser.Controller.Entities
return GetParents().FirstOrDefault(parent => parent.IsTopParent);
}
- [JsonIgnore]
- public virtual bool IsTopParent
- {
- get
- {
- if (this is BasePluginFolder || this is Channel)
- {
- return true;
- }
-
- if (this is IHasCollectionType view)
- {
- if (string.Equals(view.CollectionType, CollectionType.LiveTv, StringComparison.OrdinalIgnoreCase))
- {
- return true;
- }
- }
-
- if (GetParent() is AggregateFolder)
- {
- return true;
- }
-
- return false;
- }
- }
-
- [JsonIgnore]
- public virtual bool SupportsAncestors => true;
-
- [JsonIgnore]
- public virtual bool StopRefreshIfLocalMetadataFound => true;
-
public virtual IEnumerable GetIdsForAncestorQuery()
{
return new[] { Id };
@@ -2888,6 +2922,7 @@ namespace MediaBrowser.Controller.Entities
///
/// Updates the official rating based on content and returns true or false indicating if it changed.
///
+ /// Media children.
/// true if the rating was updated; otherwise false.
public bool UpdateRatingToItems(IList children)
{
@@ -2920,12 +2955,6 @@ namespace MediaBrowser.Controller.Entities
return ThemeVideoIds.Select(LibraryManager.GetItemById);
}
- ///
- /// Gets or sets the remote trailers.
- ///
- /// The remote trailers.
- public IReadOnlyList RemoteTrailers { get; set; }
-
///
/// Get all extras associated with this item, sorted by .
///
@@ -2963,39 +2992,11 @@ namespace MediaBrowser.Controller.Entities
}
}
- public virtual bool IsHD => Height >= 720;
-
- public bool IsShortcut { get; set; }
-
- public string ShortcutPath { get; set; }
-
- public int Width { get; set; }
-
- public int Height { get; set; }
-
- public Guid[] ExtraIds { get; set; }
-
public virtual long GetRunTimeTicksForPlayState()
{
return RunTimeTicks ?? 0;
}
- ///
- /// Extra types that should be counted and displayed as "Special Features" in the UI.
- ///
- public static readonly IReadOnlyCollection DisplayExtraTypes = new HashSet
- {
- Model.Entities.ExtraType.Unknown,
- Model.Entities.ExtraType.BehindTheScenes,
- Model.Entities.ExtraType.Clip,
- Model.Entities.ExtraType.DeletedScene,
- Model.Entities.ExtraType.Interview,
- Model.Entities.ExtraType.Sample,
- Model.Entities.ExtraType.Scene
- };
-
- public virtual bool SupportsExternalTransfer => false;
-
///
public override bool Equals(object obj)
{
diff --git a/MediaBrowser.Controller/Entities/BaseItemExtensions.cs b/MediaBrowser.Controller/Entities/BaseItemExtensions.cs
index 89ad392a4b..e88121212a 100644
--- a/MediaBrowser.Controller/Entities/BaseItemExtensions.cs
+++ b/MediaBrowser.Controller/Entities/BaseItemExtensions.cs
@@ -64,6 +64,8 @@ namespace MediaBrowser.Controller.Entities
///
/// The source object.
/// The destination object.
+ /// Source type.
+ /// Destination type.
public static void DeepCopy(this T source, TU dest)
where T : BaseItem
where TU : BaseItem
@@ -109,6 +111,9 @@ namespace MediaBrowser.Controller.Entities
/// Copies all properties on newly created object. Skips properties that do not exist.
///
/// The source object.
+ /// Source type.
+ /// Destination type.
+ /// Destination object.
public static TU DeepCopy(this T source)
where T : BaseItem
where TU : BaseItem, new()
diff --git a/MediaBrowser.Controller/Entities/BasePluginFolder.cs b/MediaBrowser.Controller/Entities/BasePluginFolder.cs
index 1bd25042f2..272a37df1b 100644
--- a/MediaBrowser.Controller/Entities/BasePluginFolder.cs
+++ b/MediaBrowser.Controller/Entities/BasePluginFolder.cs
@@ -15,6 +15,12 @@ namespace MediaBrowser.Controller.Entities
[JsonIgnore]
public virtual string CollectionType => null;
+ [JsonIgnore]
+ public override bool SupportsInheritedParentImages => false;
+
+ [JsonIgnore]
+ public override bool SupportsPeople => false;
+
public override bool CanDelete()
{
return false;
@@ -24,11 +30,5 @@ namespace MediaBrowser.Controller.Entities
{
return true;
}
-
- [JsonIgnore]
- public override bool SupportsInheritedParentImages => false;
-
- [JsonIgnore]
- public override bool SupportsPeople => false;
}
}
diff --git a/MediaBrowser.Controller/Entities/CollectionFolder.cs b/MediaBrowser.Controller/Entities/CollectionFolder.cs
index 4f367fe2b5..0fb4771dd3 100644
--- a/MediaBrowser.Controller/Entities/CollectionFolder.cs
+++ b/MediaBrowser.Controller/Entities/CollectionFolder.cs
@@ -41,6 +41,23 @@ namespace MediaBrowser.Controller.Entities
PhysicalFolderIds = Array.Empty();
}
+ ///
+ /// Gets the display preferences id.
+ ///
+ ///
+ /// Allow different display preferences for each collection folder.
+ ///
+ /// The display prefs id.
+ [JsonIgnore]
+ public override Guid DisplayPreferencesId => Id;
+
+ [JsonIgnore]
+ public override string[] PhysicalLocations => PhysicalLocationsList;
+
+ public string[] PhysicalLocationsList { get; set; }
+
+ public Guid[] PhysicalFolderIds { get; set; }
+
public static IXmlSerializer XmlSerializer { get; set; }
public static IServerApplicationHost ApplicationHost { get; set; }
@@ -63,6 +80,9 @@ namespace MediaBrowser.Controller.Entities
[JsonIgnore]
public override IEnumerable Children => GetActualChildren();
+ [JsonIgnore]
+ public override bool SupportsPeople => false;
+
public override bool CanDelete()
{
return false;
@@ -160,23 +180,6 @@ namespace MediaBrowser.Controller.Entities
}
}
- ///
- /// Gets the display preferences id.
- ///
- ///
- /// Allow different display preferences for each collection folder.
- ///
- /// The display prefs id.
- [JsonIgnore]
- public override Guid DisplayPreferencesId => Id;
-
- [JsonIgnore]
- public override string[] PhysicalLocations => PhysicalLocationsList;
-
- public string[] PhysicalLocationsList { get; set; }
-
- public Guid[] PhysicalFolderIds { get; set; }
-
public override bool IsSaveLocalMetadataEnabled()
{
return true;
@@ -373,8 +376,5 @@ namespace MediaBrowser.Controller.Entities
return result;
}
-
- [JsonIgnore]
- public override bool SupportsPeople => false;
}
}
diff --git a/MediaBrowser.Controller/Entities/Extensions.cs b/MediaBrowser.Controller/Entities/Extensions.cs
index d8bc0069c7..9ce8eebe34 100644
--- a/MediaBrowser.Controller/Entities/Extensions.cs
+++ b/MediaBrowser.Controller/Entities/Extensions.cs
@@ -15,6 +15,8 @@ namespace MediaBrowser.Controller.Entities
///
/// Adds the trailer URL.
///
+ /// Media item.
+ /// Trailer URL.
public static void AddTrailerUrl(this BaseItem item, string url)
{
if (string.IsNullOrEmpty(url))
diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs
index 6587eefab7..d45a02cf2d 100644
--- a/MediaBrowser.Controller/Entities/Folder.cs
+++ b/MediaBrowser.Controller/Entities/Folder.cs
@@ -1,6 +1,6 @@
#nullable disable
-#pragma warning disable CS1591
+#pragma warning disable CA1002, CA1721, CA1819, CS1591
using System;
using System.Collections.Generic;
@@ -165,6 +165,8 @@ namespace MediaBrowser.Controller.Entities
}
}
+ public static ICollectionManager CollectionManager { get; set; }
+
public override bool CanDelete()
{
if (IsRoot)
@@ -258,6 +260,7 @@ namespace MediaBrowser.Controller.Entities
/// Loads our children. Validation will occur externally.
/// We want this synchronous.
///
+ /// Returns children.
protected virtual List LoadChildren()
{
// logger.LogDebug("Loading children from {0} {1} {2}", GetType().Name, Id, Path);
@@ -642,6 +645,8 @@ namespace MediaBrowser.Controller.Entities
/// Get the children of this folder from the actual file system.
///
/// IEnumerable{BaseItem}.
+ /// The directory service to use for operation.
+ /// Returns set of base items.
protected virtual IEnumerable GetNonCachedChildren(IDirectoryService directoryService)
{
var collectionType = LibraryManager.GetContentType(this);
@@ -998,8 +1003,6 @@ namespace MediaBrowser.Controller.Entities
return PostFilterAndSort(items, query, true);
}
- public static ICollectionManager CollectionManager { get; set; }
-
protected QueryResult PostFilterAndSort(IEnumerable items, InternalItemsQuery query, bool enableSorting)
{
var user = query.User;
@@ -1666,7 +1669,6 @@ namespace MediaBrowser.Controller.Entities
/// The user.
/// The date played.
/// if set to true [reset position].
- /// Task.
public override void MarkPlayed(
User user,
DateTime? datePlayed,
@@ -1708,7 +1710,6 @@ namespace MediaBrowser.Controller.Entities
/// Marks the unplayed.
///
/// The user.
- /// Task.
public override void MarkUnplayed(User user)
{
var itemsResult = GetItemList(new InternalItemsQuery
diff --git a/MediaBrowser.Controller/Entities/ICollectionFolder.cs b/MediaBrowser.Controller/Entities/ICollectionFolder.cs
index 2304570fd7..89e494ebc3 100644
--- a/MediaBrowser.Controller/Entities/ICollectionFolder.cs
+++ b/MediaBrowser.Controller/Entities/ICollectionFolder.cs
@@ -1,6 +1,6 @@
#nullable disable
-#pragma warning disable CS1591
+#pragma warning disable CA1819, CS1591
using System;
diff --git a/MediaBrowser.Controller/Entities/IHasMediaSources.cs b/MediaBrowser.Controller/Entities/IHasMediaSources.cs
index 98c3b3edf6..90d9bdd2d3 100644
--- a/MediaBrowser.Controller/Entities/IHasMediaSources.cs
+++ b/MediaBrowser.Controller/Entities/IHasMediaSources.cs
@@ -20,6 +20,8 @@ namespace MediaBrowser.Controller.Entities
///
/// Gets the media sources.
///
+ /// true to enable path substitution, false to not.
+ /// A list of media sources.
List GetMediaSources(bool enablePathSubstitution);
List GetMediaStreams();
diff --git a/MediaBrowser.Controller/Entities/IHasShares.cs b/MediaBrowser.Controller/Entities/IHasShares.cs
index bdde744a37..dca5af873f 100644
--- a/MediaBrowser.Controller/Entities/IHasShares.cs
+++ b/MediaBrowser.Controller/Entities/IHasShares.cs
@@ -1,6 +1,6 @@
#nullable disable
-#pragma warning disable CS1591
+#pragma warning disable CA1819, CS1591
namespace MediaBrowser.Controller.Entities
{
diff --git a/MediaBrowser.Controller/Entities/IHasTrailers.cs b/MediaBrowser.Controller/Entities/IHasTrailers.cs
index 2bd9ded337..f4271678d4 100644
--- a/MediaBrowser.Controller/Entities/IHasTrailers.cs
+++ b/MediaBrowser.Controller/Entities/IHasTrailers.cs
@@ -39,6 +39,7 @@ namespace MediaBrowser.Controller.Entities
///
/// Gets the trailer count.
///
+ /// Media item.
/// .
public static int GetTrailerCount(this IHasTrailers item)
=> item.LocalTrailerIds.Count + item.RemoteTrailerIds.Count;
@@ -46,6 +47,7 @@ namespace MediaBrowser.Controller.Entities
///
/// Gets the trailer ids.
///
+ /// Media item.
/// .
public static IReadOnlyList GetTrailerIds(this IHasTrailers item)
{
@@ -70,6 +72,7 @@ namespace MediaBrowser.Controller.Entities
///
/// Gets the trailers.
///
+ /// Media item.
/// .
public static IReadOnlyList GetTrailers(this IHasTrailers item)
{
diff --git a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs
index ebaf5506d6..0baa7725e1 100644
--- a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs
+++ b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs
@@ -1,4 +1,4 @@
-#pragma warning disable CS1591
+#pragma warning disable CA1044, CA1819, CA2227, CS1591
using System;
using System.Collections.Generic;
@@ -12,6 +12,55 @@ namespace MediaBrowser.Controller.Entities
{
public class InternalItemsQuery
{
+ public InternalItemsQuery()
+ {
+ AlbumArtistIds = Array.Empty();
+ AlbumIds = Array.Empty();
+ AncestorIds = Array.Empty();
+ ArtistIds = Array.Empty();
+ BlockUnratedItems = Array.Empty();
+ BoxSetLibraryFolders = Array.Empty();
+ ChannelIds = Array.Empty();
+ ContributingArtistIds = Array.Empty();
+ DtoOptions = new DtoOptions();
+ EnableTotalRecordCount = true;
+ ExcludeArtistIds = Array.Empty();
+ ExcludeInheritedTags = Array.Empty();
+ ExcludeItemIds = Array.Empty();
+ ExcludeItemTypes = Array.Empty();
+ ExcludeTags = Array.Empty();
+ GenreIds = Array.Empty();
+ Genres = Array.Empty();
+ GroupByPresentationUniqueKey = true;
+ ImageTypes = Array.Empty();
+ IncludeItemTypes = Array.Empty();
+ ItemIds = Array.Empty();
+ MediaTypes = Array.Empty();
+ MinSimilarityScore = 20;
+ OfficialRatings = Array.Empty();
+ OrderBy = Array.Empty>();
+ PersonIds = Array.Empty();
+ PersonTypes = Array.Empty();
+ PresetViews = Array.Empty();
+ SeriesStatuses = Array.Empty();
+ SourceTypes = Array.Empty();
+ StudioIds = Array.Empty();
+ Tags = Array.Empty();
+ TopParentIds = Array.Empty();
+ TrailerTypes = Array.Empty();
+ VideoTypes = Array.Empty();
+ Years = Array.Empty();
+ }
+
+ public InternalItemsQuery(User? user)
+ : this()
+ {
+ if (user != null)
+ {
+ SetUser(user);
+ }
+ }
+
public bool Recursive { get; set; }
public int? StartIndex { get; set; }
@@ -186,23 +235,6 @@ namespace MediaBrowser.Controller.Entities
public Guid[] TopParentIds { get; set; }
- public BaseItem? Parent
- {
- set
- {
- if (value == null)
- {
- ParentId = Guid.Empty;
- ParentType = null;
- }
- else
- {
- ParentId = value.Id;
- ParentType = value.GetType().Name;
- }
- }
- }
-
public string[] PresetViews { get; set; }
public TrailerType[] TrailerTypes { get; set; }
@@ -270,70 +302,21 @@ namespace MediaBrowser.Controller.Entities
///
public bool? DisplayAlbumFolders { get; set; }
- public InternalItemsQuery()
- {
- AlbumArtistIds = Array.Empty();
- AlbumIds = Array.Empty();
- AncestorIds = Array.Empty();
- ArtistIds = Array.Empty();
- BlockUnratedItems = Array.Empty();
- BoxSetLibraryFolders = Array.Empty();
- ChannelIds = Array.Empty();
- ContributingArtistIds = Array.Empty();
- DtoOptions = new DtoOptions();
- EnableTotalRecordCount = true;
- ExcludeArtistIds = Array.Empty();
- ExcludeInheritedTags = Array.Empty();
- ExcludeItemIds = Array.Empty();
- ExcludeItemTypes = Array.Empty();
- ExcludeTags = Array.Empty();
- GenreIds = Array.Empty();
- Genres = Array.Empty();
- GroupByPresentationUniqueKey = true;
- ImageTypes = Array.Empty();
- IncludeItemTypes = Array.Empty();
- ItemIds = Array.Empty();
- MediaTypes = Array.Empty();
- MinSimilarityScore = 20;
- OfficialRatings = Array.Empty();
- OrderBy = Array.Empty>();
- PersonIds = Array.Empty();
- PersonTypes = Array.Empty();
- PresetViews = Array.Empty();
- SeriesStatuses = Array.Empty();
- SourceTypes = Array.Empty();
- StudioIds = Array.Empty();
- Tags = Array.Empty();
- TopParentIds = Array.Empty();
- TrailerTypes = Array.Empty();
- VideoTypes = Array.Empty();
- Years = Array.Empty();
- }
-
- public InternalItemsQuery(User? user)
- : this()
- {
- if (user != null)
- {
- SetUser(user);
- }
- }
-
- public void SetUser(User user)
+ public BaseItem? Parent
{
- MaxParentalRating = user.MaxParentalAgeRating;
-
- if (MaxParentalRating.HasValue)
+ set
{
- string other = UnratedItem.Other.ToString();
- BlockUnratedItems = user.GetPreference(PreferenceKind.BlockUnratedItems)
- .Where(i => i != other)
- .Select(e => Enum.Parse(e, true)).ToArray();
+ if (value == null)
+ {
+ ParentId = Guid.Empty;
+ ParentType = null;
+ }
+ else
+ {
+ ParentId = value.Id;
+ ParentType = value.GetType().Name;
+ }
}
-
- ExcludeInheritedTags = user.GetPreference(PreferenceKind.BlockedTags);
-
- User = user;
}
public Dictionary? HasAnyProviderId { get; set; }
@@ -361,5 +344,22 @@ namespace MediaBrowser.Controller.Entities
public string? SearchTerm { get; set; }
public string? SeriesTimerId { get; set; }
+
+ public void SetUser(User user)
+ {
+ MaxParentalRating = user.MaxParentalAgeRating;
+
+ if (MaxParentalRating.HasValue)
+ {
+ string other = UnratedItem.Other.ToString();
+ BlockUnratedItems = user.GetPreference(PreferenceKind.BlockUnratedItems)
+ .Where(i => i != other)
+ .Select(e => Enum.Parse(e, true)).ToArray();
+ }
+
+ ExcludeInheritedTags = user.GetPreference(PreferenceKind.BlockedTags);
+
+ User = user;
+ }
}
}
diff --git a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs
index 74e84288d1..e46f99cd57 100644
--- a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs
+++ b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs
@@ -1,6 +1,6 @@
#nullable disable
-#pragma warning disable CS1591
+#pragma warning disable CA1721, CA1819, CS1591
using System;
using System.Collections.Generic;
@@ -49,6 +49,30 @@ namespace MediaBrowser.Controller.Entities.Movies
/// The display order.
public string DisplayOrder { get; set; }
+ [JsonIgnore]
+ private bool IsLegacyBoxSet
+ {
+ get
+ {
+ if (string.IsNullOrEmpty(Path))
+ {
+ return false;
+ }
+
+ if (LinkedChildren.Length > 0)
+ {
+ return false;
+ }
+
+ return !FileSystem.ContainsSubPath(ConfigurationManager.ApplicationPaths.DataPath, Path);
+ }
+ }
+
+ [JsonIgnore]
+ public override bool IsPreSorted => true;
+
+ public Guid[] LibraryFolderIds { get; set; }
+
protected override bool GetBlockUnratedValue(User user)
{
return user.GetPreferenceValues(PreferenceKind.BlockUnratedItems).Contains(UnratedItem.Movie);
@@ -83,28 +107,6 @@ namespace MediaBrowser.Controller.Entities.Movies
return new List();
}
- [JsonIgnore]
- private bool IsLegacyBoxSet
- {
- get
- {
- if (string.IsNullOrEmpty(Path))
- {
- return false;
- }
-
- if (LinkedChildren.Length > 0)
- {
- return false;
- }
-
- return !FileSystem.ContainsSubPath(ConfigurationManager.ApplicationPaths.DataPath, Path);
- }
- }
-
- [JsonIgnore]
- public override bool IsPreSorted => true;
-
public override bool IsAuthorizedToDelete(User user, List allCollectionFolders)
{
return true;
@@ -191,8 +193,6 @@ namespace MediaBrowser.Controller.Entities.Movies
return IsVisible(user);
}
- public Guid[] LibraryFolderIds { get; set; }
-
private Guid[] GetLibraryFolderIds(User user)
{
return LibraryManager.GetUserRootFolder().GetChildren(user, true)
diff --git a/MediaBrowser.Controller/Entities/Person.cs b/MediaBrowser.Controller/Entities/Person.cs
index b0ab280af4..045c1b89fd 100644
--- a/MediaBrowser.Controller/Entities/Person.cs
+++ b/MediaBrowser.Controller/Entities/Person.cs
@@ -16,6 +16,26 @@ namespace MediaBrowser.Controller.Entities
///
public class Person : BaseItem, IItemByName, IHasLookupInfo
{
+ ///
+ /// Gets the folder containing the item.
+ /// If the item is a folder, it returns the folder itself.
+ ///
+ /// The containing folder path.
+ [JsonIgnore]
+ public override string ContainingFolderPath => Path;
+
+ ///
+ /// Gets a value indicating whether to enable alpha numeric sorting.
+ ///
+ [JsonIgnore]
+ public override bool EnableAlphaNumericSorting => false;
+
+ [JsonIgnore]
+ public override bool SupportsPeople => false;
+
+ [JsonIgnore]
+ public override bool SupportsAncestors => false;
+
public override List GetUserDataKeys()
{
var list = base.GetUserDataKeys();
@@ -49,14 +69,6 @@ namespace MediaBrowser.Controller.Entities
return LibraryManager.GetItemList(query);
}
- ///
- /// Gets the folder containing the item.
- /// If the item is a folder, it returns the folder itself.
- ///
- /// The containing folder path.
- [JsonIgnore]
- public override string ContainingFolderPath => Path;
-
public override bool CanDelete()
{
return false;
@@ -67,18 +79,6 @@ namespace MediaBrowser.Controller.Entities
return true;
}
- ///
- /// Gets a value indicating whether to enable alpha numeric sorting.
- ///
- [JsonIgnore]
- public override bool EnableAlphaNumericSorting => false;
-
- [JsonIgnore]
- public override bool SupportsPeople => false;
-
- [JsonIgnore]
- public override bool SupportsAncestors => false;
-
public static string GetPath(string name)
{
return GetPath(name, true);
@@ -129,6 +129,8 @@ namespace MediaBrowser.Controller.Entities
///
/// This is called before any metadata refresh and returns true or false indicating if changes were made.
///
+ /// true to replace all metadata, false to not.
+ /// true if changes were made, false if not.
public override bool BeforeMetadataRefresh(bool replaceAllMetadata)
{
var hasChanges = base.BeforeMetadataRefresh(replaceAllMetadata);
diff --git a/MediaBrowser.Controller/Entities/PersonInfo.cs b/MediaBrowser.Controller/Entities/PersonInfo.cs
index fb79323f8f..2b689ae7e2 100644
--- a/MediaBrowser.Controller/Entities/PersonInfo.cs
+++ b/MediaBrowser.Controller/Entities/PersonInfo.cs
@@ -1,6 +1,6 @@
#nullable disable
-#pragma warning disable CS1591
+#pragma warning disable CA2227, CS1591
using System;
using System.Collections.Generic;
diff --git a/MediaBrowser.Controller/Entities/Photo.cs b/MediaBrowser.Controller/Entities/Photo.cs
index 3312a0e3e2..ba6ce189ac 100644
--- a/MediaBrowser.Controller/Entities/Photo.cs
+++ b/MediaBrowser.Controller/Entities/Photo.cs
@@ -36,6 +36,30 @@ namespace MediaBrowser.Controller.Entities
}
}
+ public string CameraMake { get; set; }
+
+ public string CameraModel { get; set; }
+
+ public string Software { get; set; }
+
+ public double? ExposureTime { get; set; }
+
+ public double? FocalLength { get; set; }
+
+ public ImageOrientation? Orientation { get; set; }
+
+ public double? Aperture { get; set; }
+
+ public double? ShutterSpeed { get; set; }
+
+ public double? Latitude { get; set; }
+
+ public double? Longitude { get; set; }
+
+ public double? Altitude { get; set; }
+
+ public int? IsoSpeedRating { get; set; }
+
public override bool CanDownload()
{
return true;
@@ -69,29 +93,5 @@ namespace MediaBrowser.Controller.Entities
return base.GetDefaultPrimaryImageAspectRatio();
}
-
- public string CameraMake { get; set; }
-
- public string CameraModel { get; set; }
-
- public string Software { get; set; }
-
- public double? ExposureTime { get; set; }
-
- public double? FocalLength { get; set; }
-
- public ImageOrientation? Orientation { get; set; }
-
- public double? Aperture { get; set; }
-
- public double? ShutterSpeed { get; set; }
-
- public double? Latitude { get; set; }
-
- public double? Longitude { get; set; }
-
- public double? Altitude { get; set; }
-
- public int? IsoSpeedRating { get; set; }
}
}
diff --git a/MediaBrowser.Controller/Entities/Studio.cs b/MediaBrowser.Controller/Entities/Studio.cs
index 888b300012..c8feb1c946 100644
--- a/MediaBrowser.Controller/Entities/Studio.cs
+++ b/MediaBrowser.Controller/Entities/Studio.cs
@@ -15,19 +15,6 @@ namespace MediaBrowser.Controller.Entities
///
public class Studio : BaseItem, IItemByName
{
- public override List GetUserDataKeys()
- {
- var list = base.GetUserDataKeys();
-
- list.Insert(0, GetType().Name + "-" + (Name ?? string.Empty).RemoveDiacritics());
- return list;
- }
-
- public override string CreatePresentationUniqueKey()
- {
- return GetUserDataKeys()[0];
- }
-
///
/// Gets the folder containing the item.
/// If the item is a folder, it returns the folder itself.
@@ -42,6 +29,22 @@ namespace MediaBrowser.Controller.Entities
[JsonIgnore]
public override bool SupportsAncestors => false;
+ [JsonIgnore]
+ public override bool SupportsPeople => false;
+
+ public override List GetUserDataKeys()
+ {
+ var list = base.GetUserDataKeys();
+
+ list.Insert(0, GetType().Name + "-" + (Name ?? string.Empty).RemoveDiacritics());
+ return list;
+ }
+
+ public override string CreatePresentationUniqueKey()
+ {
+ return GetUserDataKeys()[0];
+ }
+
public override double GetDefaultPrimaryImageAspectRatio()
{
double value = 16;
@@ -67,9 +70,6 @@ namespace MediaBrowser.Controller.Entities
return LibraryManager.GetItemList(query);
}
- [JsonIgnore]
- public override bool SupportsPeople => false;
-
public static string GetPath(string name)
{
return GetPath(name, true);
@@ -105,6 +105,8 @@ namespace MediaBrowser.Controller.Entities
///
/// This is called before any metadata refresh and returns true or false indicating if changes were made.
///
+ /// true to replace all metadata, false to not.
+ /// true if changes were made, false if not.
public override bool BeforeMetadataRefresh(bool replaceAllMetadata)
{
var hasChanges = base.BeforeMetadataRefresh(replaceAllMetadata);
diff --git a/MediaBrowser.Controller/Entities/TV/Episode.cs b/MediaBrowser.Controller/Entities/TV/Episode.cs
index 31c179bcac..27c3ff81bd 100644
--- a/MediaBrowser.Controller/Entities/TV/Episode.cs
+++ b/MediaBrowser.Controller/Entities/TV/Episode.cs
@@ -49,12 +49,6 @@ namespace MediaBrowser.Controller.Entities.TV
/// The index number.
public int? IndexNumberEnd { get; set; }
- public string FindSeriesSortName()
- {
- var series = Series;
- return series == null ? SeriesName : series.SortName;
- }
-
[JsonIgnore]
protected override bool SupportsOwnedItems => IsStacked || MediaSourceCount > 1;
@@ -76,45 +70,6 @@ namespace MediaBrowser.Controller.Entities.TV
[JsonIgnore]
protected override bool EnableDefaultVideoUserDataKeys => false;
- public override double GetDefaultPrimaryImageAspectRatio()
- {
- // hack for tv plugins
- if (SourceType == SourceType.Channel)
- {
- return 0;
- }
-
- return 16.0 / 9;
- }
-
- public override List GetUserDataKeys()
- {
- var list = base.GetUserDataKeys();
-
- var series = Series;
- if (series != null && ParentIndexNumber.HasValue && IndexNumber.HasValue)
- {
- var seriesUserDataKeys = series.GetUserDataKeys();
- var take = seriesUserDataKeys.Count;
- if (seriesUserDataKeys.Count > 1)
- {
- take--;
- }
-
- var newList = seriesUserDataKeys.GetRange(0, take);
- var suffix = ParentIndexNumber.Value.ToString("000", CultureInfo.InvariantCulture) + IndexNumber.Value.ToString("000", CultureInfo.InvariantCulture);
- for (int i = 0; i < take; i++)
- {
- newList[i] = newList[i] + suffix;
- }
-
- newList.AddRange(list);
- list = newList;
- }
-
- return list;
- }
-
///
/// Gets the Episode's Series Instance.
///
@@ -161,6 +116,74 @@ namespace MediaBrowser.Controller.Entities.TV
[JsonIgnore]
public string SeasonName { get; set; }
+ [JsonIgnore]
+ public override bool SupportsRemoteImageDownloading
+ {
+ get
+ {
+ if (IsMissingEpisode)
+ {
+ return false;
+ }
+
+ return true;
+ }
+ }
+
+ [JsonIgnore]
+ public bool IsMissingEpisode => LocationType == LocationType.Virtual;
+
+ [JsonIgnore]
+ public Guid SeasonId { get; set; }
+
+ [JsonIgnore]
+ public Guid SeriesId { get; set; }
+
+ public string FindSeriesSortName()
+ {
+ var series = Series;
+ return series == null ? SeriesName : series.SortName;
+ }
+
+ public override double GetDefaultPrimaryImageAspectRatio()
+ {
+ // hack for tv plugins
+ if (SourceType == SourceType.Channel)
+ {
+ return 0;
+ }
+
+ return 16.0 / 9;
+ }
+
+ public override List GetUserDataKeys()
+ {
+ var list = base.GetUserDataKeys();
+
+ var series = Series;
+ if (series != null && ParentIndexNumber.HasValue && IndexNumber.HasValue)
+ {
+ var seriesUserDataKeys = series.GetUserDataKeys();
+ var take = seriesUserDataKeys.Count;
+ if (seriesUserDataKeys.Count > 1)
+ {
+ take--;
+ }
+
+ var newList = seriesUserDataKeys.GetRange(0, take);
+ var suffix = ParentIndexNumber.Value.ToString("000", CultureInfo.InvariantCulture) + IndexNumber.Value.ToString("000", CultureInfo.InvariantCulture);
+ for (int i = 0; i < take; i++)
+ {
+ newList[i] = newList[i] + suffix;
+ }
+
+ newList.AddRange(list);
+ list = newList;
+ }
+
+ return list;
+ }
+
public string FindSeriesPresentationUniqueKey()
{
var series = Series;
@@ -242,29 +265,6 @@ namespace MediaBrowser.Controller.Entities.TV
return false;
}
- [JsonIgnore]
- public override bool SupportsRemoteImageDownloading
- {
- get
- {
- if (IsMissingEpisode)
- {
- return false;
- }
-
- return true;
- }
- }
-
- [JsonIgnore]
- public bool IsMissingEpisode => LocationType == LocationType.Virtual;
-
- [JsonIgnore]
- public Guid SeasonId { get; set; }
-
- [JsonIgnore]
- public Guid SeriesId { get; set; }
-
public Guid FindSeriesId()
{
var series = FindParent();
diff --git a/MediaBrowser.Controller/Entities/TV/Season.cs b/MediaBrowser.Controller/Entities/TV/Season.cs
index aa62bb35b0..926c7b0459 100644
--- a/MediaBrowser.Controller/Entities/TV/Season.cs
+++ b/MediaBrowser.Controller/Entities/TV/Season.cs
@@ -38,6 +38,50 @@ namespace MediaBrowser.Controller.Entities.TV
[JsonIgnore]
public override Guid DisplayParentId => SeriesId;
+ ///
+ /// Gets this Episode's Series Instance.
+ ///
+ /// The series.
+ [JsonIgnore]
+ public Series Series
+ {
+ get
+ {
+ var seriesId = SeriesId;
+ if (seriesId == Guid.Empty)
+ {
+ seriesId = FindSeriesId();
+ }
+
+ return seriesId == Guid.Empty ? null : (LibraryManager.GetItemById(seriesId) as Series);
+ }
+ }
+
+ [JsonIgnore]
+ public string SeriesPath
+ {
+ get
+ {
+ var series = Series;
+
+ if (series != null)
+ {
+ return series.Path;
+ }
+
+ return System.IO.Path.GetDirectoryName(Path);
+ }
+ }
+
+ [JsonIgnore]
+ public string SeriesPresentationUniqueKey { get; set; }
+
+ [JsonIgnore]
+ public string SeriesName { get; set; }
+
+ [JsonIgnore]
+ public Guid SeriesId { get; set; }
+
public override double GetDefaultPrimaryImageAspectRatio()
{
double value = 2;
@@ -80,41 +124,6 @@ namespace MediaBrowser.Controller.Entities.TV
return result;
}
- ///
- /// Gets this Episode's Series Instance.
- ///
- /// The series.
- [JsonIgnore]
- public Series Series
- {
- get
- {
- var seriesId = SeriesId;
- if (seriesId == Guid.Empty)
- {
- seriesId = FindSeriesId();
- }
-
- return seriesId == Guid.Empty ? null : (LibraryManager.GetItemById(seriesId) as Series);
- }
- }
-
- [JsonIgnore]
- public string SeriesPath
- {
- get
- {
- var series = Series;
-
- if (series != null)
- {
- return series.Path;
- }
-
- return System.IO.Path.GetDirectoryName(Path);
- }
- }
-
public override string CreatePresentationUniqueKey()
{
if (IndexNumber.HasValue)
@@ -157,6 +166,9 @@ namespace MediaBrowser.Controller.Entities.TV
///
/// Gets the episodes.
///
+ /// The user.
+ /// The options to use.
+ /// Set of episodes.
public List GetEpisodes(User user, DtoOptions options)
{
return GetEpisodes(Series, user, options);
@@ -193,15 +205,6 @@ namespace MediaBrowser.Controller.Entities.TV
return UnratedItem.Series;
}
- [JsonIgnore]
- public string SeriesPresentationUniqueKey { get; set; }
-
- [JsonIgnore]
- public string SeriesName { get; set; }
-
- [JsonIgnore]
- public Guid SeriesId { get; set; }
-
public string FindSeriesPresentationUniqueKey()
{
var series = Series;
@@ -241,6 +244,7 @@ namespace MediaBrowser.Controller.Entities.TV
///
/// This is called before any metadata refresh and returns true or false indicating if changes were made.
///
+ /// true to replace metdata, false to not.
/// true if XXXX, false otherwise.
public override bool BeforeMetadataRefresh(bool replaceAllMetadata)
{
diff --git a/MediaBrowser.Controller/Entities/TV/Series.cs b/MediaBrowser.Controller/Entities/TV/Series.cs
index 44d07b4a48..beda504b91 100644
--- a/MediaBrowser.Controller/Entities/TV/Series.cs
+++ b/MediaBrowser.Controller/Entities/TV/Series.cs
@@ -72,6 +72,9 @@ namespace MediaBrowser.Controller.Entities.TV
/// The status.
public SeriesStatus? Status { get; set; }
+ [JsonIgnore]
+ public override bool StopRefreshIfLocalMetadataFound => false;
+
public override double GetDefaultPrimaryImageAspectRatio()
{
double value = 2;
@@ -394,6 +397,10 @@ namespace MediaBrowser.Controller.Entities.TV
///
/// Filters the episodes by season.
///
+ /// The episodes.
+ /// The season.
+ /// true to include special, false to not.
+ /// The set of episodes.
public static IEnumerable FilterEpisodesBySeason(IEnumerable episodes, Season parentSeason, bool includeSpecials)
{
var seasonNumber = parentSeason.IndexNumber;
@@ -424,6 +431,10 @@ namespace MediaBrowser.Controller.Entities.TV
///
/// Filters the episodes by season.
///
+ /// The episodes.
+ /// The season.
+ /// true to include special, false to not.
+ /// The set of episodes.
public static IEnumerable FilterEpisodesBySeason(IEnumerable episodes, int seasonNumber, bool includeSpecials)
{
if (!includeSpecials || seasonNumber < 1)
@@ -499,8 +510,5 @@ namespace MediaBrowser.Controller.Entities.TV
return list;
}
-
- [JsonIgnore]
- public override bool StopRefreshIfLocalMetadataFound => false;
}
}
diff --git a/MediaBrowser.Controller/Entities/Trailer.cs b/MediaBrowser.Controller/Entities/Trailer.cs
index 732b45521b..1c558d4196 100644
--- a/MediaBrowser.Controller/Entities/Trailer.cs
+++ b/MediaBrowser.Controller/Entities/Trailer.cs
@@ -1,6 +1,6 @@
#nullable disable
-#pragma warning disable CS1591
+#pragma warning disable CA1819, CS1591
using System;
using System.Collections.Generic;
@@ -23,6 +23,9 @@ namespace MediaBrowser.Controller.Entities
TrailerTypes = Array.Empty();
}
+ [JsonIgnore]
+ public override bool StopRefreshIfLocalMetadataFound => false;
+
public TrailerType[] TrailerTypes { get; set; }
public override double GetDefaultPrimaryImageAspectRatio()
@@ -97,8 +100,5 @@ namespace MediaBrowser.Controller.Entities
return list;
}
-
- [JsonIgnore]
- public override bool StopRefreshIfLocalMetadataFound => false;
}
}
diff --git a/MediaBrowser.Controller/Entities/UserItemData.cs b/MediaBrowser.Controller/Entities/UserItemData.cs
index 6ab2116d73..50ba9ef308 100644
--- a/MediaBrowser.Controller/Entities/UserItemData.cs
+++ b/MediaBrowser.Controller/Entities/UserItemData.cs
@@ -12,6 +12,13 @@ namespace MediaBrowser.Controller.Entities
///
public class UserItemData
{
+ public const double MinLikeValue = 6.5;
+
+ ///
+ /// The _rating.
+ ///
+ private double? _rating;
+
///
/// Gets or sets the user id.
///
@@ -24,11 +31,6 @@ namespace MediaBrowser.Controller.Entities
/// The key.
public string Key { get; set; }
- ///
- /// The _rating.
- ///
- private double? _rating;
-
///
/// Gets or sets the users 0-10 rating.
///
@@ -93,8 +95,6 @@ namespace MediaBrowser.Controller.Entities
/// The index of the subtitle stream.
public int? SubtitleStreamIndex { get; set; }
- public const double MinLikeValue = 6.5;
-
///
/// Gets or sets a value indicating whether the item is liked or not.
/// This should never be serialized.
diff --git a/MediaBrowser.Controller/Entities/UserRootFolder.cs b/MediaBrowser.Controller/Entities/UserRootFolder.cs
index 2b15a52f09..f3bf4749d2 100644
--- a/MediaBrowser.Controller/Entities/UserRootFolder.cs
+++ b/MediaBrowser.Controller/Entities/UserRootFolder.cs
@@ -21,8 +21,28 @@ namespace MediaBrowser.Controller.Entities
///
public class UserRootFolder : Folder
{
- private List _childrenIds = null;
private readonly object _childIdsLock = new object();
+ private List _childrenIds = null;
+
+ [JsonIgnore]
+ public override bool SupportsInheritedParentImages => false;
+
+ [JsonIgnore]
+ public override bool SupportsPlayedStatus => false;
+
+ [JsonIgnore]
+ protected override bool SupportsShortcutChildren => true;
+
+ [JsonIgnore]
+ public override bool IsPreSorted => true;
+
+ private void ClearCache()
+ {
+ lock (_childIdsLock)
+ {
+ _childrenIds = null;
+ }
+ }
protected override List LoadChildren()
{
@@ -39,20 +59,6 @@ namespace MediaBrowser.Controller.Entities
}
}
- [JsonIgnore]
- public override bool SupportsInheritedParentImages => false;
-
- [JsonIgnore]
- public override bool SupportsPlayedStatus => false;
-
- private void ClearCache()
- {
- lock (_childIdsLock)
- {
- _childrenIds = null;
- }
- }
-
protected override QueryResult GetItemsInternal(InternalItemsQuery query)
{
if (query.Recursive)
@@ -74,12 +80,6 @@ namespace MediaBrowser.Controller.Entities
return GetChildren(user, true).Count;
}
- [JsonIgnore]
- protected override bool SupportsShortcutChildren => true;
-
- [JsonIgnore]
- public override bool IsPreSorted => true;
-
protected override IEnumerable GetEligibleChildrenForRecursiveChildren(User user)
{
var list = base.GetEligibleChildrenForRecursiveChildren(user).ToList();
diff --git a/MediaBrowser.Controller/Entities/Video.cs b/MediaBrowser.Controller/Entities/Video.cs
index d05b5df2f1..7dd95b85cf 100644
--- a/MediaBrowser.Controller/Entities/Video.cs
+++ b/MediaBrowser.Controller/Entities/Video.cs
@@ -28,6 +28,14 @@ namespace MediaBrowser.Controller.Entities
ISupportsPlaceHolders,
IHasMediaSources
{
+ public Video()
+ {
+ AdditionalParts = Array.Empty();
+ LocalAlternateVersions = Array.Empty();
+ SubtitleFiles = Array.Empty();
+ LinkedAlternateVersions = Array.Empty();
+ }
+
[JsonIgnore]
public string PrimaryVersionId { get; set; }
@@ -74,30 +82,6 @@ namespace MediaBrowser.Controller.Entities
}
}
- public void SetPrimaryVersionId(string id)
- {
- if (string.IsNullOrEmpty(id))
- {
- PrimaryVersionId = null;
- }
- else
- {
- PrimaryVersionId = id;
- }
-
- PresentationUniqueKey = CreatePresentationUniqueKey();
- }
-
- public override string CreatePresentationUniqueKey()
- {
- if (!string.IsNullOrEmpty(PrimaryVersionId))
- {
- return PrimaryVersionId;
- }
-
- return base.CreatePresentationUniqueKey();
- }
-
[JsonIgnore]
public override bool SupportsThemeMedia => true;
@@ -151,24 +135,6 @@ namespace MediaBrowser.Controller.Entities
/// The aspect ratio.
public string AspectRatio { get; set; }
- public Video()
- {
- AdditionalParts = Array.Empty();
- LocalAlternateVersions = Array.Empty();
- SubtitleFiles = Array.Empty();
- LinkedAlternateVersions = Array.Empty();
- }
-
- public override bool CanDownload()
- {
- if (VideoType == VideoType.Dvd || VideoType == VideoType.BluRay)
- {
- return false;
- }
-
- return IsFileProtocol;
- }
-
[JsonIgnore]
public override bool SupportsAddingToPlaylist => true;
@@ -196,16 +162,6 @@ namespace MediaBrowser.Controller.Entities
[JsonIgnore]
public override bool HasLocalAlternateVersions => LocalAlternateVersions.Length > 0;
- public IEnumerable GetAdditionalPartIds()
- {
- return AdditionalParts.Select(i => LibraryManager.GetNewItemId(i, typeof(Video)));
- }
-
- public IEnumerable GetLocalAlternateVersionIds()
- {
- return LocalAlternateVersions.Select(i => LibraryManager.GetNewItemId(i, typeof(Video)));
- }
-
public static ILiveTvManager LiveTvManager { get; set; }
[JsonIgnore]
@@ -222,37 +178,77 @@ namespace MediaBrowser.Controller.Entities
}
}
- protected override bool IsActiveRecording()
+ [JsonIgnore]
+ public bool IsCompleteMedia
{
- return LiveTvManager.GetActiveRecordingInfo(Path) != null;
+ get
+ {
+ if (SourceType == SourceType.Channel)
+ {
+ return !Tags.Contains("livestream", StringComparer.OrdinalIgnoreCase);
+ }
+
+ return !IsActiveRecording();
+ }
}
- public override bool CanDelete()
+ [JsonIgnore]
+ protected virtual bool EnableDefaultVideoUserDataKeys => true;
+
+ [JsonIgnore]
+ public override string ContainingFolderPath
{
- if (IsActiveRecording())
+ get
{
- return false;
- }
+ if (IsStacked)
+ {
+ return System.IO.Path.GetDirectoryName(Path);
+ }
- return base.CanDelete();
+ if (!IsPlaceHolder)
+ {
+ if (VideoType == VideoType.BluRay || VideoType == VideoType.Dvd)
+ {
+ return Path;
+ }
+ }
+
+ return base.ContainingFolderPath;
+ }
}
[JsonIgnore]
- public bool IsCompleteMedia
+ public override string FileNameWithoutExtension
{
get
{
- if (SourceType == SourceType.Channel)
+ if (IsFileProtocol)
{
- return !Tags.Contains("livestream", StringComparer.OrdinalIgnoreCase);
+ if (VideoType == VideoType.BluRay || VideoType == VideoType.Dvd)
+ {
+ return System.IO.Path.GetFileName(Path);
+ }
+
+ return System.IO.Path.GetFileNameWithoutExtension(Path);
}
- return !IsActiveRecording();
+ return null;
}
}
+ ///
+ /// Gets a value indicating whether [is3 D].
+ ///
+ /// true if [is3 D]; otherwise, false.
[JsonIgnore]
- protected virtual bool EnableDefaultVideoUserDataKeys => true;
+ public bool Is3D => Video3DFormat.HasValue;
+
+ ///
+ /// Gets the type of the media.
+ ///
+ /// The type of the media.
+ [JsonIgnore]
+ public override string MediaType => Model.Entities.MediaType.Video;
public override List GetUserDataKeys()
{
@@ -293,6 +289,65 @@ namespace MediaBrowser.Controller.Entities
return list;
}
+ public void SetPrimaryVersionId(string id)
+ {
+ if (string.IsNullOrEmpty(id))
+ {
+ PrimaryVersionId = null;
+ }
+ else
+ {
+ PrimaryVersionId = id;
+ }
+
+ PresentationUniqueKey = CreatePresentationUniqueKey();
+ }
+
+ public override string CreatePresentationUniqueKey()
+ {
+ if (!string.IsNullOrEmpty(PrimaryVersionId))
+ {
+ return PrimaryVersionId;
+ }
+
+ return base.CreatePresentationUniqueKey();
+ }
+
+ public override bool CanDownload()
+ {
+ if (VideoType == VideoType.Dvd || VideoType == VideoType.BluRay)
+ {
+ return false;
+ }
+
+ return IsFileProtocol;
+ }
+
+ protected override bool IsActiveRecording()
+ {
+ return LiveTvManager.GetActiveRecordingInfo(Path) != null;
+ }
+
+ public override bool CanDelete()
+ {
+ if (IsActiveRecording())
+ {
+ return false;
+ }
+
+ return base.CanDelete();
+ }
+
+ public IEnumerable GetAdditionalPartIds()
+ {
+ return AdditionalParts.Select(i => LibraryManager.GetNewItemId(i, typeof(Video)));
+ }
+
+ public IEnumerable GetLocalAlternateVersionIds()
+ {
+ return LocalAlternateVersions.Select(i => LibraryManager.GetNewItemId(i, typeof(Video)));
+ }
+
private string GetUserDataKey(string providerId)
{
var key = providerId + "-" + ExtraType.ToString().ToLowerInvariant();
@@ -328,47 +383,6 @@ namespace MediaBrowser.Controller.Entities
.OrderBy(i => i.SortName);
}
- [JsonIgnore]
- public override string ContainingFolderPath
- {
- get
- {
- if (IsStacked)
- {
- return System.IO.Path.GetDirectoryName(Path);
- }
-
- if (!IsPlaceHolder)
- {
- if (VideoType == VideoType.BluRay || VideoType == VideoType.Dvd)
- {
- return Path;
- }
- }
-
- return base.ContainingFolderPath;
- }
- }
-
- [JsonIgnore]
- public override string FileNameWithoutExtension
- {
- get
- {
- if (IsFileProtocol)
- {
- if (VideoType == VideoType.BluRay || VideoType == VideoType.Dvd)
- {
- return System.IO.Path.GetFileName(Path);
- }
-
- return System.IO.Path.GetFileNameWithoutExtension(Path);
- }
-
- return null;
- }
- }
-
internal override ItemUpdateType UpdateFromResolvedItem(BaseItem newItem)
{
var updateType = base.UpdateFromResolvedItem(newItem);
@@ -397,20 +411,6 @@ namespace MediaBrowser.Controller.Entities
return updateType;
}
- ///
- /// Gets a value indicating whether [is3 D].
- ///
- /// true if [is3 D]; otherwise, false.
- [JsonIgnore]
- public bool Is3D => Video3DFormat.HasValue;
-
- ///
- /// Gets the type of the media.
- ///
- /// The type of the media.
- [JsonIgnore]
- public override string MediaType => Model.Entities.MediaType.Video;
-
protected override async Task RefreshedOwnedItems(MetadataRefreshOptions options, List fileSystemChildren, CancellationToken cancellationToken)
{
var hasChanges = await base.RefreshedOwnedItems(options, fileSystemChildren, cancellationToken).ConfigureAwait(false);
diff --git a/MediaBrowser.Controller/Entities/Year.cs b/MediaBrowser.Controller/Entities/Year.cs
index f268bc939e..0853200dd1 100644
--- a/MediaBrowser.Controller/Entities/Year.cs
+++ b/MediaBrowser.Controller/Entities/Year.cs
@@ -15,13 +15,11 @@ namespace MediaBrowser.Controller.Entities
///
public class Year : BaseItem, IItemByName
{
- public override List GetUserDataKeys()
- {
- var list = base.GetUserDataKeys();
+ [JsonIgnore]
+ public override bool SupportsAncestors => false;
- list.Insert(0, "Year-" + Name);
- return list;
- }
+ [JsonIgnore]
+ public override bool SupportsPeople => false;
///
/// Gets the folder containing the item.
@@ -31,6 +29,19 @@ namespace MediaBrowser.Controller.Entities
[JsonIgnore]
public override string ContainingFolderPath => Path;
+ public override bool CanDelete()
+ {
+ return false;
+ }
+
+ public override List GetUserDataKeys()
+ {
+ var list = base.GetUserDataKeys();
+
+ list.Insert(0, "Year-" + Name);
+ return list;
+ }
+
public override double GetDefaultPrimaryImageAspectRatio()
{
double value = 2;
@@ -39,14 +50,6 @@ namespace MediaBrowser.Controller.Entities
return value;
}
- [JsonIgnore]
- public override bool SupportsAncestors => false;
-
- public override bool CanDelete()
- {
- return false;
- }
-
public override bool IsSaveLocalMetadataEnabled()
{
return true;
@@ -76,9 +79,6 @@ namespace MediaBrowser.Controller.Entities
return null;
}
- [JsonIgnore]
- public override bool SupportsPeople => false;
-
public static string GetPath(string name)
{
return GetPath(name, true);
diff --git a/MediaBrowser.Controller/MediaEncoding/BaseEncodingJobOptions.cs b/MediaBrowser.Controller/MediaEncoding/BaseEncodingJobOptions.cs
index 745ee6bdb5..dd6f468dab 100644
--- a/MediaBrowser.Controller/MediaEncoding/BaseEncodingJobOptions.cs
+++ b/MediaBrowser.Controller/MediaEncoding/BaseEncodingJobOptions.cs
@@ -10,6 +10,15 @@ namespace MediaBrowser.Controller.MediaEncoding
{
public class BaseEncodingJobOptions
{
+ public BaseEncodingJobOptions()
+ {
+ EnableAutoStreamCopy = true;
+ AllowVideoStreamCopy = true;
+ AllowAudioStreamCopy = true;
+ Context = EncodingContext.Streaming;
+ StreamOptions = new Dictionary(StringComparer.OrdinalIgnoreCase);
+ }
+
///
/// Gets or sets the id.
///
@@ -191,14 +200,5 @@ namespace MediaBrowser.Controller.MediaEncoding
return null;
}
-
- public BaseEncodingJobOptions()
- {
- EnableAutoStreamCopy = true;
- AllowVideoStreamCopy = true;
- AllowAudioStreamCopy = true;
- Context = EncodingContext.Streaming;
- StreamOptions = new Dictionary(StringComparer.OrdinalIgnoreCase);
- }
}
}
\ No newline at end of file
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
index 257cd5df6d..141bb91c57 100644
--- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
+++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
@@ -7,7 +7,6 @@ using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
-using System.Runtime.InteropServices;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
@@ -16,9 +15,7 @@ using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.IO;
using MediaBrowser.Model.MediaInfo;
-using Microsoft.Extensions.Configuration;
namespace MediaBrowser.Controller.MediaEncoding
{
@@ -161,6 +158,9 @@ namespace MediaBrowser.Controller.MediaEncoding
///
/// Gets the name of the output video codec.
///
+ /// Encording state.
+ /// Encoding options.
+ /// Encoder string.
public string GetVideoEncoder(EncodingJobInfo state, EncodingOptions encodingOptions)
{
var codec = state.OutputVideoCodec;
@@ -315,6 +315,11 @@ namespace MediaBrowser.Controller.MediaEncoding
return container;
}
+ ///
+ /// Gets decoder from a codec.
+ ///
+ /// Codec to use.
+ /// Decoder string.
public string GetDecoderFromCodec(string codec)
{
// For these need to find out the ffmpeg names
@@ -344,6 +349,8 @@ namespace MediaBrowser.Controller.MediaEncoding
///
/// Infers the audio codec based on the url.
///
+ /// Container to use.
+ /// Codec string.
public string InferAudioCodec(string container)
{
var ext = "." + (container ?? string.Empty);
@@ -489,6 +496,9 @@ namespace MediaBrowser.Controller.MediaEncoding
///
/// Gets the input argument.
///
+ /// Encoding state.
+ /// Encoding options.
+ /// Input arguments.
public string GetInputArgument(EncodingJobInfo state, EncodingOptions encodingOptions)
{
var arg = new StringBuilder();
@@ -965,6 +975,11 @@ namespace MediaBrowser.Controller.MediaEncoding
///
/// Gets the video bitrate to specify on the command line.
///
+ /// Encoding state.
+ /// Video encoder to use.
+ /// Encoding options.
+ /// Default present to use for encoding.
+ /// Video bitrate.
public string GetVideoQualityParam(EncodingJobInfo state, string videoEncoder, EncodingOptions encodingOptions, string defaultPreset)
{
var param = string.Empty;
@@ -1966,8 +1981,12 @@ namespace MediaBrowser.Controller.MediaEncoding
}
///
- /// Gets the graphical subtitle param.
+ /// Gets the graphical subtitle parameter.
///
+ /// Encoding state.
+ /// Encoding options.
+ /// Video codec to use.
+ /// Graphical subtitle parameter.
public string GetGraphicalSubtitleParam(
EncodingJobInfo state,
EncodingOptions options,
@@ -2485,6 +2504,13 @@ namespace MediaBrowser.Controller.MediaEncoding
return string.Format(CultureInfo.InvariantCulture, filter, widthParam, heightParam);
}
+ ///
+ /// Gets the output size parameter.
+ ///
+ /// Encoding state.
+ /// Encoding options.
+ /// Video codec to use.
+ /// The output size parameter.
public string GetOutputSizeParam(
EncodingJobInfo state,
EncodingOptions options,
@@ -2495,8 +2521,13 @@ namespace MediaBrowser.Controller.MediaEncoding
}
///
+ /// Gets the output size parameter.
/// If we're going to put a fixed size on the command line, this will calculate it.
///
+ /// Encoding state.
+ /// Encoding options.
+ /// Video codec to use.
+ /// The output size parameter.
public string GetOutputSizeParamInternal(
EncodingJobInfo state,
EncodingOptions options,
@@ -2908,6 +2939,10 @@ namespace MediaBrowser.Controller.MediaEncoding
///
/// Gets the number of threads.
///
+ /// Encoding state.
+ /// Encoding options.
+ /// Video codec to use.
+ /// Number of threads.
#nullable enable
public static int GetNumberOfThreads(EncodingJobInfo? state, EncodingOptions encodingOptions, string? outputVideoCodec)
{
@@ -3551,6 +3586,11 @@ namespace MediaBrowser.Controller.MediaEncoding
///
/// Gets a hw decoder name.
///
+ /// Encoding options.
+ /// Decoder to use.
+ /// Video codec to use.
+ /// Specifies if color depth 10.
+ /// Hardware decoder name.
public string GetHwDecoderName(EncodingOptions options, string decoder, string videoCodec, bool isColorDepth10)
{
var isCodecAvailable = _mediaEncoder.SupportsDecoder(decoder) && options.HardwareDecodingCodecs.Contains(videoCodec, StringComparer.OrdinalIgnoreCase);
@@ -3569,6 +3609,11 @@ namespace MediaBrowser.Controller.MediaEncoding
///
/// Gets a hwaccel type to use as a hardware decoder(dxva/vaapi) depending on the system.
///
+ /// Encoding state.
+ /// Encoding options.
+ /// Video codec to use.
+ /// Specifies if color depth 10.
+ /// Hardware accelerator type.
public string GetHwaccelType(EncodingJobInfo state, EncodingOptions options, string videoCodec, bool isColorDepth10)
{
var isWindows = OperatingSystem.IsWindows();
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs
index bc0318ad7c..fa9f40d60d 100644
--- a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs
+++ b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs
@@ -1,6 +1,6 @@
#nullable disable
-#pragma warning disable CS1591
+#pragma warning disable CS1591, SA1401
using System;
using System.Collections.Generic;
@@ -20,6 +20,44 @@ namespace MediaBrowser.Controller.MediaEncoding
// For now, a common base class until the API and MediaEncoding classes are unified
public class EncodingJobInfo
{
+ public int? OutputAudioBitrate;
+ public int? OutputAudioChannels;
+
+ private TranscodeReason[] _transcodeReasons = null;
+
+ public EncodingJobInfo(TranscodingJobType jobType)
+ {
+ TranscodingType = jobType;
+ RemoteHttpHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase);
+ SupportedAudioCodecs = Array.Empty();
+ SupportedVideoCodecs = Array.Empty();
+ SupportedSubtitleCodecs = Array.Empty();
+ }
+
+ public TranscodeReason[] TranscodeReasons
+ {
+ get
+ {
+ if (_transcodeReasons == null)
+ {
+ if (BaseRequest.TranscodeReasons == null)
+ {
+ return Array.Empty();
+ }
+
+ _transcodeReasons = BaseRequest.TranscodeReasons
+ .Split(',')
+ .Where(i => !string.IsNullOrEmpty(i))
+ .Select(v => (TranscodeReason)Enum.Parse(typeof(TranscodeReason), v, true))
+ .ToArray();
+ }
+
+ return _transcodeReasons;
+ }
+ }
+
+ public IProgress Progress { get; set; }
+
public MediaStream VideoStream { get; set; }
public VideoType VideoType { get; set; }
@@ -58,40 +96,6 @@ namespace MediaBrowser.Controller.MediaEncoding
public string MimeType { get; set; }
- public string GetMimeType(string outputPath, bool enableStreamDefault = true)
- {
- if (!string.IsNullOrEmpty(MimeType))
- {
- return MimeType;
- }
-
- return MimeTypes.GetMimeType(outputPath, enableStreamDefault);
- }
-
- private TranscodeReason[] _transcodeReasons = null;
-
- public TranscodeReason[] TranscodeReasons
- {
- get
- {
- if (_transcodeReasons == null)
- {
- if (BaseRequest.TranscodeReasons == null)
- {
- return Array.Empty();
- }
-
- _transcodeReasons = BaseRequest.TranscodeReasons
- .Split(',')
- .Where(i => !string.IsNullOrEmpty(i))
- .Select(v => (TranscodeReason)Enum.Parse(typeof(TranscodeReason), v, true))
- .ToArray();
- }
-
- return _transcodeReasons;
- }
- }
-
public bool IgnoreInputDts => MediaSource.IgnoreDts;
public bool IgnoreInputIndex => MediaSource.IgnoreIndex;
@@ -144,196 +148,17 @@ namespace MediaBrowser.Controller.MediaEncoding
public BaseEncodingJobOptions BaseRequest { get; set; }
- public long? StartTimeTicks => BaseRequest.StartTimeTicks;
-
- public bool CopyTimestamps => BaseRequest.CopyTimestamps;
-
- public int? OutputAudioBitrate;
- public int? OutputAudioChannels;
-
- public bool DeInterlace(string videoCodec, bool forceDeinterlaceIfSourceIsInterlaced)
- {
- var videoStream = VideoStream;
- var isInputInterlaced = videoStream != null && videoStream.IsInterlaced;
-
- if (!isInputInterlaced)
- {
- return false;
- }
-
- // Support general param
- if (BaseRequest.DeInterlace)
- {
- return true;
- }
-
- if (!string.IsNullOrEmpty(videoCodec))
- {
- if (string.Equals(BaseRequest.GetOption(videoCodec, "deinterlace"), "true", StringComparison.OrdinalIgnoreCase))
- {
- return true;
- }
- }
-
- return forceDeinterlaceIfSourceIsInterlaced && isInputInterlaced;
- }
-
- public string[] GetRequestedProfiles(string codec)
- {
- if (!string.IsNullOrEmpty(BaseRequest.Profile))
- {
- return BaseRequest.Profile.Split(new[] { '|', ',' }, StringSplitOptions.RemoveEmptyEntries);
- }
-
- if (!string.IsNullOrEmpty(codec))
- {
- var profile = BaseRequest.GetOption(codec, "profile");
-
- if (!string.IsNullOrEmpty(profile))
- {
- return profile.Split(new[] { '|', ',' }, StringSplitOptions.RemoveEmptyEntries);
- }
- }
-
- return Array.Empty();
- }
-
- public string GetRequestedLevel(string codec)
- {
- if (!string.IsNullOrEmpty(BaseRequest.Level))
- {
- return BaseRequest.Level;
- }
-
- if (!string.IsNullOrEmpty(codec))
- {
- return BaseRequest.GetOption(codec, "level");
- }
-
- return null;
- }
-
- public int? GetRequestedMaxRefFrames(string codec)
- {
- if (BaseRequest.MaxRefFrames.HasValue)
- {
- return BaseRequest.MaxRefFrames;
- }
-
- if (!string.IsNullOrEmpty(codec))
- {
- var value = BaseRequest.GetOption(codec, "maxrefframes");
- if (!string.IsNullOrEmpty(value)
- && int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var result))
- {
- return result;
- }
- }
-
- return null;
- }
-
- public int? GetRequestedVideoBitDepth(string codec)
- {
- if (BaseRequest.MaxVideoBitDepth.HasValue)
- {
- return BaseRequest.MaxVideoBitDepth;
- }
-
- if (!string.IsNullOrEmpty(codec))
- {
- var value = BaseRequest.GetOption(codec, "videobitdepth");
- if (!string.IsNullOrEmpty(value)
- && int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var result))
- {
- return result;
- }
- }
-
- return null;
- }
-
- public int? GetRequestedAudioBitDepth(string codec)
- {
- if (BaseRequest.MaxAudioBitDepth.HasValue)
- {
- return BaseRequest.MaxAudioBitDepth;
- }
-
- if (!string.IsNullOrEmpty(codec))
- {
- var value = BaseRequest.GetOption(codec, "audiobitdepth");
- if (!string.IsNullOrEmpty(value)
- && int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var result))
- {
- return result;
- }
- }
-
- return null;
- }
-
- public int? GetRequestedAudioChannels(string codec)
- {
- if (!string.IsNullOrEmpty(codec))
- {
- var value = BaseRequest.GetOption(codec, "audiochannels");
- if (!string.IsNullOrEmpty(value)
- && int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var result))
- {
- return result;
- }
- }
-
- if (BaseRequest.MaxAudioChannels.HasValue)
- {
- return BaseRequest.MaxAudioChannels;
- }
-
- if (BaseRequest.AudioChannels.HasValue)
- {
- return BaseRequest.AudioChannels;
- }
-
- if (BaseRequest.TranscodingMaxAudioChannels.HasValue)
- {
- return BaseRequest.TranscodingMaxAudioChannels;
- }
-
- return null;
- }
-
public bool IsVideoRequest { get; set; }
public TranscodingJobType TranscodingType { get; set; }
- public EncodingJobInfo(TranscodingJobType jobType)
- {
- TranscodingType = jobType;
- RemoteHttpHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase);
- SupportedAudioCodecs = Array.Empty();
- SupportedVideoCodecs = Array.Empty();
- SupportedSubtitleCodecs = Array.Empty();
- }
+ public long? StartTimeTicks => BaseRequest.StartTimeTicks;
+
+ public bool CopyTimestamps => BaseRequest.CopyTimestamps;
public bool IsSegmentedLiveStream
=> TranscodingType != TranscodingJobType.Progressive && !RunTimeTicks.HasValue;
- public bool EnableBreakOnNonKeyFrames(string videoCodec)
- {
- if (TranscodingType != TranscodingJobType.Progressive)
- {
- if (IsSegmentedLiveStream)
- {
- return false;
- }
-
- return BaseRequest.BreakOnNonKeyFrames && EncodingHelper.IsCopyCodec(videoCodec);
- }
-
- return false;
- }
-
public int? TotalOutputBitrate => (OutputAudioBitrate ?? 0) + (OutputVideoBitrate ?? 0);
public int? OutputWidth
@@ -682,6 +507,21 @@ namespace MediaBrowser.Controller.MediaEncoding
public int HlsListSize => 0;
+ public bool EnableBreakOnNonKeyFrames(string videoCodec)
+ {
+ if (TranscodingType != TranscodingJobType.Progressive)
+ {
+ if (IsSegmentedLiveStream)
+ {
+ return false;
+ }
+
+ return BaseRequest.BreakOnNonKeyFrames && EncodingHelper.IsCopyCodec(videoCodec);
+ }
+
+ return false;
+ }
+
private int? GetMediaStreamCount(MediaStreamType type, int limit)
{
var count = MediaSource.GetStreamCount(type);
@@ -694,7 +534,167 @@ namespace MediaBrowser.Controller.MediaEncoding
return count;
}
- public IProgress Progress { get; set; }
+ public string GetMimeType(string outputPath, bool enableStreamDefault = true)
+ {
+ if (!string.IsNullOrEmpty(MimeType))
+ {
+ return MimeType;
+ }
+
+ return MimeTypes.GetMimeType(outputPath, enableStreamDefault);
+ }
+
+ public bool DeInterlace(string videoCodec, bool forceDeinterlaceIfSourceIsInterlaced)
+ {
+ var videoStream = VideoStream;
+ var isInputInterlaced = videoStream != null && videoStream.IsInterlaced;
+
+ if (!isInputInterlaced)
+ {
+ return false;
+ }
+
+ // Support general param
+ if (BaseRequest.DeInterlace)
+ {
+ return true;
+ }
+
+ if (!string.IsNullOrEmpty(videoCodec))
+ {
+ if (string.Equals(BaseRequest.GetOption(videoCodec, "deinterlace"), "true", StringComparison.OrdinalIgnoreCase))
+ {
+ return true;
+ }
+ }
+
+ return forceDeinterlaceIfSourceIsInterlaced && isInputInterlaced;
+ }
+
+ public string[] GetRequestedProfiles(string codec)
+ {
+ if (!string.IsNullOrEmpty(BaseRequest.Profile))
+ {
+ return BaseRequest.Profile.Split(new[] { '|', ',' }, StringSplitOptions.RemoveEmptyEntries);
+ }
+
+ if (!string.IsNullOrEmpty(codec))
+ {
+ var profile = BaseRequest.GetOption(codec, "profile");
+
+ if (!string.IsNullOrEmpty(profile))
+ {
+ return profile.Split(new[] { '|', ',' }, StringSplitOptions.RemoveEmptyEntries);
+ }
+ }
+
+ return Array.Empty();
+ }
+
+ public string GetRequestedLevel(string codec)
+ {
+ if (!string.IsNullOrEmpty(BaseRequest.Level))
+ {
+ return BaseRequest.Level;
+ }
+
+ if (!string.IsNullOrEmpty(codec))
+ {
+ return BaseRequest.GetOption(codec, "level");
+ }
+
+ return null;
+ }
+
+ public int? GetRequestedMaxRefFrames(string codec)
+ {
+ if (BaseRequest.MaxRefFrames.HasValue)
+ {
+ return BaseRequest.MaxRefFrames;
+ }
+
+ if (!string.IsNullOrEmpty(codec))
+ {
+ var value = BaseRequest.GetOption(codec, "maxrefframes");
+ if (!string.IsNullOrEmpty(value)
+ && int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var result))
+ {
+ return result;
+ }
+ }
+
+ return null;
+ }
+
+ public int? GetRequestedVideoBitDepth(string codec)
+ {
+ if (BaseRequest.MaxVideoBitDepth.HasValue)
+ {
+ return BaseRequest.MaxVideoBitDepth;
+ }
+
+ if (!string.IsNullOrEmpty(codec))
+ {
+ var value = BaseRequest.GetOption(codec, "videobitdepth");
+ if (!string.IsNullOrEmpty(value)
+ && int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var result))
+ {
+ return result;
+ }
+ }
+
+ return null;
+ }
+
+ public int? GetRequestedAudioBitDepth(string codec)
+ {
+ if (BaseRequest.MaxAudioBitDepth.HasValue)
+ {
+ return BaseRequest.MaxAudioBitDepth;
+ }
+
+ if (!string.IsNullOrEmpty(codec))
+ {
+ var value = BaseRequest.GetOption(codec, "audiobitdepth");
+ if (!string.IsNullOrEmpty(value)
+ && int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var result))
+ {
+ return result;
+ }
+ }
+
+ return null;
+ }
+
+ public int? GetRequestedAudioChannels(string codec)
+ {
+ if (!string.IsNullOrEmpty(codec))
+ {
+ var value = BaseRequest.GetOption(codec, "audiochannels");
+ if (!string.IsNullOrEmpty(value)
+ && int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var result))
+ {
+ return result;
+ }
+ }
+
+ if (BaseRequest.MaxAudioChannels.HasValue)
+ {
+ return BaseRequest.MaxAudioChannels;
+ }
+
+ if (BaseRequest.AudioChannels.HasValue)
+ {
+ return BaseRequest.AudioChannels;
+ }
+
+ if (BaseRequest.TranscodingMaxAudioChannels.HasValue)
+ {
+ return BaseRequest.TranscodingMaxAudioChannels;
+ }
+
+ return null;
+ }
public virtual void ReportTranscodingProgress(TimeSpan? transcodingPosition, float? framerate, double? percentComplete, long? bytesTranscoded, int? bitRate)
{
diff --git a/MediaBrowser.Controller/MediaEncoding/IEncodingManager.cs b/MediaBrowser.Controller/MediaEncoding/IEncodingManager.cs
index 773547872c..8ce40a58d1 100644
--- a/MediaBrowser.Controller/MediaEncoding/IEncodingManager.cs
+++ b/MediaBrowser.Controller/MediaEncoding/IEncodingManager.cs
@@ -16,6 +16,13 @@ namespace MediaBrowser.Controller.MediaEncoding
///
/// Refreshes the chapter images.
///
+ /// Video to use.
+ /// Directory service to use.
+ /// Set of chapters to refresh.
+ /// Option to extract images.
+ /// Option to save chapters.
+ /// CancellationToken to use for operation.
+ /// true if successful, false if not.
Task RefreshChapterImages(Video video, IDirectoryService directoryService, IReadOnlyList chapters, bool extractImages, bool saveChapters, CancellationToken cancellationToken);
}
}
diff --git a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs
index 76a9fd7c74..ff24560703 100644
--- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs
+++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs
@@ -71,13 +71,42 @@ namespace MediaBrowser.Controller.MediaEncoding
///
/// Extracts the video image.
///
+ /// Input file.
+ /// Video container type.
+ /// Media source information.
+ /// Media stream information.
+ /// Video 3D format.
+ /// Time offset.
+ /// CancellationToken to use for operation.
+ /// Location of video image.
Task ExtractVideoImage(string inputFile, string container, MediaSourceInfo mediaSource, MediaStream videoStream, Video3DFormat? threedFormat, TimeSpan? offset, CancellationToken cancellationToken);
+ ///
+ /// Extracts the video image.
+ ///
+ /// Input file.
+ /// Video container type.
+ /// Media source information.
+ /// Media stream information.
+ /// Index of the stream to extract from.
+ /// CancellationToken to use for operation.
+ /// Location of video image.
Task ExtractVideoImage(string inputFile, string container, MediaSourceInfo mediaSource, MediaStream imageStream, int? imageStreamIndex, CancellationToken cancellationToken);
///
/// Extracts the video images on interval.
///
+ /// Input file.
+ /// Video container type.
+ /// Media stream information.
+ /// Media source information.
+ /// Video 3D format.
+ /// Time interval.
+ /// Directory to write images.
+ /// Filename prefix to use.
+ /// Maximum width of image.
+ /// CancellationToken to use for operation.
+ /// A task.
Task ExtractVideoImagesOnInterval(
string inputFile,
string container,
@@ -122,10 +151,24 @@ namespace MediaBrowser.Controller.MediaEncoding
/// System.String.
string EscapeSubtitleFilterPath(string path);
+ ///
+ /// Sets the path to find FFmpeg.
+ ///
void SetFFmpegPath();
+ ///
+ /// Updates the encoder path.
+ ///
+ /// The path.
+ /// The type of path.
void UpdateEncoderPath(string path, string pathType);
+ ///
+ /// Gets the primary playlist of .vob files.
+ ///
+ /// The to the .vob files.
+ /// The title number to start with.
+ /// A playlist.
IEnumerable GetPrimaryPlaylistVobFiles(string path, uint? titleNumber);
}
}
diff --git a/MediaBrowser.Controller/MediaEncoding/ISubtitleEncoder.cs b/MediaBrowser.Controller/MediaEncoding/ISubtitleEncoder.cs
index 3fb2c47e13..4483cf708a 100644
--- a/MediaBrowser.Controller/MediaEncoding/ISubtitleEncoder.cs
+++ b/MediaBrowser.Controller/MediaEncoding/ISubtitleEncoder.cs
@@ -15,6 +15,14 @@ namespace MediaBrowser.Controller.MediaEncoding
///
/// Gets the subtitles.
///
+ /// Item to use.
+ /// Media source.
+ /// Subtitle stream to use.
+ /// Output format to use.
+ /// Start time.
+ /// End time.
+ /// Option to preserve original timestamps.
+ /// The cancellation token for the operation.
/// Task{Stream}.
Task GetSubtitles(
BaseItem item,
diff --git a/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs b/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs
index d8995ce74a..0813a8e7d5 100644
--- a/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs
+++ b/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs
@@ -1,6 +1,6 @@
#nullable disable
-#pragma warning disable CS1591
+#pragma warning disable CS1591, SA1306, SA1401
using System;
using System.Collections.Generic;
@@ -30,6 +30,21 @@ namespace MediaBrowser.Controller.Net
private readonly List> _activeConnections =
new List>();
+ ///
+ /// The logger.
+ ///
+ protected ILogger> Logger;
+
+ protected BasePeriodicWebSocketListener(ILogger> logger)
+ {
+ if (logger == null)
+ {
+ throw new ArgumentNullException(nameof(logger));
+ }
+
+ Logger = logger;
+ }
+
///
/// Gets the type used for the messages sent to the client.
///
@@ -54,21 +69,6 @@ namespace MediaBrowser.Controller.Net
/// Task{`1}.
protected abstract Task GetDataToSend();
- ///
- /// The logger.
- ///
- protected ILogger> Logger;
-
- protected BasePeriodicWebSocketListener(ILogger> logger)
- {
- if (logger == null)
- {
- throw new ArgumentNullException(nameof(logger));
- }
-
- Logger = logger;
- }
-
///
/// Processes the message.
///
diff --git a/MediaBrowser.Controller/Persistence/IUserDataRepository.cs b/MediaBrowser.Controller/Persistence/IUserDataRepository.cs
index 5fa5834c85..c43acfb6de 100644
--- a/MediaBrowser.Controller/Persistence/IUserDataRepository.cs
+++ b/MediaBrowser.Controller/Persistence/IUserDataRepository.cs
@@ -18,7 +18,6 @@ namespace MediaBrowser.Controller.Persistence
/// The key.
/// The user data.
/// The cancellation token.
- /// Task.
void SaveUserData(long userId, string key, UserItemData userData, CancellationToken cancellationToken);
///
diff --git a/MediaBrowser.Controller/Playlists/Playlist.cs b/MediaBrowser.Controller/Playlists/Playlist.cs
index 3eaf235152..5e671a725d 100644
--- a/MediaBrowser.Controller/Playlists/Playlist.cs
+++ b/MediaBrowser.Controller/Playlists/Playlist.cs
@@ -31,24 +31,18 @@ namespace MediaBrowser.Controller.Playlists
".zpl"
};
- public Guid OwnerUserId { get; set; }
-
- public Share[] Shares { get; set; }
-
public Playlist()
{
Shares = Array.Empty();
}
+ public Guid OwnerUserId { get; set; }
+
+ public Share[] Shares { get; set; }
+
[JsonIgnore]
public bool IsFile => IsPlaylistFile(Path);
- public static bool IsPlaylistFile(string path)
- {
- // The path will sometimes be a directory and "Path.HasExtension" returns true if the name contains a '.' (dot).
- return System.IO.Path.HasExtension(path) && !Directory.Exists(path);
- }
-
[JsonIgnore]
public override string ContainingFolderPath
{
@@ -80,6 +74,41 @@ namespace MediaBrowser.Controller.Playlists
[JsonIgnore]
public override bool SupportsCumulativeRunTimeTicks => true;
+ [JsonIgnore]
+ public override bool IsPreSorted => true;
+
+ public string PlaylistMediaType { get; set; }
+
+ [JsonIgnore]
+ public override string MediaType => PlaylistMediaType;
+
+ [JsonIgnore]
+ private bool IsSharedItem
+ {
+ get
+ {
+ var path = Path;
+
+ if (string.IsNullOrEmpty(path))
+ {
+ return false;
+ }
+
+ return FileSystem.ContainsSubPath(ConfigurationManager.ApplicationPaths.DataPath, path);
+ }
+ }
+
+ public static bool IsPlaylistFile(string path)
+ {
+ // The path will sometimes be a directory and "Path.HasExtension" returns true if the name contains a '.' (dot).
+ return System.IO.Path.HasExtension(path) && !Directory.Exists(path);
+ }
+
+ public void SetMediaType(string value)
+ {
+ PlaylistMediaType = value;
+ }
+
public override double GetDefaultPrimaryImageAspectRatio()
{
return 1;
@@ -197,35 +226,6 @@ namespace MediaBrowser.Controller.Playlists
return new[] { item };
}
- [JsonIgnore]
- public override bool IsPreSorted => true;
-
- public string PlaylistMediaType { get; set; }
-
- [JsonIgnore]
- public override string MediaType => PlaylistMediaType;
-
- public void SetMediaType(string value)
- {
- PlaylistMediaType = value;
- }
-
- [JsonIgnore]
- private bool IsSharedItem
- {
- get
- {
- var path = Path;
-
- if (string.IsNullOrEmpty(path))
- {
- return false;
- }
-
- return FileSystem.ContainsSubPath(ConfigurationManager.ApplicationPaths.DataPath, path);
- }
- }
-
public override bool IsVisible(User user)
{
if (!IsSharedItem)
diff --git a/MediaBrowser.Controller/Providers/IDirectoryService.cs b/MediaBrowser.Controller/Providers/IDirectoryService.cs
index e5138ca144..48d6276918 100644
--- a/MediaBrowser.Controller/Providers/IDirectoryService.cs
+++ b/MediaBrowser.Controller/Providers/IDirectoryService.cs
@@ -1,4 +1,4 @@
-#pragma warning disable CA1002, CS1591
+#pragma warning disable CA1002, CA1819, CS1591
using System.Collections.Generic;
using MediaBrowser.Model.IO;
diff --git a/MediaBrowser.Controller/Resolvers/IItemResolver.cs b/MediaBrowser.Controller/Resolvers/IItemResolver.cs
index 75286eadc0..b95d00aa3c 100644
--- a/MediaBrowser.Controller/Resolvers/IItemResolver.cs
+++ b/MediaBrowser.Controller/Resolvers/IItemResolver.cs
@@ -13,18 +13,18 @@ namespace MediaBrowser.Controller.Resolvers
///
public interface IItemResolver
{
+ ///
+ /// Gets the priority.
+ ///
+ /// The priority.
+ ResolverPriority Priority { get; }
+
///
/// Resolves the path.
///
/// The args.
/// BaseItem.
BaseItem ResolvePath(ItemResolveArgs args);
-
- ///
- /// Gets the priority.
- ///
- /// The priority.
- ResolverPriority Priority { get; }
}
public interface IMultiItemResolver
@@ -38,14 +38,14 @@ namespace MediaBrowser.Controller.Resolvers
public class MultiItemResolverResult
{
- public List Items { get; set; }
-
- public List ExtraFiles { get; set; }
-
public MultiItemResolverResult()
{
Items = new List();
ExtraFiles = new List();
}
+
+ public List Items { get; set; }
+
+ public List ExtraFiles { get; set; }
}
}
diff --git a/MediaBrowser.Controller/Session/ISessionController.cs b/MediaBrowser.Controller/Session/ISessionController.cs
index 6bc39d6f4e..b38ee11462 100644
--- a/MediaBrowser.Controller/Session/ISessionController.cs
+++ b/MediaBrowser.Controller/Session/ISessionController.cs
@@ -26,6 +26,12 @@ namespace MediaBrowser.Controller.Session
///
/// Sends the message.
///
+ /// The type of data.
+ /// Name of message type.
+ /// Message ID.
+ /// Data to send.
+ /// CancellationToken for operation.
+ /// A task.
Task SendMessage(SessionMessageType name, Guid messageId, T data, CancellationToken cancellationToken);
}
}
diff --git a/MediaBrowser.Controller/Session/ISessionManager.cs b/MediaBrowser.Controller/Session/ISessionManager.cs
index 4c3cf5ffe1..0ff32fb536 100644
--- a/MediaBrowser.Controller/Session/ISessionManager.cs
+++ b/MediaBrowser.Controller/Session/ISessionManager.cs
@@ -83,6 +83,7 @@ namespace MediaBrowser.Controller.Session
/// Name of the device.
/// The remote end point.
/// The user.
+ /// Session information.
SessionInfo LogSessionActivity(string appName, string appVersion, string deviceId, string deviceName, string remoteEndPoint, Jellyfin.Data.Entities.User user);
///
@@ -105,7 +106,7 @@ namespace MediaBrowser.Controller.Session
///
/// The info.
/// Task.
- ///
+ /// Throws if an argument is null.
Task OnPlaybackProgress(PlaybackProgressInfo info);
Task OnPlaybackProgress(PlaybackProgressInfo info, bool isAutomated);
@@ -115,14 +116,13 @@ namespace MediaBrowser.Controller.Session
///
/// The info.
/// Task.
- ///
+ /// Throws if an argument is null.
Task OnPlaybackStopped(PlaybackStopInfo info);
///
/// Reports the session ended.
///
/// The session identifier.
- /// Task.
void ReportSessionEnded(string sessionId);
///
@@ -170,6 +170,7 @@ namespace MediaBrowser.Controller.Session
/// The session.
/// The group update.
/// The cancellation token.
+ /// Type of group.
/// Task.
Task SendSyncPlayGroupUpdate(SessionInfo session, GroupUpdate command, CancellationToken cancellationToken);
@@ -196,8 +197,8 @@ namespace MediaBrowser.Controller.Session
///
/// Sends the message to admin sessions.
///
- ///
- /// The name.
+ /// Type of data.
+ /// Message type name.
/// The data.
/// The cancellation token.
/// Task.
@@ -206,18 +207,31 @@ namespace MediaBrowser.Controller.Session
///
/// Sends the message to user sessions.
///
- ///
+ /// Type of data.
+ /// Users to send messages to.
+ /// Message type name.
+ /// The data.
+ /// The cancellation token.
/// Task.
Task SendMessageToUserSessions(List userIds, SessionMessageType name, T data, CancellationToken cancellationToken);
+ ///
+ /// Sends the message to user sessions.
+ ///
+ /// Type of data.
+ /// Users to send messages to.
+ /// Message type name.
+ /// Data function.
+ /// The cancellation token.
+ /// Task.
Task SendMessageToUserSessions(List userIds, SessionMessageType name, Func dataFn, CancellationToken cancellationToken);
///
/// Sends the message to user device sessions.
///
- ///
+ /// Type of data.
/// The device identifier.
- /// The name.
+ /// Message type name.
/// The data.
/// The cancellation token.
/// Task.
@@ -353,6 +367,8 @@ namespace MediaBrowser.Controller.Session
///
/// Revokes the user tokens.
///
+ /// User ID.
+ /// Current access token.
void RevokeUserTokens(Guid userId, string currentAccessToken);
///
diff --git a/MediaBrowser.Controller/Subtitles/ISubtitleManager.cs b/MediaBrowser.Controller/Subtitles/ISubtitleManager.cs
index 9e661cbe42..3330dd5408 100644
--- a/MediaBrowser.Controller/Subtitles/ISubtitleManager.cs
+++ b/MediaBrowser.Controller/Subtitles/ISubtitleManager.cs
@@ -28,6 +28,11 @@ namespace MediaBrowser.Controller.Subtitles
///
/// Searches the subtitles.
///
+ /// The video.
+ /// Subtitle language.
+ /// Require perfect match.
+ /// CancellationToken to use for the operation.
+ /// Subtitles, wrapped in task.
Task SearchSubtitles(
Video video,
string language,
@@ -47,11 +52,20 @@ namespace MediaBrowser.Controller.Subtitles
///
/// Downloads the subtitles.
///
+ /// The video.
+ /// Subtitle ID.
+ /// CancellationToken to use for the operation.
+ /// A task.
Task DownloadSubtitles(Video video, string subtitleId, CancellationToken cancellationToken);
///
/// Downloads the subtitles.
///
+ /// The video.
+ /// Library options to use.
+ /// Subtitle ID.
+ /// CancellationToken to use for the operation.
+ /// A task.
Task DownloadSubtitles(Video video, LibraryOptions libraryOptions, string subtitleId, CancellationToken cancellationToken);
///
@@ -73,11 +87,16 @@ namespace MediaBrowser.Controller.Subtitles
///
/// Deletes the subtitles.
///
+ /// Media item.
+ /// Subtitle index.
+ /// A task.
Task DeleteSubtitles(BaseItem item, int index);
///
/// Gets the providers.
///
+ /// The media item.
+ /// Subtitles providers.
SubtitleProviderInfo[] GetSupportedProviders(BaseItem item);
}
}
diff --git a/MediaBrowser.Controller/Subtitles/SubtitleSearchRequest.cs b/MediaBrowser.Controller/Subtitles/SubtitleSearchRequest.cs
index 0f7c47e767..767d87d465 100644
--- a/MediaBrowser.Controller/Subtitles/SubtitleSearchRequest.cs
+++ b/MediaBrowser.Controller/Subtitles/SubtitleSearchRequest.cs
@@ -11,6 +11,15 @@ namespace MediaBrowser.Controller.Subtitles
{
public class SubtitleSearchRequest : IHasProviderIds
{
+ public SubtitleSearchRequest()
+ {
+ SearchAllProviders = true;
+ ProviderIds = new Dictionary(StringComparer.OrdinalIgnoreCase);
+
+ DisabledSubtitleFetchers = Array.Empty();
+ SubtitleFetcherOrder = Array.Empty();
+ }
+
public string Language { get; set; }
public string TwoLetterISOLanguageName { get; set; }
@@ -42,14 +51,5 @@ namespace MediaBrowser.Controller.Subtitles
public string[] DisabledSubtitleFetchers { get; set; }
public string[] SubtitleFetcherOrder { get; set; }
-
- public SubtitleSearchRequest()
- {
- SearchAllProviders = true;
- ProviderIds = new Dictionary(StringComparer.OrdinalIgnoreCase);
-
- DisabledSubtitleFetchers = Array.Empty();
- SubtitleFetcherOrder = Array.Empty();
- }
}
}
diff --git a/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs b/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs
index 32e5ac7615..ef130ee747 100644
--- a/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs
+++ b/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs
@@ -81,7 +81,7 @@ namespace MediaBrowser.LocalMetadata.Parsers
var id = info.Key + "Id";
if (!_validProviderIds.ContainsKey(id))
{
- _validProviderIds.Add(id, info.Key!);
+ _validProviderIds.Add(id, info.Key);
}
}
@@ -750,46 +750,6 @@ namespace MediaBrowser.LocalMetadata.Parsers
item.Shares = list.ToArray();
}
- private Share GetShareFromNode(XmlReader reader)
- {
- var share = new Share();
-
- reader.MoveToContent();
- reader.Read();
-
- // Loop through each element
- while (!reader.EOF && reader.ReadState == ReadState.Interactive)
- {
- if (reader.NodeType == XmlNodeType.Element)
- {
- switch (reader.Name)
- {
- case "UserId":
- {
- share.UserId = reader.ReadElementContentAsString();
- break;
- }
-
- case "CanEdit":
- {
- share.CanEdit = string.Equals(reader.ReadElementContentAsString(), true.ToString(CultureInfo.InvariantCulture), StringComparison.OrdinalIgnoreCase);
- break;
- }
-
- default:
- reader.Skip();
- break;
- }
- }
- else
- {
- reader.Read();
- }
- }
-
- return share;
- }
-
private void FetchFromCountriesNode(XmlReader reader)
{
reader.MoveToContent();
@@ -1101,7 +1061,7 @@ namespace MediaBrowser.LocalMetadata.Parsers
switch (reader.Name)
{
case "Name":
- name = reader.ReadElementContentAsString() ?? string.Empty;
+ name = reader.ReadElementContentAsString();
break;
case "Type":
@@ -1270,8 +1230,6 @@ namespace MediaBrowser.LocalMetadata.Parsers
/// IEnumerable{System.String}.
private IEnumerable SplitNames(string value)
{
- value ??= string.Empty;
-
// Only split by comma if there is no pipe in the string
// We have to be careful to not split names like Matthew, Jr.
var separator = !value.Contains('|', StringComparison.Ordinal)
diff --git a/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs b/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs
index 98ed3dcf7d..dd824206f7 100644
--- a/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs
+++ b/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs
@@ -33,16 +33,12 @@ namespace MediaBrowser.LocalMetadata.Savers
/// Instance of the interface.
/// Instance of the interface.
/// Instance of the interface.
- /// Instance of the interface.
- /// Instance of the interface.
/// Instance of the interface.
- protected BaseXmlSaver(IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, IUserManager userManager, IUserDataManager userDataManager, ILogger logger)
+ protected BaseXmlSaver(IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, ILogger logger)
{
FileSystem = fileSystem;
ConfigurationManager = configurationManager;
LibraryManager = libraryManager;
- UserManager = userManager;
- UserDataManager = userDataManager;
Logger = logger;
}
@@ -61,16 +57,6 @@ namespace MediaBrowser.LocalMetadata.Savers
///
protected ILibraryManager LibraryManager { get; private set; }
- ///
- /// Gets the user manager.
- ///
- protected IUserManager UserManager { get; private set; }
-
- ///
- /// Gets the user data manager.
- ///
- protected IUserDataManager UserDataManager { get; private set; }
-
///
/// Gets the logger.
///
@@ -334,7 +320,7 @@ namespace MediaBrowser.LocalMetadata.Savers
if (runTimeTicks.HasValue)
{
- var timespan = TimeSpan.FromTicks(runTimeTicks!.Value);
+ var timespan = TimeSpan.FromTicks(runTimeTicks.Value);
writer.WriteElementString("RunningTime", Math.Floor(timespan.TotalMinutes).ToString(_usCulture));
}
diff --git a/MediaBrowser.LocalMetadata/Savers/BoxSetXmlSaver.cs b/MediaBrowser.LocalMetadata/Savers/BoxSetXmlSaver.cs
index b08387b0c6..8a5da95bf3 100644
--- a/MediaBrowser.LocalMetadata/Savers/BoxSetXmlSaver.cs
+++ b/MediaBrowser.LocalMetadata/Savers/BoxSetXmlSaver.cs
@@ -20,11 +20,9 @@ namespace MediaBrowser.LocalMetadata.Savers
/// Instance of the interface.
/// Instance of the interface.
/// Instance of the interface.
- /// Instance of the interface.
- /// Instance of the interface.
/// Instance of the interface.
- public BoxSetXmlSaver(IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, IUserManager userManager, IUserDataManager userDataManager, ILogger logger)
- : base(fileSystem, configurationManager, libraryManager, userManager, userDataManager, logger)
+ public BoxSetXmlSaver(IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, ILogger logger)
+ : base(fileSystem, configurationManager, libraryManager, logger)
{
}
diff --git a/MediaBrowser.LocalMetadata/Savers/PlaylistXmlSaver.cs b/MediaBrowser.LocalMetadata/Savers/PlaylistXmlSaver.cs
index c2f1064238..76252bc090 100644
--- a/MediaBrowser.LocalMetadata/Savers/PlaylistXmlSaver.cs
+++ b/MediaBrowser.LocalMetadata/Savers/PlaylistXmlSaver.cs
@@ -25,11 +25,9 @@ namespace MediaBrowser.LocalMetadata.Savers
/// Instance of the interface.
/// Instance of the interface.
/// Instance of the interface.
- /// Instance of the interface.
- /// Instance of the interface.
/// Instance of the interface.
- public PlaylistXmlSaver(IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, IUserManager userManager, IUserDataManager userDataManager, ILogger logger)
- : base(fileSystem, configurationManager, libraryManager, userManager, userDataManager, logger)
+ public PlaylistXmlSaver(IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, ILogger logger)
+ : base(fileSystem, configurationManager, libraryManager, logger)
{
}
diff --git a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
index 411b7c82ba..6da9886a4b 100644
--- a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
+++ b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
@@ -26,7 +26,7 @@
-
+
diff --git a/MediaBrowser.MediaEncoding/Probing/FFProbeHelpers.cs b/MediaBrowser.MediaEncoding/Probing/FFProbeHelpers.cs
index d0a76c4caf..9196fe1397 100644
--- a/MediaBrowser.MediaEncoding/Probing/FFProbeHelpers.cs
+++ b/MediaBrowser.MediaEncoding/Probing/FFProbeHelpers.cs
@@ -63,7 +63,8 @@ namespace MediaBrowser.MediaEncoding.Probing
public static DateTime? GetDictionaryDateTime(IReadOnlyDictionary tags, string key)
{
if (tags.TryGetValue(key, out var val)
- && DateTime.TryParse(val, DateTimeFormatInfo.CurrentInfo, DateTimeStyles.AssumeUniversal, out var dateTime))
+ && (DateTime.TryParse(val, DateTimeFormatInfo.CurrentInfo, DateTimeStyles.AssumeUniversal, out var dateTime)
+ || DateTime.TryParseExact(val, "yyyy", DateTimeFormatInfo.CurrentInfo, DateTimeStyles.AssumeUniversal, out dateTime)))
{
return dateTime.ToUniversalTime();
}
diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
index c9ad3c41eb..875ee6f04c 100644
--- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
+++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
@@ -121,6 +121,7 @@ namespace MediaBrowser.MediaEncoding.Probing
// Several different forms of retail/premiere date
info.PremiereDate =
+ FFProbeHelpers.GetDictionaryDateTime(tags, "originaldate") ??
FFProbeHelpers.GetDictionaryDateTime(tags, "retaildate") ??
FFProbeHelpers.GetDictionaryDateTime(tags, "retail date") ??
FFProbeHelpers.GetDictionaryDateTime(tags, "retail_date") ??
diff --git a/MediaBrowser.Model/Globalization/ILocalizationManager.cs b/MediaBrowser.Model/Globalization/ILocalizationManager.cs
index baefeb39cf..b213e7aa00 100644
--- a/MediaBrowser.Model/Globalization/ILocalizationManager.cs
+++ b/MediaBrowser.Model/Globalization/ILocalizationManager.cs
@@ -1,4 +1,3 @@
-#nullable disable
using System.Collections.Generic;
using System.Globalization;
using MediaBrowser.Model.Entities;
@@ -56,19 +55,11 @@ namespace MediaBrowser.Model.Globalization
/// .
IEnumerable GetLocalizationOptions();
- ///
- /// Checks if the string contains a character with the specified unicode category.
- ///
- /// The string.
- /// The unicode category.
- /// Wether or not the string contains a character with the specified unicode category.
- bool HasUnicodeCategory(string value, UnicodeCategory category);
-
///
/// Returns the correct for the given language.
///
/// The language.
/// The correct for the given language.
- CultureDto FindLanguageInfo(string language);
+ CultureDto? FindLanguageInfo(string language);
}
}
diff --git a/MediaBrowser.Model/Session/HardwareEncodingType.cs b/MediaBrowser.Model/Session/HardwareEncodingType.cs
new file mode 100644
index 0000000000..0e172f35f3
--- /dev/null
+++ b/MediaBrowser.Model/Session/HardwareEncodingType.cs
@@ -0,0 +1,48 @@
+namespace MediaBrowser.Model.Session
+{
+ ///
+ /// Enum HardwareEncodingType.
+ ///
+ public enum HardwareEncodingType
+ {
+ ///
+ /// AMD AMF
+ ///
+ AMF = 0,
+
+ ///
+ /// Intel Quick Sync Video
+ ///
+ QSV = 1,
+
+ ///
+ /// NVIDIA NVENC
+ ///
+ NVENC = 2,
+
+ ///
+ /// OpenMax OMX
+ ///
+ OMX = 3,
+
+ ///
+ /// Exynos V4L2 MFC
+ ///
+ V4L2M2M = 4,
+
+ ///
+ /// MediaCodec Android
+ ///
+ MediaCodec = 5,
+
+ ///
+ /// Video Acceleration API (VAAPI)
+ ///
+ VAAPI = 6,
+
+ ///
+ /// Video ToolBox
+ ///
+ VideoToolBox = 7
+ }
+}
diff --git a/MediaBrowser.Model/Session/TranscodingInfo.cs b/MediaBrowser.Model/Session/TranscodingInfo.cs
index 064a087d5d..68ab691f88 100644
--- a/MediaBrowser.Model/Session/TranscodingInfo.cs
+++ b/MediaBrowser.Model/Session/TranscodingInfo.cs
@@ -34,6 +34,8 @@ namespace MediaBrowser.Model.Session
public int? AudioChannels { get; set; }
+ public HardwareEncodingType? HardwareAccelerationType { get; set; }
+
public TranscodeReason[] TranscodeReasons { get; set; }
}
}
diff --git a/MediaBrowser.Model/Users/UserActionType.cs b/MediaBrowser.Model/Users/UserActionType.cs
deleted file mode 100644
index dbb1513f22..0000000000
--- a/MediaBrowser.Model/Users/UserActionType.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-#pragma warning disable CS1591
-
-namespace MediaBrowser.Model.Users
-{
- public enum UserActionType
- {
- PlayedItem = 0
- }
-}
diff --git a/MediaBrowser.Providers/BoxSets/BoxSetMetadataService.cs b/MediaBrowser.Providers/BoxSets/BoxSetMetadataService.cs
index e5326da71c..88ce8d087d 100644
--- a/MediaBrowser.Providers/BoxSets/BoxSetMetadataService.cs
+++ b/MediaBrowser.Providers/BoxSets/BoxSetMetadataService.cs
@@ -59,9 +59,9 @@ namespace MediaBrowser.Providers.BoxSets
}
///
- protected override ItemUpdateType BeforeSaveInternal(BoxSet item, bool isFullRefresh, ItemUpdateType currentUpdateType)
+ protected override ItemUpdateType BeforeSaveInternal(BoxSet item, bool isFullRefresh, ItemUpdateType updateType)
{
- var updateType = base.BeforeSaveInternal(item, isFullRefresh, currentUpdateType);
+ var updatedType = base.BeforeSaveInternal(item, isFullRefresh, updateType);
var libraryFolderIds = item.GetLibraryFolderIds();
@@ -69,10 +69,10 @@ namespace MediaBrowser.Providers.BoxSets
if (itemLibraryFolderIds == null || !libraryFolderIds.SequenceEqual(itemLibraryFolderIds))
{
item.LibraryFolderIds = libraryFolderIds;
- updateType |= ItemUpdateType.MetadataImport;
+ updatedType |= ItemUpdateType.MetadataImport;
}
- return updateType;
+ return updatedType;
}
}
}
diff --git a/MediaBrowser.Providers/Manager/ItemImageProvider.cs b/MediaBrowser.Providers/Manager/ItemImageProvider.cs
index 416723d49c..607fd127b2 100644
--- a/MediaBrowser.Providers/Manager/ItemImageProvider.cs
+++ b/MediaBrowser.Providers/Manager/ItemImageProvider.cs
@@ -1,7 +1,8 @@
-#pragma warning disable CS1591
+#pragma warning disable CA1002, CS1591
using System;
using System.Collections.Generic;
+using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Net;
@@ -536,6 +537,7 @@ namespace MediaBrowser.Providers.Manager
return true;
}
}
+
// We always want to use prefetched images
return false;
}
diff --git a/MediaBrowser.Providers/Manager/MetadataService.cs b/MediaBrowser.Providers/Manager/MetadataService.cs
index 333f47f876..3a42eb4c19 100644
--- a/MediaBrowser.Providers/Manager/MetadataService.cs
+++ b/MediaBrowser.Providers/Manager/MetadataService.cs
@@ -505,6 +505,11 @@ namespace MediaBrowser.Providers.Manager
///
/// Gets the providers.
///
+ /// A media item.
+ /// The LibraryOptions to use.
+ /// The MetadataRefreshOptions to use.
+ /// Specifies first refresh mode.
+ /// Specifies refresh mode.
/// IEnumerable{`0}.
protected IEnumerable GetProviders(BaseItem item, LibraryOptions libraryOptions, MetadataRefreshOptions options, bool isFirstRefresh, bool requiresRefresh)
{
diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj
index 6174f18b2d..3d866cdc2b 100644
--- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj
+++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj
@@ -29,12 +29,10 @@
net5.0falsetrue
- false
- disable
-
-
- true
+ AllEnabledByDefault
+ ../jellyfin.ruleset
+ disable
diff --git a/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs b/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs
index 03e45fb869..12125cbb95 100644
--- a/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs
+++ b/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs
@@ -1,4 +1,4 @@
-#pragma warning disable CS1591
+#pragma warning disable CA1002, CS1591
using System;
using System.Collections.Generic;
diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs
index 12e1fbea55..1f17d8cd4c 100644
--- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs
+++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs
@@ -1,4 +1,4 @@
-#pragma warning disable CS1591
+#pragma warning disable CA1068, CS1591
using System;
using System.Collections.Generic;
diff --git a/MediaBrowser.Providers/MediaInfo/SubtitleDownloader.cs b/MediaBrowser.Providers/MediaInfo/SubtitleDownloader.cs
index 44ab5aa5b9..aa0743bd02 100644
--- a/MediaBrowser.Providers/MediaInfo/SubtitleDownloader.cs
+++ b/MediaBrowser.Providers/MediaInfo/SubtitleDownloader.cs
@@ -1,7 +1,8 @@
-#pragma warning disable CS1591
+#pragma warning disable CA1002, CS1591
using System;
using System.Collections.Generic;
+using System.Collections.ObjectModel;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
diff --git a/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs b/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs
index 3cd7ec7728..b3d0659290 100644
--- a/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs
+++ b/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs
@@ -1,7 +1,8 @@
-#pragma warning disable CS1591
+#pragma warning disable CA1002, CS1591
using System;
using System.Collections.Generic;
+using System.Collections.ObjectModel;
using System.IO;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Providers;
diff --git a/MediaBrowser.Providers/Plugins/AudioDb/AlbumImageProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumImageProvider.cs
similarity index 100%
rename from MediaBrowser.Providers/Plugins/AudioDb/AlbumImageProvider.cs
rename to MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumImageProvider.cs
diff --git a/MediaBrowser.Providers/Plugins/AudioDb/AlbumProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumProvider.cs
similarity index 98%
rename from MediaBrowser.Providers/Plugins/AudioDb/AlbumProvider.cs
rename to MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumProvider.cs
index 9539c396d9..9f2f7fc11e 100644
--- a/MediaBrowser.Providers/Plugins/AudioDb/AlbumProvider.cs
+++ b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumProvider.cs
@@ -1,4 +1,4 @@
-#pragma warning disable CS1591
+#pragma warning disable CA1002, CS1591, SA1300
using System;
using System.Collections.Generic;
@@ -9,9 +9,9 @@ using System.Net.Http;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
+using Jellyfin.Extensions.Json;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
-using Jellyfin.Extensions.Json;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities.Audio;
@@ -30,7 +30,9 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
private readonly IHttpClientFactory _httpClientFactory;
private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
+#pragma warning disable SA1401, CA2211
public static AudioDbAlbumProvider Current;
+#pragma warning restore SA1401, CA2211
public AudioDbAlbumProvider(IServerConfigurationManager config, IFileSystem fileSystem, IHttpClientFactory httpClientFactory)
{
@@ -196,6 +198,13 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
return Path.Combine(dataPath, "album.json");
}
+ ///
+ public Task GetImageResponse(string url, CancellationToken cancellationToken)
+ {
+ throw new NotImplementedException();
+ }
+
+#pragma warning disable CA1034, CA2227
public class Album
{
public string idAlbum { get; set; }
@@ -279,11 +288,5 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
{
public List album { get; set; }
}
-
- ///
- public Task GetImageResponse(string url, CancellationToken cancellationToken)
- {
- throw new NotImplementedException();
- }
}
}
diff --git a/MediaBrowser.Providers/Plugins/AudioDb/ArtistImageProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistImageProvider.cs
similarity index 100%
rename from MediaBrowser.Providers/Plugins/AudioDb/ArtistImageProvider.cs
rename to MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistImageProvider.cs
diff --git a/MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistProvider.cs
similarity index 99%
rename from MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs
rename to MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistProvider.cs
index b2f05d76de..2857c6c13a 100644
--- a/MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs
+++ b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistProvider.cs
@@ -1,4 +1,4 @@
-#pragma warning disable CS1591
+#pragma warning disable CA1034, CS1591, CA1002, SA1028, SA1300
using System;
using System.Collections.Generic;
@@ -8,9 +8,9 @@ using System.Net.Http;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
+using Jellyfin.Extensions.Json;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
-using Jellyfin.Extensions.Json;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities.Audio;
@@ -183,6 +183,12 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
return Path.Combine(dataPath, "artist.json");
}
+ ///
+ public Task GetImageResponse(string url, CancellationToken cancellationToken)
+ {
+ throw new NotImplementedException();
+ }
+
public class Artist
{
public string idArtist { get; set; }
@@ -268,15 +274,10 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
public string strLocked { get; set; }
}
+#pragma warning disable CA2227
public class RootObject
{
public List artists { get; set; }
}
-
- ///
- public Task GetImageResponse(string url, CancellationToken cancellationToken)
- {
- throw new NotImplementedException();
- }
}
}
diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/ExternalIds.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/ExternalIds.cs
deleted file mode 100644
index 5600c389c0..0000000000
--- a/MediaBrowser.Providers/Plugins/MusicBrainz/ExternalIds.cs
+++ /dev/null
@@ -1,119 +0,0 @@
-#pragma warning disable CS1591
-
-using MediaBrowser.Controller.Entities.Audio;
-using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Providers;
-using MediaBrowser.Providers.Plugins.MusicBrainz;
-
-namespace MediaBrowser.Providers.Music
-{
- public class MusicBrainzReleaseGroupExternalId : IExternalId
- {
- ///
- public string ProviderName => "MusicBrainz";
-
- ///
- public string Key => MetadataProvider.MusicBrainzReleaseGroup.ToString();
-
- ///
- public ExternalIdMediaType? Type => ExternalIdMediaType.ReleaseGroup;
-
- ///
- public string UrlFormatString => Plugin.Instance.Configuration.Server + "/release-group/{0}";
-
- ///
- public bool Supports(IHasProviderIds item) => item is Audio || item is MusicAlbum;
- }
-
- public class MusicBrainzAlbumArtistExternalId : IExternalId
- {
- ///
- public string ProviderName => "MusicBrainz";
-
- ///
- public string Key => MetadataProvider.MusicBrainzAlbumArtist.ToString();
-
- ///
- public ExternalIdMediaType? Type => ExternalIdMediaType.AlbumArtist;
-
- ///
- public string UrlFormatString => Plugin.Instance.Configuration.Server + "/artist/{0}";
-
- ///
- public bool Supports(IHasProviderIds item) => item is Audio;
- }
-
- public class MusicBrainzAlbumExternalId : IExternalId
- {
- ///
- public string ProviderName => "MusicBrainz";
-
- ///
- public string Key => MetadataProvider.MusicBrainzAlbum.ToString();
-
- ///
- public ExternalIdMediaType? Type => ExternalIdMediaType.Album;
-
- ///
- public string UrlFormatString => Plugin.Instance.Configuration.Server + "/release/{0}";
-
- ///
- public bool Supports(IHasProviderIds item) => item is Audio || item is MusicAlbum;
- }
-
- public class MusicBrainzArtistExternalId : IExternalId
- {
- ///
- public string ProviderName => "MusicBrainz";
-
- ///
- public string Key => MetadataProvider.MusicBrainzArtist.ToString();
-
- ///
- public ExternalIdMediaType? Type => ExternalIdMediaType.Artist;
-
- ///
- public string UrlFormatString => Plugin.Instance.Configuration.Server + "/artist/{0}";
-
- ///
- public bool Supports(IHasProviderIds item) => item is MusicArtist;
- }
-
- public class MusicBrainzOtherArtistExternalId : IExternalId
- {
- ///
- public string ProviderName => "MusicBrainz";
-
- ///
-
- public string Key => MetadataProvider.MusicBrainzArtist.ToString();
-
- ///
- public ExternalIdMediaType? Type => ExternalIdMediaType.OtherArtist;
-
- ///
- public string UrlFormatString => Plugin.Instance.Configuration.Server + "/artist/{0}";
-
- ///
- public bool Supports(IHasProviderIds item) => item is Audio || item is MusicAlbum;
- }
-
- public class MusicBrainzTrackId : IExternalId
- {
- ///
- public string ProviderName => "MusicBrainz";
-
- ///
- public string Key => MetadataProvider.MusicBrainzTrack.ToString();
-
- ///
- public ExternalIdMediaType? Type => ExternalIdMediaType.Track;
-
- ///
- public string UrlFormatString => Plugin.Instance.Configuration.Server + "/track/{0}";
-
- ///
- public bool Supports(IHasProviderIds item) => item is Audio;
- }
-}
diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumArtistExternalId.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumArtistExternalId.cs
new file mode 100644
index 0000000000..1b37e2a60d
--- /dev/null
+++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumArtistExternalId.cs
@@ -0,0 +1,28 @@
+#pragma warning disable CS1591
+
+using MediaBrowser.Controller.Entities.Audio;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Providers;
+using MediaBrowser.Providers.Plugins.MusicBrainz;
+
+namespace MediaBrowser.Providers.Music
+{
+ public class MusicBrainzAlbumArtistExternalId : IExternalId
+ {
+ ///
+ public string ProviderName => "MusicBrainz";
+
+ ///
+ public string Key => MetadataProvider.MusicBrainzAlbumArtist.ToString();
+
+ ///
+ public ExternalIdMediaType? Type => ExternalIdMediaType.AlbumArtist;
+
+ ///
+ public string UrlFormatString => Plugin.Instance.Configuration.Server + "/artist/{0}";
+
+ ///
+ public bool Supports(IHasProviderIds item) => item is Audio;
+ }
+}
diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumExternalId.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumExternalId.cs
new file mode 100644
index 0000000000..ef095111a8
--- /dev/null
+++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumExternalId.cs
@@ -0,0 +1,28 @@
+#pragma warning disable CS1591
+
+using MediaBrowser.Controller.Entities.Audio;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Providers;
+using MediaBrowser.Providers.Plugins.MusicBrainz;
+
+namespace MediaBrowser.Providers.Music
+{
+ public class MusicBrainzAlbumExternalId : IExternalId
+ {
+ ///
+ public string ProviderName => "MusicBrainz";
+
+ ///
+ public string Key => MetadataProvider.MusicBrainzAlbum.ToString();
+
+ ///
+ public ExternalIdMediaType? Type => ExternalIdMediaType.Album;
+
+ ///
+ public string UrlFormatString => Plugin.Instance.Configuration.Server + "/release/{0}";
+
+ ///
+ public bool Supports(IHasProviderIds item) => item is Audio || item is MusicAlbum;
+ }
+}
diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs
index 8db3c391e9..c97affdbf2 100644
--- a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs
+++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs
@@ -1,4 +1,4 @@
-#pragma warning disable CS1591
+#pragma warning disable CS1591, SA1401
using System;
using System.Collections.Generic;
@@ -23,7 +23,7 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.Providers.Music
{
- public class MusicBrainzAlbumProvider : IRemoteMetadataProvider, IHasOrder
+ public class MusicBrainzAlbumProvider : IRemoteMetadataProvider, IHasOrder, IDisposable
{
///
/// For each single MB lookup/search, this is the maximum number of
@@ -36,7 +36,7 @@ namespace MediaBrowser.Providers.Music
/// The Jellyfin user-agent is unrestricted but source IP must not exceed
/// one request per second, therefore we rate limit to avoid throttling.
/// Be prudent, use a value slightly above the minimun required.
- /// https://musicbrainz.org/doc/XML_Web_Service/Rate_Limiting
+ /// https://musicbrainz.org/doc/XML_Web_Service/Rate_Limiting.
///
private readonly long _musicBrainzQueryIntervalMs;
@@ -302,181 +302,6 @@ namespace MediaBrowser.Providers.Music
return ReleaseResult.Parse(reader).FirstOrDefault();
}
- private class ReleaseResult
- {
- public string ReleaseId;
- public string ReleaseGroupId;
- public string Title;
- public string Overview;
- public int? Year;
-
- public List> Artists = new List>();
-
- public static IEnumerable Parse(XmlReader reader)
- {
- reader.MoveToContent();
- reader.Read();
-
- // Loop through each element
- while (!reader.EOF && reader.ReadState == ReadState.Interactive)
- {
- if (reader.NodeType == XmlNodeType.Element)
- {
- switch (reader.Name)
- {
- case "release-list":
- {
- if (reader.IsEmptyElement)
- {
- reader.Read();
- continue;
- }
-
- using var subReader = reader.ReadSubtree();
- return ParseReleaseList(subReader).ToList();
- }
-
- default:
- {
- reader.Skip();
- break;
- }
- }
- }
- else
- {
- reader.Read();
- }
- }
-
- return Enumerable.Empty();
- }
-
- private static IEnumerable ParseReleaseList(XmlReader reader)
- {
- reader.MoveToContent();
- reader.Read();
-
- // Loop through each element
- while (!reader.EOF && reader.ReadState == ReadState.Interactive)
- {
- if (reader.NodeType == XmlNodeType.Element)
- {
- switch (reader.Name)
- {
- case "release":
- {
- if (reader.IsEmptyElement)
- {
- reader.Read();
- continue;
- }
-
- var releaseId = reader.GetAttribute("id");
-
- using var subReader = reader.ReadSubtree();
- var release = ParseRelease(subReader, releaseId);
- if (release != null)
- {
- yield return release;
- }
-
- break;
- }
-
- default:
- {
- reader.Skip();
- break;
- }
- }
- }
- else
- {
- reader.Read();
- }
- }
- }
-
- private static ReleaseResult ParseRelease(XmlReader reader, string releaseId)
- {
- var result = new ReleaseResult
- {
- ReleaseId = releaseId
- };
-
- reader.MoveToContent();
- reader.Read();
-
- // http://stackoverflow.com/questions/2299632/why-does-xmlreader-skip-every-other-element-if-there-is-no-whitespace-separator
-
- // Loop through each element
- while (!reader.EOF && reader.ReadState == ReadState.Interactive)
- {
- if (reader.NodeType == XmlNodeType.Element)
- {
- switch (reader.Name)
- {
- case "title":
- {
- result.Title = reader.ReadElementContentAsString();
- break;
- }
-
- case "date":
- {
- var val = reader.ReadElementContentAsString();
- if (DateTime.TryParse(val, out var date))
- {
- result.Year = date.Year;
- }
-
- break;
- }
-
- case "annotation":
- {
- result.Overview = reader.ReadElementContentAsString();
- break;
- }
-
- case "release-group":
- {
- result.ReleaseGroupId = reader.GetAttribute("id");
- reader.Skip();
- break;
- }
-
- case "artist-credit":
- {
- using var subReader = reader.ReadSubtree();
- var artist = ParseArtistCredit(subReader);
-
- if (!string.IsNullOrEmpty(artist.Item1))
- {
- result.Artists.Add(artist);
- }
-
- break;
- }
-
- default:
- {
- reader.Skip();
- break;
- }
- }
- }
- else
- {
- reader.Read();
- }
- }
-
- return result;
- }
- }
-
private static (string, string) ParseArtistCredit(XmlReader reader)
{
reader.MoveToContent();
@@ -496,6 +321,7 @@ namespace MediaBrowser.Providers.Music
using var subReader = reader.ReadSubtree();
return ParseArtistNameCredit(subReader);
}
+
default:
{
reader.Skip();
@@ -707,6 +533,9 @@ namespace MediaBrowser.Providers.Music
/// A number of retries shall be made in order to try and satisfy the request before
/// giving up and returning null.
///
+ /// Address of MusicBrainz server.
+ /// CancellationToken to use for method.
+ /// Returns response from MusicBrainz service.
internal async Task GetMusicBrainzResponse(string url, CancellationToken cancellationToken)
{
await _apiRequestLock.WaitAsync(cancellationToken).ConfigureAwait(false);
@@ -762,5 +591,195 @@ namespace MediaBrowser.Providers.Music
{
throw new NotImplementedException();
}
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ _apiRequestLock?.Dispose();
+ }
+ }
+
+ ///
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ private class ReleaseResult
+ {
+ public string ReleaseId;
+ public string ReleaseGroupId;
+ public string Title;
+ public string Overview;
+ public int? Year;
+
+ public List> Artists = new List>();
+
+ public static IEnumerable Parse(XmlReader reader)
+ {
+ reader.MoveToContent();
+ reader.Read();
+
+ // Loop through each element
+ while (!reader.EOF && reader.ReadState == ReadState.Interactive)
+ {
+ if (reader.NodeType == XmlNodeType.Element)
+ {
+ switch (reader.Name)
+ {
+ case "release-list":
+ {
+ if (reader.IsEmptyElement)
+ {
+ reader.Read();
+ continue;
+ }
+
+ using var subReader = reader.ReadSubtree();
+ return ParseReleaseList(subReader).ToList();
+ }
+
+ default:
+ {
+ reader.Skip();
+ break;
+ }
+ }
+ }
+ else
+ {
+ reader.Read();
+ }
+ }
+
+ return Enumerable.Empty();
+ }
+
+ private static IEnumerable ParseReleaseList(XmlReader reader)
+ {
+ reader.MoveToContent();
+ reader.Read();
+
+ // Loop through each element
+ while (!reader.EOF && reader.ReadState == ReadState.Interactive)
+ {
+ if (reader.NodeType == XmlNodeType.Element)
+ {
+ switch (reader.Name)
+ {
+ case "release":
+ {
+ if (reader.IsEmptyElement)
+ {
+ reader.Read();
+ continue;
+ }
+
+ var releaseId = reader.GetAttribute("id");
+
+ using var subReader = reader.ReadSubtree();
+ var release = ParseRelease(subReader, releaseId);
+ if (release != null)
+ {
+ yield return release;
+ }
+
+ break;
+ }
+
+ default:
+ {
+ reader.Skip();
+ break;
+ }
+ }
+ }
+ else
+ {
+ reader.Read();
+ }
+ }
+ }
+
+ private static ReleaseResult ParseRelease(XmlReader reader, string releaseId)
+ {
+ var result = new ReleaseResult
+ {
+ ReleaseId = releaseId
+ };
+
+ reader.MoveToContent();
+ reader.Read();
+
+ // http://stackoverflow.com/questions/2299632/why-does-xmlreader-skip-every-other-element-if-there-is-no-whitespace-separator
+
+ // Loop through each element
+ while (!reader.EOF && reader.ReadState == ReadState.Interactive)
+ {
+ if (reader.NodeType == XmlNodeType.Element)
+ {
+ switch (reader.Name)
+ {
+ case "title":
+ {
+ result.Title = reader.ReadElementContentAsString();
+ break;
+ }
+
+ case "date":
+ {
+ var val = reader.ReadElementContentAsString();
+ if (DateTime.TryParse(val, out var date))
+ {
+ result.Year = date.Year;
+ }
+
+ break;
+ }
+
+ case "annotation":
+ {
+ result.Overview = reader.ReadElementContentAsString();
+ break;
+ }
+
+ case "release-group":
+ {
+ result.ReleaseGroupId = reader.GetAttribute("id");
+ reader.Skip();
+ break;
+ }
+
+ case "artist-credit":
+ {
+ using var subReader = reader.ReadSubtree();
+ var artist = ParseArtistCredit(subReader);
+
+ if (!string.IsNullOrEmpty(artist.Item1))
+ {
+ result.Artists.Add(artist);
+ }
+
+ break;
+ }
+
+ default:
+ {
+ reader.Skip();
+ break;
+ }
+ }
+ }
+ else
+ {
+ reader.Read();
+ }
+ }
+
+ return result;
+ }
+ }
}
}
diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistExternalId.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistExternalId.cs
new file mode 100644
index 0000000000..d654e1372f
--- /dev/null
+++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistExternalId.cs
@@ -0,0 +1,28 @@
+#pragma warning disable CS1591
+
+using MediaBrowser.Controller.Entities.Audio;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Providers;
+using MediaBrowser.Providers.Plugins.MusicBrainz;
+
+namespace MediaBrowser.Providers.Music
+{
+ public class MusicBrainzArtistExternalId : IExternalId
+ {
+ ///
+ public string ProviderName => "MusicBrainz";
+
+ ///
+ public string Key => MetadataProvider.MusicBrainzArtist.ToString();
+
+ ///
+ public ExternalIdMediaType? Type => ExternalIdMediaType.Artist;
+
+ ///
+ public string UrlFormatString => Plugin.Instance.Configuration.Server + "/artist/{0}";
+
+ ///
+ public bool Supports(IHasProviderIds item) => item is MusicArtist;
+ }
+}
diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/ArtistProvider.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistProvider.cs
similarity index 100%
rename from MediaBrowser.Providers/Plugins/MusicBrainz/ArtistProvider.cs
rename to MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistProvider.cs
index 7a9379af7a..7cff5f5952 100644
--- a/MediaBrowser.Providers/Plugins/MusicBrainz/ArtistProvider.cs
+++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistProvider.cs
@@ -22,6 +22,8 @@ namespace MediaBrowser.Providers.Music
{
public class MusicBrainzArtistProvider : IRemoteMetadataProvider
{
+ public string Name => "MusicBrainz";
+
///
public async Task> GetSearchResults(ArtistInfo searchInfo, CancellationToken cancellationToken)
{
@@ -262,8 +264,6 @@ namespace MediaBrowser.Providers.Music
return WebUtility.UrlEncode(name);
}
- public string Name => "MusicBrainz";
-
public Task GetImageResponse(string url, CancellationToken cancellationToken)
{
throw new NotImplementedException();
diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzOtherArtistExternalId.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzOtherArtistExternalId.cs
new file mode 100644
index 0000000000..f889a34b5c
--- /dev/null
+++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzOtherArtistExternalId.cs
@@ -0,0 +1,28 @@
+#pragma warning disable CS1591
+
+using MediaBrowser.Controller.Entities.Audio;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Providers;
+using MediaBrowser.Providers.Plugins.MusicBrainz;
+
+namespace MediaBrowser.Providers.Music
+{
+ public class MusicBrainzOtherArtistExternalId : IExternalId
+ {
+ ///
+ public string ProviderName => "MusicBrainz";
+
+ ///
+ public string Key => MetadataProvider.MusicBrainzArtist.ToString();
+
+ ///
+ public ExternalIdMediaType? Type => ExternalIdMediaType.OtherArtist;
+
+ ///
+ public string UrlFormatString => Plugin.Instance.Configuration.Server + "/artist/{0}";
+
+ ///
+ public bool Supports(IHasProviderIds item) => item is Audio || item is MusicAlbum;
+ }
+}
diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzReleaseGroupExternalId.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzReleaseGroupExternalId.cs
new file mode 100644
index 0000000000..53783d2c0c
--- /dev/null
+++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzReleaseGroupExternalId.cs
@@ -0,0 +1,28 @@
+#pragma warning disable CS1591
+
+using MediaBrowser.Controller.Entities.Audio;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Providers;
+using MediaBrowser.Providers.Plugins.MusicBrainz;
+
+namespace MediaBrowser.Providers.Music
+{
+ public class MusicBrainzReleaseGroupExternalId : IExternalId
+ {
+ ///
+ public string ProviderName => "MusicBrainz";
+
+ ///
+ public string Key => MetadataProvider.MusicBrainzReleaseGroup.ToString();
+
+ ///
+ public ExternalIdMediaType? Type => ExternalIdMediaType.ReleaseGroup;
+
+ ///
+ public string UrlFormatString => Plugin.Instance.Configuration.Server + "/release-group/{0}";
+
+ ///
+ public bool Supports(IHasProviderIds item) => item is Audio || item is MusicAlbum;
+ }
+}
diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzTrackId.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzTrackId.cs
new file mode 100644
index 0000000000..627f8f098d
--- /dev/null
+++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzTrackId.cs
@@ -0,0 +1,28 @@
+#pragma warning disable CS1591
+
+using MediaBrowser.Controller.Entities.Audio;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Providers;
+using MediaBrowser.Providers.Plugins.MusicBrainz;
+
+namespace MediaBrowser.Providers.Music
+{
+ public class MusicBrainzTrackId : IExternalId
+ {
+ ///
+ public string ProviderName => "MusicBrainz";
+
+ ///
+ public string Key => MetadataProvider.MusicBrainzTrack.ToString();
+
+ ///
+ public ExternalIdMediaType? Type => ExternalIdMediaType.Track;
+
+ ///
+ public string UrlFormatString => Plugin.Instance.Configuration.Server + "/track/{0}";
+
+ ///
+ public bool Supports(IHasProviderIds item) => item is Audio;
+ }
+}
diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/Plugin.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/Plugin.cs
index 9eeb4750b1..69b69be428 100644
--- a/MediaBrowser.Providers/Plugins/MusicBrainz/Plugin.cs
+++ b/MediaBrowser.Providers/Plugins/MusicBrainz/Plugin.cs
@@ -11,6 +11,10 @@ namespace MediaBrowser.Providers.Plugins.MusicBrainz
{
public class Plugin : BasePlugin, IHasWebPages
{
+ public const string DefaultServer = "https://musicbrainz.org";
+
+ public const long DefaultRateLimit = 2000u;
+
public Plugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer)
: base(applicationPaths, xmlSerializer)
{
@@ -25,10 +29,6 @@ namespace MediaBrowser.Providers.Plugins.MusicBrainz
public override string Description => "Get artist and album metadata from any MusicBrainz server.";
- public const string DefaultServer = "https://musicbrainz.org";
-
- public const long DefaultRateLimit = 2000u;
-
// TODO remove when plugin removed from server.
public override string ConfigurationFileName => "Jellyfin.Plugin.MusicBrainz.xml";
diff --git a/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs
index d9b0600c3c..02e696de51 100644
--- a/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs
@@ -1,4 +1,4 @@
-#pragma warning disable CS1591
+#pragma warning disable CS1591, SA1300
using System;
using System.Collections.Generic;
diff --git a/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs
index eafcae4ac6..1ae712e9e2 100644
--- a/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs
@@ -1,4 +1,4 @@
-#pragma warning disable CS1591
+#pragma warning disable CS159, SA1300
using System;
using System.Collections.Generic;
@@ -20,6 +20,7 @@ using MediaBrowser.Model.IO;
namespace MediaBrowser.Providers.Plugins.Omdb
{
+ /// Provider for OMDB service.
public class OmdbProvider
{
private readonly IFileSystem _fileSystem;
@@ -29,6 +30,11 @@ namespace MediaBrowser.Providers.Plugins.Omdb
private readonly IApplicationHost _appHost;
private readonly JsonSerializerOptions _jsonOptions;
+ /// Initializes a new instance of the class.
+ /// HttpClientFactory to use for calls to OMDB service.
+ /// IFileSystem to use for store OMDB data.
+ /// IApplicationHost to use.
+ /// IServerConfigurationManager to use.
public OmdbProvider(IHttpClientFactory httpClientFactory, IFileSystem fileSystem, IApplicationHost appHost, IServerConfigurationManager configurationManager)
{
_httpClientFactory = httpClientFactory;
@@ -41,6 +47,14 @@ namespace MediaBrowser.Providers.Plugins.Omdb
_jsonOptions.Converters.Add(new JsonOmdbNotAvailableInt32Converter());
}
+ /// Fetches data from OMDB service.
+ /// Metadata about media item.
+ /// IMDB ID for media.
+ /// Media language.
+ /// Country of origin.
+ /// CancellationToken to use for operation.
+ /// The first generic type parameter.
+ /// Returns a Task object that can be awaited.
public async Task Fetch(MetadataResult itemResult, string imdbId, string language, string country, CancellationToken cancellationToken)
where T : BaseItem
{
@@ -105,6 +119,17 @@ namespace MediaBrowser.Providers.Plugins.Omdb
ParseAdditionalMetadata(itemResult, result);
}
+ /// Gets data about an episode.
+ /// Metadata about episode.
+ /// Episode number.
+ /// Season number.
+ /// Episode ID.
+ /// Season ID.
+ /// Episode language.
+ /// Country of origin.
+ /// CancellationToken to use for operation.
+ /// The first generic type parameter.
+ /// Whether operation was successful.
public async Task FetchEpisodeData(MetadataResult itemResult, int episodeNumber, int seasonNumber, string episodeImdbId, string seriesImdbId, string language, string country, CancellationToken cancellationToken)
where T : BaseItem
{
@@ -236,6 +261,9 @@ namespace MediaBrowser.Providers.Plugins.Omdb
return false;
}
+ /// Gets OMDB URL.
+ /// Appends query string to URL.
+ /// OMDB URL with optional query string.
public static string GetOmdbUrl(string query)
{
const string Url = "https://www.omdbapi.com?apikey=2c9d9507";
@@ -327,6 +355,12 @@ namespace MediaBrowser.Providers.Plugins.Omdb
return path;
}
+ /// Gets response from OMDB service as type T.
+ /// HttpClient instance to use for service call.
+ /// Http URL to use for service call.
+ /// CancellationToken to use for service call.
+ /// The first generic type parameter.
+ /// OMDB service response as type T.
public async Task GetDeserializedOmdbResponse(HttpClient httpClient, string url, CancellationToken cancellationToken)
{
using var response = await GetOmdbResponse(httpClient, url, cancellationToken).ConfigureAwait(false);
@@ -335,6 +369,11 @@ namespace MediaBrowser.Providers.Plugins.Omdb
return await JsonSerializer.DeserializeAsync(content, _jsonOptions, cancellationToken).ConfigureAwait(false);
}
+ /// Gets response from OMDB service.
+ /// HttpClient instance to use for service call.
+ /// Http URL to use for service call.
+ /// CancellationToken to use for service call.
+ /// OMDB service response as HttpResponseMessage.
public static Task GetOmdbResponse(HttpClient httpClient, string url, CancellationToken cancellationToken)
{
return httpClient.GetAsync(url, cancellationToken);
@@ -538,10 +577,14 @@ namespace MediaBrowser.Providers.Plugins.Omdb
}
}
+#pragma warning disable CA1034
+ /// Describes OMDB rating.
public class OmdbRating
{
+ /// Gets or sets rating source.
public string Source { get; set; }
+ /// Gets or sets rating value.
public string Value { get; set; }
}
}
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonProvider.cs
index 6db550b1d0..dac1183889 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonProvider.cs
@@ -77,14 +77,14 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People
return remoteSearchResults;
}
- public async Task> GetMetadata(PersonLookupInfo id, CancellationToken cancellationToken)
+ public async Task> GetMetadata(PersonLookupInfo info, CancellationToken cancellationToken)
{
- var personTmdbId = Convert.ToInt32(id.GetProviderId(MetadataProvider.Tmdb), CultureInfo.InvariantCulture);
+ var personTmdbId = Convert.ToInt32(info.GetProviderId(MetadataProvider.Tmdb), CultureInfo.InvariantCulture);
// We don't already have an Id, need to fetch it
if (personTmdbId <= 0)
{
- var personSearchResults = await _tmdbClientManager.SearchPersonAsync(id.Name, cancellationToken).ConfigureAwait(false);
+ var personSearchResults = await _tmdbClientManager.SearchPersonAsync(info.Name, cancellationToken).ConfigureAwait(false);
if (personSearchResults.Count > 0)
{
personTmdbId = personSearchResults[0].Id;
@@ -95,7 +95,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People
if (personTmdbId > 0)
{
- var person = await _tmdbClientManager.GetPersonAsync(personTmdbId, id.MetadataLanguage, cancellationToken).ConfigureAwait(false);
+ var person = await _tmdbClientManager.GetPersonAsync(personTmdbId, info.MetadataLanguage, cancellationToken).ConfigureAwait(false);
result.HasMetadata = true;
@@ -103,7 +103,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People
{
// Take name from incoming info, don't rename the person
// TODO: This should go in PersonMetadataService, not each person provider
- Name = id.Name,
+ Name = info.Name,
HomePageUrl = person.Homepage,
Overview = person.Biography,
PremiereDate = person.Birthday?.ToUniversalTime(),
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs b/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs
index 3980b7da0e..4de4bf4db6 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs
@@ -18,7 +18,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
///
/// Manager class for abstracting the TMDb API client library.
///
- public class TmdbClientManager
+ public class TmdbClientManager : IDisposable
{
private const int CacheDurationInHours = 1;
@@ -532,5 +532,25 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
{
return !_tmDbClient.HasConfig ? _tmDbClient.GetConfigAsync() : Task.CompletedTask;
}
+
+ ///
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+///
+ /// Releases unmanaged and - optionally - managed resources.
+ ///
+ /// true to release both managed and unmanaged resources; false to release only unmanaged resources.
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ _memoryCache?.Dispose();
+ _tmDbClient?.Dispose();
+ }
+ }
}
}
diff --git a/MediaBrowser.Providers/Studios/StudioMetadataService.cs b/MediaBrowser.Providers/Studios/StudioMetadataService.cs
index 78042b40de..091b33ce0e 100644
--- a/MediaBrowser.Providers/Studios/StudioMetadataService.cs
+++ b/MediaBrowser.Providers/Studios/StudioMetadataService.cs
@@ -17,7 +17,8 @@ namespace MediaBrowser.Providers.Studios
IServerConfigurationManager serverConfigurationManager,
ILogger logger,
IProviderManager providerManager,
- IFileSystem fileSystem, ILibraryManager libraryManager)
+ IFileSystem fileSystem,
+ ILibraryManager libraryManager)
: base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager)
{
}
diff --git a/MediaBrowser.Providers/Subtitles/SubtitleManager.cs b/MediaBrowser.Providers/Subtitles/SubtitleManager.cs
index 13f15b173c..0c791a2fee 100644
--- a/MediaBrowser.Providers/Subtitles/SubtitleManager.cs
+++ b/MediaBrowser.Providers/Subtitles/SubtitleManager.cs
@@ -252,8 +252,15 @@ namespace MediaBrowser.Providers.Subtitles
}
catch (Exception ex)
{
- (exs ??= new List()).Add(ex);
- }
+// Bug in analyzer -- https://github.com/dotnet/roslyn-analyzers/issues/5160
+#pragma warning disable CA1508
+ exs ??= new List()
+ {
+ ex
+ };
+#pragma warning restore CA1508
+
+ }
finally
{
_monitor.ReportFileSystemChangeComplete(savePath, false);
diff --git a/MediaBrowser.Providers/TV/EpisodeMetadataService.cs b/MediaBrowser.Providers/TV/EpisodeMetadataService.cs
index 170f1bdd8c..08cb6ced90 100644
--- a/MediaBrowser.Providers/TV/EpisodeMetadataService.cs
+++ b/MediaBrowser.Providers/TV/EpisodeMetadataService.cs
@@ -25,46 +25,46 @@ namespace MediaBrowser.Providers.TV
}
///
- protected override ItemUpdateType BeforeSaveInternal(Episode item, bool isFullRefresh, ItemUpdateType currentUpdateType)
+ protected override ItemUpdateType BeforeSaveInternal(Episode item, bool isFullRefresh, ItemUpdateType updateType)
{
- var updateType = base.BeforeSaveInternal(item, isFullRefresh, currentUpdateType);
+ var updatedType = base.BeforeSaveInternal(item, isFullRefresh, updateType);
var seriesName = item.FindSeriesName();
if (!string.Equals(item.SeriesName, seriesName, StringComparison.Ordinal))
{
item.SeriesName = seriesName;
- updateType |= ItemUpdateType.MetadataImport;
+ updatedType |= ItemUpdateType.MetadataImport;
}
var seasonName = item.FindSeasonName();
if (!string.Equals(item.SeasonName, seasonName, StringComparison.Ordinal))
{
item.SeasonName = seasonName;
- updateType |= ItemUpdateType.MetadataImport;
+ updatedType |= ItemUpdateType.MetadataImport;
}
var seriesId = item.FindSeriesId();
if (!item.SeriesId.Equals(seriesId))
{
item.SeriesId = seriesId;
- updateType |= ItemUpdateType.MetadataImport;
+ updatedType |= ItemUpdateType.MetadataImport;
}
var seasonId = item.FindSeasonId();
if (!item.SeasonId.Equals(seasonId))
{
item.SeasonId = seasonId;
- updateType |= ItemUpdateType.MetadataImport;
+ updatedType |= ItemUpdateType.MetadataImport;
}
var seriesPresentationUniqueKey = item.FindSeriesPresentationUniqueKey();
if (!string.Equals(item.SeriesPresentationUniqueKey, seriesPresentationUniqueKey, StringComparison.Ordinal))
{
item.SeriesPresentationUniqueKey = seriesPresentationUniqueKey;
- updateType |= ItemUpdateType.MetadataImport;
+ updatedType |= ItemUpdateType.MetadataImport;
}
- return updateType;
+ return updatedType;
}
///
diff --git a/MediaBrowser.Providers/TV/SeasonMetadataService.cs b/MediaBrowser.Providers/TV/SeasonMetadataService.cs
index 4e59f78bc4..0f22f8a9b6 100644
--- a/MediaBrowser.Providers/TV/SeasonMetadataService.cs
+++ b/MediaBrowser.Providers/TV/SeasonMetadataService.cs
@@ -31,9 +31,9 @@ namespace MediaBrowser.Providers.TV
protected override bool EnableUpdatingPremiereDateFromChildren => true;
///
- protected override ItemUpdateType BeforeSaveInternal(Season item, bool isFullRefresh, ItemUpdateType currentUpdateType)
+ protected override ItemUpdateType BeforeSaveInternal(Season item, bool isFullRefresh, ItemUpdateType updateType)
{
- var updateType = base.BeforeSaveInternal(item, isFullRefresh, currentUpdateType);
+ var updatedType = base.BeforeSaveInternal(item, isFullRefresh, updateType);
if (item.IndexNumber.HasValue && item.IndexNumber.Value == 0)
{
@@ -42,7 +42,7 @@ namespace MediaBrowser.Providers.TV
if (!string.Equals(item.Name, seasonZeroDisplayName, StringComparison.OrdinalIgnoreCase))
{
item.Name = seasonZeroDisplayName;
- updateType = updateType | ItemUpdateType.MetadataEdit;
+ updatedType = updatedType | ItemUpdateType.MetadataEdit;
}
}
@@ -50,24 +50,24 @@ namespace MediaBrowser.Providers.TV
if (!string.Equals(item.SeriesName, seriesName, StringComparison.Ordinal))
{
item.SeriesName = seriesName;
- updateType |= ItemUpdateType.MetadataImport;
+ updatedType |= ItemUpdateType.MetadataImport;
}
var seriesPresentationUniqueKey = item.FindSeriesPresentationUniqueKey();
if (!string.Equals(item.SeriesPresentationUniqueKey, seriesPresentationUniqueKey, StringComparison.Ordinal))
{
item.SeriesPresentationUniqueKey = seriesPresentationUniqueKey;
- updateType |= ItemUpdateType.MetadataImport;
+ updatedType |= ItemUpdateType.MetadataImport;
}
var seriesId = item.FindSeriesId();
if (!item.SeriesId.Equals(seriesId))
{
item.SeriesId = seriesId;
- updateType |= ItemUpdateType.MetadataImport;
+ updatedType |= ItemUpdateType.MetadataImport;
}
- return updateType;
+ return updatedType;
}
///
diff --git a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs
index 302c93f0bc..1125154ac8 100644
--- a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs
+++ b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs
@@ -148,80 +148,76 @@ namespace MediaBrowser.XbmcMetadata.Parsers
return;
}
- using (var fileStream = File.OpenRead(metadataFile))
- using (var streamReader = new StreamReader(fileStream, Encoding.UTF8))
- {
- item.ResetPeople();
-
- // Need to handle a url after the xml data
- // http://kodi.wiki/view/NFO_files/movies
+ item.ResetPeople();
- var xml = streamReader.ReadToEnd();
+ // Need to handle a url after the xml data
+ // http://kodi.wiki/view/NFO_files/movies
- // Find last closing Tag
- // Need to do this in two steps to account for random > characters after the closing xml
- var index = xml.LastIndexOf(@"", StringComparison.Ordinal);
+ var xml = File.ReadAllText(metadataFile);
- // If closing tag exists, move to end of Tag
- if (index != -1)
- {
- index = xml.IndexOf('>', index);
- }
+ // Find last closing Tag
+ // Need to do this in two steps to account for random > characters after the closing xml
+ var index = xml.LastIndexOf(@"", StringComparison.Ordinal);
- if (index != -1)
- {
- var endingXml = xml.Substring(index);
+ // If closing tag exists, move to end of Tag
+ if (index != -1)
+ {
+ index = xml.IndexOf('>', index);
+ }
- ParseProviderLinks(item.Item, endingXml);
+ if (index != -1)
+ {
+ var endingXml = xml.AsSpan().Slice(index);
- // If the file is just an imdb url, don't go any further
- if (index == 0)
- {
- return;
- }
+ ParseProviderLinks(item.Item, endingXml);
- xml = xml.Substring(0, index + 1);
- }
- else
+ // If the file is just an imdb url, don't go any further
+ if (index == 0)
{
- // If the file is just provider urls, handle that
- ParseProviderLinks(item.Item, xml);
-
return;
}
- // These are not going to be valid xml so no sense in causing the provider to fail and spamming the log with exceptions
- try
+ xml = xml.Substring(0, index + 1);
+ }
+ else
+ {
+ // If the file is just provider urls, handle that
+ ParseProviderLinks(item.Item, xml);
+
+ return;
+ }
+
+ // These are not going to be valid xml so no sense in causing the provider to fail and spamming the log with exceptions
+ try
+ {
+ using (var stringReader = new StringReader(xml))
+ using (var reader = XmlReader.Create(stringReader, settings))
{
- using (var stringReader = new StringReader(xml))
- using (var reader = XmlReader.Create(stringReader, settings))
+ reader.MoveToContent();
+ reader.Read();
+
+ // Loop through each element
+ while (!reader.EOF && reader.ReadState == ReadState.Interactive)
{
- reader.MoveToContent();
- reader.Read();
+ cancellationToken.ThrowIfCancellationRequested();
- // Loop through each element
- while (!reader.EOF && reader.ReadState == ReadState.Interactive)
+ if (reader.NodeType == XmlNodeType.Element)
{
- cancellationToken.ThrowIfCancellationRequested();
-
- if (reader.NodeType == XmlNodeType.Element)
- {
- FetchDataFromXmlNode(reader, item);
- }
- else
- {
- reader.Read();
- }
+ FetchDataFromXmlNode(reader, item);
+ }
+ else
+ {
+ reader.Read();
}
}
}
- catch (XmlException)
- {
- }
+ }
+ catch (XmlException)
+ {
}
}
- protected void ParseProviderLinks(T item, string xml)
+ protected void ParseProviderLinks(T item, ReadOnlySpan xml)
{
if (ProviderIdParsers.TryFindImdbId(xml, out var imdbId))
{
@@ -1205,7 +1201,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers
switch (reader.Name)
{
case "name":
- name = reader.ReadElementContentAsString() ?? string.Empty;
+ name = reader.ReadElementContentAsString();
break;
case "role":
diff --git a/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs
index 6b16075305..ca3ec79b78 100644
--- a/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs
+++ b/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs
@@ -40,72 +40,68 @@ namespace MediaBrowser.XbmcMetadata.Parsers
///
protected override void Fetch(MetadataResult item, string metadataFile, XmlReaderSettings settings, CancellationToken cancellationToken)
{
- using (var fileStream = File.OpenRead(metadataFile))
- using (var streamReader = new StreamReader(fileStream, Encoding.UTF8))
- {
- item.ResetPeople();
+ item.ResetPeople();
- var xmlFile = streamReader.ReadToEnd();
+ var xmlFile = File.ReadAllText(metadataFile);
- var srch = "";
- var index = xmlFile.IndexOf(srch, StringComparison.OrdinalIgnoreCase);
+ var srch = "";
+ var index = xmlFile.IndexOf(srch, StringComparison.OrdinalIgnoreCase);
- var xml = xmlFile;
+ var xml = xmlFile;
- if (index != -1)
- {
- xml = xmlFile.Substring(0, index + srch.Length);
- xmlFile = xmlFile.Substring(index + srch.Length);
- }
+ if (index != -1)
+ {
+ xml = xmlFile.Substring(0, index + srch.Length);
+ xmlFile = xmlFile.Substring(index + srch.Length);
+ }
- // These are not going to be valid xml so no sense in causing the provider to fail and spamming the log with exceptions
- try
+ // These are not going to be valid xml so no sense in causing the provider to fail and spamming the log with exceptions
+ try
+ {
+ // Extract episode details from the first episodedetails block
+ using (var stringReader = new StringReader(xml))
+ using (var reader = XmlReader.Create(stringReader, settings))
{
- // Extract episode details from the first episodedetails block
- using (var stringReader = new StringReader(xml))
- using (var reader = XmlReader.Create(stringReader, settings))
+ reader.MoveToContent();
+ reader.Read();
+
+ // Loop through each element
+ while (!reader.EOF && reader.ReadState == ReadState.Interactive)
{
- reader.MoveToContent();
- reader.Read();
+ cancellationToken.ThrowIfCancellationRequested();
- // Loop through each element
- while (!reader.EOF && reader.ReadState == ReadState.Interactive)
+ if (reader.NodeType == XmlNodeType.Element)
{
- cancellationToken.ThrowIfCancellationRequested();
-
- if (reader.NodeType == XmlNodeType.Element)
- {
- FetchDataFromXmlNode(reader, item);
- }
- else
- {
- reader.Read();
- }
+ FetchDataFromXmlNode(reader, item);
+ }
+ else
+ {
+ reader.Read();
}
}
+ }
- // Extract the last episode number from nfo
- // This is needed because XBMC metadata uses multiple episodedetails blocks instead of episodenumberend tag
- while ((index = xmlFile.IndexOf(srch, StringComparison.OrdinalIgnoreCase)) != -1)
+ // Extract the last episode number from nfo
+ // This is needed because XBMC metadata uses multiple episodedetails blocks instead of episodenumberend tag
+ while ((index = xmlFile.IndexOf(srch, StringComparison.OrdinalIgnoreCase)) != -1)
+ {
+ xml = xmlFile.Substring(0, index + srch.Length);
+ xmlFile = xmlFile.Substring(index + srch.Length);
+
+ using (var stringReader = new StringReader(xml))
+ using (var reader = XmlReader.Create(stringReader, settings))
{
- xml = xmlFile.Substring(0, index + srch.Length);
- xmlFile = xmlFile.Substring(index + srch.Length);
+ reader.MoveToContent();
- using (var stringReader = new StringReader(xml))
- using (var reader = XmlReader.Create(stringReader, settings))
+ if (reader.ReadToDescendant("episode") && int.TryParse(reader.ReadElementContentAsString(), out var num))
{
- reader.MoveToContent();
-
- if (reader.ReadToDescendant("episode") && int.TryParse(reader.ReadElementContentAsString(), out var num))
- {
- item.Item.IndexNumberEnd = Math.Max(num, item.Item.IndexNumberEnd ?? num);
- }
+ item.Item.IndexNumberEnd = Math.Max(num, item.Item.IndexNumberEnd ?? num);
}
}
}
- catch (XmlException)
- {
- }
+ }
+ catch (XmlException)
+ {
}
}
diff --git a/README.md b/README.md
index 6859a8a76f..3aef84b991 100644
--- a/README.md
+++ b/README.md
@@ -41,6 +41,9 @@
+
+
+
---
@@ -68,6 +71,8 @@ Check out our Weblate instance to h
+---
+
## Jellyfin Server
This repository contains the code for Jellyfin's backend server. Note that this is only one of many projects under the Jellyfin GitHub [organization](https://github.com/jellyfin/) on GitHub. If you want to contribute, you can start by checking out our [documentation](https://jellyfin.org/docs/general/contributing/index.html) to see what to work on.
@@ -162,3 +167,13 @@ switch `--nowebclient` or the environment variable `JELLYFIN_NOWEBCONTENT=true`.
Since this is a common scenario, there is also a separate launch profile defined for Visual Studio called `Jellyfin.Server (nowebcontent)` that can be selected from the 'Start Debugging' dropdown in the main toolbar.
**NOTE:** The setup wizard can not be run if the web client is hosted separately.
+
+---
+