Merge branch 'master' into baseitemkind-fixes

pull/5228/head
Claus Vium 3 years ago committed by GitHub
commit 663c79cba8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -213,3 +213,4 @@
- [SvenVandenbrande](https://github.com/SvenVandenbrande) - [SvenVandenbrande](https://github.com/SvenVandenbrande)
- [olsh](https://github.com/olsh) - [olsh](https://github.com/olsh)
- [lbenini](https://github.com/lbenini) - [lbenini](https://github.com/lbenini)
- [gnuyent](https://github.com/gnuyent)

@ -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 \ && npm ci --no-audit --unsafe-perm \
&& mv dist /dist && mv dist /dist
FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION} as builder FROM debian:buster-slim as app
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
# https://askubuntu.com/questions/972516/debian-frontend-environment-variable # https://askubuntu.com/questions/972516/debian-frontend-environment-variable
ARG DEBIAN_FRONTEND="noninteractive" 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) # https://github.com/NVIDIA/nvidia-docker/wiki/Installation-(Native-GPU-Support)
ENV NVIDIA_DRIVER_CAPABILITIES="compute,video,utility" 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 # https://github.com/intel/compute-runtime/releases
ARG GMMLIB_VERSION=20.3.2 ARG GMMLIB_VERSION=20.3.2
ARG IGC_VERSION=1.0.5435 ARG IGC_VERSION=1.0.5435
@ -73,6 +62,19 @@ ENV LC_ALL en_US.UTF-8
ENV LANG en_US.UTF-8 ENV LANG en_US.UTF-8
ENV LANGUAGE en_US:en 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 EXPOSE 8096
VOLUME /cache /config /media VOLUME /cache /config /media
ENTRYPOINT ["./jellyfin/jellyfin", \ ENTRYPOINT ["./jellyfin/jellyfin", \

@ -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 \ && npm ci --no-audit --unsafe-perm \
&& mv dist /dist && 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 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 # https://askubuntu.com/questions/972516/debian-frontend-environment-variable
ARG DEBIAN_FRONTEND="noninteractive" ARG DEBIAN_FRONTEND="noninteractive"
@ -61,14 +50,25 @@ RUN apt-get update \
&& chmod 777 /cache /config /media \ && 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 && 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 DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1
ENV LC_ALL en_US.UTF-8 ENV LC_ALL en_US.UTF-8
ENV LANG en_US.UTF-8 ENV LANG en_US.UTF-8
ENV LANGUAGE en_US:en 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 EXPOSE 8096
VOLUME /cache /config /media VOLUME /cache /config /media
ENTRYPOINT ["./jellyfin/jellyfin", \ ENTRYPOINT ["./jellyfin/jellyfin", \

@ -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 \ && npm ci --no-audit --unsafe-perm \
&& mv dist /dist && 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 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 # https://askubuntu.com/questions/972516/debian-frontend-environment-variable
ARG DEBIAN_FRONTEND="noninteractive" 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 \ && 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 && 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 DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1
ENV LC_ALL en_US.UTF-8 ENV LC_ALL en_US.UTF-8
ENV LANG en_US.UTF-8 ENV LANG en_US.UTF-8
ENV LANGUAGE en_US:en 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 EXPOSE 8096
VOLUME /cache /config /media VOLUME /cache /config /media
ENTRYPOINT ["./jellyfin/jellyfin", \ ENTRYPOINT ["./jellyfin/jellyfin", \

@ -288,21 +288,14 @@ namespace Emby.Dlna.ContentDirectory
/// <returns>The xml feature list.</returns> /// <returns>The xml feature list.</returns>
private static string WriteFeatureListXml() private static string WriteFeatureListXml()
{ {
// TODO: clean this up return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
var builder = new StringBuilder(); + "<Features xmlns=\"urn:schemas-upnp-org:av:avs\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"urn:schemas-upnp-org:av:avs http://www.upnp.org/schemas/av/avs.xsd\">"
+ "<Feature name=\"samsung.com_BASICVIEW\" version=\"1\">"
builder.Append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"); + "<container id=\"I\" type=\"object.item.imageItem\"/>"
builder.Append("<Features xmlns=\"urn:schemas-upnp-org:av:avs\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"urn:schemas-upnp-org:av:avs http://www.upnp.org/schemas/av/avs.xsd\">"); + "<container id=\"A\" type=\"object.item.audioItem\"/>"
+ "<container id=\"V\" type=\"object.item.videoItem\"/>"
builder.Append("<Feature name=\"samsung.com_BASICVIEW\" version=\"1\">"); + "</Feature>"
builder.Append("<container id=\"I\" type=\"object.item.imageItem\"/>"); + "</Features>";
builder.Append("<container id=\"A\" type=\"object.item.audioItem\"/>");
builder.Append("<container id=\"V\" type=\"object.item.videoItem\"/>");
builder.Append("</Feature>");
builder.Append("</Features>");
return builder.ToString();
} }
/// <summary> /// <summary>

@ -1,7 +1,4 @@
#nullable disable
#pragma warning disable CS1591 #pragma warning disable CS1591
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
@ -96,12 +93,14 @@ namespace Emby.Dlna
} }
} }
/// <inheritdoc />
public DeviceProfile GetDefaultProfile() public DeviceProfile GetDefaultProfile()
{ {
return new DefaultProfile(); return new DefaultProfile();
} }
public DeviceProfile GetProfile(DeviceIdentification deviceInfo) /// <inheritdoc />
public DeviceProfile? GetProfile(DeviceIdentification deviceInfo)
{ {
if (deviceInfo == null) if (deviceInfo == null)
{ {
@ -111,13 +110,13 @@ namespace Emby.Dlna
var profile = GetProfiles() var profile = GetProfiles()
.FirstOrDefault(i => i.Identification != null && IsMatch(deviceInfo, i.Identification)); .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 else
{ {
LogUnmatchedProfile(deviceInfo); _logger.LogDebug("Found matching device profile: {ProfileName}", profile.Name);
} }
return profile; return profile;
@ -187,7 +186,8 @@ namespace Emby.Dlna
} }
} }
public DeviceProfile GetProfile(IHeaderDictionary headers) /// <inheritdoc />
public DeviceProfile? GetProfile(IHeaderDictionary headers)
{ {
if (headers == null) if (headers == null)
{ {
@ -195,15 +195,13 @@ namespace Emby.Dlna
} }
var profile = GetProfiles().FirstOrDefault(i => i.Identification != null && IsMatch(headers, i.Identification)); 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 else
{ {
var headerString = string.Join(", ", headers.Select(i => string.Format(CultureInfo.InvariantCulture, "{0}={1}", i.Key, i.Value))); _logger.LogDebug("Found matching device profile: {0}", profile.Name);
_logger.LogDebug("No matching device profile found. {0}", headerString);
} }
return profile; return profile;
@ -253,19 +251,19 @@ namespace Emby.Dlna
return xmlFies return xmlFies
.Select(i => ParseProfileFile(i, type)) .Select(i => ParseProfileFile(i, type))
.Where(i => i != null) .Where(i => i != null)
.ToList(); .ToList()!; // We just filtered out all the nulls
} }
catch (IOException) catch (IOException)
{ {
return new List<DeviceProfile>(); return Array.Empty<DeviceProfile>();
} }
} }
private DeviceProfile ParseProfileFile(string path, DeviceProfileType type) private DeviceProfile? ParseProfileFile(string path, DeviceProfileType type)
{ {
lock (_profiles) lock (_profiles)
{ {
if (_profiles.TryGetValue(path, out Tuple<InternalProfileInfo, DeviceProfile> profileTuple)) if (_profiles.TryGetValue(path, out Tuple<InternalProfileInfo, DeviceProfile>? profileTuple))
{ {
return profileTuple.Item2; return profileTuple.Item2;
} }
@ -293,7 +291,8 @@ namespace Emby.Dlna
} }
} }
public DeviceProfile GetProfile(string id) /// <inheritdoc />
public DeviceProfile? GetProfile(string id)
{ {
if (string.IsNullOrEmpty(id)) if (string.IsNullOrEmpty(id))
{ {
@ -322,6 +321,7 @@ namespace Emby.Dlna
} }
} }
/// <inheritdoc />
public IEnumerable<DeviceProfileInfo> GetProfileInfos() public IEnumerable<DeviceProfileInfo> GetProfileInfos()
{ {
return GetProfileInfosInternal().Select(i => i.Info); return GetProfileInfosInternal().Select(i => i.Info);
@ -329,17 +329,14 @@ namespace Emby.Dlna
private InternalProfileInfo GetInternalProfileInfo(FileSystemMetadata file, DeviceProfileType type) private InternalProfileInfo GetInternalProfileInfo(FileSystemMetadata file, DeviceProfileType type)
{ {
return new InternalProfileInfo return new InternalProfileInfo(
{ new DeviceProfileInfo
Path = file.FullName,
Info = new DeviceProfileInfo
{ {
Id = file.FullName.ToLowerInvariant().GetMD5().ToString("N", CultureInfo.InvariantCulture), Id = file.FullName.ToLowerInvariant().GetMD5().ToString("N", CultureInfo.InvariantCulture),
Name = _fileSystem.GetFileNameWithoutExtension(file), Name = _fileSystem.GetFileNameWithoutExtension(file),
Type = type Type = type
} },
}; file.FullName);
} }
private async Task ExtractSystemProfilesAsync() private async Task ExtractSystemProfilesAsync()
@ -359,7 +356,8 @@ namespace Emby.Dlna
systemProfilesPath, systemProfilesPath,
Path.GetFileName(name.AsSpan()).Slice(namespaceName.Length)); 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); var fileInfo = _fileSystem.GetFileInfo(path);
@ -380,6 +378,7 @@ namespace Emby.Dlna
Directory.CreateDirectory(UserProfilesPath); Directory.CreateDirectory(UserProfilesPath);
} }
/// <inheritdoc />
public void DeleteProfile(string id) public void DeleteProfile(string id)
{ {
var info = GetProfileInfosInternal().First(i => string.Equals(id, i.Info.Id, StringComparison.OrdinalIgnoreCase)); var info = GetProfileInfosInternal().First(i => string.Equals(id, i.Info.Id, StringComparison.OrdinalIgnoreCase));
@ -397,6 +396,7 @@ namespace Emby.Dlna
} }
} }
/// <inheritdoc />
public void CreateProfile(DeviceProfile profile) public void CreateProfile(DeviceProfile profile)
{ {
profile = ReserializeProfile(profile); profile = ReserializeProfile(profile);
@ -412,6 +412,7 @@ namespace Emby.Dlna
SaveProfile(profile, path, DeviceProfileType.User); SaveProfile(profile, path, DeviceProfileType.User);
} }
/// <inheritdoc />
public void UpdateProfile(DeviceProfile profile) public void UpdateProfile(DeviceProfile profile)
{ {
profile = ReserializeProfile(profile); profile = ReserializeProfile(profile);
@ -470,9 +471,11 @@ namespace Emby.Dlna
var json = JsonSerializer.Serialize(profile, _jsonOptions); var json = JsonSerializer.Serialize(profile, _jsonOptions);
return JsonSerializer.Deserialize<DeviceProfile>(json, _jsonOptions); // Output can't be null if the input isn't null
return JsonSerializer.Deserialize<DeviceProfile>(json, _jsonOptions)!;
} }
/// <inheritdoc />
public string GetServerDescriptionXml(IHeaderDictionary headers, string serverUuId, string serverAddress) public string GetServerDescriptionXml(IHeaderDictionary headers, string serverUuId, string serverAddress)
{ {
var profile = GetDefaultProfile(); var profile = GetDefaultProfile();
@ -482,6 +485,7 @@ namespace Emby.Dlna
return new DescriptionXmlBuilder(profile, serverUuId, serverAddress, _appHost.FriendlyName, serverId).GetXml(); return new DescriptionXmlBuilder(profile, serverUuId, serverAddress, _appHost.FriendlyName, serverId).GetXml();
} }
/// <inheritdoc />
public ImageStream GetIcon(string filename) public ImageStream GetIcon(string filename)
{ {
var format = filename.EndsWith(".png", StringComparison.OrdinalIgnoreCase) var format = filename.EndsWith(".png", StringComparison.OrdinalIgnoreCase)
@ -499,9 +503,15 @@ namespace Emby.Dlna
private class InternalProfileInfo 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; }
} }
} }

@ -15,7 +15,7 @@ namespace Emby.Naming.AudioBook
/// <param name="files">List of files composing the actual audiobook.</param> /// <param name="files">List of files composing the actual audiobook.</param>
/// <param name="extras">List of extra files.</param> /// <param name="extras">List of extra files.</param>
/// <param name="alternateVersions">Alternative version of files.</param> /// <param name="alternateVersions">Alternative version of files.</param>
public AudioBookInfo(string name, int? year, List<AudioBookFileInfo> files, List<AudioBookFileInfo> extras, List<AudioBookFileInfo> alternateVersions) public AudioBookInfo(string name, int? year, IReadOnlyList<AudioBookFileInfo> files, IReadOnlyList<AudioBookFileInfo> extras, IReadOnlyList<AudioBookFileInfo> alternateVersions)
{ {
Name = name; Name = name;
Year = year; Year = year;
@ -39,18 +39,18 @@ namespace Emby.Naming.AudioBook
/// Gets or sets the files. /// Gets or sets the files.
/// </summary> /// </summary>
/// <value>The files.</value> /// <value>The files.</value>
public List<AudioBookFileInfo> Files { get; set; } public IReadOnlyList<AudioBookFileInfo> Files { get; set; }
/// <summary> /// <summary>
/// Gets or sets the extras. /// Gets or sets the extras.
/// </summary> /// </summary>
/// <value>The extras.</value> /// <value>The extras.</value>
public List<AudioBookFileInfo> Extras { get; set; } public IReadOnlyList<AudioBookFileInfo> Extras { get; set; }
/// <summary> /// <summary>
/// Gets or sets the alternate versions. /// Gets or sets the alternate versions.
/// </summary> /// </summary>
/// <value>The alternate versions.</value> /// <value>The alternate versions.</value>
public List<AudioBookFileInfo> AlternateVersions { get; set; } public IReadOnlyList<AudioBookFileInfo> AlternateVersions { get; set; }
} }
} }

@ -87,7 +87,7 @@ namespace Emby.Naming.AudioBook
foreach (var audioFile in group) foreach (var audioFile in group)
{ {
var name = Path.GetFileNameWithoutExtension(audioFile.Path); var name = Path.GetFileNameWithoutExtension(audioFile.Path);
if (name.Equals("audiobook") || if (name.Equals("audiobook", StringComparison.OrdinalIgnoreCase) ||
name.Contains(nameParserResult.Name, StringComparison.OrdinalIgnoreCase) || name.Contains(nameParserResult.Name, StringComparison.OrdinalIgnoreCase) ||
name.Contains(nameWithReplacedDots, StringComparison.OrdinalIgnoreCase)) name.Contains(nameWithReplacedDots, StringComparison.OrdinalIgnoreCase))
{ {

@ -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 // 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 // [bar] Foo - 1 [baz] special case of below expression to prevent false positives with digits in the series name
new EpisodeExpression(@".*?(\[.*?\])+.*?(?<seriesname>[\w\s]+?)[\s_]*-[\s_]*(?<epnumber>[0-9]+).*$") new EpisodeExpression(@".*[\\\/]?.*?(\[.*?\])+.*?(?<seriesname>[-\w\s]+?)[\s_]*-[\s_]*(?<epnumber>[0-9]+).*$")
{ {
IsNamed = true IsNamed = true
}, },

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<!-- ProjectGuid is only included as a requirement for SonarQube analysis --> <!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
<PropertyGroup> <PropertyGroup>
@ -49,7 +49,4 @@
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup> </ItemGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
</PropertyGroup>
</Project> </Project>

@ -1,6 +1,5 @@
using System; using System;
using System.IO; using System.IO;
using System.Linq;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Emby.Naming.Audio; using Emby.Naming.Audio;
using Emby.Naming.Common; using Emby.Naming.Common;

@ -21,7 +21,7 @@ namespace Emby.Naming.Video
/// <param name="namingOptions">The naming options.</param> /// <param name="namingOptions">The naming options.</param>
/// <param name="supportMultiVersion">Indication we should consider multi-versions of content.</param> /// <param name="supportMultiVersion">Indication we should consider multi-versions of content.</param>
/// <returns>Returns enumerable of <see cref="VideoInfo"/> which groups files together when related.</returns> /// <returns>Returns enumerable of <see cref="VideoInfo"/> which groups files together when related.</returns>
public static IEnumerable<VideoInfo> Resolve(List<FileSystemMetadata> files, NamingOptions namingOptions, bool supportMultiVersion = true) public static IEnumerable<VideoInfo> Resolve(IEnumerable<FileSystemMetadata> files, NamingOptions namingOptions, bool supportMultiVersion = true)
{ {
var videoInfos = files var videoInfos = files
.Select(i => VideoResolver.Resolve(i.FullName, i.IsDirectory, namingOptions)) .Select(i => VideoResolver.Resolve(i.FullName, i.IsDirectory, namingOptions))

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

@ -1,5 +1,3 @@
#nullable disable
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
@ -63,13 +61,13 @@ namespace Emby.Server.Implementations.Collections
} }
/// <inheritdoc /> /// <inheritdoc />
public event EventHandler<CollectionCreatedEventArgs> CollectionCreated; public event EventHandler<CollectionCreatedEventArgs>? CollectionCreated;
/// <inheritdoc /> /// <inheritdoc />
public event EventHandler<CollectionModifiedEventArgs> ItemsAddedToCollection; public event EventHandler<CollectionModifiedEventArgs>? ItemsAddedToCollection;
/// <inheritdoc /> /// <inheritdoc />
public event EventHandler<CollectionModifiedEventArgs> ItemsRemovedFromCollection; public event EventHandler<CollectionModifiedEventArgs>? ItemsRemovedFromCollection;
private IEnumerable<Folder> FindFolders(string path) private IEnumerable<Folder> FindFolders(string path)
{ {
@ -80,7 +78,7 @@ namespace Emby.Server.Implementations.Collections
.Where(i => _fileSystem.AreEqual(path, i.Path) || _fileSystem.ContainsSubPath(i.Path, path)); .Where(i => _fileSystem.AreEqual(path, i.Path) || _fileSystem.ContainsSubPath(i.Path, path));
} }
internal async Task<Folder> EnsureLibraryFolder(string path, bool createIfNeeded) internal async Task<Folder?> EnsureLibraryFolder(string path, bool createIfNeeded)
{ {
var existingFolder = FindFolders(path).FirstOrDefault(); var existingFolder = FindFolders(path).FirstOrDefault();
if (existingFolder != null) if (existingFolder != null)
@ -114,7 +112,7 @@ namespace Emby.Server.Implementations.Collections
return Path.Combine(_appPaths.DataPath, "collections"); return Path.Combine(_appPaths.DataPath, "collections");
} }
private Task<Folder> GetCollectionsFolder(bool createIfNeeded) private Task<Folder?> GetCollectionsFolder(bool createIfNeeded)
{ {
return EnsureLibraryFolder(GetCollectionsFolderPath(), createIfNeeded); return EnsureLibraryFolder(GetCollectionsFolderPath(), createIfNeeded);
} }
@ -203,8 +201,7 @@ namespace Emby.Server.Implementations.Collections
private async Task AddToCollectionAsync(Guid collectionId, IEnumerable<Guid> ids, bool fireEvent, MetadataRefreshOptions refreshOptions) private async Task AddToCollectionAsync(Guid collectionId, IEnumerable<Guid> ids, bool fireEvent, MetadataRefreshOptions refreshOptions)
{ {
var collection = _libraryManager.GetItemById(collectionId) as BoxSet; if (_libraryManager.GetItemById(collectionId) is not BoxSet collection)
if (collection == null)
{ {
throw new ArgumentException("No collection exists with the supplied Id"); throw new ArgumentException("No collection exists with the supplied Id");
} }
@ -256,9 +253,7 @@ namespace Emby.Server.Implementations.Collections
/// <inheritdoc /> /// <inheritdoc />
public async Task RemoveFromCollectionAsync(Guid collectionId, IEnumerable<Guid> itemIds) public async Task RemoveFromCollectionAsync(Guid collectionId, IEnumerable<Guid> itemIds)
{ {
var collection = _libraryManager.GetItemById(collectionId) as BoxSet; if (_libraryManager.GetItemById(collectionId) is not BoxSet collection)
if (collection == null)
{ {
throw new ArgumentException("No collection exists with the supplied Id"); throw new ArgumentException("No collection exists with the supplied Id");
} }
@ -312,11 +307,7 @@ namespace Emby.Server.Implementations.Collections
foreach (var item in items) foreach (var item in items)
{ {
if (item is not ISupportsBoxSetGrouping) if (item is ISupportsBoxSetGrouping)
{
results[item.Id] = item;
}
else
{ {
var itemId = item.Id; var itemId = item.Id;
@ -340,6 +331,7 @@ namespace Emby.Server.Implementations.Collections
} }
var alreadyInResults = false; var alreadyInResults = false;
// this is kind of a performance hack because only Video has alternate versions that should be in a box set? // 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) 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; return results.Values;

@ -28,7 +28,7 @@
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="5.0.0" /> <PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="5.0.0" /> <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="5.0.0" /> <PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="5.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="5.0.8" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="5.0.9" />
<PackageReference Include="Mono.Nat" Version="3.0.1" /> <PackageReference Include="Mono.Nat" Version="3.0.1" />
<PackageReference Include="prometheus-net.DotNetRuntime" Version="4.1.0" /> <PackageReference Include="prometheus-net.DotNetRuntime" Version="4.1.0" />
<PackageReference Include="sharpcompress" Version="0.28.3" /> <PackageReference Include="sharpcompress" Version="0.28.3" />

@ -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 // 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)) if (string.IsNullOrWhiteSpace(authInfo.Device))
{ {

@ -13,7 +13,6 @@ using System.Threading.Tasks;
using Jellyfin.Extensions; using Jellyfin.Extensions;
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net; using MediaBrowser.Common.Net;
using MediaBrowser.Controller;
using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Model.LiveTv; using MediaBrowser.Model.LiveTv;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -44,22 +43,29 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
public async Task<Stream> GetListingsStream(TunerHostInfo info, CancellationToken cancellationToken) public async Task<Stream> GetListingsStream(TunerHostInfo info, CancellationToken cancellationToken)
{ {
if (info.Url.StartsWith("http", StringComparison.OrdinalIgnoreCase)) if (info == null)
{ {
throw new ArgumentNullException(nameof(info));
}
if (!info.Url.StartsWith("http", StringComparison.OrdinalIgnoreCase))
{
return File.OpenRead(info.Url);
}
using var requestMessage = new HttpRequestMessage(HttpMethod.Get, info.Url); using var requestMessage = new HttpRequestMessage(HttpMethod.Get, info.Url);
if (!string.IsNullOrEmpty(info.UserAgent)) if (!string.IsNullOrEmpty(info.UserAgent))
{ {
requestMessage.Headers.UserAgent.TryParseAdd(info.UserAgent); requestMessage.Headers.UserAgent.TryParseAdd(info.UserAgent);
} }
// Set HttpCompletionOption.ResponseHeadersRead to prevent timeouts on larger files
var response = await _httpClientFactory.CreateClient(NamedClient.Default) var response = await _httpClientFactory.CreateClient(NamedClient.Default)
.SendAsync(requestMessage, cancellationToken) .SendAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead, cancellationToken)
.ConfigureAwait(false); .ConfigureAwait(false);
response.EnsureSuccessStatusCode(); response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
}
return File.OpenRead(info.Url); return await response.Content.ReadAsStreamAsync(cancellationToken);
} }
private async Task<List<ChannelInfo>> GetChannelsAsync(TextReader reader, string channelIdPrefix, string tunerHostId) private async Task<List<ChannelInfo>> GetChannelsAsync(TextReader reader, string channelIdPrefix, string tunerHostId)
@ -83,7 +89,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
if (trimmedLine.StartsWith(ExtInfPrefix, StringComparison.OrdinalIgnoreCase)) if (trimmedLine.StartsWith(ExtInfPrefix, StringComparison.OrdinalIgnoreCase))
{ {
extInf = trimmedLine.Substring(ExtInfPrefix.Length).Trim(); extInf = trimmedLine.Substring(ExtInfPrefix.Length).Trim();
_logger.LogInformation("Found m3u channel: {0}", extInf);
} }
else if (!string.IsNullOrWhiteSpace(extInf) && !trimmedLine.StartsWith('#')) else if (!string.IsNullOrWhiteSpace(extInf) && !trimmedLine.StartsWith('#'))
{ {
@ -99,6 +104,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
channel.Path = trimmedLine; channel.Path = trimmedLine;
channels.Add(channel); channels.Add(channel);
_logger.LogInformation("Parsed channel: {ChannelName}", channel.Name);
extInf = string.Empty; extInf = string.Empty;
} }
} }

@ -2,24 +2,24 @@
"Artists": "Kunstenare", "Artists": "Kunstenare",
"Channels": "Kanale", "Channels": "Kanale",
"Folders": "Lêergidse", "Folders": "Lêergidse",
"Favorites": "Gunstellinge", "Favorites": "Gunstelinge",
"HeaderFavoriteShows": "Gunsteling Vertonings", "HeaderFavoriteShows": "Gunsteling Vertonings",
"ValueSpecialEpisodeName": "Spesiale - {0}", "ValueSpecialEpisodeName": "Spesiale - {0}",
"HeaderAlbumArtists": "Album Kunstenaars", "HeaderAlbumArtists": "Kunstenaars se Album",
"Books": "Boeke", "Books": "Boeke",
"HeaderNextUp": "Volgende", "HeaderNextUp": "Volgende",
"Movies": "Flieks", "Movies": "Flieks",
"Shows": "Televisie Reekse", "Shows": "Televisie Reekse",
"HeaderContinueWatching": "Kyk Verder", "HeaderContinueWatching": "Kyk Verder",
"HeaderFavoriteEpisodes": "Gunsteling Episodes", "HeaderFavoriteEpisodes": "Gunsteling Episodes",
"Photos": "Fotos", "Photos": "Foto's",
"Playlists": "Snitlyste", "Playlists": "Snitlyste",
"HeaderFavoriteArtists": "Gunsteling Kunstenaars", "HeaderFavoriteArtists": "Gunsteling Kunstenaars",
"HeaderFavoriteAlbums": "Gunsteling Albums", "HeaderFavoriteAlbums": "Gunsteling Albums",
"Sync": "Sinkroniseer", "Sync": "Sinkroniseer",
"HeaderFavoriteSongs": "Gunsteling Liedjies", "HeaderFavoriteSongs": "Gunsteling Liedjies",
"Songs": "Liedjies", "Songs": "Liedjies",
"DeviceOnlineWithName": "{0} gekoppel is", "DeviceOnlineWithName": "{0} is gekoppel",
"DeviceOfflineWithName": "{0} is ontkoppel", "DeviceOfflineWithName": "{0} is ontkoppel",
"Collections": "Versamelings", "Collections": "Versamelings",
"Inherit": "Ontvang", "Inherit": "Ontvang",
@ -71,7 +71,7 @@
"NameSeasonUnknown": "Seisoen Onbekend", "NameSeasonUnknown": "Seisoen Onbekend",
"NameSeasonNumber": "Seisoen {0}", "NameSeasonNumber": "Seisoen {0}",
"NameInstallFailed": "{0} installering het misluk", "NameInstallFailed": "{0} installering het misluk",
"MusicVideos": "Musiek videos", "MusicVideos": "Musiek Videos",
"Music": "Musiek", "Music": "Musiek",
"MixedContent": "Gemengde inhoud", "MixedContent": "Gemengde inhoud",
"MessageServerConfigurationUpdated": "Bediener konfigurasie is opgedateer", "MessageServerConfigurationUpdated": "Bediener konfigurasie is opgedateer",
@ -79,15 +79,15 @@
"MessageApplicationUpdatedTo": "Jellyfin Bediener is opgedateer na {0}", "MessageApplicationUpdatedTo": "Jellyfin Bediener is opgedateer na {0}",
"MessageApplicationUpdated": "Jellyfin Bediener is opgedateer", "MessageApplicationUpdated": "Jellyfin Bediener is opgedateer",
"Latest": "Nuutste", "Latest": "Nuutste",
"LabelRunningTimeValue": "Lopende tyd: {0}", "LabelRunningTimeValue": "Werktyd: {0}",
"LabelIpAddressValue": "IP adres: {0}", "LabelIpAddressValue": "IP adres: {0}",
"ItemRemovedWithName": "{0} is uit versameling verwyder", "ItemRemovedWithName": "{0} is uit versameling verwyder",
"ItemAddedWithName": "{0} is in die versameling", "ItemAddedWithName": "{0} is by die versameling gevoeg",
"HomeVideos": "Tuis opnames", "HomeVideos": "Tuis Videos",
"HeaderRecordingGroups": "Groep Opnames", "HeaderRecordingGroups": "Groep Opnames",
"Genres": "Genres", "Genres": "Genres",
"FailedLoginAttemptWithUserName": "Mislukte aansluiting van {0}", "FailedLoginAttemptWithUserName": "Mislukte aansluiting van {0}",
"ChapterNameValue": "Hoofstuk", "ChapterNameValue": "Hoofstuk {0}",
"CameraImageUploadedFrom": "'n Nuwe kamera photo opgelaai van {0}", "CameraImageUploadedFrom": "'n Nuwe kamera photo opgelaai van {0}",
"AuthenticationSucceededWithUserName": "{0} suksesvol geverifieer", "AuthenticationSucceededWithUserName": "{0} suksesvol geverifieer",
"Albums": "Albums", "Albums": "Albums",
@ -117,5 +117,7 @@
"Forced": "Geforseer", "Forced": "Geforseer",
"Default": "Oorspronklik", "Default": "Oorspronklik",
"TaskCleanActivityLogDescription": "Verwyder aktiwiteitsaantekeninge ouer as die opgestelde ouderdom.", "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"
} }

@ -5,7 +5,7 @@
"Artists": "Artistes", "Artists": "Artistes",
"AuthenticationSucceededWithUserName": "{0} s'ha autenticat correctament", "AuthenticationSucceededWithUserName": "{0} s'ha autenticat correctament",
"Books": "Llibres", "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", "Channels": "Canals",
"ChapterNameValue": "Capítol {0}", "ChapterNameValue": "Capítol {0}",
"Collections": "Col·leccions", "Collections": "Col·leccions",

@ -1,5 +1,5 @@
{ {
"Albums": "Άλμπουμς", "Albums": "Άλμπουμ",
"AppDeviceValues": "Εφαρμογή: {0}, Συσκευή: {1}", "AppDeviceValues": "Εφαρμογή: {0}, Συσκευή: {1}",
"Application": "Εφαρμογή", "Application": "Εφαρμογή",
"Artists": "Καλλιτέχνες", "Artists": "Καλλιτέχνες",
@ -15,7 +15,7 @@
"Favorites": "Αγαπημένα", "Favorites": "Αγαπημένα",
"Folders": "Φάκελοι", "Folders": "Φάκελοι",
"Genres": "Είδη", "Genres": "Είδη",
"HeaderAlbumArtists": "Καλλιτέχνες του Άλμπουμ", "HeaderAlbumArtists": "Άλμπουμ Καλλιτέχνη",
"HeaderContinueWatching": "Συνεχίστε την παρακολούθηση", "HeaderContinueWatching": "Συνεχίστε την παρακολούθηση",
"HeaderFavoriteAlbums": "Αγαπημένα Άλμπουμ", "HeaderFavoriteAlbums": "Αγαπημένα Άλμπουμ",
"HeaderFavoriteArtists": "Αγαπημένοι Καλλιτέχνες", "HeaderFavoriteArtists": "Αγαπημένοι Καλλιτέχνες",
@ -39,7 +39,7 @@
"MixedContent": "Ανάμεικτο Περιεχόμενο", "MixedContent": "Ανάμεικτο Περιεχόμενο",
"Movies": "Ταινίες", "Movies": "Ταινίες",
"Music": "Μουσική", "Music": "Μουσική",
"MusicVideos": "Μουσικά βίντεο", "MusicVideos": "Μουσικά Βίντεο",
"NameInstallFailed": "{0} η εγκατάσταση απέτυχε", "NameInstallFailed": "{0} η εγκατάσταση απέτυχε",
"NameSeasonNumber": "Κύκλος {0}", "NameSeasonNumber": "Κύκλος {0}",
"NameSeasonUnknown": "Άγνωστος Κύκλος", "NameSeasonUnknown": "Άγνωστος Κύκλος",
@ -62,7 +62,7 @@
"NotificationOptionVideoPlaybackStopped": "Η αναπαραγωγή βίντεο σταμάτησε", "NotificationOptionVideoPlaybackStopped": "Η αναπαραγωγή βίντεο σταμάτησε",
"Photos": "Φωτογραφίες", "Photos": "Φωτογραφίες",
"Playlists": "Λίστες αναπαραγωγής", "Playlists": "Λίστες αναπαραγωγής",
"Plugin": "Plugin", "Plugin": "Πρόσθετο",
"PluginInstalledWithName": "{0} εγκαταστήθηκε", "PluginInstalledWithName": "{0} εγκαταστήθηκε",
"PluginUninstalledWithName": "{0} έχει απεγκατασταθεί", "PluginUninstalledWithName": "{0} έχει απεγκατασταθεί",
"PluginUpdatedWithName": "{0} έχει αναβαθμιστεί", "PluginUpdatedWithName": "{0} έχει αναβαθμιστεί",
@ -118,5 +118,7 @@
"TaskCleanActivityLog": "Καθαρό Αρχείο Καταγραφής Δραστηριοτήτων", "TaskCleanActivityLog": "Καθαρό Αρχείο Καταγραφής Δραστηριοτήτων",
"Undefined": "Απροσδιόριστο", "Undefined": "Απροσδιόριστο",
"Forced": "Εξαναγκασμένο", "Forced": "Εξαναγκασμένο",
"Default": "Προεπιλογή" "Default": "Προεπιλογή",
"TaskOptimizeDatabaseDescription": "Συμπιέζει τη βάση δεδομένων και δημιουργεί ελεύθερο χώρο. Η εκτέλεση αυτής της εργασίας μετά τη σάρωση της βιβλιοθήκης ή την πραγματοποίηση άλλων αλλαγών που συνεπάγονται τροποποιήσεις της βάσης δεδομένων μπορεί να βελτιώσει την απόδοση.",
"TaskOptimizeDatabase": "Βελτιστοποίηση βάσης δεδομένων"
} }

@ -17,7 +17,7 @@
"Folders": "Folders", "Folders": "Folders",
"Forced": "Forced", "Forced": "Forced",
"Genres": "Genres", "Genres": "Genres",
"HeaderAlbumArtists": "Album Artists", "HeaderAlbumArtists": "Artist's Album",
"HeaderContinueWatching": "Continue Watching", "HeaderContinueWatching": "Continue Watching",
"HeaderFavoriteAlbums": "Favorite Albums", "HeaderFavoriteAlbums": "Favorite Albums",
"HeaderFavoriteArtists": "Favorite Artists", "HeaderFavoriteArtists": "Favorite Artists",
@ -27,7 +27,7 @@
"HeaderLiveTV": "Live TV", "HeaderLiveTV": "Live TV",
"HeaderNextUp": "Next Up", "HeaderNextUp": "Next Up",
"HeaderRecordingGroups": "Recording Groups", "HeaderRecordingGroups": "Recording Groups",
"HomeVideos": "Home videos", "HomeVideos": "Home Videos",
"Inherit": "Inherit", "Inherit": "Inherit",
"ItemAddedWithName": "{0} was added to the library", "ItemAddedWithName": "{0} was added to the library",
"ItemRemovedWithName": "{0} was removed from the library", "ItemRemovedWithName": "{0} was removed from the library",
@ -41,7 +41,7 @@
"MixedContent": "Mixed content", "MixedContent": "Mixed content",
"Movies": "Movies", "Movies": "Movies",
"Music": "Music", "Music": "Music",
"MusicVideos": "Music videos", "MusicVideos": "Music Videos",
"NameInstallFailed": "{0} installation failed", "NameInstallFailed": "{0} installation failed",
"NameSeasonNumber": "Season {0}", "NameSeasonNumber": "Season {0}",
"NameSeasonUnknown": "Season Unknown", "NameSeasonUnknown": "Season Unknown",

@ -15,7 +15,7 @@
"Favorites": "Favoritos", "Favorites": "Favoritos",
"Folders": "Carpetas", "Folders": "Carpetas",
"Genres": "Géneros", "Genres": "Géneros",
"HeaderAlbumArtists": "Artistas del álbum", "HeaderAlbumArtists": "Artistas del Álbum",
"HeaderContinueWatching": "Continuar viendo", "HeaderContinueWatching": "Continuar viendo",
"HeaderFavoriteAlbums": "Álbumes favoritos", "HeaderFavoriteAlbums": "Álbumes favoritos",
"HeaderFavoriteArtists": "Artistas favoritos", "HeaderFavoriteArtists": "Artistas favoritos",
@ -25,7 +25,7 @@
"HeaderLiveTV": "TV en vivo", "HeaderLiveTV": "TV en vivo",
"HeaderNextUp": "A continuación", "HeaderNextUp": "A continuación",
"HeaderRecordingGroups": "Grupos de grabación", "HeaderRecordingGroups": "Grupos de grabación",
"HomeVideos": "Videos caseros", "HomeVideos": "Videos Caseros",
"Inherit": "Heredar", "Inherit": "Heredar",
"ItemAddedWithName": "{0} fue agregado a la biblioteca", "ItemAddedWithName": "{0} fue agregado a la biblioteca",
"ItemRemovedWithName": "{0} fue removido de la biblioteca", "ItemRemovedWithName": "{0} fue removido de la biblioteca",
@ -39,7 +39,7 @@
"MixedContent": "Contenido mezclado", "MixedContent": "Contenido mezclado",
"Movies": "Películas", "Movies": "Películas",
"Music": "Música", "Music": "Música",
"MusicVideos": "Videos musicales", "MusicVideos": "Videos Musicales",
"NameInstallFailed": "Falló la instalación de {0}", "NameInstallFailed": "Falló la instalación de {0}",
"NameSeasonNumber": "Temporada {0}", "NameSeasonNumber": "Temporada {0}",
"NameSeasonUnknown": "Temporada desconocida", "NameSeasonUnknown": "Temporada desconocida",
@ -49,7 +49,7 @@
"NotificationOptionAudioPlayback": "Reproducción de audio iniciada", "NotificationOptionAudioPlayback": "Reproducción de audio iniciada",
"NotificationOptionAudioPlaybackStopped": "Reproducción de audio detenida", "NotificationOptionAudioPlaybackStopped": "Reproducción de audio detenida",
"NotificationOptionCameraImageUploaded": "Imagen de la cámara subida", "NotificationOptionCameraImageUploaded": "Imagen de la cámara subida",
"NotificationOptionInstallationFailed": "Falla de instalación", "NotificationOptionInstallationFailed": "Fallo en la instalación",
"NotificationOptionNewLibraryContent": "Nuevo contenido agregado", "NotificationOptionNewLibraryContent": "Nuevo contenido agregado",
"NotificationOptionPluginError": "Falla de complemento", "NotificationOptionPluginError": "Falla de complemento",
"NotificationOptionPluginInstalled": "Complemento instalado", "NotificationOptionPluginInstalled": "Complemento instalado",
@ -69,7 +69,7 @@
"ProviderValue": "Proveedor: {0}", "ProviderValue": "Proveedor: {0}",
"ScheduledTaskFailedWithName": "{0} falló", "ScheduledTaskFailedWithName": "{0} falló",
"ScheduledTaskStartedWithName": "{0} iniciado", "ScheduledTaskStartedWithName": "{0} iniciado",
"ServerNameNeedsToBeRestarted": "{0} debe ser reiniciado", "ServerNameNeedsToBeRestarted": "{0} necesita ser reiniciado",
"Shows": "Programas", "Shows": "Programas",
"Songs": "Canciones", "Songs": "Canciones",
"StartupEmbyServerIsLoading": "El servidor Jellyfin está cargando. Por favor, intente de nuevo pronto.", "StartupEmbyServerIsLoading": "El servidor Jellyfin está cargando. Por favor, intente de nuevo pronto.",
@ -94,9 +94,9 @@
"VersionNumber": "Versión {0}", "VersionNumber": "Versión {0}",
"TaskDownloadMissingSubtitlesDescription": "Busca subtítulos faltantes en Internet basándose en la configuración de metadatos.", "TaskDownloadMissingSubtitlesDescription": "Busca subtítulos faltantes en Internet basándose en la configuración de metadatos.",
"TaskDownloadMissingSubtitles": "Descargar subtítulos faltantes", "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", "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", "TaskCleanTranscode": "Limpiar directorio de transcodificado",
"TaskUpdatePluginsDescription": "Descarga e instala actualizaciones para complementos que están configurados para actualizarse automáticamente.", "TaskUpdatePluginsDescription": "Descarga e instala actualizaciones para complementos que están configurados para actualizarse automáticamente.",
"TaskUpdatePlugins": "Actualizar complementos", "TaskUpdatePlugins": "Actualizar complementos",
@ -118,5 +118,7 @@
"TaskCleanActivityLog": "Limpiar registro de actividades", "TaskCleanActivityLog": "Limpiar registro de actividades",
"Undefined": "Sin definir", "Undefined": "Sin definir",
"Forced": "Forzado", "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."
} }

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

@ -15,7 +15,7 @@
"Favorites": "Kedvencek", "Favorites": "Kedvencek",
"Folders": "Könyvtárak", "Folders": "Könyvtárak",
"Genres": "Műfajok", "Genres": "Műfajok",
"HeaderAlbumArtists": "Album előadók", "HeaderAlbumArtists": "Előadó albumai",
"HeaderContinueWatching": "Megtekintés folytatása", "HeaderContinueWatching": "Megtekintés folytatása",
"HeaderFavoriteAlbums": "Kedvenc albumok", "HeaderFavoriteAlbums": "Kedvenc albumok",
"HeaderFavoriteArtists": "Kedvenc előadók", "HeaderFavoriteArtists": "Kedvenc előadók",

@ -15,7 +15,7 @@
"Favorites": "Preferiti", "Favorites": "Preferiti",
"Folders": "Cartelle", "Folders": "Cartelle",
"Genres": "Generi", "Genres": "Generi",
"HeaderAlbumArtists": "Artisti degli Album", "HeaderAlbumArtists": "Artisti dell'Album",
"HeaderContinueWatching": "Continua a guardare", "HeaderContinueWatching": "Continua a guardare",
"HeaderFavoriteAlbums": "Album Preferiti", "HeaderFavoriteAlbums": "Album Preferiti",
"HeaderFavoriteArtists": "Artisti Preferiti", "HeaderFavoriteArtists": "Artisti Preferiti",
@ -25,7 +25,7 @@
"HeaderLiveTV": "Diretta TV", "HeaderLiveTV": "Diretta TV",
"HeaderNextUp": "Prossimo", "HeaderNextUp": "Prossimo",
"HeaderRecordingGroups": "Gruppi di Registrazione", "HeaderRecordingGroups": "Gruppi di Registrazione",
"HomeVideos": "Video personali", "HomeVideos": "Video Personali",
"Inherit": "Eredita", "Inherit": "Eredita",
"ItemAddedWithName": "{0} è stato aggiunto alla libreria", "ItemAddedWithName": "{0} è stato aggiunto alla libreria",
"ItemRemovedWithName": "{0} è stato rimosso dalla libreria", "ItemRemovedWithName": "{0} è stato rimosso dalla libreria",
@ -39,7 +39,7 @@
"MixedContent": "Contenuto misto", "MixedContent": "Contenuto misto",
"Movies": "Film", "Movies": "Film",
"Music": "Musica", "Music": "Musica",
"MusicVideos": "Video musicali", "MusicVideos": "Video Musicali",
"NameInstallFailed": "{0} installazione fallita", "NameInstallFailed": "{0} installazione fallita",
"NameSeasonNumber": "Stagione {0}", "NameSeasonNumber": "Stagione {0}",
"NameSeasonUnknown": "Stagione sconosciuta", "NameSeasonUnknown": "Stagione sconosciuta",
@ -70,7 +70,7 @@
"ScheduledTaskFailedWithName": "{0} fallito", "ScheduledTaskFailedWithName": "{0} fallito",
"ScheduledTaskStartedWithName": "{0} avviati", "ScheduledTaskStartedWithName": "{0} avviati",
"ServerNameNeedsToBeRestarted": "{0} deve essere riavviato", "ServerNameNeedsToBeRestarted": "{0} deve essere riavviato",
"Shows": "Programmi", "Shows": "Serie TV",
"Songs": "Canzoni", "Songs": "Canzoni",
"StartupEmbyServerIsLoading": "Jellyfin server si sta avviando. Per favore riprova più tardi.", "StartupEmbyServerIsLoading": "Jellyfin server si sta avviando. Per favore riprova più tardi.",
"SubtitleDownloadFailureForItem": "Impossibile scaricare i sottotitoli per {0}", "SubtitleDownloadFailureForItem": "Impossibile scaricare i sottotitoli per {0}",

@ -15,7 +15,7 @@
"Favorites": "Tañdaulylar", "Favorites": "Tañdaulylar",
"Folders": "Qaltalar", "Folders": "Qaltalar",
"Genres": "Janrlar", "Genres": "Janrlar",
"HeaderAlbumArtists": "Älbom oryndauşylary", "HeaderAlbumArtists": "Oryndauşynyñ älbomy",
"HeaderContinueWatching": "Qaraudy jalğastyru", "HeaderContinueWatching": "Qaraudy jalğastyru",
"HeaderFavoriteAlbums": "Tañdauly älbomdar", "HeaderFavoriteAlbums": "Tañdauly älbomdar",
"HeaderFavoriteArtists": "Tañdauly oryndauşylar", "HeaderFavoriteArtists": "Tañdauly oryndauşylar",

@ -103,7 +103,7 @@
"ValueSpecialEpisodeName": "പ്രത്യേക - {0}", "ValueSpecialEpisodeName": "പ്രത്യേക - {0}",
"Collections": "ശേഖരങ്ങൾ", "Collections": "ശേഖരങ്ങൾ",
"Folders": "ഫോൾഡറുകൾ", "Folders": "ഫോൾഡറുകൾ",
"HeaderAlbumArtists": "ആൽബം ആർട്ടിസ്റ്റുകൾ", "HeaderAlbumArtists": "കലാകാരന്റെ ആൽബം",
"Sync": "സമന്വയിപ്പിക്കുക", "Sync": "സമന്വയിപ്പിക്കുക",
"Movies": "സിനിമകൾ", "Movies": "സിനിമകൾ",
"Photos": "ഫോട്ടോകൾ", "Photos": "ഫോട്ടോകൾ",

@ -25,7 +25,7 @@
"HeaderLiveTV": "Эфир", "HeaderLiveTV": "Эфир",
"HeaderNextUp": "Очередное", "HeaderNextUp": "Очередное",
"HeaderRecordingGroups": "Группы записей", "HeaderRecordingGroups": "Группы записей",
"HomeVideos": "Домашнее видео", "HomeVideos": "Домашние видео",
"Inherit": "Наследуемое", "Inherit": "Наследуемое",
"ItemAddedWithName": "{0} - добавлено в медиатеку", "ItemAddedWithName": "{0} - добавлено в медиатеку",
"ItemRemovedWithName": "{0} - изъято из медиатеки", "ItemRemovedWithName": "{0} - изъято из медиатеки",

@ -39,7 +39,7 @@
"MixedContent": "Zmiešaný obsah", "MixedContent": "Zmiešaný obsah",
"Movies": "Filmy", "Movies": "Filmy",
"Music": "Hudba", "Music": "Hudba",
"MusicVideos": "Hudobné videoklipy", "MusicVideos": "Hudobné videá",
"NameInstallFailed": "Inštalácia {0} zlyhala", "NameInstallFailed": "Inštalácia {0} zlyhala",
"NameSeasonNumber": "Séria {0}", "NameSeasonNumber": "Séria {0}",
"NameSeasonUnknown": "Neznáma séria", "NameSeasonUnknown": "Neznáma séria",

@ -118,5 +118,6 @@
"TaskCleanActivityLog": "Rensa Aktivitets Logg", "TaskCleanActivityLog": "Rensa Aktivitets Logg",
"Undefined": "odefinierad", "Undefined": "odefinierad",
"Forced": "Tvingad", "Forced": "Tvingad",
"Default": "Standard" "Default": "Standard",
"TaskOptimizeDatabase": "Optimera databasen"
} }

@ -43,7 +43,7 @@
"NameInstallFailed": "{0} kurulumu başarısız", "NameInstallFailed": "{0} kurulumu başarısız",
"NameSeasonNumber": "Sezon {0}", "NameSeasonNumber": "Sezon {0}",
"NameSeasonUnknown": "Bilinmeyen Sezon", "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", "NotificationOptionApplicationUpdateAvailable": "Uygulama güncellemesi mevcut",
"NotificationOptionApplicationUpdateInstalled": "Uygulama güncellemesi yüklendi", "NotificationOptionApplicationUpdateInstalled": "Uygulama güncellemesi yüklendi",
"NotificationOptionAudioPlayback": "Ses çalma başladı", "NotificationOptionAudioPlayback": "Ses çalma başladı",
@ -75,7 +75,7 @@
"StartupEmbyServerIsLoading": "Jellyfin Sunucusu yükleniyor. Lütfen kısa süre sonra tekrar deneyin.", "StartupEmbyServerIsLoading": "Jellyfin Sunucusu yükleniyor. Lütfen kısa süre sonra tekrar deneyin.",
"SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}", "SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}",
"SubtitleDownloadFailureFromForItem": "{1} için alt yazılar {0} 'dan indirilemedi", "SubtitleDownloadFailureFromForItem": "{1} için alt yazılar {0} 'dan indirilemedi",
"Sync": "Eşitle", "Sync": "Eşzamanlama",
"System": "Sistem", "System": "Sistem",
"TvShows": "Diziler", "TvShows": "Diziler",
"User": "Kullanıcı", "User": "Kullanıcı",
@ -89,34 +89,36 @@
"UserPolicyUpdatedWithName": "Kullanıcı politikası {0} için güncellendi", "UserPolicyUpdatedWithName": "Kullanıcı politikası {0} için güncellendi",
"UserStartedPlayingItemWithValues": "{0}, {2} cihazında {1} izliyor", "UserStartedPlayingItemWithValues": "{0}, {2} cihazında {1} izliyor",
"UserStoppedPlayingItemWithValues": "{0}, {2} cihazında {1} izlemeyi bitirdi", "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}", "ValueSpecialEpisodeName": "Özel - {0}",
"VersionNumber": "Versiyon {0}", "VersionNumber": "Sürüm {0}",
"TaskCleanCache": "Geçici dosya klasörünü temizle", "TaskCleanCache": "Geçici dosya klasörünü temizle",
"TasksChannelsCategory": "İnternet kanalları", "TasksChannelsCategory": "İnternet kanalları",
"TasksApplicationCategory": "Uygulama", "TasksApplicationCategory": "Uygulama",
"TasksLibraryCategory": "Kütüphane", "TasksLibraryCategory": "Kütüphane",
"TasksMaintenanceCategory": "Onarım", "TasksMaintenanceCategory": "Bakım",
"TaskRefreshPeopleDescription": "Medya kütüphanenizdeki videoların oyuncu ve yönetmen bilgilerini günceller.", "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.", "TaskDownloadMissingSubtitlesDescription": "Metadata ayarlarını baz alarak eksik altyazıları internette arar.",
"TaskDownloadMissingSubtitles": "Eksik altyazıları indir", "TaskDownloadMissingSubtitles": "Eksik altyazıları indir",
"TaskRefreshChannelsDescription": "Internet kanal bilgilerini yenile.", "TaskRefreshChannelsDescription": "Internet kanal bilgilerini yenile.",
"TaskRefreshChannels": "Kanalları 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", "TaskCleanTranscode": "Dönüşüm Dizinini Temizle",
"TaskUpdatePluginsDescription": "Otomatik güncellenmeye ayarlanmış eklentilerin güncellemelerini indirir ve kurar.", "TaskUpdatePluginsDescription": "Otomatik güncellenmeye ayarlanmış eklentilerin güncellemelerini indirir ve kurar.",
"TaskUpdatePlugins": "Eklentileri Güncelle", "TaskUpdatePlugins": "Eklentileri Güncelle",
"TaskRefreshPeople": "Kullanıcıları Yenile", "TaskRefreshPeople": "Kullanıcıları Yenile",
"TaskCleanLogsDescription": "{0} günden eski log dosyalarını siler.", "TaskCleanLogsDescription": "{0} günden eski günlük dosyalarını siler.",
"TaskCleanLogs": "Log Dizinini Temizle", "TaskCleanLogs": "Günlük Dizinini Temizle",
"TaskRefreshLibraryDescription": "Medya kütüphanenize eklenen yeni dosyaları arar ve bilgileri yeniler.", "TaskRefreshLibraryDescription": "Medya kütüphanenize eklenen yeni dosyaları arar ve ortam bilgilerini yeniler.",
"TaskRefreshLibrary": "Medya Kütüphanesini Tara", "TaskRefreshLibrary": "Medya Kütüphanesini Tara",
"TaskRefreshChapterImagesDescription": "Sahnelere ayrılmış videolar için küçük resimler oluştur.", "TaskRefreshChapterImagesDescription": "Sahnelere ayrılmış videolar için küçük resimler oluştur.",
"TaskRefreshChapterImages": "Bölüm Resimlerini Çıkar", "TaskRefreshChapterImages": "Bölüm Resimlerini Çıkar",
"TaskCleanCacheDescription": "Sistem tarafından artık ihtiyaç duyulmayan önbellek dosyalarını siler.", "TaskCleanCacheDescription": "Sistem tarafından artık ihtiyaç duyulmayan önbellek dosyalarını siler.",
"TaskCleanActivityLog": "İşlem Günlüğünü Temizle", "TaskCleanActivityLog": "Etkinlik Günlüğünü Temizle",
"TaskCleanActivityLogDescription": "Belirtilen sureden daha eski etkinlik log kayıtları silindi.", "TaskCleanActivityLogDescription": "Yapılandırılan tarihten daha eski olan etkinlik günlüğü girişlerini siler.",
"Undefined": "Bilinmeyen", "Undefined": "Bilinmeyen",
"Default": "Varsayılan", "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"
} }

@ -3,7 +3,7 @@
"Favorites": "Yêu Thích", "Favorites": "Yêu Thích",
"Folders": "Thư Mục", "Folders": "Thư Mục",
"Genres": "Thể Loại", "Genres": "Thể Loại",
"HeaderAlbumArtists": "Tuyển Tập Nghệ sĩ", "HeaderAlbumArtists": "Album Nghệ sĩ",
"HeaderContinueWatching": "Xem Tiếp", "HeaderContinueWatching": "Xem Tiếp",
"HeaderLiveTV": "TV Trực Tiếp", "HeaderLiveTV": "TV Trực Tiếp",
"Movies": "Phim", "Movies": "Phim",
@ -82,7 +82,7 @@
"NameSeasonUnknown": "Không Rõ Mùa", "NameSeasonUnknown": "Không Rõ Mùa",
"NameSeasonNumber": "Phần {0}", "NameSeasonNumber": "Phần {0}",
"NameInstallFailed": "{0} cài đặt thất bại", "NameInstallFailed": "{0} cài đặt thất bại",
"MusicVideos": "Video Nhạc", "MusicVideos": "Videos Nhạc",
"Music": "Nhạc", "Music": "Nhạc",
"MixedContent": "Nội dung hỗn hợp", "MixedContent": "Nội dung hỗn hợp",
"MessageServerConfigurationUpdated": "Cấu hình máy chủ đã được cập nhật", "MessageServerConfigurationUpdated": "Cấu hình máy chủ đã được cập nhật",

@ -1,5 +1,3 @@
#nullable disable
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
@ -23,6 +21,9 @@ namespace Emby.Server.Implementations.Localization
public class LocalizationManager : ILocalizationManager public class LocalizationManager : ILocalizationManager
{ {
private const string DefaultCulture = "en-US"; 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 Assembly _assembly = typeof(LocalizationManager).Assembly;
private static readonly string[] _unratedValues = { "n/a", "unrated", "not rated" }; private static readonly string[] _unratedValues = { "n/a", "unrated", "not rated" };
@ -35,10 +36,10 @@ namespace Emby.Server.Implementations.Localization
private readonly ConcurrentDictionary<string, Dictionary<string, string>> _dictionaries = private readonly ConcurrentDictionary<string, Dictionary<string, string>> _dictionaries =
new ConcurrentDictionary<string, Dictionary<string, string>>(StringComparer.OrdinalIgnoreCase); new ConcurrentDictionary<string, Dictionary<string, string>>(StringComparer.OrdinalIgnoreCase);
private List<CultureDto> _cultures;
private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options; private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
private List<CultureDto> _cultures = new List<CultureDto>();
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="LocalizationManager" /> class. /// Initializes a new instance of the <see cref="LocalizationManager" /> class.
/// </summary> /// </summary>
@ -58,22 +59,19 @@ namespace Emby.Server.Implementations.Localization
/// <returns><see cref="Task" />.</returns> /// <returns><see cref="Task" />.</returns>
public async Task LoadAll() public async Task LoadAll()
{ {
const string RatingsResource = "Emby.Server.Implementations.Localization.Ratings.";
// Extract from the assembly // Extract from the assembly
foreach (var resource in _assembly.GetManifestResourceNames()) foreach (var resource in _assembly.GetManifestResourceNames())
{ {
if (!resource.StartsWith(RatingsResource, StringComparison.Ordinal)) if (!resource.StartsWith(RatingsPath, StringComparison.Ordinal))
{ {
continue; continue;
} }
string countryCode = resource.Substring(RatingsResource.Length, 2); string countryCode = resource.Substring(RatingsPath.Length, 2);
var dict = new Dictionary<string, ParentalRating>(StringComparer.OrdinalIgnoreCase); var dict = new Dictionary<string, ParentalRating>(StringComparer.OrdinalIgnoreCase);
using (var str = _assembly.GetManifestResourceStream(resource)) await using var stream = _assembly.GetManifestResourceStream(resource);
using (var reader = new StreamReader(str)) 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))
@ -95,7 +93,6 @@ namespace Emby.Server.Implementations.Localization
} }
#endif #endif
} }
}
_allParentalRatings[countryCode] = dict; _allParentalRatings[countryCode] = dict;
} }
@ -114,11 +111,9 @@ namespace Emby.Server.Implementations.Localization
{ {
List<CultureDto> list = new List<CultureDto>(); List<CultureDto> list = new List<CultureDto>();
const string ResourcePath = "Emby.Server.Implementations.Localization.iso6392.txt"; await using var stream = _assembly.GetManifestResourceStream(CulturesPath)
?? throw new InvalidOperationException($"Invalid resource path: '{CulturesPath}'");
using (var stream = _assembly.GetManifestResourceStream(ResourcePath)) using var reader = new StreamReader(stream);
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))
@ -161,13 +156,12 @@ namespace Emby.Server.Implementations.Localization
}); });
} }
} }
}
_cultures = list; _cultures = list;
} }
/// <inheritdoc /> /// <inheritdoc />
public CultureDto FindLanguageInfo(string language) public CultureDto? FindLanguageInfo(string language)
{ {
// TODO language should ideally be a ReadOnlySpan but moq cannot mock ref structs // TODO language should ideally be a ReadOnlySpan but moq cannot mock ref structs
for (var i = 0; i < _cultures.Count; i++) for (var i = 0; i < _cultures.Count; i++)
@ -188,9 +182,10 @@ namespace Emby.Server.Implementations.Localization
/// <inheritdoc /> /// <inheritdoc />
public IEnumerable<CountryInfo> GetCountries() public IEnumerable<CountryInfo> GetCountries()
{ {
using StreamReader reader = new StreamReader(_assembly.GetManifestResourceStream("Emby.Server.Implementations.Localization.countries.json")); using StreamReader reader = new StreamReader(
_assembly.GetManifestResourceStream(CountriesPath) ?? throw new InvalidOperationException($"Invalid resource path: '{CountriesPath}'"));
return JsonSerializer.Deserialize<IEnumerable<CountryInfo>>(reader.ReadToEnd(), _jsonOptions); return JsonSerializer.Deserialize<IEnumerable<CountryInfo>>(reader.ReadToEnd(), _jsonOptions)
?? throw new InvalidOperationException($"Resource contains invalid data: '{CountriesPath}'");
} }
/// <inheritdoc /> /// <inheritdoc />
@ -210,7 +205,9 @@ namespace Emby.Server.Implementations.Localization
countryCode = "us"; countryCode = "us";
} }
return GetRatings(countryCode) ?? GetRatings("us"); return GetRatings(countryCode)
?? GetRatings("us")
?? throw new InvalidOperationException($"Invalid resource path: '{CountriesPath}'");
} }
/// <summary> /// <summary>
@ -218,7 +215,7 @@ namespace Emby.Server.Implementations.Localization
/// </summary> /// </summary>
/// <param name="countryCode">The country code.</param> /// <param name="countryCode">The country code.</param>
/// <returns>The ratings.</returns> /// <returns>The ratings.</returns>
private Dictionary<string, ParentalRating> GetRatings(string countryCode) private Dictionary<string, ParentalRating>? GetRatings(string countryCode)
{ {
_allParentalRatings.TryGetValue(countryCode, out var value); _allParentalRatings.TryGetValue(countryCode, out var value);
@ -243,7 +240,7 @@ namespace Emby.Server.Implementations.Localization
var ratingsDictionary = GetParentalRatingsDictionary(); var ratingsDictionary = GetParentalRatingsDictionary();
if (ratingsDictionary.TryGetValue(rating, out ParentalRating value)) if (ratingsDictionary.TryGetValue(rating, out ParentalRating? value))
{ {
return value.Value; return value.Value;
} }
@ -273,20 +270,6 @@ namespace Emby.Server.Implementations.Localization
return null; return null;
} }
/// <inheritdoc />
public bool HasUnicodeCategory(string value, UnicodeCategory category)
{
foreach (var chr in value)
{
if (char.GetUnicodeCategory(chr) == category)
{
return true;
}
}
return false;
}
/// <inheritdoc /> /// <inheritdoc />
public string GetLocalizedString(string phrase) public string GetLocalizedString(string phrase)
{ {
@ -350,24 +333,25 @@ namespace Emby.Server.Implementations.Localization
private async Task CopyInto(IDictionary<string, string> dictionary, string resourcePath) private async Task CopyInto(IDictionary<string, string> 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 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 (stream == null)
{ {
_logger.LogError("Missing translation/culture resource: {ResourcePath}", resourcePath);
return;
}
var dict = await JsonSerializer.DeserializeAsync<Dictionary<string, string>>(stream, _jsonOptions).ConfigureAwait(false); var dict = await JsonSerializer.DeserializeAsync<Dictionary<string, string>>(stream, _jsonOptions).ConfigureAwait(false);
if (dict == null)
{
throw new InvalidOperationException($"Resource contains invalid data: '{stream}'");
}
foreach (var key in dict.Keys) foreach (var key in dict.Keys)
{ {
dictionary[key] = dict[key]; dictionary[key] = dict[key];
} }
} }
else
{
_logger.LogError("Missing translation/culture resource: {ResourcePath}", resourcePath);
}
}
}
private static string GetResourceFilename(string culture) private static string GetResourceFilename(string culture)
{ {

@ -55,9 +55,19 @@ namespace Emby.Server.Implementations.ScheduledTasks
_localization = localization; _localization = localization;
} }
/// <summary> /// <inheritdoc />
/// Creates the triggers that define when the task will run. public string Name => _localization.GetLocalizedString("TaskRefreshChapterImages");
/// </summary>
/// <inheritdoc />
public string Description => _localization.GetLocalizedString("TaskRefreshChapterImagesDescription");
/// <inheritdoc />
public string Category => _localization.GetLocalizedString("TasksLibraryCategory");
/// <inheritdoc />
public string Key => "RefreshChapterImages";
/// <inheritdoc />
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers() public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
{ {
return new[] return new[]
@ -162,26 +172,5 @@ namespace Emby.Server.Implementations.ScheduledTasks
} }
} }
} }
/// <inheritdoc />
public string Name => _localization.GetLocalizedString("TaskRefreshChapterImages");
/// <inheritdoc />
public string Description => _localization.GetLocalizedString("TaskRefreshChapterImagesDescription");
/// <inheritdoc />
public string Category => _localization.GetLocalizedString("TasksLibraryCategory");
/// <inheritdoc />
public string Key => "RefreshChapterImages";
/// <inheritdoc />
public bool IsHidden => false;
/// <inheritdoc />
public bool IsEnabled => true;
/// <inheritdoc />
public bool IsLogged => true;
} }
} }

@ -444,6 +444,10 @@ namespace Jellyfin.Api.Helpers
{ {
var audioCodec = state.ActualOutputAudioCodec; var audioCodec = state.ActualOutputAudioCodec;
var videoCodec = state.ActualOutputVideoCodec; 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 _sessionManager.ReportTranscodingInfo(deviceId, new TranscodingInfo
{ {
@ -458,6 +462,7 @@ namespace Jellyfin.Api.Helpers
AudioChannels = state.OutputAudioChannels, AudioChannels = state.OutputAudioChannels,
IsAudioDirect = EncodingHelper.IsCopyCodec(state.OutputAudioCodec), IsAudioDirect = EncodingHelper.IsCopyCodec(state.OutputAudioCodec),
IsVideoDirect = EncodingHelper.IsCopyCodec(state.OutputVideoCodec), IsVideoDirect = EncodingHelper.IsCopyCodec(state.OutputVideoCodec),
HardwareAccelerationType = hardwareAccelerationType,
TranscodeReasons = state.TranscodeReasons TranscodeReasons = state.TranscodeReasons
}); });
} }

@ -14,7 +14,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="5.0.8" /> <PackageReference Include="Microsoft.AspNetCore.Authorization" Version="5.0.9" />
<PackageReference Include="Microsoft.Extensions.Http" Version="5.0.0" /> <PackageReference Include="Microsoft.Extensions.Http" Version="5.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.1.5" /> <PackageReference Include="Swashbuckle.AspNetCore" Version="6.1.5" />
<PackageReference Include="Swashbuckle.AspNetCore.ReDoc" Version="6.1.5" /> <PackageReference Include="Swashbuckle.AspNetCore.ReDoc" Version="6.1.5" />

@ -19,13 +19,13 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="System.Linq.Async" Version="5.0.0" /> <PackageReference Include="System.Linq.Async" Version="5.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.8" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.9" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="5.0.8" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="5.0.9" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="5.0.8"> <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="5.0.9">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="5.0.8"> <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="5.0.9">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>

@ -303,7 +303,7 @@ namespace Jellyfin.Server.Extensions
{ {
description.TryGetMethodInfo(out MethodInfo methodInfo); description.TryGetMethodInfo(out MethodInfo methodInfo);
// Attribute name, method name, none. // Attribute name, method name, none.
return description?.ActionDescriptor?.AttributeRouteInfo?.Name return description?.ActionDescriptor.AttributeRouteInfo?.Name
?? methodInfo?.Name ?? methodInfo?.Name
?? null; ?? null;
}); });
@ -341,7 +341,7 @@ namespace Jellyfin.Server.Extensions
{ {
foreach (var address in host.GetAddresses()) 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", Type = "object",
Properties = typeof(ImageType).GetEnumNames().ToDictionary( Properties = typeof(ImageType).GetEnumNames().ToDictionary(
name => name, name => name,
name => new OpenApiSchema _ => new OpenApiSchema
{ {
Type = "object", Type = "object",
AdditionalProperties = new OpenApiSchema AdditionalProperties = new OpenApiSchema

@ -33,13 +33,13 @@
<PackageReference Include="CommandLineParser" Version="2.8.0" /> <PackageReference Include="CommandLineParser" Version="2.8.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="5.0.0" /> <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="5.0.0" /> <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="5.0.8" /> <PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="5.0.9" />
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="5.0.8" /> <PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="5.0.9" />
<PackageReference Include="prometheus-net" Version="4.2.0" /> <PackageReference Include="prometheus-net" Version="4.2.0" />
<PackageReference Include="prometheus-net.AspNetCore" Version="4.2.0" /> <PackageReference Include="prometheus-net.AspNetCore" Version="4.2.0" />
<PackageReference Include="Serilog.AspNetCore" Version="4.1.0" /> <PackageReference Include="Serilog.AspNetCore" Version="4.1.0" />
<PackageReference Include="Serilog.Enrichers.Thread" Version="3.1.0" /> <PackageReference Include="Serilog.Enrichers.Thread" Version="3.1.0" />
<PackageReference Include="Serilog.Settings.Configuration" Version="3.1.0" /> <PackageReference Include="Serilog.Settings.Configuration" Version="3.2.0" />
<PackageReference Include="Serilog.Sinks.Async" Version="1.5.0" /> <PackageReference Include="Serilog.Sinks.Async" Version="1.5.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="4.0.0" /> <PackageReference Include="Serilog.Sinks.Console" Version="4.0.0" />
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" /> <PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />

@ -58,9 +58,12 @@ namespace Jellyfin.Server.Middleware
return; 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); _logger.LogDebug("Normalizing an URL at {LocalPath}", localPath);
httpContext.Response.Redirect(baseUrlPrefix + "/" + _configuration[ConfigurationExtensions.DefaultRedirectKey]); httpContext.Response.Redirect(baseUrlPrefix + "/" + _configuration[ConfigurationExtensions.DefaultRedirectKey]);
return; return;

@ -137,11 +137,6 @@ namespace Jellyfin.Server.Middleware
private string NormalizeExceptionMessage(string msg) private string NormalizeExceptionMessage(string msg)
{ {
if (msg == null)
{
return string.Empty;
}
// Strip any information we don't want to reveal // Strip any information we don't want to reveal
return msg.Replace( return msg.Replace(
_configuration.ApplicationPaths.ProgramSystemPath, _configuration.ApplicationPaths.ProgramSystemPath,

@ -40,7 +40,7 @@ namespace Jellyfin.Server.Migrations
.Select(m => ActivatorUtilities.CreateInstance(host.ServiceProvider, m)) .Select(m => ActivatorUtilities.CreateInstance(host.ServiceProvider, m))
.OfType<IMigrationRoutine>() .OfType<IMigrationRoutine>()
.ToArray(); .ToArray();
var migrationOptions = ((IConfigurationManager)host.ConfigurationManager).GetConfiguration<MigrationOptions>(MigrationsListStore.StoreKey); var migrationOptions = host.ConfigurationManager.GetConfiguration<MigrationOptions>(MigrationsListStore.StoreKey);
if (!host.ConfigurationManager.Configuration.IsStartupWizardCompleted && migrationOptions.Applied.Count == 0) if (!host.ConfigurationManager.Configuration.IsStartupWizardCompleted && migrationOptions.Applied.Count == 0)
{ {

@ -92,7 +92,7 @@ namespace Jellyfin.Server.Migrations.Routines
if (entry[6].SQLiteType != SQLiteType.Null && !Guid.TryParse(entry[6].ToString(), out guid)) 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 // 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"); using var statement = userDbConnection.PrepareStatement("SELECT guid FROM LocalUsersv2 WHERE Id=@Id");
statement.TryBind("@Id", entry[6].ToString()); statement.TryBind("@Id", entry[6].ToString());

@ -1,6 +1,5 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text.Json; using System.Text.Json;

@ -5,7 +5,6 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Reflection; using System.Reflection;
using System.Runtime.InteropServices;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -121,11 +120,11 @@ namespace Jellyfin.Server
// Log uncaught exceptions to the logging instead of std error // Log uncaught exceptions to the logging instead of std error
AppDomain.CurrentDomain.UnhandledException -= UnhandledExceptionToConsole; AppDomain.CurrentDomain.UnhandledException -= UnhandledExceptionToConsole;
AppDomain.CurrentDomain.UnhandledException += (sender, e) AppDomain.CurrentDomain.UnhandledException += (_, e)
=> _logger.LogCritical((Exception)e.ExceptionObject, "Unhandled Exception"); => _logger.LogCritical((Exception)e.ExceptionObject, "Unhandled Exception");
// Intercept Ctrl+C and Ctrl+Break // Intercept Ctrl+C and Ctrl+Break
Console.CancelKeyPress += (sender, e) => Console.CancelKeyPress += (_, e) =>
{ {
if (_tokenSource.IsCancellationRequested) if (_tokenSource.IsCancellationRequested)
{ {
@ -139,7 +138,7 @@ namespace Jellyfin.Server
}; };
// Register a SIGTERM handler // Register a SIGTERM handler
AppDomain.CurrentDomain.ProcessExit += (sender, e) => AppDomain.CurrentDomain.ProcessExit += (_, _) =>
{ {
if (_tokenSource.IsCancellationRequested) 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 " + "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 " + $"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" + "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 // Get a stream of the resource contents
// NOTE: The .csproj name is used instead of the assembly name in the resource path // NOTE: The .csproj name is used instead of the assembly name in the resource path
const string ResourcePath = "Jellyfin.Server.Resources.Configuration.logging.json"; 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}'"); ?? throw new InvalidOperationException($"Invalid resource path: '{ResourcePath}'");
// Copy the resource contents to the expected file path for the config file // Copy the resource contents to the expected file path for the config file

@ -40,7 +40,7 @@ namespace MediaBrowser.Common.Extensions
// Add an event handler for the process exit event // Add an event handler for the process exit event
var tcs = new TaskCompletionSource<bool>(); var tcs = new TaskCompletionSource<bool>();
process.Exited += (sender, args) => tcs.TrySetResult(true); process.Exited += (_, _) => tcs.TrySetResult(true);
// Return immediately if the process has already exited // Return immediately if the process has already exited
if (process.HasExitedSafe()) if (process.HasExitedSafe())

@ -4,7 +4,6 @@ using System.Linq;
using System.Net; using System.Net;
using System.Net.Sockets; using System.Net.Sockets;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace MediaBrowser.Common.Net namespace MediaBrowser.Common.Net
{ {
@ -196,7 +195,7 @@ namespace MediaBrowser.Common.Net
return res; return res;
} }
throw new InvalidCastException("Host does not contain a valid value. {host}"); throw new InvalidCastException($"Host does not contain a valid value. {host}");
} }
/// <summary> /// <summary>
@ -221,7 +220,7 @@ namespace MediaBrowser.Common.Net
return res; return res;
} }
throw new InvalidCastException("Host does not contain a valid value. {host}"); throw new InvalidCastException($"Host does not contain a valid value. {host}");
} }
/// <summary> /// <summary>
@ -349,7 +348,7 @@ namespace MediaBrowser.Common.Net
} }
} }
output = output[0..^1]; output = output[..^1];
if (moreThanOne) if (moreThanOne)
{ {
@ -400,7 +399,7 @@ namespace MediaBrowser.Common.Net
if ((_addresses.Length == 0 && !Resolved) || (DateTime.UtcNow > _lastResolved.Value.AddMinutes(Timeout))) if ((_addresses.Length == 0 && !Resolved) || (DateTime.UtcNow > _lastResolved.Value.AddMinutes(Timeout)))
{ {
_lastResolved = DateTime.UtcNow; _lastResolved = DateTime.UtcNow;
ResolveHostInternal().GetAwaiter().GetResult(); ResolveHostInternal();
Resolved = true; Resolved = true;
} }
@ -410,30 +409,31 @@ namespace MediaBrowser.Common.Net
/// <summary> /// <summary>
/// Task that looks up a Host name and returns its IP addresses. /// Task that looks up a Host name and returns its IP addresses.
/// </summary> /// </summary>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns> private void ResolveHostInternal()
private async Task ResolveHostInternal()
{ {
if (!string.IsNullOrEmpty(HostName)) var hostName = HostName;
if (string.IsNullOrEmpty(hostName))
{ {
return;
}
// Resolves the host name - so save a DNS lookup. // Resolves the host name - so save a DNS lookup.
if (string.Equals(HostName, "localhost", StringComparison.OrdinalIgnoreCase)) if (string.Equals(hostName, "localhost", StringComparison.OrdinalIgnoreCase))
{ {
_addresses = new IPAddress[] { IPAddress.Loopback, IPAddress.IPv6Loopback }; _addresses = new IPAddress[] { IPAddress.Loopback, IPAddress.IPv6Loopback };
return; return;
} }
if (Uri.CheckHostName(HostName).Equals(UriHostNameType.Dns)) if (Uri.CheckHostName(hostName) == UriHostNameType.Dns)
{ {
try try
{ {
IPHostEntry ip = await Dns.GetHostEntryAsync(HostName).ConfigureAwait(false); _addresses = Dns.GetHostEntry(hostName).AddressList;
_addresses = ip.AddressList;
} }
catch (SocketException ex) catch (SocketException ex)
{ {
// Log and then ignore socket errors, as the result value will just be an empty array. // Log and then ignore socket errors, as the result value will just be an empty array.
Debug.WriteLine("GetHostEntryAsync failed with {Message}.", ex.Message); Debug.WriteLine("GetHostAddresses failed with {Message}.", ex.Message);
}
} }
} }
} }

@ -47,10 +47,10 @@ namespace MediaBrowser.Common.Plugins
var assemblyFilePath = assembly.Location; var assemblyFilePath = assembly.Location;
var dataFolderPath = Path.Combine(ApplicationPaths.PluginsPath, Path.GetFileNameWithoutExtension(assemblyFilePath)); 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. // Try again with the version number appended to the folder name.
dataFolderPath = dataFolderPath + "_" + Version.ToString(); dataFolderPath += "_" + Version.ToString();
} }
SetAttributes(assemblyFilePath, dataFolderPath, assemblyName.Version); SetAttributes(assemblyFilePath, dataFolderPath, assemblyName.Version);

@ -18,7 +18,7 @@ namespace MediaBrowser.Common.Providers
/// <param name="text">The text to parse.</param> /// <param name="text">The text to parse.</param>
/// <param name="imdbId">The parsed IMDb id.</param> /// <param name="imdbId">The parsed IMDb id.</param>
/// <returns>True if parsing was successful, false otherwise.</returns> /// <returns>True if parsing was successful, false otherwise.</returns>
public static bool TryFindImdbId(ReadOnlySpan<char> text, [NotNullWhen(true)] out ReadOnlySpan<char> imdbId) public static bool TryFindImdbId(ReadOnlySpan<char> text, out ReadOnlySpan<char> imdbId)
{ {
// imdb id is at least 9 chars (tt + 7 numbers) // imdb id is at least 9 chars (tt + 7 numbers)
while (text.Length >= 2 + ImdbMinNumbers) while (text.Length >= 2 + ImdbMinNumbers)
@ -62,7 +62,7 @@ namespace MediaBrowser.Common.Providers
/// <param name="text">The text with the url to parse.</param> /// <param name="text">The text with the url to parse.</param>
/// <param name="tmdbId">The parsed TMDb id.</param> /// <param name="tmdbId">The parsed TMDb id.</param>
/// <returns>True if parsing was successful, false otherwise.</returns> /// <returns>True if parsing was successful, false otherwise.</returns>
public static bool TryFindTmdbMovieId(ReadOnlySpan<char> text, [NotNullWhen(true)] out ReadOnlySpan<char> tmdbId) public static bool TryFindTmdbMovieId(ReadOnlySpan<char> text, out ReadOnlySpan<char> tmdbId)
=> TryFindProviderId(text, "themoviedb.org/movie/", out tmdbId); => TryFindProviderId(text, "themoviedb.org/movie/", out tmdbId);
/// <summary> /// <summary>
@ -71,7 +71,7 @@ namespace MediaBrowser.Common.Providers
/// <param name="text">The text with the url to parse.</param> /// <param name="text">The text with the url to parse.</param>
/// <param name="tmdbId">The parsed TMDb id.</param> /// <param name="tmdbId">The parsed TMDb id.</param>
/// <returns>True if parsing was successful, false otherwise.</returns> /// <returns>True if parsing was successful, false otherwise.</returns>
public static bool TryFindTmdbSeriesId(ReadOnlySpan<char> text, [NotNullWhen(true)] out ReadOnlySpan<char> tmdbId) public static bool TryFindTmdbSeriesId(ReadOnlySpan<char> text, out ReadOnlySpan<char> tmdbId)
=> TryFindProviderId(text, "themoviedb.org/tv/", out tmdbId); => TryFindProviderId(text, "themoviedb.org/tv/", out tmdbId);
/// <summary> /// <summary>
@ -80,7 +80,7 @@ namespace MediaBrowser.Common.Providers
/// <param name="text">The text with the url to parse.</param> /// <param name="text">The text with the url to parse.</param>
/// <param name="tvdbId">The parsed TVDb id.</param> /// <param name="tvdbId">The parsed TVDb id.</param>
/// <returns>True if parsing was successful, false otherwise.</returns> /// <returns>True if parsing was successful, false otherwise.</returns>
public static bool TryFindTvdbId(ReadOnlySpan<char> text, [NotNullWhen(true)] out ReadOnlySpan<char> tvdbId) public static bool TryFindTvdbId(ReadOnlySpan<char> text, out ReadOnlySpan<char> tvdbId)
=> TryFindProviderId(text, "thetvdb.com/?tab=series&id=", out tvdbId); => TryFindProviderId(text, "thetvdb.com/?tab=series&id=", out tvdbId);
private static bool TryFindProviderId(ReadOnlySpan<char> text, ReadOnlySpan<char> searchString, [NotNullWhen(true)] out ReadOnlySpan<char> providerId) private static bool TryFindProviderId(ReadOnlySpan<char> text, ReadOnlySpan<char> searchString, [NotNullWhen(true)] out ReadOnlySpan<char> providerId)

@ -1,6 +1,5 @@
#nullable disable
using System; using System;
using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using Jellyfin.Extensions; using Jellyfin.Extensions;
@ -16,7 +15,7 @@ namespace MediaBrowser.Controller.BaseItemManager
{ {
private readonly IServerConfigurationManager _serverConfigurationManager; private readonly IServerConfigurationManager _serverConfigurationManager;
private int _metadataRefreshConcurrency = 0; private int _metadataRefreshConcurrency;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="BaseItemManager"/> class. /// Initializes a new instance of the <see cref="BaseItemManager"/> class.
@ -101,7 +100,7 @@ namespace MediaBrowser.Controller.BaseItemManager
/// Called when the configuration is updated. /// Called when the configuration is updated.
/// It will refresh the metadata throttler if the relevant config changed. /// It will refresh the metadata throttler if the relevant config changed.
/// </summary> /// </summary>
private void OnConfigurationUpdated(object sender, EventArgs e) private void OnConfigurationUpdated(object? sender, EventArgs e)
{ {
int newMetadataRefreshConcurrency = GetMetadataRefreshConcurrency(); int newMetadataRefreshConcurrency = GetMetadataRefreshConcurrency();
if (_metadataRefreshConcurrency != newMetadataRefreshConcurrency) if (_metadataRefreshConcurrency != newMetadataRefreshConcurrency)
@ -114,6 +113,7 @@ namespace MediaBrowser.Controller.BaseItemManager
/// <summary> /// <summary>
/// Creates the metadata refresh throttler. /// Creates the metadata refresh throttler.
/// </summary> /// </summary>
[MemberNotNull(nameof(MetadataRefreshThrottler))]
private void SetupMetadataThrottler() private void SetupMetadataThrottler()
{ {
MetadataRefreshThrottler = new SemaphoreSlim(_metadataRefreshConcurrency); MetadataRefreshThrottler = new SemaphoreSlim(_metadataRefreshConcurrency);

@ -1,5 +1,3 @@
#nullable disable
using System.Threading; using System.Threading;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Configuration;

@ -1,7 +1,6 @@
#nullable disable #pragma warning disable CS1591
#pragma warning disable CA1002, CA2227, CS1591
using System;
using System.Collections.Generic; using System.Collections.Generic;
namespace MediaBrowser.Controller.Channels namespace MediaBrowser.Controller.Channels
@ -10,10 +9,10 @@ namespace MediaBrowser.Controller.Channels
{ {
public ChannelItemResult() public ChannelItemResult()
{ {
Items = new List<ChannelItemInfo>(); Items = Array.Empty<ChannelItemInfo>();
} }
public List<ChannelItemInfo> Items { get; set; } public IReadOnlyList<ChannelItemInfo> Items { get; set; }
public int? TotalRecordCount { get; set; } public int? TotalRecordCount { get; set; }
} }

@ -1,6 +1,6 @@
#nullable disable #nullable disable
#pragma warning disable CA2227, CS1591 #pragma warning disable CS1591
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;

@ -1,5 +1,3 @@
#nullable disable
#pragma warning disable CS1591 #pragma warning disable CS1591
using System; using System;

@ -1,5 +1,3 @@
#nullable disable
#pragma warning disable CS1591 #pragma warning disable CS1591
using System; using System;
@ -16,17 +14,17 @@ namespace MediaBrowser.Controller.Collections
/// <summary> /// <summary>
/// Occurs when [collection created]. /// Occurs when [collection created].
/// </summary> /// </summary>
event EventHandler<CollectionCreatedEventArgs> CollectionCreated; event EventHandler<CollectionCreatedEventArgs>? CollectionCreated;
/// <summary> /// <summary>
/// Occurs when [items added to collection]. /// Occurs when [items added to collection].
/// </summary> /// </summary>
event EventHandler<CollectionModifiedEventArgs> ItemsAddedToCollection; event EventHandler<CollectionModifiedEventArgs>? ItemsAddedToCollection;
/// <summary> /// <summary>
/// Occurs when [items removed from collection]. /// Occurs when [items removed from collection].
/// </summary> /// </summary>
event EventHandler<CollectionModifiedEventArgs> ItemsRemovedFromCollection; event EventHandler<CollectionModifiedEventArgs>? ItemsRemovedFromCollection;
/// <summary> /// <summary>
/// Creates the collection. /// Creates the collection.

@ -1,5 +1,3 @@
#nullable disable
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Configuration;

@ -1,5 +1,3 @@
#nullable disable
#pragma warning disable CS1591 #pragma warning disable CS1591
using System.Collections.Generic; using System.Collections.Generic;
@ -22,7 +20,7 @@ namespace MediaBrowser.Controller.Dlna
/// </summary> /// </summary>
/// <param name="headers">The headers.</param> /// <param name="headers">The headers.</param>
/// <returns>DeviceProfile.</returns> /// <returns>DeviceProfile.</returns>
DeviceProfile GetProfile(IHeaderDictionary headers); DeviceProfile? GetProfile(IHeaderDictionary headers);
/// <summary> /// <summary>
/// Gets the default profile. /// Gets the default profile.
@ -53,14 +51,14 @@ namespace MediaBrowser.Controller.Dlna
/// </summary> /// </summary>
/// <param name="id">The identifier.</param> /// <param name="id">The identifier.</param>
/// <returns>DeviceProfile.</returns> /// <returns>DeviceProfile.</returns>
DeviceProfile GetProfile(string id); DeviceProfile? GetProfile(string id);
/// <summary> /// <summary>
/// Gets the profile. /// Gets the profile.
/// </summary> /// </summary>
/// <param name="deviceInfo">The device information.</param> /// <param name="deviceInfo">The device information.</param>
/// <returns>DeviceProfile.</returns> /// <returns>DeviceProfile.</returns>
DeviceProfile GetProfile(DeviceIdentification deviceInfo); DeviceProfile? GetProfile(DeviceIdentification deviceInfo);
/// <summary> /// <summary>
/// Gets the server description XML. /// Gets the server description XML.

@ -1,6 +1,6 @@
#nullable disable #nullable disable
#pragma warning disable CS1591 #pragma warning disable CA1819, CS1591
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
@ -18,52 +18,51 @@ namespace MediaBrowser.Controller.Entities
{ {
/// <summary> /// <summary>
/// Specialized folder that can have items added to it's children by external 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.
/// </summary> /// </summary>
public class AggregateFolder : Folder public class AggregateFolder : Folder
{ {
private readonly object _childIdsLock = new object();
/// <summary>
/// The _virtual children.
/// </summary>
private readonly ConcurrentBag<BaseItem> _virtualChildren = new ConcurrentBag<BaseItem>();
private bool _requiresRefresh; private bool _requiresRefresh;
private Guid[] _childrenIds = null;
public AggregateFolder() public AggregateFolder()
{ {
PhysicalLocationsList = Array.Empty<string>(); PhysicalLocationsList = Array.Empty<string>();
} }
[JsonIgnore]
public override bool IsPhysicalRoot => true;
public override bool CanDelete()
{
return false;
}
[JsonIgnore]
public override bool SupportsPlayedStatus => false;
/// <summary>
/// The _virtual children.
/// </summary>
private readonly ConcurrentBag<BaseItem> _virtualChildren = new ConcurrentBag<BaseItem>();
/// <summary> /// <summary>
/// Gets the virtual children. /// Gets the virtual children.
/// </summary> /// </summary>
/// <value>The virtual children.</value> /// <value>The virtual children.</value>
public ConcurrentBag<BaseItem> VirtualChildren => _virtualChildren; public ConcurrentBag<BaseItem> VirtualChildren => _virtualChildren;
[JsonIgnore]
public override bool IsPhysicalRoot => true;
[JsonIgnore]
public override bool SupportsPlayedStatus => false;
[JsonIgnore] [JsonIgnore]
public override string[] PhysicalLocations => PhysicalLocationsList; public override string[] PhysicalLocations => PhysicalLocationsList;
public string[] PhysicalLocationsList { get; set; } public string[] PhysicalLocationsList { get; set; }
public override bool CanDelete()
{
return false;
}
protected override FileSystemMetadata[] GetFileSystemChildren(IDirectoryService directoryService) protected override FileSystemMetadata[] GetFileSystemChildren(IDirectoryService directoryService)
{ {
return CreateResolveArgs(directoryService, true).FileSystemChildren; return CreateResolveArgs(directoryService, true).FileSystemChildren;
} }
private Guid[] _childrenIds = null;
private readonly object _childIdsLock = new object();
protected override List<BaseItem> LoadChildren() protected override List<BaseItem> LoadChildren()
{ {
lock (_childIdsLock) lock (_childIdsLock)
@ -169,7 +168,7 @@ namespace MediaBrowser.Controller.Entities
/// Adds the virtual child. /// Adds the virtual child.
/// </summary> /// </summary>
/// <param name="child">The child.</param> /// <param name="child">The child.</param>
/// <exception cref="ArgumentNullException"></exception> /// <exception cref="ArgumentNullException">Throws if child is null.</exception>
public void AddVirtualChild(BaseItem child) public void AddVirtualChild(BaseItem child)
{ {
if (child == null) if (child == null)

@ -1,6 +1,6 @@
#nullable disable #nullable disable
#pragma warning disable CS1591 #pragma warning disable CA1002, CA1724, CA1826, CS1591
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@ -25,6 +25,12 @@ namespace MediaBrowser.Controller.Entities.Audio
IHasLookupInfo<SongInfo>, IHasLookupInfo<SongInfo>,
IHasMediaSources IHasMediaSources
{ {
public Audio()
{
Artists = Array.Empty<string>();
AlbumArtists = Array.Empty<string>();
}
/// <inheritdoc /> /// <inheritdoc />
[JsonIgnore] [JsonIgnore]
public IReadOnlyList<string> Artists { get; set; } public IReadOnlyList<string> Artists { get; set; }
@ -33,17 +39,6 @@ namespace MediaBrowser.Controller.Entities.Audio
[JsonIgnore] [JsonIgnore]
public IReadOnlyList<string> AlbumArtists { get; set; } public IReadOnlyList<string> AlbumArtists { get; set; }
public Audio()
{
Artists = Array.Empty<string>();
AlbumArtists = Array.Empty<string>();
}
public override double GetDefaultPrimaryImageAspectRatio()
{
return 1;
}
[JsonIgnore] [JsonIgnore]
public override bool SupportsPlayedStatus => true; public override bool SupportsPlayedStatus => true;
@ -62,11 +57,6 @@ namespace MediaBrowser.Controller.Entities.Audio
[JsonIgnore] [JsonIgnore]
public override Folder LatestItemsIndexContainer => AlbumEntity; public override Folder LatestItemsIndexContainer => AlbumEntity;
public override bool CanDownload()
{
return IsFileProtocol;
}
[JsonIgnore] [JsonIgnore]
public MusicAlbum AlbumEntity => FindParent<MusicAlbum>(); public MusicAlbum AlbumEntity => FindParent<MusicAlbum>();
@ -77,6 +67,16 @@ namespace MediaBrowser.Controller.Entities.Audio
[JsonIgnore] [JsonIgnore]
public override string MediaType => Model.Entities.MediaType.Audio; public override string MediaType => Model.Entities.MediaType.Audio;
public override double GetDefaultPrimaryImageAspectRatio()
{
return 1;
}
public override bool CanDownload()
{
return IsFileProtocol;
}
/// <summary> /// <summary>
/// Creates the name of the sort. /// Creates the name of the sort.
/// </summary> /// </summary>

@ -1,6 +1,6 @@
#nullable disable #nullable disable
#pragma warning disable CS1591 #pragma warning disable CA1819, CS1591
namespace MediaBrowser.Controller.Entities.Audio namespace MediaBrowser.Controller.Entities.Audio
{ {

@ -1,6 +1,6 @@
#nullable disable #nullable disable
#pragma warning disable CS1591 #pragma warning disable CA1721, CA1826, CS1591
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@ -23,18 +23,18 @@ namespace MediaBrowser.Controller.Entities.Audio
/// </summary> /// </summary>
public class MusicAlbum : Folder, IHasAlbumArtist, IHasArtist, IHasMusicGenres, IHasLookupInfo<AlbumInfo>, IMetadataContainer public class MusicAlbum : Folder, IHasAlbumArtist, IHasArtist, IHasMusicGenres, IHasLookupInfo<AlbumInfo>, IMetadataContainer
{ {
/// <inheritdoc />
public IReadOnlyList<string> AlbumArtists { get; set; }
/// <inheritdoc />
public IReadOnlyList<string> Artists { get; set; }
public MusicAlbum() public MusicAlbum()
{ {
Artists = Array.Empty<string>(); Artists = Array.Empty<string>();
AlbumArtists = Array.Empty<string>(); AlbumArtists = Array.Empty<string>();
} }
/// <inheritdoc />
public IReadOnlyList<string> AlbumArtists { get; set; }
/// <inheritdoc />
public IReadOnlyList<string> Artists { get; set; }
[JsonIgnore] [JsonIgnore]
public override bool SupportsAddingToPlaylist => true; public override bool SupportsAddingToPlaylist => true;
@ -44,6 +44,25 @@ namespace MediaBrowser.Controller.Entities.Audio
[JsonIgnore] [JsonIgnore]
public MusicArtist MusicArtist => GetMusicArtist(new DtoOptions(true)); 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;
/// <summary>
/// Gets the tracks.
/// </summary>
/// <value>The tracks.</value>
[JsonIgnore]
public IEnumerable<Audio> Tracks => GetRecursiveChildren(i => i is Audio).Cast<Audio>();
public MusicArtist GetMusicArtist(DtoOptions options) public MusicArtist GetMusicArtist(DtoOptions options)
{ {
var parents = GetParents(); var parents = GetParents();
@ -64,25 +83,6 @@ namespace MediaBrowser.Controller.Entities.Audio
return null; return null;
} }
[JsonIgnore]
public override bool SupportsPlayedStatus => false;
[JsonIgnore]
public override bool SupportsCumulativeRunTimeTicks => true;
[JsonIgnore]
public string AlbumArtist => AlbumArtists.FirstOrDefault();
[JsonIgnore]
public override bool SupportsPeople => false;
/// <summary>
/// Gets the tracks.
/// </summary>
/// <value>The tracks.</value>
[JsonIgnore]
public IEnumerable<Audio> Tracks => GetRecursiveChildren(i => i is Audio).Cast<Audio>();
protected override IEnumerable<BaseItem> GetEligibleChildrenForRecursiveChildren(User user) protected override IEnumerable<BaseItem> GetEligibleChildrenForRecursiveChildren(User user)
{ {
return Tracks; return Tracks;

@ -44,6 +44,36 @@ namespace MediaBrowser.Controller.Entities.Audio
[JsonIgnore] [JsonIgnore]
public override bool SupportsPlayedStatus => false; public override bool SupportsPlayedStatus => false;
/// <summary>
/// Gets the folder containing the item.
/// If the item is a folder, it returns the folder itself.
/// </summary>
/// <value>The containing folder path.</value>
[JsonIgnore]
public override string ContainingFolderPath => Path;
[JsonIgnore]
public override IEnumerable<BaseItem> Children
{
get
{
if (IsAccessedByName)
{
return new List<BaseItem>();
}
return base.Children;
}
}
[JsonIgnore]
public override bool SupportsPeople => false;
public static string GetPath(string name)
{
return GetPath(name, true);
}
public override double GetDefaultPrimaryImageAspectRatio() public override double GetDefaultPrimaryImageAspectRatio()
{ {
return 1; return 1;
@ -65,20 +95,6 @@ namespace MediaBrowser.Controller.Entities.Audio
return LibraryManager.GetItemList(query); return LibraryManager.GetItemList(query);
} }
[JsonIgnore]
public override IEnumerable<BaseItem> Children
{
get
{
if (IsAccessedByName)
{
return new List<BaseItem>();
}
return base.Children;
}
}
public override int GetChildCount(User user) public override int GetChildCount(User user)
{ {
return IsAccessedByName ? 0 : base.GetChildCount(user); return IsAccessedByName ? 0 : base.GetChildCount(user);
@ -113,14 +129,6 @@ namespace MediaBrowser.Controller.Entities.Audio
return list; return list;
} }
/// <summary>
/// Gets the folder containing the item.
/// If the item is a folder, it returns the folder itself.
/// </summary>
/// <value>The containing folder path.</value>
[JsonIgnore]
public override string ContainingFolderPath => Path;
/// <summary> /// <summary>
/// Gets the user data key. /// Gets the user data key.
/// </summary> /// </summary>
@ -167,14 +175,6 @@ namespace MediaBrowser.Controller.Entities.Audio
return info; return info;
} }
[JsonIgnore]
public override bool SupportsPeople => false;
public static string GetPath(string name)
{
return GetPath(name, true);
}
public static string GetPath(string name, bool normalizeName) public static string GetPath(string name, bool normalizeName)
{ {
// Trim the period at the end because windows will have a hard time with that // Trim the period at the end because windows will have a hard time with that
@ -208,6 +208,8 @@ namespace MediaBrowser.Controller.Entities.Audio
/// <summary> /// <summary>
/// This is called before any metadata refresh and returns true or false indicating if changes were made. /// This is called before any metadata refresh and returns true or false indicating if changes were made.
/// </summary> /// </summary>
/// <param name="replaceAllMetadata">Option to replace metadata.</param>
/// <returns>True if metadata changed.</returns>
public override bool BeforeMetadataRefresh(bool replaceAllMetadata) public override bool BeforeMetadataRefresh(bool replaceAllMetadata)
{ {
var hasChanges = base.BeforeMetadataRefresh(replaceAllMetadata); var hasChanges = base.BeforeMetadataRefresh(replaceAllMetadata);

@ -15,19 +15,6 @@ namespace MediaBrowser.Controller.Entities.Audio
/// </summary> /// </summary>
public class MusicGenre : BaseItem, IItemByName public class MusicGenre : BaseItem, IItemByName
{ {
public override List<string> GetUserDataKeys()
{
var list = base.GetUserDataKeys();
list.Insert(0, GetType().Name + "-" + (Name ?? string.Empty).RemoveDiacritics());
return list;
}
public override string CreatePresentationUniqueKey()
{
return GetUserDataKeys()[0];
}
[JsonIgnore] [JsonIgnore]
public override bool SupportsAddingToPlaylist => true; public override bool SupportsAddingToPlaylist => true;
@ -45,6 +32,22 @@ namespace MediaBrowser.Controller.Entities.Audio
[JsonIgnore] [JsonIgnore]
public override string ContainingFolderPath => Path; public override string ContainingFolderPath => Path;
[JsonIgnore]
public override bool SupportsPeople => false;
public override List<string> 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() public override double GetDefaultPrimaryImageAspectRatio()
{ {
return 1; return 1;
@ -60,9 +63,6 @@ namespace MediaBrowser.Controller.Entities.Audio
return true; return true;
} }
[JsonIgnore]
public override bool SupportsPeople => false;
public IList<BaseItem> GetTaggedItems(InternalItemsQuery query) public IList<BaseItem> GetTaggedItems(InternalItemsQuery query)
{ {
query.GenreIds = new[] { Id }; query.GenreIds = new[] { Id };
@ -106,6 +106,8 @@ namespace MediaBrowser.Controller.Entities.Audio
/// <summary> /// <summary>
/// This is called before any metadata refresh and returns true or false indicating if changes were made. /// This is called before any metadata refresh and returns true or false indicating if changes were made.
/// </summary> /// </summary>
/// <param name="replaceAllMetadata">Option to replace metadata.</param>
/// <returns>True if metadata changed.</returns>
public override bool BeforeMetadataRefresh(bool replaceAllMetadata) public override bool BeforeMetadataRefresh(bool replaceAllMetadata)
{ {
var hasChanges = base.BeforeMetadataRefresh(replaceAllMetadata); var hasChanges = base.BeforeMetadataRefresh(replaceAllMetadata);

@ -1,6 +1,6 @@
#nullable disable #nullable disable
#pragma warning disable CS1591 #pragma warning disable CA1724, CS1591
using System; using System;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;

File diff suppressed because it is too large Load Diff

@ -64,6 +64,8 @@ namespace MediaBrowser.Controller.Entities
/// </summary> /// </summary>
/// <param name="source">The source object.</param> /// <param name="source">The source object.</param>
/// <param name="dest">The destination object.</param> /// <param name="dest">The destination object.</param>
/// <typeparam name="T">Source type.</typeparam>
/// <typeparam name="TU">Destination type.</typeparam>
public static void DeepCopy<T, TU>(this T source, TU dest) public static void DeepCopy<T, TU>(this T source, TU dest)
where T : BaseItem where T : BaseItem
where TU : 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. /// Copies all properties on newly created object. Skips properties that do not exist.
/// </summary> /// </summary>
/// <param name="source">The source object.</param> /// <param name="source">The source object.</param>
/// <typeparam name="T">Source type.</typeparam>
/// <typeparam name="TU">Destination type.</typeparam>
/// <returns>Destination object.</returns>
public static TU DeepCopy<T, TU>(this T source) public static TU DeepCopy<T, TU>(this T source)
where T : BaseItem where T : BaseItem
where TU : BaseItem, new() where TU : BaseItem, new()

@ -15,6 +15,12 @@ namespace MediaBrowser.Controller.Entities
[JsonIgnore] [JsonIgnore]
public virtual string CollectionType => null; public virtual string CollectionType => null;
[JsonIgnore]
public override bool SupportsInheritedParentImages => false;
[JsonIgnore]
public override bool SupportsPeople => false;
public override bool CanDelete() public override bool CanDelete()
{ {
return false; return false;
@ -24,11 +30,5 @@ namespace MediaBrowser.Controller.Entities
{ {
return true; return true;
} }
[JsonIgnore]
public override bool SupportsInheritedParentImages => false;
[JsonIgnore]
public override bool SupportsPeople => false;
} }
} }

@ -41,6 +41,23 @@ namespace MediaBrowser.Controller.Entities
PhysicalFolderIds = Array.Empty<Guid>(); PhysicalFolderIds = Array.Empty<Guid>();
} }
/// <summary>
/// Gets the display preferences id.
/// </summary>
/// <remarks>
/// Allow different display preferences for each collection folder.
/// </remarks>
/// <value>The display prefs id.</value>
[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 IXmlSerializer XmlSerializer { get; set; }
public static IServerApplicationHost ApplicationHost { get; set; } public static IServerApplicationHost ApplicationHost { get; set; }
@ -63,6 +80,9 @@ namespace MediaBrowser.Controller.Entities
[JsonIgnore] [JsonIgnore]
public override IEnumerable<BaseItem> Children => GetActualChildren(); public override IEnumerable<BaseItem> Children => GetActualChildren();
[JsonIgnore]
public override bool SupportsPeople => false;
public override bool CanDelete() public override bool CanDelete()
{ {
return false; return false;
@ -160,23 +180,6 @@ namespace MediaBrowser.Controller.Entities
} }
} }
/// <summary>
/// Gets the display preferences id.
/// </summary>
/// <remarks>
/// Allow different display preferences for each collection folder.
/// </remarks>
/// <value>The display prefs id.</value>
[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() public override bool IsSaveLocalMetadataEnabled()
{ {
return true; return true;
@ -373,8 +376,5 @@ namespace MediaBrowser.Controller.Entities
return result; return result;
} }
[JsonIgnore]
public override bool SupportsPeople => false;
} }
} }

@ -15,6 +15,8 @@ namespace MediaBrowser.Controller.Entities
/// <summary> /// <summary>
/// Adds the trailer URL. /// Adds the trailer URL.
/// </summary> /// </summary>
/// <param name="item">Media item.</param>
/// <param name="url">Trailer URL.</param>
public static void AddTrailerUrl(this BaseItem item, string url) public static void AddTrailerUrl(this BaseItem item, string url)
{ {
if (string.IsNullOrEmpty(url)) if (string.IsNullOrEmpty(url))

@ -1,6 +1,6 @@
#nullable disable #nullable disable
#pragma warning disable CS1591 #pragma warning disable CA1002, CA1721, CA1819, CS1591
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@ -165,6 +165,8 @@ namespace MediaBrowser.Controller.Entities
} }
} }
public static ICollectionManager CollectionManager { get; set; }
public override bool CanDelete() public override bool CanDelete()
{ {
if (IsRoot) if (IsRoot)
@ -258,6 +260,7 @@ namespace MediaBrowser.Controller.Entities
/// Loads our children. Validation will occur externally. /// Loads our children. Validation will occur externally.
/// We want this synchronous. /// We want this synchronous.
/// </summary> /// </summary>
/// <returns>Returns children.</returns>
protected virtual List<BaseItem> LoadChildren() protected virtual List<BaseItem> LoadChildren()
{ {
// logger.LogDebug("Loading children from {0} {1} {2}", GetType().Name, Id, Path); // 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. /// Get the children of this folder from the actual file system.
/// </summary> /// </summary>
/// <returns>IEnumerable{BaseItem}.</returns> /// <returns>IEnumerable{BaseItem}.</returns>
/// <param name="directoryService">The directory service to use for operation.</param>
/// <returns>Returns set of base items.</returns>
protected virtual IEnumerable<BaseItem> GetNonCachedChildren(IDirectoryService directoryService) protected virtual IEnumerable<BaseItem> GetNonCachedChildren(IDirectoryService directoryService)
{ {
var collectionType = LibraryManager.GetContentType(this); var collectionType = LibraryManager.GetContentType(this);
@ -998,8 +1003,6 @@ namespace MediaBrowser.Controller.Entities
return PostFilterAndSort(items, query, true); return PostFilterAndSort(items, query, true);
} }
public static ICollectionManager CollectionManager { get; set; }
protected QueryResult<BaseItem> PostFilterAndSort(IEnumerable<BaseItem> items, InternalItemsQuery query, bool enableSorting) protected QueryResult<BaseItem> PostFilterAndSort(IEnumerable<BaseItem> items, InternalItemsQuery query, bool enableSorting)
{ {
var user = query.User; var user = query.User;
@ -1666,7 +1669,6 @@ namespace MediaBrowser.Controller.Entities
/// <param name="user">The user.</param> /// <param name="user">The user.</param>
/// <param name="datePlayed">The date played.</param> /// <param name="datePlayed">The date played.</param>
/// <param name="resetPosition">if set to <c>true</c> [reset position].</param> /// <param name="resetPosition">if set to <c>true</c> [reset position].</param>
/// <returns>Task.</returns>
public override void MarkPlayed( public override void MarkPlayed(
User user, User user,
DateTime? datePlayed, DateTime? datePlayed,
@ -1708,7 +1710,6 @@ namespace MediaBrowser.Controller.Entities
/// Marks the unplayed. /// Marks the unplayed.
/// </summary> /// </summary>
/// <param name="user">The user.</param> /// <param name="user">The user.</param>
/// <returns>Task.</returns>
public override void MarkUnplayed(User user) public override void MarkUnplayed(User user)
{ {
var itemsResult = GetItemList(new InternalItemsQuery var itemsResult = GetItemList(new InternalItemsQuery

@ -1,6 +1,6 @@
#nullable disable #nullable disable
#pragma warning disable CS1591 #pragma warning disable CA1819, CS1591
using System; using System;

@ -20,6 +20,8 @@ namespace MediaBrowser.Controller.Entities
/// <summary> /// <summary>
/// Gets the media sources. /// Gets the media sources.
/// </summary> /// </summary>
/// <param name="enablePathSubstitution"><c>true</c> to enable path substitution, <c>false</c> to not.</param>
/// <returns>A list of media sources.</returns>
List<MediaSourceInfo> GetMediaSources(bool enablePathSubstitution); List<MediaSourceInfo> GetMediaSources(bool enablePathSubstitution);
List<MediaStream> GetMediaStreams(); List<MediaStream> GetMediaStreams();

@ -1,6 +1,6 @@
#nullable disable #nullable disable
#pragma warning disable CS1591 #pragma warning disable CA1819, CS1591
namespace MediaBrowser.Controller.Entities namespace MediaBrowser.Controller.Entities
{ {

@ -39,6 +39,7 @@ namespace MediaBrowser.Controller.Entities
/// <summary> /// <summary>
/// Gets the trailer count. /// Gets the trailer count.
/// </summary> /// </summary>
/// <param name="item">Media item.</param>
/// <returns><see cref="IReadOnlyList{Guid}" />.</returns> /// <returns><see cref="IReadOnlyList{Guid}" />.</returns>
public static int GetTrailerCount(this IHasTrailers item) public static int GetTrailerCount(this IHasTrailers item)
=> item.LocalTrailerIds.Count + item.RemoteTrailerIds.Count; => item.LocalTrailerIds.Count + item.RemoteTrailerIds.Count;
@ -46,6 +47,7 @@ namespace MediaBrowser.Controller.Entities
/// <summary> /// <summary>
/// Gets the trailer ids. /// Gets the trailer ids.
/// </summary> /// </summary>
/// <param name="item">Media item.</param>
/// <returns><see cref="IReadOnlyList{Guid}" />.</returns> /// <returns><see cref="IReadOnlyList{Guid}" />.</returns>
public static IReadOnlyList<Guid> GetTrailerIds(this IHasTrailers item) public static IReadOnlyList<Guid> GetTrailerIds(this IHasTrailers item)
{ {
@ -70,6 +72,7 @@ namespace MediaBrowser.Controller.Entities
/// <summary> /// <summary>
/// Gets the trailers. /// Gets the trailers.
/// </summary> /// </summary>
/// <param name="item">Media item.</param>
/// <returns><see cref="IReadOnlyList{BaseItem}" />.</returns> /// <returns><see cref="IReadOnlyList{BaseItem}" />.</returns>
public static IReadOnlyList<BaseItem> GetTrailers(this IHasTrailers item) public static IReadOnlyList<BaseItem> GetTrailers(this IHasTrailers item)
{ {

@ -1,4 +1,4 @@
#pragma warning disable CS1591 #pragma warning disable CA1044, CA1819, CA2227, CS1591
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@ -12,6 +12,55 @@ namespace MediaBrowser.Controller.Entities
{ {
public class InternalItemsQuery public class InternalItemsQuery
{ {
public InternalItemsQuery()
{
AlbumArtistIds = Array.Empty<Guid>();
AlbumIds = Array.Empty<Guid>();
AncestorIds = Array.Empty<Guid>();
ArtistIds = Array.Empty<Guid>();
BlockUnratedItems = Array.Empty<UnratedItem>();
BoxSetLibraryFolders = Array.Empty<Guid>();
ChannelIds = Array.Empty<Guid>();
ContributingArtistIds = Array.Empty<Guid>();
DtoOptions = new DtoOptions();
EnableTotalRecordCount = true;
ExcludeArtistIds = Array.Empty<Guid>();
ExcludeInheritedTags = Array.Empty<string>();
ExcludeItemIds = Array.Empty<Guid>();
ExcludeItemTypes = Array.Empty<string>();
ExcludeTags = Array.Empty<string>();
GenreIds = Array.Empty<Guid>();
Genres = Array.Empty<string>();
GroupByPresentationUniqueKey = true;
ImageTypes = Array.Empty<ImageType>();
IncludeItemTypes = Array.Empty<string>();
ItemIds = Array.Empty<Guid>();
MediaTypes = Array.Empty<string>();
MinSimilarityScore = 20;
OfficialRatings = Array.Empty<string>();
OrderBy = Array.Empty<ValueTuple<string, SortOrder>>();
PersonIds = Array.Empty<Guid>();
PersonTypes = Array.Empty<string>();
PresetViews = Array.Empty<string>();
SeriesStatuses = Array.Empty<SeriesStatus>();
SourceTypes = Array.Empty<SourceType>();
StudioIds = Array.Empty<Guid>();
Tags = Array.Empty<string>();
TopParentIds = Array.Empty<Guid>();
TrailerTypes = Array.Empty<TrailerType>();
VideoTypes = Array.Empty<VideoType>();
Years = Array.Empty<int>();
}
public InternalItemsQuery(User? user)
: this()
{
if (user != null)
{
SetUser(user);
}
}
public bool Recursive { get; set; } public bool Recursive { get; set; }
public int? StartIndex { get; set; } public int? StartIndex { get; set; }
@ -186,23 +235,6 @@ namespace MediaBrowser.Controller.Entities
public Guid[] TopParentIds { get; set; } 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 string[] PresetViews { get; set; }
public TrailerType[] TrailerTypes { get; set; } public TrailerType[] TrailerTypes { get; set; }
@ -270,70 +302,21 @@ namespace MediaBrowser.Controller.Entities
/// </summary> /// </summary>
public bool? DisplayAlbumFolders { get; set; } public bool? DisplayAlbumFolders { get; set; }
public InternalItemsQuery() public BaseItem? Parent
{ {
AlbumArtistIds = Array.Empty<Guid>(); set
AlbumIds = Array.Empty<Guid>();
AncestorIds = Array.Empty<Guid>();
ArtistIds = Array.Empty<Guid>();
BlockUnratedItems = Array.Empty<UnratedItem>();
BoxSetLibraryFolders = Array.Empty<Guid>();
ChannelIds = Array.Empty<Guid>();
ContributingArtistIds = Array.Empty<Guid>();
DtoOptions = new DtoOptions();
EnableTotalRecordCount = true;
ExcludeArtistIds = Array.Empty<Guid>();
ExcludeInheritedTags = Array.Empty<string>();
ExcludeItemIds = Array.Empty<Guid>();
ExcludeItemTypes = Array.Empty<string>();
ExcludeTags = Array.Empty<string>();
GenreIds = Array.Empty<Guid>();
Genres = Array.Empty<string>();
GroupByPresentationUniqueKey = true;
ImageTypes = Array.Empty<ImageType>();
IncludeItemTypes = Array.Empty<string>();
ItemIds = Array.Empty<Guid>();
MediaTypes = Array.Empty<string>();
MinSimilarityScore = 20;
OfficialRatings = Array.Empty<string>();
OrderBy = Array.Empty<ValueTuple<string, SortOrder>>();
PersonIds = Array.Empty<Guid>();
PersonTypes = Array.Empty<string>();
PresetViews = Array.Empty<string>();
SeriesStatuses = Array.Empty<SeriesStatus>();
SourceTypes = Array.Empty<SourceType>();
StudioIds = Array.Empty<Guid>();
Tags = Array.Empty<string>();
TopParentIds = Array.Empty<Guid>();
TrailerTypes = Array.Empty<TrailerType>();
VideoTypes = Array.Empty<VideoType>();
Years = Array.Empty<int>();
}
public InternalItemsQuery(User? user)
: this()
{ {
if (user != null) if (value == null)
{ {
SetUser(user); ParentId = Guid.Empty;
} ParentType = null;
} }
else
public void SetUser(User user)
{
MaxParentalRating = user.MaxParentalAgeRating;
if (MaxParentalRating.HasValue)
{ {
string other = UnratedItem.Other.ToString(); ParentId = value.Id;
BlockUnratedItems = user.GetPreference(PreferenceKind.BlockUnratedItems) ParentType = value.GetType().Name;
.Where(i => i != other) }
.Select(e => Enum.Parse<UnratedItem>(e, true)).ToArray();
} }
ExcludeInheritedTags = user.GetPreference(PreferenceKind.BlockedTags);
User = user;
} }
public Dictionary<string, string>? HasAnyProviderId { get; set; } public Dictionary<string, string>? HasAnyProviderId { get; set; }
@ -361,5 +344,22 @@ namespace MediaBrowser.Controller.Entities
public string? SearchTerm { get; set; } public string? SearchTerm { get; set; }
public string? SeriesTimerId { 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<UnratedItem>(e, true)).ToArray();
}
ExcludeInheritedTags = user.GetPreference(PreferenceKind.BlockedTags);
User = user;
}
} }
} }

@ -1,6 +1,6 @@
#nullable disable #nullable disable
#pragma warning disable CS1591 #pragma warning disable CA1721, CA1819, CS1591
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@ -49,6 +49,30 @@ namespace MediaBrowser.Controller.Entities.Movies
/// <value>The display order.</value> /// <value>The display order.</value>
public string DisplayOrder { get; set; } 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) protected override bool GetBlockUnratedValue(User user)
{ {
return user.GetPreferenceValues<UnratedItem>(PreferenceKind.BlockUnratedItems).Contains(UnratedItem.Movie); return user.GetPreferenceValues<UnratedItem>(PreferenceKind.BlockUnratedItems).Contains(UnratedItem.Movie);
@ -83,28 +107,6 @@ namespace MediaBrowser.Controller.Entities.Movies
return new List<BaseItem>(); return new List<BaseItem>();
} }
[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<Folder> allCollectionFolders) public override bool IsAuthorizedToDelete(User user, List<Folder> allCollectionFolders)
{ {
return true; return true;
@ -191,8 +193,6 @@ namespace MediaBrowser.Controller.Entities.Movies
return IsVisible(user); return IsVisible(user);
} }
public Guid[] LibraryFolderIds { get; set; }
private Guid[] GetLibraryFolderIds(User user) private Guid[] GetLibraryFolderIds(User user)
{ {
return LibraryManager.GetUserRootFolder().GetChildren(user, true) return LibraryManager.GetUserRootFolder().GetChildren(user, true)

@ -16,6 +16,26 @@ namespace MediaBrowser.Controller.Entities
/// </summary> /// </summary>
public class Person : BaseItem, IItemByName, IHasLookupInfo<PersonLookupInfo> public class Person : BaseItem, IItemByName, IHasLookupInfo<PersonLookupInfo>
{ {
/// <summary>
/// Gets the folder containing the item.
/// If the item is a folder, it returns the folder itself.
/// </summary>
/// <value>The containing folder path.</value>
[JsonIgnore]
public override string ContainingFolderPath => Path;
/// <summary>
/// Gets a value indicating whether to enable alpha numeric sorting.
/// </summary>
[JsonIgnore]
public override bool EnableAlphaNumericSorting => false;
[JsonIgnore]
public override bool SupportsPeople => false;
[JsonIgnore]
public override bool SupportsAncestors => false;
public override List<string> GetUserDataKeys() public override List<string> GetUserDataKeys()
{ {
var list = base.GetUserDataKeys(); var list = base.GetUserDataKeys();
@ -49,14 +69,6 @@ namespace MediaBrowser.Controller.Entities
return LibraryManager.GetItemList(query); return LibraryManager.GetItemList(query);
} }
/// <summary>
/// Gets the folder containing the item.
/// If the item is a folder, it returns the folder itself.
/// </summary>
/// <value>The containing folder path.</value>
[JsonIgnore]
public override string ContainingFolderPath => Path;
public override bool CanDelete() public override bool CanDelete()
{ {
return false; return false;
@ -67,18 +79,6 @@ namespace MediaBrowser.Controller.Entities
return true; return true;
} }
/// <summary>
/// Gets a value indicating whether to enable alpha numeric sorting.
/// </summary>
[JsonIgnore]
public override bool EnableAlphaNumericSorting => false;
[JsonIgnore]
public override bool SupportsPeople => false;
[JsonIgnore]
public override bool SupportsAncestors => false;
public static string GetPath(string name) public static string GetPath(string name)
{ {
return GetPath(name, true); return GetPath(name, true);
@ -129,6 +129,8 @@ namespace MediaBrowser.Controller.Entities
/// <summary> /// <summary>
/// This is called before any metadata refresh and returns true or false indicating if changes were made. /// This is called before any metadata refresh and returns true or false indicating if changes were made.
/// </summary> /// </summary>
/// <param name="replaceAllMetadata"><c>true</c> to replace all metadata, <c>false</c> to not.</param>
/// <returns><c>true</c> if changes were made, <c>false</c> if not.</returns>
public override bool BeforeMetadataRefresh(bool replaceAllMetadata) public override bool BeforeMetadataRefresh(bool replaceAllMetadata)
{ {
var hasChanges = base.BeforeMetadataRefresh(replaceAllMetadata); var hasChanges = base.BeforeMetadataRefresh(replaceAllMetadata);

@ -1,6 +1,6 @@
#nullable disable #nullable disable
#pragma warning disable CS1591 #pragma warning disable CA2227, CS1591
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;

@ -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() public override bool CanDownload()
{ {
return true; return true;
@ -69,29 +93,5 @@ namespace MediaBrowser.Controller.Entities
return base.GetDefaultPrimaryImageAspectRatio(); 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; }
} }
} }

@ -15,19 +15,6 @@ namespace MediaBrowser.Controller.Entities
/// </summary> /// </summary>
public class Studio : BaseItem, IItemByName public class Studio : BaseItem, IItemByName
{ {
public override List<string> GetUserDataKeys()
{
var list = base.GetUserDataKeys();
list.Insert(0, GetType().Name + "-" + (Name ?? string.Empty).RemoveDiacritics());
return list;
}
public override string CreatePresentationUniqueKey()
{
return GetUserDataKeys()[0];
}
/// <summary> /// <summary>
/// Gets the folder containing the item. /// Gets the folder containing the item.
/// If the item is a folder, it returns the folder itself. /// If the item is a folder, it returns the folder itself.
@ -42,6 +29,22 @@ namespace MediaBrowser.Controller.Entities
[JsonIgnore] [JsonIgnore]
public override bool SupportsAncestors => false; public override bool SupportsAncestors => false;
[JsonIgnore]
public override bool SupportsPeople => false;
public override List<string> 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() public override double GetDefaultPrimaryImageAspectRatio()
{ {
double value = 16; double value = 16;
@ -67,9 +70,6 @@ namespace MediaBrowser.Controller.Entities
return LibraryManager.GetItemList(query); return LibraryManager.GetItemList(query);
} }
[JsonIgnore]
public override bool SupportsPeople => false;
public static string GetPath(string name) public static string GetPath(string name)
{ {
return GetPath(name, true); return GetPath(name, true);
@ -105,6 +105,8 @@ namespace MediaBrowser.Controller.Entities
/// <summary> /// <summary>
/// This is called before any metadata refresh and returns true or false indicating if changes were made. /// This is called before any metadata refresh and returns true or false indicating if changes were made.
/// </summary> /// </summary>
/// <param name="replaceAllMetadata"><c>true</c> to replace all metadata, <c>false</c> to not.</param>
/// <returns><c>true</c> if changes were made, <c>false</c> if not.</returns>
public override bool BeforeMetadataRefresh(bool replaceAllMetadata) public override bool BeforeMetadataRefresh(bool replaceAllMetadata)
{ {
var hasChanges = base.BeforeMetadataRefresh(replaceAllMetadata); var hasChanges = base.BeforeMetadataRefresh(replaceAllMetadata);

@ -49,12 +49,6 @@ namespace MediaBrowser.Controller.Entities.TV
/// <value>The index number.</value> /// <value>The index number.</value>
public int? IndexNumberEnd { get; set; } public int? IndexNumberEnd { get; set; }
public string FindSeriesSortName()
{
var series = Series;
return series == null ? SeriesName : series.SortName;
}
[JsonIgnore] [JsonIgnore]
protected override bool SupportsOwnedItems => IsStacked || MediaSourceCount > 1; protected override bool SupportsOwnedItems => IsStacked || MediaSourceCount > 1;
@ -76,45 +70,6 @@ namespace MediaBrowser.Controller.Entities.TV
[JsonIgnore] [JsonIgnore]
protected override bool EnableDefaultVideoUserDataKeys => false; 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<string> 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;
}
/// <summary> /// <summary>
/// Gets the Episode's Series Instance. /// Gets the Episode's Series Instance.
/// </summary> /// </summary>
@ -161,6 +116,74 @@ namespace MediaBrowser.Controller.Entities.TV
[JsonIgnore] [JsonIgnore]
public string SeasonName { get; set; } 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<string> 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() public string FindSeriesPresentationUniqueKey()
{ {
var series = Series; var series = Series;
@ -242,29 +265,6 @@ namespace MediaBrowser.Controller.Entities.TV
return false; 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() public Guid FindSeriesId()
{ {
var series = FindParent<Series>(); var series = FindParent<Series>();

@ -38,6 +38,50 @@ namespace MediaBrowser.Controller.Entities.TV
[JsonIgnore] [JsonIgnore]
public override Guid DisplayParentId => SeriesId; public override Guid DisplayParentId => SeriesId;
/// <summary>
/// Gets this Episode's Series Instance.
/// </summary>
/// <value>The series.</value>
[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() public override double GetDefaultPrimaryImageAspectRatio()
{ {
double value = 2; double value = 2;
@ -80,41 +124,6 @@ namespace MediaBrowser.Controller.Entities.TV
return result; return result;
} }
/// <summary>
/// Gets this Episode's Series Instance.
/// </summary>
/// <value>The series.</value>
[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() public override string CreatePresentationUniqueKey()
{ {
if (IndexNumber.HasValue) if (IndexNumber.HasValue)
@ -157,6 +166,9 @@ namespace MediaBrowser.Controller.Entities.TV
/// <summary> /// <summary>
/// Gets the episodes. /// Gets the episodes.
/// </summary> /// </summary>
/// <param name="user">The user.</param>
/// <param name="options">The options to use.</param>
/// <returns>Set of episodes.</returns>
public List<BaseItem> GetEpisodes(User user, DtoOptions options) public List<BaseItem> GetEpisodes(User user, DtoOptions options)
{ {
return GetEpisodes(Series, user, options); return GetEpisodes(Series, user, options);
@ -193,15 +205,6 @@ namespace MediaBrowser.Controller.Entities.TV
return UnratedItem.Series; return UnratedItem.Series;
} }
[JsonIgnore]
public string SeriesPresentationUniqueKey { get; set; }
[JsonIgnore]
public string SeriesName { get; set; }
[JsonIgnore]
public Guid SeriesId { get; set; }
public string FindSeriesPresentationUniqueKey() public string FindSeriesPresentationUniqueKey()
{ {
var series = Series; var series = Series;
@ -241,6 +244,7 @@ namespace MediaBrowser.Controller.Entities.TV
/// <summary> /// <summary>
/// This is called before any metadata refresh and returns true or false indicating if changes were made. /// This is called before any metadata refresh and returns true or false indicating if changes were made.
/// </summary> /// </summary>
/// <param name="replaceAllMetadata"><c>true</c> to replace metdata, <c>false</c> to not.</param>
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns> /// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns>
public override bool BeforeMetadataRefresh(bool replaceAllMetadata) public override bool BeforeMetadataRefresh(bool replaceAllMetadata)
{ {

@ -72,6 +72,9 @@ namespace MediaBrowser.Controller.Entities.TV
/// <value>The status.</value> /// <value>The status.</value>
public SeriesStatus? Status { get; set; } public SeriesStatus? Status { get; set; }
[JsonIgnore]
public override bool StopRefreshIfLocalMetadataFound => false;
public override double GetDefaultPrimaryImageAspectRatio() public override double GetDefaultPrimaryImageAspectRatio()
{ {
double value = 2; double value = 2;
@ -394,6 +397,10 @@ namespace MediaBrowser.Controller.Entities.TV
/// <summary> /// <summary>
/// Filters the episodes by season. /// Filters the episodes by season.
/// </summary> /// </summary>
/// <param name="episodes">The episodes.</param>
/// <param name="parentSeason">The season.</param>
/// <param name="includeSpecials"><c>true</c> to include special, <c>false</c> to not.</param>
/// <returns>The set of episodes.</returns>
public static IEnumerable<BaseItem> FilterEpisodesBySeason(IEnumerable<BaseItem> episodes, Season parentSeason, bool includeSpecials) public static IEnumerable<BaseItem> FilterEpisodesBySeason(IEnumerable<BaseItem> episodes, Season parentSeason, bool includeSpecials)
{ {
var seasonNumber = parentSeason.IndexNumber; var seasonNumber = parentSeason.IndexNumber;
@ -424,6 +431,10 @@ namespace MediaBrowser.Controller.Entities.TV
/// <summary> /// <summary>
/// Filters the episodes by season. /// Filters the episodes by season.
/// </summary> /// </summary>
/// <param name="episodes">The episodes.</param>
/// <param name="seasonNumber">The season.</param>
/// <param name="includeSpecials"><c>true</c> to include special, <c>false</c> to not.</param>
/// <returns>The set of episodes.</returns>
public static IEnumerable<Episode> FilterEpisodesBySeason(IEnumerable<Episode> episodes, int seasonNumber, bool includeSpecials) public static IEnumerable<Episode> FilterEpisodesBySeason(IEnumerable<Episode> episodes, int seasonNumber, bool includeSpecials)
{ {
if (!includeSpecials || seasonNumber < 1) if (!includeSpecials || seasonNumber < 1)
@ -499,8 +510,5 @@ namespace MediaBrowser.Controller.Entities.TV
return list; return list;
} }
[JsonIgnore]
public override bool StopRefreshIfLocalMetadataFound => false;
} }
} }

@ -1,6 +1,6 @@
#nullable disable #nullable disable
#pragma warning disable CS1591 #pragma warning disable CA1819, CS1591
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@ -23,6 +23,9 @@ namespace MediaBrowser.Controller.Entities
TrailerTypes = Array.Empty<TrailerType>(); TrailerTypes = Array.Empty<TrailerType>();
} }
[JsonIgnore]
public override bool StopRefreshIfLocalMetadataFound => false;
public TrailerType[] TrailerTypes { get; set; } public TrailerType[] TrailerTypes { get; set; }
public override double GetDefaultPrimaryImageAspectRatio() public override double GetDefaultPrimaryImageAspectRatio()
@ -97,8 +100,5 @@ namespace MediaBrowser.Controller.Entities
return list; return list;
} }
[JsonIgnore]
public override bool StopRefreshIfLocalMetadataFound => false;
} }
} }

@ -12,6 +12,13 @@ namespace MediaBrowser.Controller.Entities
/// </summary> /// </summary>
public class UserItemData public class UserItemData
{ {
public const double MinLikeValue = 6.5;
/// <summary>
/// The _rating.
/// </summary>
private double? _rating;
/// <summary> /// <summary>
/// Gets or sets the user id. /// Gets or sets the user id.
/// </summary> /// </summary>
@ -24,11 +31,6 @@ namespace MediaBrowser.Controller.Entities
/// <value>The key.</value> /// <value>The key.</value>
public string Key { get; set; } public string Key { get; set; }
/// <summary>
/// The _rating.
/// </summary>
private double? _rating;
/// <summary> /// <summary>
/// Gets or sets the users 0-10 rating. /// Gets or sets the users 0-10 rating.
/// </summary> /// </summary>
@ -93,8 +95,6 @@ namespace MediaBrowser.Controller.Entities
/// <value>The index of the subtitle stream.</value> /// <value>The index of the subtitle stream.</value>
public int? SubtitleStreamIndex { get; set; } public int? SubtitleStreamIndex { get; set; }
public const double MinLikeValue = 6.5;
/// <summary> /// <summary>
/// Gets or sets a value indicating whether the item is liked or not. /// Gets or sets a value indicating whether the item is liked or not.
/// This should never be serialized. /// This should never be serialized.

@ -21,8 +21,28 @@ namespace MediaBrowser.Controller.Entities
/// </summary> /// </summary>
public class UserRootFolder : Folder public class UserRootFolder : Folder
{ {
private List<Guid> _childrenIds = null;
private readonly object _childIdsLock = new object(); private readonly object _childIdsLock = new object();
private List<Guid> _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<BaseItem> LoadChildren() protected override List<BaseItem> 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<BaseItem> GetItemsInternal(InternalItemsQuery query) protected override QueryResult<BaseItem> GetItemsInternal(InternalItemsQuery query)
{ {
if (query.Recursive) if (query.Recursive)
@ -74,12 +80,6 @@ namespace MediaBrowser.Controller.Entities
return GetChildren(user, true).Count; return GetChildren(user, true).Count;
} }
[JsonIgnore]
protected override bool SupportsShortcutChildren => true;
[JsonIgnore]
public override bool IsPreSorted => true;
protected override IEnumerable<BaseItem> GetEligibleChildrenForRecursiveChildren(User user) protected override IEnumerable<BaseItem> GetEligibleChildrenForRecursiveChildren(User user)
{ {
var list = base.GetEligibleChildrenForRecursiveChildren(user).ToList(); var list = base.GetEligibleChildrenForRecursiveChildren(user).ToList();

@ -28,6 +28,14 @@ namespace MediaBrowser.Controller.Entities
ISupportsPlaceHolders, ISupportsPlaceHolders,
IHasMediaSources IHasMediaSources
{ {
public Video()
{
AdditionalParts = Array.Empty<string>();
LocalAlternateVersions = Array.Empty<string>();
SubtitleFiles = Array.Empty<string>();
LinkedAlternateVersions = Array.Empty<LinkedChild>();
}
[JsonIgnore] [JsonIgnore]
public string PrimaryVersionId { get; set; } 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] [JsonIgnore]
public override bool SupportsThemeMedia => true; public override bool SupportsThemeMedia => true;
@ -151,24 +135,6 @@ namespace MediaBrowser.Controller.Entities
/// <value>The aspect ratio.</value> /// <value>The aspect ratio.</value>
public string AspectRatio { get; set; } public string AspectRatio { get; set; }
public Video()
{
AdditionalParts = Array.Empty<string>();
LocalAlternateVersions = Array.Empty<string>();
SubtitleFiles = Array.Empty<string>();
LinkedAlternateVersions = Array.Empty<LinkedChild>();
}
public override bool CanDownload()
{
if (VideoType == VideoType.Dvd || VideoType == VideoType.BluRay)
{
return false;
}
return IsFileProtocol;
}
[JsonIgnore] [JsonIgnore]
public override bool SupportsAddingToPlaylist => true; public override bool SupportsAddingToPlaylist => true;
@ -196,16 +162,6 @@ namespace MediaBrowser.Controller.Entities
[JsonIgnore] [JsonIgnore]
public override bool HasLocalAlternateVersions => LocalAlternateVersions.Length > 0; public override bool HasLocalAlternateVersions => LocalAlternateVersions.Length > 0;
public IEnumerable<Guid> GetAdditionalPartIds()
{
return AdditionalParts.Select(i => LibraryManager.GetNewItemId(i, typeof(Video)));
}
public IEnumerable<Guid> GetLocalAlternateVersionIds()
{
return LocalAlternateVersions.Select(i => LibraryManager.GetNewItemId(i, typeof(Video)));
}
public static ILiveTvManager LiveTvManager { get; set; } public static ILiveTvManager LiveTvManager { get; set; }
[JsonIgnore] [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);
} }
public override bool CanDelete() return !IsActiveRecording();
}
}
[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] [JsonIgnore]
public bool IsCompleteMedia public override string FileNameWithoutExtension
{ {
get 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 !IsActiveRecording(); return System.IO.Path.GetFileNameWithoutExtension(Path);
}
return null;
} }
} }
/// <summary>
/// Gets a value indicating whether [is3 D].
/// </summary>
/// <value><c>true</c> if [is3 D]; otherwise, <c>false</c>.</value>
[JsonIgnore] [JsonIgnore]
protected virtual bool EnableDefaultVideoUserDataKeys => true; public bool Is3D => Video3DFormat.HasValue;
/// <summary>
/// Gets the type of the media.
/// </summary>
/// <value>The type of the media.</value>
[JsonIgnore]
public override string MediaType => Model.Entities.MediaType.Video;
public override List<string> GetUserDataKeys() public override List<string> GetUserDataKeys()
{ {
@ -293,6 +289,65 @@ namespace MediaBrowser.Controller.Entities
return list; 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<Guid> GetAdditionalPartIds()
{
return AdditionalParts.Select(i => LibraryManager.GetNewItemId(i, typeof(Video)));
}
public IEnumerable<Guid> GetLocalAlternateVersionIds()
{
return LocalAlternateVersions.Select(i => LibraryManager.GetNewItemId(i, typeof(Video)));
}
private string GetUserDataKey(string providerId) private string GetUserDataKey(string providerId)
{ {
var key = providerId + "-" + ExtraType.ToString().ToLowerInvariant(); var key = providerId + "-" + ExtraType.ToString().ToLowerInvariant();
@ -328,47 +383,6 @@ namespace MediaBrowser.Controller.Entities
.OrderBy(i => i.SortName); .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) internal override ItemUpdateType UpdateFromResolvedItem(BaseItem newItem)
{ {
var updateType = base.UpdateFromResolvedItem(newItem); var updateType = base.UpdateFromResolvedItem(newItem);
@ -397,20 +411,6 @@ namespace MediaBrowser.Controller.Entities
return updateType; return updateType;
} }
/// <summary>
/// Gets a value indicating whether [is3 D].
/// </summary>
/// <value><c>true</c> if [is3 D]; otherwise, <c>false</c>.</value>
[JsonIgnore]
public bool Is3D => Video3DFormat.HasValue;
/// <summary>
/// Gets the type of the media.
/// </summary>
/// <value>The type of the media.</value>
[JsonIgnore]
public override string MediaType => Model.Entities.MediaType.Video;
protected override async Task<bool> RefreshedOwnedItems(MetadataRefreshOptions options, List<FileSystemMetadata> fileSystemChildren, CancellationToken cancellationToken) protected override async Task<bool> RefreshedOwnedItems(MetadataRefreshOptions options, List<FileSystemMetadata> fileSystemChildren, CancellationToken cancellationToken)
{ {
var hasChanges = await base.RefreshedOwnedItems(options, fileSystemChildren, cancellationToken).ConfigureAwait(false); var hasChanges = await base.RefreshedOwnedItems(options, fileSystemChildren, cancellationToken).ConfigureAwait(false);

@ -15,13 +15,11 @@ namespace MediaBrowser.Controller.Entities
/// </summary> /// </summary>
public class Year : BaseItem, IItemByName public class Year : BaseItem, IItemByName
{ {
public override List<string> GetUserDataKeys() [JsonIgnore]
{ public override bool SupportsAncestors => false;
var list = base.GetUserDataKeys();
list.Insert(0, "Year-" + Name); [JsonIgnore]
return list; public override bool SupportsPeople => false;
}
/// <summary> /// <summary>
/// Gets the folder containing the item. /// Gets the folder containing the item.
@ -31,6 +29,19 @@ namespace MediaBrowser.Controller.Entities
[JsonIgnore] [JsonIgnore]
public override string ContainingFolderPath => Path; public override string ContainingFolderPath => Path;
public override bool CanDelete()
{
return false;
}
public override List<string> GetUserDataKeys()
{
var list = base.GetUserDataKeys();
list.Insert(0, "Year-" + Name);
return list;
}
public override double GetDefaultPrimaryImageAspectRatio() public override double GetDefaultPrimaryImageAspectRatio()
{ {
double value = 2; double value = 2;
@ -39,14 +50,6 @@ namespace MediaBrowser.Controller.Entities
return value; return value;
} }
[JsonIgnore]
public override bool SupportsAncestors => false;
public override bool CanDelete()
{
return false;
}
public override bool IsSaveLocalMetadataEnabled() public override bool IsSaveLocalMetadataEnabled()
{ {
return true; return true;
@ -76,9 +79,6 @@ namespace MediaBrowser.Controller.Entities
return null; return null;
} }
[JsonIgnore]
public override bool SupportsPeople => false;
public static string GetPath(string name) public static string GetPath(string name)
{ {
return GetPath(name, true); return GetPath(name, true);

@ -10,6 +10,15 @@ namespace MediaBrowser.Controller.MediaEncoding
{ {
public class BaseEncodingJobOptions public class BaseEncodingJobOptions
{ {
public BaseEncodingJobOptions()
{
EnableAutoStreamCopy = true;
AllowVideoStreamCopy = true;
AllowAudioStreamCopy = true;
Context = EncodingContext.Streaming;
StreamOptions = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
}
/// <summary> /// <summary>
/// Gets or sets the id. /// Gets or sets the id.
/// </summary> /// </summary>
@ -191,14 +200,5 @@ namespace MediaBrowser.Controller.MediaEncoding
return null; return null;
} }
public BaseEncodingJobOptions()
{
EnableAutoStreamCopy = true;
AllowVideoStreamCopy = true;
AllowAudioStreamCopy = true;
Context = EncodingContext.Streaming;
StreamOptions = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
}
} }
} }

@ -7,7 +7,6 @@ using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Runtime.InteropServices;
using System.Text; using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading; using System.Threading;
@ -16,9 +15,7 @@ using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Dto; using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.MediaInfo;
using Microsoft.Extensions.Configuration;
namespace MediaBrowser.Controller.MediaEncoding namespace MediaBrowser.Controller.MediaEncoding
{ {
@ -161,6 +158,9 @@ namespace MediaBrowser.Controller.MediaEncoding
/// <summary> /// <summary>
/// Gets the name of the output video codec. /// Gets the name of the output video codec.
/// </summary> /// </summary>
/// <param name="state">Encording state.</param>
/// <param name="encodingOptions">Encoding options.</param>
/// <returns>Encoder string.</returns>
public string GetVideoEncoder(EncodingJobInfo state, EncodingOptions encodingOptions) public string GetVideoEncoder(EncodingJobInfo state, EncodingOptions encodingOptions)
{ {
var codec = state.OutputVideoCodec; var codec = state.OutputVideoCodec;
@ -315,6 +315,11 @@ namespace MediaBrowser.Controller.MediaEncoding
return container; return container;
} }
/// <summary>
/// Gets decoder from a codec.
/// </summary>
/// <param name="codec">Codec to use.</param>
/// <returns>Decoder string.</returns>
public string GetDecoderFromCodec(string codec) public string GetDecoderFromCodec(string codec)
{ {
// For these need to find out the ffmpeg names // For these need to find out the ffmpeg names
@ -344,6 +349,8 @@ namespace MediaBrowser.Controller.MediaEncoding
/// <summary> /// <summary>
/// Infers the audio codec based on the url. /// Infers the audio codec based on the url.
/// </summary> /// </summary>
/// <param name="container">Container to use.</param>
/// <returns>Codec string.</returns>
public string InferAudioCodec(string container) public string InferAudioCodec(string container)
{ {
var ext = "." + (container ?? string.Empty); var ext = "." + (container ?? string.Empty);
@ -489,6 +496,9 @@ namespace MediaBrowser.Controller.MediaEncoding
/// <summary> /// <summary>
/// Gets the input argument. /// Gets the input argument.
/// </summary> /// </summary>
/// <param name="state">Encoding state.</param>
/// <param name="encodingOptions">Encoding options.</param>
/// <returns>Input arguments.</returns>
public string GetInputArgument(EncodingJobInfo state, EncodingOptions encodingOptions) public string GetInputArgument(EncodingJobInfo state, EncodingOptions encodingOptions)
{ {
var arg = new StringBuilder(); var arg = new StringBuilder();
@ -965,6 +975,11 @@ namespace MediaBrowser.Controller.MediaEncoding
/// <summary> /// <summary>
/// Gets the video bitrate to specify on the command line. /// Gets the video bitrate to specify on the command line.
/// </summary> /// </summary>
/// <param name="state">Encoding state.</param>
/// <param name="videoEncoder">Video encoder to use.</param>
/// <param name="encodingOptions">Encoding options.</param>
/// <param name="defaultPreset">Default present to use for encoding.</param>
/// <returns>Video bitrate.</returns>
public string GetVideoQualityParam(EncodingJobInfo state, string videoEncoder, EncodingOptions encodingOptions, string defaultPreset) public string GetVideoQualityParam(EncodingJobInfo state, string videoEncoder, EncodingOptions encodingOptions, string defaultPreset)
{ {
var param = string.Empty; var param = string.Empty;
@ -1966,8 +1981,12 @@ namespace MediaBrowser.Controller.MediaEncoding
} }
/// <summary> /// <summary>
/// Gets the graphical subtitle param. /// Gets the graphical subtitle parameter.
/// </summary> /// </summary>
/// <param name="state">Encoding state.</param>
/// <param name="options">Encoding options.</param>
/// <param name="outputVideoCodec">Video codec to use.</param>
/// <returns>Graphical subtitle parameter.</returns>
public string GetGraphicalSubtitleParam( public string GetGraphicalSubtitleParam(
EncodingJobInfo state, EncodingJobInfo state,
EncodingOptions options, EncodingOptions options,
@ -2485,6 +2504,13 @@ namespace MediaBrowser.Controller.MediaEncoding
return string.Format(CultureInfo.InvariantCulture, filter, widthParam, heightParam); return string.Format(CultureInfo.InvariantCulture, filter, widthParam, heightParam);
} }
/// <summary>
/// Gets the output size parameter.
/// </summary>
/// <param name="state">Encoding state.</param>
/// <param name="options">Encoding options.</param>
/// <param name="outputVideoCodec">Video codec to use.</param>
/// <returns>The output size parameter.</returns>
public string GetOutputSizeParam( public string GetOutputSizeParam(
EncodingJobInfo state, EncodingJobInfo state,
EncodingOptions options, EncodingOptions options,
@ -2495,8 +2521,13 @@ namespace MediaBrowser.Controller.MediaEncoding
} }
/// <summary> /// <summary>
/// Gets the output size parameter.
/// If we're going to put a fixed size on the command line, this will calculate it. /// If we're going to put a fixed size on the command line, this will calculate it.
/// </summary> /// </summary>
/// <param name="state">Encoding state.</param>
/// <param name="options">Encoding options.</param>
/// <param name="outputVideoCodec">Video codec to use.</param>
/// <returns>The output size parameter.</returns>
public string GetOutputSizeParamInternal( public string GetOutputSizeParamInternal(
EncodingJobInfo state, EncodingJobInfo state,
EncodingOptions options, EncodingOptions options,
@ -2908,6 +2939,10 @@ namespace MediaBrowser.Controller.MediaEncoding
/// <summary> /// <summary>
/// Gets the number of threads. /// Gets the number of threads.
/// </summary> /// </summary>
/// <param name="state">Encoding state.</param>
/// <param name="encodingOptions">Encoding options.</param>
/// <param name="outputVideoCodec">Video codec to use.</param>
/// <returns>Number of threads.</returns>
#nullable enable #nullable enable
public static int GetNumberOfThreads(EncodingJobInfo? state, EncodingOptions encodingOptions, string? outputVideoCodec) public static int GetNumberOfThreads(EncodingJobInfo? state, EncodingOptions encodingOptions, string? outputVideoCodec)
{ {
@ -3551,6 +3586,11 @@ namespace MediaBrowser.Controller.MediaEncoding
/// <summary> /// <summary>
/// Gets a hw decoder name. /// Gets a hw decoder name.
/// </summary> /// </summary>
/// <param name="options">Encoding options.</param>
/// <param name="decoder">Decoder to use.</param>
/// <param name="videoCodec">Video codec to use.</param>
/// <param name="isColorDepth10">Specifies if color depth 10.</param>
/// <returns>Hardware decoder name.</returns>
public string GetHwDecoderName(EncodingOptions options, string decoder, string videoCodec, bool isColorDepth10) public string GetHwDecoderName(EncodingOptions options, string decoder, string videoCodec, bool isColorDepth10)
{ {
var isCodecAvailable = _mediaEncoder.SupportsDecoder(decoder) && options.HardwareDecodingCodecs.Contains(videoCodec, StringComparer.OrdinalIgnoreCase); var isCodecAvailable = _mediaEncoder.SupportsDecoder(decoder) && options.HardwareDecodingCodecs.Contains(videoCodec, StringComparer.OrdinalIgnoreCase);
@ -3569,6 +3609,11 @@ namespace MediaBrowser.Controller.MediaEncoding
/// <summary> /// <summary>
/// Gets a hwaccel type to use as a hardware decoder(dxva/vaapi) depending on the system. /// Gets a hwaccel type to use as a hardware decoder(dxva/vaapi) depending on the system.
/// </summary> /// </summary>
/// <param name="state">Encoding state.</param>
/// <param name="options">Encoding options.</param>
/// <param name="videoCodec">Video codec to use.</param>
/// <param name="isColorDepth10">Specifies if color depth 10.</param>
/// <returns>Hardware accelerator type.</returns>
public string GetHwaccelType(EncodingJobInfo state, EncodingOptions options, string videoCodec, bool isColorDepth10) public string GetHwaccelType(EncodingJobInfo state, EncodingOptions options, string videoCodec, bool isColorDepth10)
{ {
var isWindows = OperatingSystem.IsWindows(); var isWindows = OperatingSystem.IsWindows();

@ -1,6 +1,6 @@
#nullable disable #nullable disable
#pragma warning disable CS1591 #pragma warning disable CS1591, SA1401
using System; using System;
using System.Collections.Generic; 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 // For now, a common base class until the API and MediaEncoding classes are unified
public class EncodingJobInfo public class EncodingJobInfo
{ {
public int? OutputAudioBitrate;
public int? OutputAudioChannels;
private TranscodeReason[] _transcodeReasons = null;
public EncodingJobInfo(TranscodingJobType jobType)
{
TranscodingType = jobType;
RemoteHttpHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
SupportedAudioCodecs = Array.Empty<string>();
SupportedVideoCodecs = Array.Empty<string>();
SupportedSubtitleCodecs = Array.Empty<string>();
}
public TranscodeReason[] TranscodeReasons
{
get
{
if (_transcodeReasons == null)
{
if (BaseRequest.TranscodeReasons == null)
{
return Array.Empty<TranscodeReason>();
}
_transcodeReasons = BaseRequest.TranscodeReasons
.Split(',')
.Where(i => !string.IsNullOrEmpty(i))
.Select(v => (TranscodeReason)Enum.Parse(typeof(TranscodeReason), v, true))
.ToArray();
}
return _transcodeReasons;
}
}
public IProgress<double> Progress { get; set; }
public MediaStream VideoStream { get; set; } public MediaStream VideoStream { get; set; }
public VideoType VideoType { get; set; } public VideoType VideoType { get; set; }
@ -58,40 +96,6 @@ namespace MediaBrowser.Controller.MediaEncoding
public string MimeType { get; set; } 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<TranscodeReason>();
}
_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 IgnoreInputDts => MediaSource.IgnoreDts;
public bool IgnoreInputIndex => MediaSource.IgnoreIndex; public bool IgnoreInputIndex => MediaSource.IgnoreIndex;
@ -144,196 +148,17 @@ namespace MediaBrowser.Controller.MediaEncoding
public BaseEncodingJobOptions BaseRequest { get; set; } 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<string>();
}
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 bool IsVideoRequest { get; set; }
public TranscodingJobType TranscodingType { get; set; } public TranscodingJobType TranscodingType { get; set; }
public EncodingJobInfo(TranscodingJobType jobType) public long? StartTimeTicks => BaseRequest.StartTimeTicks;
{
TranscodingType = jobType; public bool CopyTimestamps => BaseRequest.CopyTimestamps;
RemoteHttpHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
SupportedAudioCodecs = Array.Empty<string>();
SupportedVideoCodecs = Array.Empty<string>();
SupportedSubtitleCodecs = Array.Empty<string>();
}
public bool IsSegmentedLiveStream public bool IsSegmentedLiveStream
=> TranscodingType != TranscodingJobType.Progressive && !RunTimeTicks.HasValue; => 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? TotalOutputBitrate => (OutputAudioBitrate ?? 0) + (OutputVideoBitrate ?? 0);
public int? OutputWidth public int? OutputWidth
@ -682,6 +507,21 @@ namespace MediaBrowser.Controller.MediaEncoding
public int HlsListSize => 0; 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) private int? GetMediaStreamCount(MediaStreamType type, int limit)
{ {
var count = MediaSource.GetStreamCount(type); var count = MediaSource.GetStreamCount(type);
@ -694,7 +534,167 @@ namespace MediaBrowser.Controller.MediaEncoding
return count; return count;
} }
public IProgress<double> 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<string>();
}
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) public virtual void ReportTranscodingProgress(TimeSpan? transcodingPosition, float? framerate, double? percentComplete, long? bytesTranscoded, int? bitRate)
{ {

@ -16,6 +16,13 @@ namespace MediaBrowser.Controller.MediaEncoding
/// <summary> /// <summary>
/// Refreshes the chapter images. /// Refreshes the chapter images.
/// </summary> /// </summary>
/// <param name="video">Video to use.</param>
/// <param name="directoryService">Directory service to use.</param>
/// <param name="chapters">Set of chapters to refresh.</param>
/// <param name="extractImages">Option to extract images.</param>
/// <param name="saveChapters">Option to save chapters.</param>
/// <param name="cancellationToken">CancellationToken to use for operation.</param>
/// <returns><c>true</c> if successful, <c>false</c> if not.</returns>
Task<bool> RefreshChapterImages(Video video, IDirectoryService directoryService, IReadOnlyList<ChapterInfo> chapters, bool extractImages, bool saveChapters, CancellationToken cancellationToken); Task<bool> RefreshChapterImages(Video video, IDirectoryService directoryService, IReadOnlyList<ChapterInfo> chapters, bool extractImages, bool saveChapters, CancellationToken cancellationToken);
} }
} }

@ -71,13 +71,42 @@ namespace MediaBrowser.Controller.MediaEncoding
/// <summary> /// <summary>
/// Extracts the video image. /// Extracts the video image.
/// </summary> /// </summary>
/// <param name="inputFile">Input file.</param>
/// <param name="container">Video container type.</param>
/// <param name="mediaSource">Media source information.</param>
/// <param name="videoStream">Media stream information.</param>
/// <param name="threedFormat">Video 3D format.</param>
/// <param name="offset">Time offset.</param>
/// <param name="cancellationToken">CancellationToken to use for operation.</param>
/// <returns>Location of video image.</returns>
Task<string> ExtractVideoImage(string inputFile, string container, MediaSourceInfo mediaSource, MediaStream videoStream, Video3DFormat? threedFormat, TimeSpan? offset, CancellationToken cancellationToken); Task<string> ExtractVideoImage(string inputFile, string container, MediaSourceInfo mediaSource, MediaStream videoStream, Video3DFormat? threedFormat, TimeSpan? offset, CancellationToken cancellationToken);
/// <summary>
/// Extracts the video image.
/// </summary>
/// <param name="inputFile">Input file.</param>
/// <param name="container">Video container type.</param>
/// <param name="mediaSource">Media source information.</param>
/// <param name="imageStream">Media stream information.</param>
/// <param name="imageStreamIndex">Index of the stream to extract from.</param>
/// <param name="cancellationToken">CancellationToken to use for operation.</param>
/// <returns>Location of video image.</returns>
Task<string> ExtractVideoImage(string inputFile, string container, MediaSourceInfo mediaSource, MediaStream imageStream, int? imageStreamIndex, CancellationToken cancellationToken); Task<string> ExtractVideoImage(string inputFile, string container, MediaSourceInfo mediaSource, MediaStream imageStream, int? imageStreamIndex, CancellationToken cancellationToken);
/// <summary> /// <summary>
/// Extracts the video images on interval. /// Extracts the video images on interval.
/// </summary> /// </summary>
/// <param name="inputFile">Input file.</param>
/// <param name="container">Video container type.</param>
/// <param name="videoStream">Media stream information.</param>
/// <param name="mediaSource">Media source information.</param>
/// <param name="threedFormat">Video 3D format.</param>
/// <param name="interval">Time interval.</param>
/// <param name="targetDirectory">Directory to write images.</param>
/// <param name="filenamePrefix">Filename prefix to use.</param>
/// <param name="maxWidth">Maximum width of image.</param>
/// <param name="cancellationToken">CancellationToken to use for operation.</param>
/// <returns>A task.</returns>
Task ExtractVideoImagesOnInterval( Task ExtractVideoImagesOnInterval(
string inputFile, string inputFile,
string container, string container,
@ -122,10 +151,24 @@ namespace MediaBrowser.Controller.MediaEncoding
/// <returns>System.String.</returns> /// <returns>System.String.</returns>
string EscapeSubtitleFilterPath(string path); string EscapeSubtitleFilterPath(string path);
/// <summary>
/// Sets the path to find FFmpeg.
/// </summary>
void SetFFmpegPath(); void SetFFmpegPath();
/// <summary>
/// Updates the encoder path.
/// </summary>
/// <param name="path">The path.</param>
/// <param name="pathType">The type of path.</param>
void UpdateEncoderPath(string path, string pathType); void UpdateEncoderPath(string path, string pathType);
/// <summary>
/// Gets the primary playlist of .vob files.
/// </summary>
/// <param name="path">The to the .vob files.</param>
/// <param name="titleNumber">The title number to start with.</param>
/// <returns>A playlist.</returns>
IEnumerable<string> GetPrimaryPlaylistVobFiles(string path, uint? titleNumber); IEnumerable<string> GetPrimaryPlaylistVobFiles(string path, uint? titleNumber);
} }
} }

@ -15,6 +15,14 @@ namespace MediaBrowser.Controller.MediaEncoding
/// <summary> /// <summary>
/// Gets the subtitles. /// Gets the subtitles.
/// </summary> /// </summary>
/// <param name="item">Item to use.</param>
/// <param name="mediaSourceId">Media source.</param>
/// <param name="subtitleStreamIndex">Subtitle stream to use.</param>
/// <param name="outputFormat">Output format to use.</param>
/// <param name="startTimeTicks">Start time.</param>
/// <param name="endTimeTicks">End time.</param>
/// <param name="preserveOriginalTimestamps">Option to preserve original timestamps.</param>
/// <param name="cancellationToken">The cancellation token for the operation.</param>
/// <returns>Task{Stream}.</returns> /// <returns>Task{Stream}.</returns>
Task<Stream> GetSubtitles( Task<Stream> GetSubtitles(
BaseItem item, BaseItem item,

@ -1,6 +1,6 @@
#nullable disable #nullable disable
#pragma warning disable CS1591 #pragma warning disable CS1591, SA1306, SA1401
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@ -30,6 +30,21 @@ namespace MediaBrowser.Controller.Net
private readonly List<Tuple<IWebSocketConnection, CancellationTokenSource, TStateType>> _activeConnections = private readonly List<Tuple<IWebSocketConnection, CancellationTokenSource, TStateType>> _activeConnections =
new List<Tuple<IWebSocketConnection, CancellationTokenSource, TStateType>>(); new List<Tuple<IWebSocketConnection, CancellationTokenSource, TStateType>>();
/// <summary>
/// The logger.
/// </summary>
protected ILogger<BasePeriodicWebSocketListener<TReturnDataType, TStateType>> Logger;
protected BasePeriodicWebSocketListener(ILogger<BasePeriodicWebSocketListener<TReturnDataType, TStateType>> logger)
{
if (logger == null)
{
throw new ArgumentNullException(nameof(logger));
}
Logger = logger;
}
/// <summary> /// <summary>
/// Gets the type used for the messages sent to the client. /// Gets the type used for the messages sent to the client.
/// </summary> /// </summary>
@ -54,21 +69,6 @@ namespace MediaBrowser.Controller.Net
/// <returns>Task{`1}.</returns> /// <returns>Task{`1}.</returns>
protected abstract Task<TReturnDataType> GetDataToSend(); protected abstract Task<TReturnDataType> GetDataToSend();
/// <summary>
/// The logger.
/// </summary>
protected ILogger<BasePeriodicWebSocketListener<TReturnDataType, TStateType>> Logger;
protected BasePeriodicWebSocketListener(ILogger<BasePeriodicWebSocketListener<TReturnDataType, TStateType>> logger)
{
if (logger == null)
{
throw new ArgumentNullException(nameof(logger));
}
Logger = logger;
}
/// <summary> /// <summary>
/// Processes the message. /// Processes the message.
/// </summary> /// </summary>

@ -18,7 +18,6 @@ namespace MediaBrowser.Controller.Persistence
/// <param name="key">The key.</param> /// <param name="key">The key.</param>
/// <param name="userData">The user data.</param> /// <param name="userData">The user data.</param>
/// <param name="cancellationToken">The cancellation token.</param> /// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
void SaveUserData(long userId, string key, UserItemData userData, CancellationToken cancellationToken); void SaveUserData(long userId, string key, UserItemData userData, CancellationToken cancellationToken);
/// <summary> /// <summary>

@ -31,24 +31,18 @@ namespace MediaBrowser.Controller.Playlists
".zpl" ".zpl"
}; };
public Guid OwnerUserId { get; set; }
public Share[] Shares { get; set; }
public Playlist() public Playlist()
{ {
Shares = Array.Empty<Share>(); Shares = Array.Empty<Share>();
} }
public Guid OwnerUserId { get; set; }
public Share[] Shares { get; set; }
[JsonIgnore] [JsonIgnore]
public bool IsFile => IsPlaylistFile(Path); 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] [JsonIgnore]
public override string ContainingFolderPath public override string ContainingFolderPath
{ {
@ -80,6 +74,41 @@ namespace MediaBrowser.Controller.Playlists
[JsonIgnore] [JsonIgnore]
public override bool SupportsCumulativeRunTimeTicks => true; 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() public override double GetDefaultPrimaryImageAspectRatio()
{ {
return 1; return 1;
@ -197,35 +226,6 @@ namespace MediaBrowser.Controller.Playlists
return new[] { item }; 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) public override bool IsVisible(User user)
{ {
if (!IsSharedItem) if (!IsSharedItem)

@ -1,4 +1,4 @@
#pragma warning disable CA1002, CS1591 #pragma warning disable CA1002, CA1819, CS1591
using System.Collections.Generic; using System.Collections.Generic;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;

@ -13,18 +13,18 @@ namespace MediaBrowser.Controller.Resolvers
/// </summary> /// </summary>
public interface IItemResolver public interface IItemResolver
{ {
/// <summary>
/// Gets the priority.
/// </summary>
/// <value>The priority.</value>
ResolverPriority Priority { get; }
/// <summary> /// <summary>
/// Resolves the path. /// Resolves the path.
/// </summary> /// </summary>
/// <param name="args">The args.</param> /// <param name="args">The args.</param>
/// <returns>BaseItem.</returns> /// <returns>BaseItem.</returns>
BaseItem ResolvePath(ItemResolveArgs args); BaseItem ResolvePath(ItemResolveArgs args);
/// <summary>
/// Gets the priority.
/// </summary>
/// <value>The priority.</value>
ResolverPriority Priority { get; }
} }
public interface IMultiItemResolver public interface IMultiItemResolver
@ -38,14 +38,14 @@ namespace MediaBrowser.Controller.Resolvers
public class MultiItemResolverResult public class MultiItemResolverResult
{ {
public List<BaseItem> Items { get; set; }
public List<FileSystemMetadata> ExtraFiles { get; set; }
public MultiItemResolverResult() public MultiItemResolverResult()
{ {
Items = new List<BaseItem>(); Items = new List<BaseItem>();
ExtraFiles = new List<FileSystemMetadata>(); ExtraFiles = new List<FileSystemMetadata>();
} }
public List<BaseItem> Items { get; set; }
public List<FileSystemMetadata> ExtraFiles { get; set; }
} }
} }

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

Loading…
Cancel
Save