Merge branch 'master' into baseitemkind-fixes
This commit is contained in:
commit
663c79cba8
|
@ -213,3 +213,4 @@
|
|||
- [SvenVandenbrande](https://github.com/SvenVandenbrande)
|
||||
- [olsh](https://github.com/olsh)
|
||||
- [lbenini](https://github.com/lbenini)
|
||||
- [gnuyent](https://github.com/gnuyent)
|
||||
|
|
26
Dockerfile
26
Dockerfile
|
@ -8,15 +8,7 @@ RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-
|
|||
&& npm ci --no-audit --unsafe-perm \
|
||||
&& mv dist /dist
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION} as builder
|
||||
WORKDIR /repo
|
||||
COPY . .
|
||||
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
|
||||
# because of changes in docker and systemd we need to not build in parallel at the moment
|
||||
# see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting
|
||||
RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="/jellyfin" --self-contained --runtime linux-x64 "-p:DebugSymbols=false;DebugType=none"
|
||||
|
||||
FROM debian:buster-slim
|
||||
FROM debian:buster-slim as app
|
||||
|
||||
# https://askubuntu.com/questions/972516/debian-frontend-environment-variable
|
||||
ARG DEBIAN_FRONTEND="noninteractive"
|
||||
|
@ -25,9 +17,6 @@ ARG APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE=DontWarn
|
|||
# https://github.com/NVIDIA/nvidia-docker/wiki/Installation-(Native-GPU-Support)
|
||||
ENV NVIDIA_DRIVER_CAPABILITIES="compute,video,utility"
|
||||
|
||||
COPY --from=builder /jellyfin /jellyfin
|
||||
COPY --from=web-builder /dist /jellyfin/jellyfin-web
|
||||
|
||||
# https://github.com/intel/compute-runtime/releases
|
||||
ARG GMMLIB_VERSION=20.3.2
|
||||
ARG IGC_VERSION=1.0.5435
|
||||
|
@ -73,6 +62,19 @@ ENV LC_ALL en_US.UTF-8
|
|||
ENV LANG en_US.UTF-8
|
||||
ENV LANGUAGE en_US:en
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION} as builder
|
||||
WORKDIR /repo
|
||||
COPY . .
|
||||
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
|
||||
# because of changes in docker and systemd we need to not build in parallel at the moment
|
||||
# see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting
|
||||
RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="/jellyfin" --self-contained --runtime linux-x64 "-p:DebugSymbols=false;DebugType=none"
|
||||
|
||||
FROM app
|
||||
|
||||
COPY --from=builder /jellyfin /jellyfin
|
||||
COPY --from=web-builder /dist /jellyfin/jellyfin-web
|
||||
|
||||
EXPOSE 8096
|
||||
VOLUME /cache /config /media
|
||||
ENTRYPOINT ["./jellyfin/jellyfin", \
|
||||
|
|
|
@ -13,19 +13,8 @@ RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-
|
|||
&& npm ci --no-audit --unsafe-perm \
|
||||
&& mv dist /dist
|
||||
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION} as builder
|
||||
WORKDIR /repo
|
||||
COPY . .
|
||||
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
|
||||
# Discard objs - may cause failures if exists
|
||||
RUN find . -type d -name obj | xargs -r rm -r
|
||||
# Build
|
||||
RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm "-p:DebugSymbols=false;DebugType=none"
|
||||
|
||||
|
||||
FROM multiarch/qemu-user-static:x86_64-arm as qemu
|
||||
FROM arm32v7/debian:buster-slim
|
||||
FROM arm32v7/debian:buster-slim as app
|
||||
|
||||
# https://askubuntu.com/questions/972516/debian-frontend-environment-variable
|
||||
ARG DEBIAN_FRONTEND="noninteractive"
|
||||
|
@ -61,14 +50,25 @@ RUN apt-get update \
|
|||
&& chmod 777 /cache /config /media \
|
||||
&& sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && locale-gen
|
||||
|
||||
COPY --from=builder /jellyfin /jellyfin
|
||||
COPY --from=web-builder /dist /jellyfin/jellyfin-web
|
||||
|
||||
ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1
|
||||
ENV LC_ALL en_US.UTF-8
|
||||
ENV LANG en_US.UTF-8
|
||||
ENV LANGUAGE en_US:en
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION} as builder
|
||||
WORKDIR /repo
|
||||
COPY . .
|
||||
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
|
||||
# Discard objs - may cause failures if exists
|
||||
RUN find . -type d -name obj | xargs -r rm -r
|
||||
# Build
|
||||
RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm "-p:DebugSymbols=false;DebugType=none"
|
||||
|
||||
FROM app
|
||||
|
||||
COPY --from=builder /jellyfin /jellyfin
|
||||
COPY --from=web-builder /dist /jellyfin/jellyfin-web
|
||||
|
||||
EXPOSE 8096
|
||||
VOLUME /cache /config /media
|
||||
ENTRYPOINT ["./jellyfin/jellyfin", \
|
||||
|
|
|
@ -13,18 +13,8 @@ RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-
|
|||
&& npm ci --no-audit --unsafe-perm \
|
||||
&& mv dist /dist
|
||||
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION} as builder
|
||||
WORKDIR /repo
|
||||
COPY . .
|
||||
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
|
||||
# Discard objs - may cause failures if exists
|
||||
RUN find . -type d -name obj | xargs -r rm -r
|
||||
# Build
|
||||
RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm64 "-p:DebugSymbols=false;DebugType=none"
|
||||
|
||||
FROM multiarch/qemu-user-static:x86_64-aarch64 as qemu
|
||||
FROM arm64v8/debian:buster-slim
|
||||
FROM arm64v8/debian:buster-slim as app
|
||||
|
||||
# https://askubuntu.com/questions/972516/debian-frontend-environment-variable
|
||||
ARG DEBIAN_FRONTEND="noninteractive"
|
||||
|
@ -50,14 +40,25 @@ RUN apt-get update && apt-get install --no-install-recommends --no-install-sugge
|
|||
&& chmod 777 /cache /config /media \
|
||||
&& sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && locale-gen
|
||||
|
||||
COPY --from=builder /jellyfin /jellyfin
|
||||
COPY --from=web-builder /dist /jellyfin/jellyfin-web
|
||||
|
||||
ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1
|
||||
ENV LC_ALL en_US.UTF-8
|
||||
ENV LANG en_US.UTF-8
|
||||
ENV LANGUAGE en_US:en
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION} as builder
|
||||
WORKDIR /repo
|
||||
COPY . .
|
||||
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
|
||||
# Discard objs - may cause failures if exists
|
||||
RUN find . -type d -name obj | xargs -r rm -r
|
||||
# Build
|
||||
RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm64 "-p:DebugSymbols=false;DebugType=none"
|
||||
|
||||
FROM app
|
||||
|
||||
COPY --from=builder /jellyfin /jellyfin
|
||||
COPY --from=web-builder /dist /jellyfin/jellyfin-web
|
||||
|
||||
EXPOSE 8096
|
||||
VOLUME /cache /config /media
|
||||
ENTRYPOINT ["./jellyfin/jellyfin", \
|
||||
|
|
|
@ -288,21 +288,14 @@ namespace Emby.Dlna.ContentDirectory
|
|||
/// <returns>The xml feature list.</returns>
|
||||
private static string WriteFeatureListXml()
|
||||
{
|
||||
// TODO: clean this up
|
||||
var builder = new StringBuilder();
|
||||
|
||||
builder.Append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
|
||||
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\">");
|
||||
|
||||
builder.Append("<Feature name=\"samsung.com_BASICVIEW\" version=\"1\">");
|
||||
builder.Append("<container id=\"I\" type=\"object.item.imageItem\"/>");
|
||||
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();
|
||||
return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
|
||||
+ "<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\">"
|
||||
+ "<container id=\"I\" type=\"object.item.imageItem\"/>"
|
||||
+ "<container id=\"A\" type=\"object.item.audioItem\"/>"
|
||||
+ "<container id=\"V\" type=\"object.item.videoItem\"/>"
|
||||
+ "</Feature>"
|
||||
+ "</Features>";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
#nullable disable
|
||||
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
|
@ -96,12 +93,14 @@ namespace Emby.Dlna
|
|||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public DeviceProfile GetDefaultProfile()
|
||||
{
|
||||
return new DefaultProfile();
|
||||
}
|
||||
|
||||
public DeviceProfile GetProfile(DeviceIdentification deviceInfo)
|
||||
/// <inheritdoc />
|
||||
public DeviceProfile? GetProfile(DeviceIdentification deviceInfo)
|
||||
{
|
||||
if (deviceInfo == null)
|
||||
{
|
||||
|
@ -111,13 +110,13 @@ namespace Emby.Dlna
|
|||
var profile = GetProfiles()
|
||||
.FirstOrDefault(i => i.Identification != null && IsMatch(deviceInfo, i.Identification));
|
||||
|
||||
if (profile != null)
|
||||
if (profile == null)
|
||||
{
|
||||
_logger.LogDebug("Found matching device profile: {ProfileName}", profile.Name);
|
||||
LogUnmatchedProfile(deviceInfo);
|
||||
}
|
||||
else
|
||||
{
|
||||
LogUnmatchedProfile(deviceInfo);
|
||||
_logger.LogDebug("Found matching device profile: {ProfileName}", profile.Name);
|
||||
}
|
||||
|
||||
return profile;
|
||||
|
@ -187,7 +186,8 @@ namespace Emby.Dlna
|
|||
}
|
||||
}
|
||||
|
||||
public DeviceProfile GetProfile(IHeaderDictionary headers)
|
||||
/// <inheritdoc />
|
||||
public DeviceProfile? GetProfile(IHeaderDictionary headers)
|
||||
{
|
||||
if (headers == null)
|
||||
{
|
||||
|
@ -195,15 +195,13 @@ namespace Emby.Dlna
|
|||
}
|
||||
|
||||
var profile = GetProfiles().FirstOrDefault(i => i.Identification != null && IsMatch(headers, i.Identification));
|
||||
|
||||
if (profile != null)
|
||||
if (profile == null)
|
||||
{
|
||||
_logger.LogDebug("Found matching device profile: {0}", profile.Name);
|
||||
_logger.LogDebug("No matching device profile found. {@Headers}", headers);
|
||||
}
|
||||
else
|
||||
{
|
||||
var headerString = string.Join(", ", headers.Select(i => string.Format(CultureInfo.InvariantCulture, "{0}={1}", i.Key, i.Value)));
|
||||
_logger.LogDebug("No matching device profile found. {0}", headerString);
|
||||
_logger.LogDebug("Found matching device profile: {0}", profile.Name);
|
||||
}
|
||||
|
||||
return profile;
|
||||
|
@ -253,19 +251,19 @@ namespace Emby.Dlna
|
|||
return xmlFies
|
||||
.Select(i => ParseProfileFile(i, type))
|
||||
.Where(i => i != null)
|
||||
.ToList();
|
||||
.ToList()!; // We just filtered out all the nulls
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
return new List<DeviceProfile>();
|
||||
return Array.Empty<DeviceProfile>();
|
||||
}
|
||||
}
|
||||
|
||||
private DeviceProfile ParseProfileFile(string path, DeviceProfileType type)
|
||||
private DeviceProfile? ParseProfileFile(string path, DeviceProfileType type)
|
||||
{
|
||||
lock (_profiles)
|
||||
{
|
||||
if (_profiles.TryGetValue(path, out Tuple<InternalProfileInfo, DeviceProfile> profileTuple))
|
||||
if (_profiles.TryGetValue(path, out Tuple<InternalProfileInfo, DeviceProfile>? profileTuple))
|
||||
{
|
||||
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))
|
||||
{
|
||||
|
@ -322,6 +321,7 @@ namespace Emby.Dlna
|
|||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<DeviceProfileInfo> GetProfileInfos()
|
||||
{
|
||||
return GetProfileInfosInternal().Select(i => i.Info);
|
||||
|
@ -329,17 +329,14 @@ namespace Emby.Dlna
|
|||
|
||||
private InternalProfileInfo GetInternalProfileInfo(FileSystemMetadata file, DeviceProfileType type)
|
||||
{
|
||||
return new InternalProfileInfo
|
||||
{
|
||||
Path = file.FullName,
|
||||
|
||||
Info = new DeviceProfileInfo
|
||||
return new InternalProfileInfo(
|
||||
new DeviceProfileInfo
|
||||
{
|
||||
Id = file.FullName.ToLowerInvariant().GetMD5().ToString("N", CultureInfo.InvariantCulture),
|
||||
Name = _fileSystem.GetFileNameWithoutExtension(file),
|
||||
Type = type
|
||||
}
|
||||
};
|
||||
},
|
||||
file.FullName);
|
||||
}
|
||||
|
||||
private async Task ExtractSystemProfilesAsync()
|
||||
|
@ -359,7 +356,8 @@ namespace Emby.Dlna
|
|||
systemProfilesPath,
|
||||
Path.GetFileName(name.AsSpan()).Slice(namespaceName.Length));
|
||||
|
||||
using (var stream = _assembly.GetManifestResourceStream(name))
|
||||
// The stream should exist as we just got its name from GetManifestResourceNames
|
||||
using (var stream = _assembly.GetManifestResourceStream(name)!)
|
||||
{
|
||||
var fileInfo = _fileSystem.GetFileInfo(path);
|
||||
|
||||
|
@ -380,6 +378,7 @@ namespace Emby.Dlna
|
|||
Directory.CreateDirectory(UserProfilesPath);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void DeleteProfile(string id)
|
||||
{
|
||||
var info = GetProfileInfosInternal().First(i => string.Equals(id, i.Info.Id, StringComparison.OrdinalIgnoreCase));
|
||||
|
@ -397,6 +396,7 @@ namespace Emby.Dlna
|
|||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void CreateProfile(DeviceProfile profile)
|
||||
{
|
||||
profile = ReserializeProfile(profile);
|
||||
|
@ -412,6 +412,7 @@ namespace Emby.Dlna
|
|||
SaveProfile(profile, path, DeviceProfileType.User);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void UpdateProfile(DeviceProfile profile)
|
||||
{
|
||||
profile = ReserializeProfile(profile);
|
||||
|
@ -470,9 +471,11 @@ namespace Emby.Dlna
|
|||
|
||||
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)
|
||||
{
|
||||
var profile = GetDefaultProfile();
|
||||
|
@ -482,6 +485,7 @@ namespace Emby.Dlna
|
|||
return new DescriptionXmlBuilder(profile, serverUuId, serverAddress, _appHost.FriendlyName, serverId).GetXml();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ImageStream GetIcon(string filename)
|
||||
{
|
||||
var format = filename.EndsWith(".png", StringComparison.OrdinalIgnoreCase)
|
||||
|
@ -499,9 +503,15 @@ namespace Emby.Dlna
|
|||
|
||||
private class InternalProfileInfo
|
||||
{
|
||||
internal DeviceProfileInfo Info { get; set; }
|
||||
internal InternalProfileInfo(DeviceProfileInfo info, string path)
|
||||
{
|
||||
Info = info;
|
||||
Path = path;
|
||||
}
|
||||
|
||||
internal string Path { get; set; }
|
||||
internal DeviceProfileInfo Info { get; }
|
||||
|
||||
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="extras">List of extra 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;
|
||||
Year = year;
|
||||
|
@ -39,18 +39,18 @@ namespace Emby.Naming.AudioBook
|
|||
/// Gets or sets the files.
|
||||
/// </summary>
|
||||
/// <value>The files.</value>
|
||||
public List<AudioBookFileInfo> Files { get; set; }
|
||||
public IReadOnlyList<AudioBookFileInfo> Files { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the extras.
|
||||
/// </summary>
|
||||
/// <value>The extras.</value>
|
||||
public List<AudioBookFileInfo> Extras { get; set; }
|
||||
public IReadOnlyList<AudioBookFileInfo> Extras { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the alternate versions.
|
||||
/// </summary>
|
||||
/// <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)
|
||||
{
|
||||
var name = Path.GetFileNameWithoutExtension(audioFile.Path);
|
||||
if (name.Equals("audiobook") ||
|
||||
if (name.Equals("audiobook", StringComparison.OrdinalIgnoreCase) ||
|
||||
name.Contains(nameParserResult.Name, StringComparison.OrdinalIgnoreCase) ||
|
||||
name.Contains(nameWithReplacedDots, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
|
|
|
@ -284,7 +284,7 @@ namespace Emby.Naming.Common
|
|||
|
||||
// Not a Kodi rule as well, but below rule also causes false positives for triple-digit episode names
|
||||
// [bar] Foo - 1 [baz] special case of below expression to prevent false positives with digits in the series name
|
||||
new EpisodeExpression(@".*?(\[.*?\])+.*?(?<seriesname>[\w\s]+?)[\s_]*-[\s_]*(?<epnumber>[0-9]+).*$")
|
||||
new EpisodeExpression(@".*[\\\/]?.*?(\[.*?\])+.*?(?<seriesname>[-\w\s]+?)[\s_]*-[\s_]*(?<epnumber>[0-9]+).*$")
|
||||
{
|
||||
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 -->
|
||||
<PropertyGroup>
|
||||
|
@ -49,7 +49,4 @@
|
|||
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using Emby.Naming.Audio;
|
||||
using Emby.Naming.Common;
|
||||
|
|
|
@ -21,7 +21,7 @@ namespace Emby.Naming.Video
|
|||
/// <param name="namingOptions">The naming options.</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>
|
||||
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
|
||||
.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
|
||||
{
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
@ -63,13 +61,13 @@ namespace Emby.Server.Implementations.Collections
|
|||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public event EventHandler<CollectionCreatedEventArgs> CollectionCreated;
|
||||
public event EventHandler<CollectionCreatedEventArgs>? CollectionCreated;
|
||||
|
||||
/// <inheritdoc />
|
||||
public event EventHandler<CollectionModifiedEventArgs> ItemsAddedToCollection;
|
||||
public event EventHandler<CollectionModifiedEventArgs>? ItemsAddedToCollection;
|
||||
|
||||
/// <inheritdoc />
|
||||
public event EventHandler<CollectionModifiedEventArgs> ItemsRemovedFromCollection;
|
||||
public event EventHandler<CollectionModifiedEventArgs>? ItemsRemovedFromCollection;
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
internal async Task<Folder> EnsureLibraryFolder(string path, bool createIfNeeded)
|
||||
internal async Task<Folder?> EnsureLibraryFolder(string path, bool createIfNeeded)
|
||||
{
|
||||
var existingFolder = FindFolders(path).FirstOrDefault();
|
||||
if (existingFolder != null)
|
||||
|
@ -114,7 +112,7 @@ namespace Emby.Server.Implementations.Collections
|
|||
return Path.Combine(_appPaths.DataPath, "collections");
|
||||
}
|
||||
|
||||
private Task<Folder> GetCollectionsFolder(bool createIfNeeded)
|
||||
private Task<Folder?> GetCollectionsFolder(bool 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)
|
||||
{
|
||||
var collection = _libraryManager.GetItemById(collectionId) as BoxSet;
|
||||
if (collection == null)
|
||||
if (_libraryManager.GetItemById(collectionId) is not BoxSet collection)
|
||||
{
|
||||
throw new ArgumentException("No collection exists with the supplied Id");
|
||||
}
|
||||
|
@ -256,9 +253,7 @@ namespace Emby.Server.Implementations.Collections
|
|||
/// <inheritdoc />
|
||||
public async Task RemoveFromCollectionAsync(Guid collectionId, IEnumerable<Guid> itemIds)
|
||||
{
|
||||
var collection = _libraryManager.GetItemById(collectionId) as BoxSet;
|
||||
|
||||
if (collection == null)
|
||||
if (_libraryManager.GetItemById(collectionId) is not BoxSet collection)
|
||||
{
|
||||
throw new ArgumentException("No collection exists with the supplied Id");
|
||||
}
|
||||
|
@ -312,11 +307,7 @@ namespace Emby.Server.Implementations.Collections
|
|||
|
||||
foreach (var item in items)
|
||||
{
|
||||
if (item is not ISupportsBoxSetGrouping)
|
||||
{
|
||||
results[item.Id] = item;
|
||||
}
|
||||
else
|
||||
if (item is ISupportsBoxSetGrouping)
|
||||
{
|
||||
var itemId = item.Id;
|
||||
|
||||
|
@ -340,6 +331,7 @@ namespace Emby.Server.Implementations.Collections
|
|||
}
|
||||
|
||||
var alreadyInResults = false;
|
||||
|
||||
// this is kind of a performance hack because only Video has alternate versions that should be in a box set?
|
||||
if (item is Video video)
|
||||
{
|
||||
|
@ -355,11 +347,13 @@ namespace Emby.Server.Implementations.Collections
|
|||
}
|
||||
}
|
||||
|
||||
if (!alreadyInResults)
|
||||
if (alreadyInResults)
|
||||
{
|
||||
results[itemId] = item;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
results[item.Id] = item;
|
||||
}
|
||||
|
||||
return results.Values;
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" 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.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="prometheus-net.DotNetRuntime" Version="4.1.0" />
|
||||
<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
|
||||
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))
|
||||
{
|
||||
|
|
|
@ -13,7 +13,6 @@ using System.Threading.Tasks;
|
|||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.LiveTv;
|
||||
using MediaBrowser.Model.LiveTv;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
@ -44,22 +43,29 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
|||
|
||||
public async Task<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);
|
||||
if (!string.IsNullOrEmpty(info.UserAgent))
|
||||
{
|
||||
requestMessage.Headers.UserAgent.TryParseAdd(info.UserAgent);
|
||||
}
|
||||
|
||||
// Set HttpCompletionOption.ResponseHeadersRead to prevent timeouts on larger files
|
||||
var response = await _httpClientFactory.CreateClient(NamedClient.Default)
|
||||
.SendAsync(requestMessage, cancellationToken)
|
||||
.SendAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
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)
|
||||
|
@ -83,7 +89,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
|||
if (trimmedLine.StartsWith(ExtInfPrefix, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
extInf = trimmedLine.Substring(ExtInfPrefix.Length).Trim();
|
||||
_logger.LogInformation("Found m3u channel: {0}", extInf);
|
||||
}
|
||||
else if (!string.IsNullOrWhiteSpace(extInf) && !trimmedLine.StartsWith('#'))
|
||||
{
|
||||
|
@ -99,6 +104,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
|||
|
||||
channel.Path = trimmedLine;
|
||||
channels.Add(channel);
|
||||
_logger.LogInformation("Parsed channel: {ChannelName}", channel.Name);
|
||||
extInf = string.Empty;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,24 +2,24 @@
|
|||
"Artists": "Kunstenare",
|
||||
"Channels": "Kanale",
|
||||
"Folders": "Lêergidse",
|
||||
"Favorites": "Gunstellinge",
|
||||
"Favorites": "Gunstelinge",
|
||||
"HeaderFavoriteShows": "Gunsteling Vertonings",
|
||||
"ValueSpecialEpisodeName": "Spesiale - {0}",
|
||||
"HeaderAlbumArtists": "Album Kunstenaars",
|
||||
"HeaderAlbumArtists": "Kunstenaars se Album",
|
||||
"Books": "Boeke",
|
||||
"HeaderNextUp": "Volgende",
|
||||
"Movies": "Flieks",
|
||||
"Shows": "Televisie Reekse",
|
||||
"HeaderContinueWatching": "Kyk Verder",
|
||||
"HeaderFavoriteEpisodes": "Gunsteling Episodes",
|
||||
"Photos": "Fotos",
|
||||
"Photos": "Foto's",
|
||||
"Playlists": "Snitlyste",
|
||||
"HeaderFavoriteArtists": "Gunsteling Kunstenaars",
|
||||
"HeaderFavoriteAlbums": "Gunsteling Albums",
|
||||
"Sync": "Sinkroniseer",
|
||||
"HeaderFavoriteSongs": "Gunsteling Liedjies",
|
||||
"Songs": "Liedjies",
|
||||
"DeviceOnlineWithName": "{0} gekoppel is",
|
||||
"DeviceOnlineWithName": "{0} is gekoppel",
|
||||
"DeviceOfflineWithName": "{0} is ontkoppel",
|
||||
"Collections": "Versamelings",
|
||||
"Inherit": "Ontvang",
|
||||
|
@ -71,7 +71,7 @@
|
|||
"NameSeasonUnknown": "Seisoen Onbekend",
|
||||
"NameSeasonNumber": "Seisoen {0}",
|
||||
"NameInstallFailed": "{0} installering het misluk",
|
||||
"MusicVideos": "Musiek videos",
|
||||
"MusicVideos": "Musiek Videos",
|
||||
"Music": "Musiek",
|
||||
"MixedContent": "Gemengde inhoud",
|
||||
"MessageServerConfigurationUpdated": "Bediener konfigurasie is opgedateer",
|
||||
|
@ -79,15 +79,15 @@
|
|||
"MessageApplicationUpdatedTo": "Jellyfin Bediener is opgedateer na {0}",
|
||||
"MessageApplicationUpdated": "Jellyfin Bediener is opgedateer",
|
||||
"Latest": "Nuutste",
|
||||
"LabelRunningTimeValue": "Lopende tyd: {0}",
|
||||
"LabelRunningTimeValue": "Werktyd: {0}",
|
||||
"LabelIpAddressValue": "IP adres: {0}",
|
||||
"ItemRemovedWithName": "{0} is uit versameling verwyder",
|
||||
"ItemAddedWithName": "{0} is in die versameling",
|
||||
"HomeVideos": "Tuis opnames",
|
||||
"ItemAddedWithName": "{0} is by die versameling gevoeg",
|
||||
"HomeVideos": "Tuis Videos",
|
||||
"HeaderRecordingGroups": "Groep Opnames",
|
||||
"Genres": "Genres",
|
||||
"FailedLoginAttemptWithUserName": "Mislukte aansluiting van {0}",
|
||||
"ChapterNameValue": "Hoofstuk",
|
||||
"ChapterNameValue": "Hoofstuk {0}",
|
||||
"CameraImageUploadedFrom": "'n Nuwe kamera photo opgelaai van {0}",
|
||||
"AuthenticationSucceededWithUserName": "{0} suksesvol geverifieer",
|
||||
"Albums": "Albums",
|
||||
|
@ -117,5 +117,7 @@
|
|||
"Forced": "Geforseer",
|
||||
"Default": "Oorspronklik",
|
||||
"TaskCleanActivityLogDescription": "Verwyder aktiwiteitsaantekeninge ouer as die opgestelde ouderdom.",
|
||||
"TaskCleanActivityLog": "Maak Aktiwiteitsaantekeninge Skoon"
|
||||
"TaskCleanActivityLog": "Maak Aktiwiteitsaantekeninge Skoon",
|
||||
"TaskOptimizeDatabaseDescription": "Komprimeer databasis en verkort vrye ruimte. As hierdie taak uitgevoer word nadat die media versameling geskandeer is of ander veranderings aangebring is wat databasisaanpassings impliseer, kan dit die prestasie verbeter.",
|
||||
"TaskOptimizeDatabase": "Optimaliseer databasis"
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
"Artists": "Artistes",
|
||||
"AuthenticationSucceededWithUserName": "{0} s'ha autenticat correctament",
|
||||
"Books": "Llibres",
|
||||
"CameraImageUploadedFrom": "Una nova imatge de la càmera ha estat pujada des de {0}",
|
||||
"CameraImageUploadedFrom": "S'ha pujat una nova imatge des de la camera desde {0}",
|
||||
"Channels": "Canals",
|
||||
"ChapterNameValue": "Capítol {0}",
|
||||
"Collections": "Col·leccions",
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"Albums": "Άλμπουμς",
|
||||
"Albums": "Άλμπουμ",
|
||||
"AppDeviceValues": "Εφαρμογή: {0}, Συσκευή: {1}",
|
||||
"Application": "Εφαρμογή",
|
||||
"Artists": "Καλλιτέχνες",
|
||||
|
@ -15,7 +15,7 @@
|
|||
"Favorites": "Αγαπημένα",
|
||||
"Folders": "Φάκελοι",
|
||||
"Genres": "Είδη",
|
||||
"HeaderAlbumArtists": "Καλλιτέχνες του Άλμπουμ",
|
||||
"HeaderAlbumArtists": "Άλμπουμ Καλλιτέχνη",
|
||||
"HeaderContinueWatching": "Συνεχίστε την παρακολούθηση",
|
||||
"HeaderFavoriteAlbums": "Αγαπημένα Άλμπουμ",
|
||||
"HeaderFavoriteArtists": "Αγαπημένοι Καλλιτέχνες",
|
||||
|
@ -39,7 +39,7 @@
|
|||
"MixedContent": "Ανάμεικτο Περιεχόμενο",
|
||||
"Movies": "Ταινίες",
|
||||
"Music": "Μουσική",
|
||||
"MusicVideos": "Μουσικά βίντεο",
|
||||
"MusicVideos": "Μουσικά Βίντεο",
|
||||
"NameInstallFailed": "{0} η εγκατάσταση απέτυχε",
|
||||
"NameSeasonNumber": "Κύκλος {0}",
|
||||
"NameSeasonUnknown": "Άγνωστος Κύκλος",
|
||||
|
@ -62,7 +62,7 @@
|
|||
"NotificationOptionVideoPlaybackStopped": "Η αναπαραγωγή βίντεο σταμάτησε",
|
||||
"Photos": "Φωτογραφίες",
|
||||
"Playlists": "Λίστες αναπαραγωγής",
|
||||
"Plugin": "Plugin",
|
||||
"Plugin": "Πρόσθετο",
|
||||
"PluginInstalledWithName": "{0} εγκαταστήθηκε",
|
||||
"PluginUninstalledWithName": "{0} έχει απεγκατασταθεί",
|
||||
"PluginUpdatedWithName": "{0} έχει αναβαθμιστεί",
|
||||
|
@ -118,5 +118,7 @@
|
|||
"TaskCleanActivityLog": "Καθαρό Αρχείο Καταγραφής Δραστηριοτήτων",
|
||||
"Undefined": "Απροσδιόριστο",
|
||||
"Forced": "Εξαναγκασμένο",
|
||||
"Default": "Προεπιλογή"
|
||||
"Default": "Προεπιλογή",
|
||||
"TaskOptimizeDatabaseDescription": "Συμπιέζει τη βάση δεδομένων και δημιουργεί ελεύθερο χώρο. Η εκτέλεση αυτής της εργασίας μετά τη σάρωση της βιβλιοθήκης ή την πραγματοποίηση άλλων αλλαγών που συνεπάγονται τροποποιήσεις της βάσης δεδομένων μπορεί να βελτιώσει την απόδοση.",
|
||||
"TaskOptimizeDatabase": "Βελτιστοποίηση βάσης δεδομένων"
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
"Folders": "Folders",
|
||||
"Forced": "Forced",
|
||||
"Genres": "Genres",
|
||||
"HeaderAlbumArtists": "Album Artists",
|
||||
"HeaderAlbumArtists": "Artist's Album",
|
||||
"HeaderContinueWatching": "Continue Watching",
|
||||
"HeaderFavoriteAlbums": "Favorite Albums",
|
||||
"HeaderFavoriteArtists": "Favorite Artists",
|
||||
|
@ -27,7 +27,7 @@
|
|||
"HeaderLiveTV": "Live TV",
|
||||
"HeaderNextUp": "Next Up",
|
||||
"HeaderRecordingGroups": "Recording Groups",
|
||||
"HomeVideos": "Home videos",
|
||||
"HomeVideos": "Home Videos",
|
||||
"Inherit": "Inherit",
|
||||
"ItemAddedWithName": "{0} was added to the library",
|
||||
"ItemRemovedWithName": "{0} was removed from the library",
|
||||
|
@ -41,7 +41,7 @@
|
|||
"MixedContent": "Mixed content",
|
||||
"Movies": "Movies",
|
||||
"Music": "Music",
|
||||
"MusicVideos": "Music videos",
|
||||
"MusicVideos": "Music Videos",
|
||||
"NameInstallFailed": "{0} installation failed",
|
||||
"NameSeasonNumber": "Season {0}",
|
||||
"NameSeasonUnknown": "Season Unknown",
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
"Favorites": "Favoritos",
|
||||
"Folders": "Carpetas",
|
||||
"Genres": "Géneros",
|
||||
"HeaderAlbumArtists": "Artistas del álbum",
|
||||
"HeaderAlbumArtists": "Artistas del Álbum",
|
||||
"HeaderContinueWatching": "Continuar viendo",
|
||||
"HeaderFavoriteAlbums": "Álbumes favoritos",
|
||||
"HeaderFavoriteArtists": "Artistas favoritos",
|
||||
|
@ -25,7 +25,7 @@
|
|||
"HeaderLiveTV": "TV en vivo",
|
||||
"HeaderNextUp": "A continuación",
|
||||
"HeaderRecordingGroups": "Grupos de grabación",
|
||||
"HomeVideos": "Videos caseros",
|
||||
"HomeVideos": "Videos Caseros",
|
||||
"Inherit": "Heredar",
|
||||
"ItemAddedWithName": "{0} fue agregado a la biblioteca",
|
||||
"ItemRemovedWithName": "{0} fue removido de la biblioteca",
|
||||
|
@ -39,7 +39,7 @@
|
|||
"MixedContent": "Contenido mezclado",
|
||||
"Movies": "Películas",
|
||||
"Music": "Música",
|
||||
"MusicVideos": "Videos musicales",
|
||||
"MusicVideos": "Videos Musicales",
|
||||
"NameInstallFailed": "Falló la instalación de {0}",
|
||||
"NameSeasonNumber": "Temporada {0}",
|
||||
"NameSeasonUnknown": "Temporada desconocida",
|
||||
|
@ -49,7 +49,7 @@
|
|||
"NotificationOptionAudioPlayback": "Reproducción de audio iniciada",
|
||||
"NotificationOptionAudioPlaybackStopped": "Reproducción de audio detenida",
|
||||
"NotificationOptionCameraImageUploaded": "Imagen de la cámara subida",
|
||||
"NotificationOptionInstallationFailed": "Falla de instalación",
|
||||
"NotificationOptionInstallationFailed": "Fallo en la instalación",
|
||||
"NotificationOptionNewLibraryContent": "Nuevo contenido agregado",
|
||||
"NotificationOptionPluginError": "Falla de complemento",
|
||||
"NotificationOptionPluginInstalled": "Complemento instalado",
|
||||
|
@ -69,7 +69,7 @@
|
|||
"ProviderValue": "Proveedor: {0}",
|
||||
"ScheduledTaskFailedWithName": "{0} falló",
|
||||
"ScheduledTaskStartedWithName": "{0} iniciado",
|
||||
"ServerNameNeedsToBeRestarted": "{0} debe ser reiniciado",
|
||||
"ServerNameNeedsToBeRestarted": "{0} necesita ser reiniciado",
|
||||
"Shows": "Programas",
|
||||
"Songs": "Canciones",
|
||||
"StartupEmbyServerIsLoading": "El servidor Jellyfin está cargando. Por favor, intente de nuevo pronto.",
|
||||
|
@ -94,9 +94,9 @@
|
|||
"VersionNumber": "Versión {0}",
|
||||
"TaskDownloadMissingSubtitlesDescription": "Busca subtítulos faltantes en Internet basándose en la configuración de metadatos.",
|
||||
"TaskDownloadMissingSubtitles": "Descargar subtítulos faltantes",
|
||||
"TaskRefreshChannelsDescription": "Actualiza la información de canales de Internet.",
|
||||
"TaskRefreshChannelsDescription": "Actualiza la información de los canales de Internet.",
|
||||
"TaskRefreshChannels": "Actualizar canales",
|
||||
"TaskCleanTranscodeDescription": "Elimina archivos transcodificados que tengan más de un día.",
|
||||
"TaskCleanTranscodeDescription": "Elimina archivos transcodificados que tengan más de un día de antigüedad.",
|
||||
"TaskCleanTranscode": "Limpiar directorio de transcodificado",
|
||||
"TaskUpdatePluginsDescription": "Descarga e instala actualizaciones para complementos que están configurados para actualizarse automáticamente.",
|
||||
"TaskUpdatePlugins": "Actualizar complementos",
|
||||
|
@ -118,5 +118,7 @@
|
|||
"TaskCleanActivityLog": "Limpiar registro de actividades",
|
||||
"Undefined": "Sin definir",
|
||||
"Forced": "Forzado",
|
||||
"Default": "Predeterminado"
|
||||
"Default": "Predeterminado",
|
||||
"TaskOptimizeDatabase": "Optimizar base de datos",
|
||||
"TaskOptimizeDatabaseDescription": "Compacta la base de datos y trunca el espacio libre. Puede mejorar el rendimiento si se realiza esta tarea después de escanear la biblioteca o después de realizar otros cambios que impliquen modificar la base de datos."
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
"Favorites": "Favoritos",
|
||||
"Folders": "Carpetas",
|
||||
"Genres": "Géneros",
|
||||
"HeaderAlbumArtists": "Artistas del álbum",
|
||||
"HeaderAlbumArtists": "Artista del álbum",
|
||||
"HeaderContinueWatching": "Continuar viendo",
|
||||
"HeaderFavoriteAlbums": "Álbumes favoritos",
|
||||
"HeaderFavoriteArtists": "Artistas favoritos",
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
"Favorites": "Kedvencek",
|
||||
"Folders": "Könyvtárak",
|
||||
"Genres": "Műfajok",
|
||||
"HeaderAlbumArtists": "Album előadók",
|
||||
"HeaderAlbumArtists": "Előadó albumai",
|
||||
"HeaderContinueWatching": "Megtekintés folytatása",
|
||||
"HeaderFavoriteAlbums": "Kedvenc albumok",
|
||||
"HeaderFavoriteArtists": "Kedvenc előadók",
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
"Favorites": "Preferiti",
|
||||
"Folders": "Cartelle",
|
||||
"Genres": "Generi",
|
||||
"HeaderAlbumArtists": "Artisti degli Album",
|
||||
"HeaderAlbumArtists": "Artisti dell'Album",
|
||||
"HeaderContinueWatching": "Continua a guardare",
|
||||
"HeaderFavoriteAlbums": "Album Preferiti",
|
||||
"HeaderFavoriteArtists": "Artisti Preferiti",
|
||||
|
@ -25,7 +25,7 @@
|
|||
"HeaderLiveTV": "Diretta TV",
|
||||
"HeaderNextUp": "Prossimo",
|
||||
"HeaderRecordingGroups": "Gruppi di Registrazione",
|
||||
"HomeVideos": "Video personali",
|
||||
"HomeVideos": "Video Personali",
|
||||
"Inherit": "Eredita",
|
||||
"ItemAddedWithName": "{0} è stato aggiunto alla libreria",
|
||||
"ItemRemovedWithName": "{0} è stato rimosso dalla libreria",
|
||||
|
@ -39,7 +39,7 @@
|
|||
"MixedContent": "Contenuto misto",
|
||||
"Movies": "Film",
|
||||
"Music": "Musica",
|
||||
"MusicVideos": "Video musicali",
|
||||
"MusicVideos": "Video Musicali",
|
||||
"NameInstallFailed": "{0} installazione fallita",
|
||||
"NameSeasonNumber": "Stagione {0}",
|
||||
"NameSeasonUnknown": "Stagione sconosciuta",
|
||||
|
@ -70,7 +70,7 @@
|
|||
"ScheduledTaskFailedWithName": "{0} fallito",
|
||||
"ScheduledTaskStartedWithName": "{0} avviati",
|
||||
"ServerNameNeedsToBeRestarted": "{0} deve essere riavviato",
|
||||
"Shows": "Programmi",
|
||||
"Shows": "Serie TV",
|
||||
"Songs": "Canzoni",
|
||||
"StartupEmbyServerIsLoading": "Jellyfin server si sta avviando. Per favore riprova più tardi.",
|
||||
"SubtitleDownloadFailureForItem": "Impossibile scaricare i sottotitoli per {0}",
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
"Favorites": "Tañdaulylar",
|
||||
"Folders": "Qaltalar",
|
||||
"Genres": "Janrlar",
|
||||
"HeaderAlbumArtists": "Älbom oryndauşylary",
|
||||
"HeaderAlbumArtists": "Oryndauşynyñ älbomy",
|
||||
"HeaderContinueWatching": "Qaraudy jalğastyru",
|
||||
"HeaderFavoriteAlbums": "Tañdauly älbomdar",
|
||||
"HeaderFavoriteArtists": "Tañdauly oryndauşylar",
|
||||
|
|
|
@ -103,7 +103,7 @@
|
|||
"ValueSpecialEpisodeName": "പ്രത്യേക - {0}",
|
||||
"Collections": "ശേഖരങ്ങൾ",
|
||||
"Folders": "ഫോൾഡറുകൾ",
|
||||
"HeaderAlbumArtists": "ആൽബം ആർട്ടിസ്റ്റുകൾ",
|
||||
"HeaderAlbumArtists": "കലാകാരന്റെ ആൽബം",
|
||||
"Sync": "സമന്വയിപ്പിക്കുക",
|
||||
"Movies": "സിനിമകൾ",
|
||||
"Photos": "ഫോട്ടോകൾ",
|
||||
|
|
1
Emby.Server.Implementations/Localization/Core/pr.json
Normal file
1
Emby.Server.Implementations/Localization/Core/pr.json
Normal file
|
@ -0,0 +1 @@
|
|||
{}
|
|
@ -25,7 +25,7 @@
|
|||
"HeaderLiveTV": "Эфир",
|
||||
"HeaderNextUp": "Очередное",
|
||||
"HeaderRecordingGroups": "Группы записей",
|
||||
"HomeVideos": "Домашнее видео",
|
||||
"HomeVideos": "Домашние видео",
|
||||
"Inherit": "Наследуемое",
|
||||
"ItemAddedWithName": "{0} - добавлено в медиатеку",
|
||||
"ItemRemovedWithName": "{0} - изъято из медиатеки",
|
||||
|
|
|
@ -39,7 +39,7 @@
|
|||
"MixedContent": "Zmiešaný obsah",
|
||||
"Movies": "Filmy",
|
||||
"Music": "Hudba",
|
||||
"MusicVideos": "Hudobné videoklipy",
|
||||
"MusicVideos": "Hudobné videá",
|
||||
"NameInstallFailed": "Inštalácia {0} zlyhala",
|
||||
"NameSeasonNumber": "Séria {0}",
|
||||
"NameSeasonUnknown": "Neznáma séria",
|
||||
|
|
|
@ -118,5 +118,6 @@
|
|||
"TaskCleanActivityLog": "Rensa Aktivitets Logg",
|
||||
"Undefined": "odefinierad",
|
||||
"Forced": "Tvingad",
|
||||
"Default": "Standard"
|
||||
"Default": "Standard",
|
||||
"TaskOptimizeDatabase": "Optimera databasen"
|
||||
}
|
||||
|
|
|
@ -43,7 +43,7 @@
|
|||
"NameInstallFailed": "{0} kurulumu başarısız",
|
||||
"NameSeasonNumber": "Sezon {0}",
|
||||
"NameSeasonUnknown": "Bilinmeyen Sezon",
|
||||
"NewVersionIsAvailable": "Jellyfin Sunucusunun yeni bir versiyonu indirmek için hazır.",
|
||||
"NewVersionIsAvailable": "Jellyfin Sunucusunun yeni bir sürümü indirmek için hazır.",
|
||||
"NotificationOptionApplicationUpdateAvailable": "Uygulama güncellemesi mevcut",
|
||||
"NotificationOptionApplicationUpdateInstalled": "Uygulama güncellemesi yüklendi",
|
||||
"NotificationOptionAudioPlayback": "Ses çalma başladı",
|
||||
|
@ -75,7 +75,7 @@
|
|||
"StartupEmbyServerIsLoading": "Jellyfin Sunucusu yükleniyor. Lütfen kısa süre sonra tekrar deneyin.",
|
||||
"SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}",
|
||||
"SubtitleDownloadFailureFromForItem": "{1} için alt yazılar {0} 'dan indirilemedi",
|
||||
"Sync": "Eşitle",
|
||||
"Sync": "Eşzamanlama",
|
||||
"System": "Sistem",
|
||||
"TvShows": "Diziler",
|
||||
"User": "Kullanıcı",
|
||||
|
@ -89,34 +89,36 @@
|
|||
"UserPolicyUpdatedWithName": "Kullanıcı politikası {0} için güncellendi",
|
||||
"UserStartedPlayingItemWithValues": "{0}, {2} cihazında {1} izliyor",
|
||||
"UserStoppedPlayingItemWithValues": "{0}, {2} cihazında {1} izlemeyi bitirdi",
|
||||
"ValueHasBeenAddedToLibrary": "Medya kitaplığınıza {0} eklendi",
|
||||
"ValueHasBeenAddedToLibrary": "Medya kütüphanenize {0} eklendi",
|
||||
"ValueSpecialEpisodeName": "Özel - {0}",
|
||||
"VersionNumber": "Versiyon {0}",
|
||||
"VersionNumber": "Sürüm {0}",
|
||||
"TaskCleanCache": "Geçici dosya klasörünü temizle",
|
||||
"TasksChannelsCategory": "İnternet kanalları",
|
||||
"TasksApplicationCategory": "Uygulama",
|
||||
"TasksLibraryCategory": "Kütüphane",
|
||||
"TasksMaintenanceCategory": "Onarım",
|
||||
"TasksMaintenanceCategory": "Bakım",
|
||||
"TaskRefreshPeopleDescription": "Medya kütüphanenizdeki videoların oyuncu ve yönetmen bilgilerini günceller.",
|
||||
"TaskDownloadMissingSubtitlesDescription": "Metadata ayarlarını baz alarak eksik altyazıları internette arar.",
|
||||
"TaskDownloadMissingSubtitles": "Eksik altyazıları indir",
|
||||
"TaskRefreshChannelsDescription": "Internet kanal bilgilerini yenile.",
|
||||
"TaskRefreshChannels": "Kanalları Yenile",
|
||||
"TaskCleanTranscodeDescription": "Bir günü dolmuş dönüştürme bilgisi içeren dosyaları siler.",
|
||||
"TaskCleanTranscodeDescription": "Bir günden daha eski dönüştürme dosyalarını siler.",
|
||||
"TaskCleanTranscode": "Dönüşüm Dizinini Temizle",
|
||||
"TaskUpdatePluginsDescription": "Otomatik güncellenmeye ayarlanmış eklentilerin güncellemelerini indirir ve kurar.",
|
||||
"TaskUpdatePlugins": "Eklentileri Güncelle",
|
||||
"TaskRefreshPeople": "Kullanıcıları Yenile",
|
||||
"TaskCleanLogsDescription": "{0} günden eski log dosyalarını siler.",
|
||||
"TaskCleanLogs": "Log Dizinini Temizle",
|
||||
"TaskRefreshLibraryDescription": "Medya kütüphanenize eklenen yeni dosyaları arar ve bilgileri yeniler.",
|
||||
"TaskCleanLogsDescription": "{0} günden eski günlük dosyalarını siler.",
|
||||
"TaskCleanLogs": "Günlük Dizinini Temizle",
|
||||
"TaskRefreshLibraryDescription": "Medya kütüphanenize eklenen yeni dosyaları arar ve ortam bilgilerini yeniler.",
|
||||
"TaskRefreshLibrary": "Medya Kütüphanesini Tara",
|
||||
"TaskRefreshChapterImagesDescription": "Sahnelere ayrılmış videolar için küçük resimler oluştur.",
|
||||
"TaskRefreshChapterImages": "Bölüm Resimlerini Çıkar",
|
||||
"TaskCleanCacheDescription": "Sistem tarafından artık ihtiyaç duyulmayan önbellek dosyalarını siler.",
|
||||
"TaskCleanActivityLog": "İşlem Günlüğünü Temizle",
|
||||
"TaskCleanActivityLogDescription": "Belirtilen sureden daha eski etkinlik log kayıtları silindi.",
|
||||
"TaskCleanActivityLog": "Etkinlik Günlüğünü Temizle",
|
||||
"TaskCleanActivityLogDescription": "Yapılandırılan tarihten daha eski olan etkinlik günlüğü girişlerini siler.",
|
||||
"Undefined": "Bilinmeyen",
|
||||
"Default": "Varsayılan",
|
||||
"Forced": "Zorla"
|
||||
"Forced": "Zorla",
|
||||
"TaskOptimizeDatabaseDescription": "Veritabanını sıkıştırır ve boş alanı keser. Kitaplığı taradıktan sonra veya veritabanında değişiklik anlamına gelen diğer işlemleri yaptıktan sonra bu görevi çalıştırmak performansı artırabilir.",
|
||||
"TaskOptimizeDatabase": "Veritabanını optimize et"
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
"Favorites": "Yêu Thích",
|
||||
"Folders": "Thư Mục",
|
||||
"Genres": "Thể Loại",
|
||||
"HeaderAlbumArtists": "Tuyển Tập Nghệ sĩ",
|
||||
"HeaderAlbumArtists": "Album Nghệ sĩ",
|
||||
"HeaderContinueWatching": "Xem Tiếp",
|
||||
"HeaderLiveTV": "TV Trực Tiếp",
|
||||
"Movies": "Phim",
|
||||
|
@ -82,7 +82,7 @@
|
|||
"NameSeasonUnknown": "Không Rõ Mùa",
|
||||
"NameSeasonNumber": "Phần {0}",
|
||||
"NameInstallFailed": "{0} cài đặt thất bại",
|
||||
"MusicVideos": "Video Nhạc",
|
||||
"MusicVideos": "Videos Nhạc",
|
||||
"Music": "Nhạc",
|
||||
"MixedContent": "Nội dung hỗn hợp",
|
||||
"MessageServerConfigurationUpdated": "Cấu hình máy chủ đã được cập nhật",
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
|
@ -23,6 +21,9 @@ namespace Emby.Server.Implementations.Localization
|
|||
public class LocalizationManager : ILocalizationManager
|
||||
{
|
||||
private const string DefaultCulture = "en-US";
|
||||
private const string RatingsPath = "Emby.Server.Implementations.Localization.Ratings.";
|
||||
private const string CulturesPath = "Emby.Server.Implementations.Localization.iso6392.txt";
|
||||
private const string CountriesPath = "Emby.Server.Implementations.Localization.countries.json";
|
||||
private static readonly Assembly _assembly = typeof(LocalizationManager).Assembly;
|
||||
private static readonly string[] _unratedValues = { "n/a", "unrated", "not rated" };
|
||||
|
||||
|
@ -35,10 +36,10 @@ namespace Emby.Server.Implementations.Localization
|
|||
private readonly ConcurrentDictionary<string, Dictionary<string, string>> _dictionaries =
|
||||
new ConcurrentDictionary<string, Dictionary<string, string>>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
private List<CultureDto> _cultures;
|
||||
|
||||
private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
|
||||
|
||||
private List<CultureDto> _cultures = new List<CultureDto>();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LocalizationManager" /> class.
|
||||
/// </summary>
|
||||
|
@ -58,22 +59,19 @@ namespace Emby.Server.Implementations.Localization
|
|||
/// <returns><see cref="Task" />.</returns>
|
||||
public async Task LoadAll()
|
||||
{
|
||||
const string RatingsResource = "Emby.Server.Implementations.Localization.Ratings.";
|
||||
|
||||
// Extract from the assembly
|
||||
foreach (var resource in _assembly.GetManifestResourceNames())
|
||||
{
|
||||
if (!resource.StartsWith(RatingsResource, StringComparison.Ordinal))
|
||||
if (!resource.StartsWith(RatingsPath, StringComparison.Ordinal))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
string countryCode = resource.Substring(RatingsResource.Length, 2);
|
||||
string countryCode = resource.Substring(RatingsPath.Length, 2);
|
||||
var dict = new Dictionary<string, ParentalRating>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
using (var str = _assembly.GetManifestResourceStream(resource))
|
||||
using (var reader = new StreamReader(str))
|
||||
{
|
||||
await using var stream = _assembly.GetManifestResourceStream(resource);
|
||||
using var reader = new StreamReader(stream!); // shouldn't be null here, we just got the resource path from Assembly.GetManifestResourceNames()
|
||||
await foreach (var line in reader.ReadAllLinesAsync().ConfigureAwait(false))
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(line))
|
||||
|
@ -95,7 +93,6 @@ namespace Emby.Server.Implementations.Localization
|
|||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
_allParentalRatings[countryCode] = dict;
|
||||
}
|
||||
|
@ -114,11 +111,9 @@ namespace Emby.Server.Implementations.Localization
|
|||
{
|
||||
List<CultureDto> list = new List<CultureDto>();
|
||||
|
||||
const string ResourcePath = "Emby.Server.Implementations.Localization.iso6392.txt";
|
||||
|
||||
using (var stream = _assembly.GetManifestResourceStream(ResourcePath))
|
||||
using (var reader = new StreamReader(stream))
|
||||
{
|
||||
await using var stream = _assembly.GetManifestResourceStream(CulturesPath)
|
||||
?? throw new InvalidOperationException($"Invalid resource path: '{CulturesPath}'");
|
||||
using var reader = new StreamReader(stream);
|
||||
await foreach (var line in reader.ReadAllLinesAsync().ConfigureAwait(false))
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(line))
|
||||
|
@ -161,13 +156,12 @@ namespace Emby.Server.Implementations.Localization
|
|||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_cultures = list;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public CultureDto FindLanguageInfo(string language)
|
||||
public CultureDto? FindLanguageInfo(string language)
|
||||
{
|
||||
// TODO language should ideally be a ReadOnlySpan but moq cannot mock ref structs
|
||||
for (var i = 0; i < _cultures.Count; i++)
|
||||
|
@ -188,9 +182,10 @@ namespace Emby.Server.Implementations.Localization
|
|||
/// <inheritdoc />
|
||||
public IEnumerable<CountryInfo> GetCountries()
|
||||
{
|
||||
using StreamReader reader = new StreamReader(_assembly.GetManifestResourceStream("Emby.Server.Implementations.Localization.countries.json"));
|
||||
|
||||
return JsonSerializer.Deserialize<IEnumerable<CountryInfo>>(reader.ReadToEnd(), _jsonOptions);
|
||||
using StreamReader reader = new StreamReader(
|
||||
_assembly.GetManifestResourceStream(CountriesPath) ?? throw new InvalidOperationException($"Invalid resource path: '{CountriesPath}'"));
|
||||
return JsonSerializer.Deserialize<IEnumerable<CountryInfo>>(reader.ReadToEnd(), _jsonOptions)
|
||||
?? throw new InvalidOperationException($"Resource contains invalid data: '{CountriesPath}'");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
@ -210,7 +205,9 @@ namespace Emby.Server.Implementations.Localization
|
|||
countryCode = "us";
|
||||
}
|
||||
|
||||
return GetRatings(countryCode) ?? GetRatings("us");
|
||||
return GetRatings(countryCode)
|
||||
?? GetRatings("us")
|
||||
?? throw new InvalidOperationException($"Invalid resource path: '{CountriesPath}'");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -218,7 +215,7 @@ namespace Emby.Server.Implementations.Localization
|
|||
/// </summary>
|
||||
/// <param name="countryCode">The country code.</param>
|
||||
/// <returns>The ratings.</returns>
|
||||
private Dictionary<string, ParentalRating> GetRatings(string countryCode)
|
||||
private Dictionary<string, ParentalRating>? GetRatings(string countryCode)
|
||||
{
|
||||
_allParentalRatings.TryGetValue(countryCode, out var value);
|
||||
|
||||
|
@ -243,7 +240,7 @@ namespace Emby.Server.Implementations.Localization
|
|||
|
||||
var ratingsDictionary = GetParentalRatingsDictionary();
|
||||
|
||||
if (ratingsDictionary.TryGetValue(rating, out ParentalRating value))
|
||||
if (ratingsDictionary.TryGetValue(rating, out ParentalRating? value))
|
||||
{
|
||||
return value.Value;
|
||||
}
|
||||
|
@ -273,20 +270,6 @@ namespace Emby.Server.Implementations.Localization
|
|||
return null;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool HasUnicodeCategory(string value, UnicodeCategory category)
|
||||
{
|
||||
foreach (var chr in value)
|
||||
{
|
||||
if (char.GetUnicodeCategory(chr) == category)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string GetLocalizedString(string phrase)
|
||||
{
|
||||
|
@ -350,24 +333,25 @@ namespace Emby.Server.Implementations.Localization
|
|||
|
||||
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 (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);
|
||||
if (dict == null)
|
||||
{
|
||||
throw new InvalidOperationException($"Resource contains invalid data: '{stream}'");
|
||||
}
|
||||
|
||||
foreach (var key in dict.Keys)
|
||||
{
|
||||
dictionary[key] = dict[key];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogError("Missing translation/culture resource: {ResourcePath}", resourcePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetResourceFilename(string culture)
|
||||
{
|
||||
|
|
|
@ -55,9 +55,19 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
|||
_localization = localization;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the triggers that define when the task will run.
|
||||
/// </summary>
|
||||
/// <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 IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
|
||||
{
|
||||
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 videoCodec = state.ActualOutputVideoCodec;
|
||||
var hardwareAccelerationTypeString = _serverConfigurationManager.GetEncodingOptions().HardwareAccelerationType;
|
||||
HardwareEncodingType? hardwareAccelerationType = string.IsNullOrEmpty(hardwareAccelerationTypeString)
|
||||
? null
|
||||
: (HardwareEncodingType)Enum.Parse(typeof(HardwareEncodingType), hardwareAccelerationTypeString, true);
|
||||
|
||||
_sessionManager.ReportTranscodingInfo(deviceId, new TranscodingInfo
|
||||
{
|
||||
|
@ -458,6 +462,7 @@ namespace Jellyfin.Api.Helpers
|
|||
AudioChannels = state.OutputAudioChannels,
|
||||
IsAudioDirect = EncodingHelper.IsCopyCodec(state.OutputAudioCodec),
|
||||
IsVideoDirect = EncodingHelper.IsCopyCodec(state.OutputVideoCodec),
|
||||
HardwareAccelerationType = hardwareAccelerationType,
|
||||
TranscodeReasons = state.TranscodeReasons
|
||||
});
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<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="Swashbuckle.AspNetCore" Version="6.1.5" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.ReDoc" Version="6.1.5" />
|
||||
|
|
|
@ -19,13 +19,13 @@
|
|||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Linq.Async" Version="5.0.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.8" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="5.0.8" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="5.0.8">
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.9" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="5.0.9" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="5.0.9">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="5.0.8">
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="5.0.9">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
|
|
@ -303,7 +303,7 @@ namespace Jellyfin.Server.Extensions
|
|||
{
|
||||
description.TryGetMethodInfo(out MethodInfo methodInfo);
|
||||
// Attribute name, method name, none.
|
||||
return description?.ActionDescriptor?.AttributeRouteInfo?.Name
|
||||
return description?.ActionDescriptor.AttributeRouteInfo?.Name
|
||||
?? methodInfo?.Name
|
||||
?? null;
|
||||
});
|
||||
|
@ -341,7 +341,7 @@ namespace Jellyfin.Server.Extensions
|
|||
{
|
||||
foreach (var address in host.GetAddresses())
|
||||
{
|
||||
AddIpAddress(config, options, addr.Address, addr.PrefixLength);
|
||||
AddIpAddress(config, options, address, address.AddressFamily == AddressFamily.InterNetwork ? 32 : 128);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -397,7 +397,7 @@ namespace Jellyfin.Server.Extensions
|
|||
Type = "object",
|
||||
Properties = typeof(ImageType).GetEnumNames().ToDictionary(
|
||||
name => name,
|
||||
name => new OpenApiSchema
|
||||
_ => new OpenApiSchema
|
||||
{
|
||||
Type = "object",
|
||||
AdditionalProperties = new OpenApiSchema
|
||||
|
|
|
@ -33,13 +33,13 @@
|
|||
<PackageReference Include="CommandLineParser" Version="2.8.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.Diagnostics.HealthChecks" Version="5.0.8" />
|
||||
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="5.0.8" />
|
||||
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="5.0.9" />
|
||||
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="5.0.9" />
|
||||
<PackageReference Include="prometheus-net" Version="4.2.0" />
|
||||
<PackageReference Include="prometheus-net.AspNetCore" Version="4.2.0" />
|
||||
<PackageReference Include="Serilog.AspNetCore" Version="4.1.0" />
|
||||
<PackageReference Include="Serilog.Enrichers.Thread" Version="3.1.0" />
|
||||
<PackageReference Include="Serilog.Settings.Configuration" Version="3.1.0" />
|
||||
<PackageReference Include="Serilog.Settings.Configuration" Version="3.2.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Async" Version="1.5.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="4.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
|
||||
|
|
|
@ -58,9 +58,12 @@ namespace Jellyfin.Server.Middleware
|
|||
return;
|
||||
}
|
||||
|
||||
if (!startsWithBaseUrl)
|
||||
if (!startsWithBaseUrl
|
||||
|| localPath.Length == baseUrlPrefix.Length
|
||||
// Local path is /baseUrl/
|
||||
|| (localPath.Length == baseUrlPrefix.Length + 1 && localPath[^1] == '/'))
|
||||
{
|
||||
// Always redirect back to the default path if the base prefix is invalid or missing
|
||||
// Always redirect back to the default path if the base prefix is invalid, missing, or is the full path.
|
||||
_logger.LogDebug("Normalizing an URL at {LocalPath}", localPath);
|
||||
httpContext.Response.Redirect(baseUrlPrefix + "/" + _configuration[ConfigurationExtensions.DefaultRedirectKey]);
|
||||
return;
|
||||
|
|
|
@ -137,11 +137,6 @@ namespace Jellyfin.Server.Middleware
|
|||
|
||||
private string NormalizeExceptionMessage(string msg)
|
||||
{
|
||||
if (msg == null)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
// Strip any information we don't want to reveal
|
||||
return msg.Replace(
|
||||
_configuration.ApplicationPaths.ProgramSystemPath,
|
||||
|
|
|
@ -40,7 +40,7 @@ namespace Jellyfin.Server.Migrations
|
|||
.Select(m => ActivatorUtilities.CreateInstance(host.ServiceProvider, m))
|
||||
.OfType<IMigrationRoutine>()
|
||||
.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)
|
||||
{
|
||||
|
|
|
@ -92,7 +92,7 @@ namespace Jellyfin.Server.Migrations.Routines
|
|||
if (entry[6].SQLiteType != SQLiteType.Null && !Guid.TryParse(entry[6].ToString(), out guid))
|
||||
{
|
||||
// This is not a valid Guid, see if it is an internal ID from an old Emby schema
|
||||
_logger.LogWarning("Invalid Guid in UserId column: ", entry[6].ToString());
|
||||
_logger.LogWarning("Invalid Guid in UserId column: {Guid}", entry[6].ToString());
|
||||
|
||||
using var statement = userDbConnection.PrepareStatement("SELECT guid FROM LocalUsersv2 WHERE Id=@Id");
|
||||
statement.TryBind("@Id", entry[6].ToString());
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
|
|
|
@ -5,7 +5,6 @@ using System.IO;
|
|||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
@ -121,11 +120,11 @@ namespace Jellyfin.Server
|
|||
|
||||
// Log uncaught exceptions to the logging instead of std error
|
||||
AppDomain.CurrentDomain.UnhandledException -= UnhandledExceptionToConsole;
|
||||
AppDomain.CurrentDomain.UnhandledException += (sender, e)
|
||||
AppDomain.CurrentDomain.UnhandledException += (_, e)
|
||||
=> _logger.LogCritical((Exception)e.ExceptionObject, "Unhandled Exception");
|
||||
|
||||
// Intercept Ctrl+C and Ctrl+Break
|
||||
Console.CancelKeyPress += (sender, e) =>
|
||||
Console.CancelKeyPress += (_, e) =>
|
||||
{
|
||||
if (_tokenSource.IsCancellationRequested)
|
||||
{
|
||||
|
@ -139,7 +138,7 @@ namespace Jellyfin.Server
|
|||
};
|
||||
|
||||
// Register a SIGTERM handler
|
||||
AppDomain.CurrentDomain.ProcessExit += (sender, e) =>
|
||||
AppDomain.CurrentDomain.ProcessExit += (_, _) =>
|
||||
{
|
||||
if (_tokenSource.IsCancellationRequested)
|
||||
{
|
||||
|
@ -180,7 +179,7 @@ namespace Jellyfin.Server
|
|||
"The server is expected to host the web client, but the provided content directory is either " +
|
||||
$"invalid or empty: {webContentPath}. If you do not want to host the web client with the " +
|
||||
"server, you may set the '--nowebclient' command line flag, or set" +
|
||||
$"'{MediaBrowser.Controller.Extensions.ConfigurationExtensions.HostWebClientKey}=false' in your config settings.");
|
||||
$"'{ConfigurationExtensions.HostWebClientKey}=false' in your config settings.");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -543,7 +542,7 @@ namespace Jellyfin.Server
|
|||
// Get a stream of the resource contents
|
||||
// NOTE: The .csproj name is used instead of the assembly name in the resource path
|
||||
const string ResourcePath = "Jellyfin.Server.Resources.Configuration.logging.json";
|
||||
await using Stream? resource = typeof(Program).Assembly.GetManifestResourceStream(ResourcePath)
|
||||
await using Stream resource = typeof(Program).Assembly.GetManifestResourceStream(ResourcePath)
|
||||
?? throw new InvalidOperationException($"Invalid resource path: '{ResourcePath}'");
|
||||
|
||||
// Copy the resource contents to the expected file path for the config file
|
||||
|
|
|
@ -40,7 +40,7 @@ namespace MediaBrowser.Common.Extensions
|
|||
|
||||
// Add an event handler for the process exit event
|
||||
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
|
||||
if (process.HasExitedSafe())
|
||||
|
|
|
@ -4,7 +4,6 @@ using System.Linq;
|
|||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MediaBrowser.Common.Net
|
||||
{
|
||||
|
@ -196,7 +195,7 @@ namespace MediaBrowser.Common.Net
|
|||
return res;
|
||||
}
|
||||
|
||||
throw new InvalidCastException("Host does not contain a valid value. {host}");
|
||||
throw new InvalidCastException($"Host does not contain a valid value. {host}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -221,7 +220,7 @@ namespace MediaBrowser.Common.Net
|
|||
return res;
|
||||
}
|
||||
|
||||
throw new InvalidCastException("Host does not contain a valid value. {host}");
|
||||
throw new InvalidCastException($"Host does not contain a valid value. {host}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -349,7 +348,7 @@ namespace MediaBrowser.Common.Net
|
|||
}
|
||||
}
|
||||
|
||||
output = output[0..^1];
|
||||
output = output[..^1];
|
||||
|
||||
if (moreThanOne)
|
||||
{
|
||||
|
@ -400,7 +399,7 @@ namespace MediaBrowser.Common.Net
|
|||
if ((_addresses.Length == 0 && !Resolved) || (DateTime.UtcNow > _lastResolved.Value.AddMinutes(Timeout)))
|
||||
{
|
||||
_lastResolved = DateTime.UtcNow;
|
||||
ResolveHostInternal().GetAwaiter().GetResult();
|
||||
ResolveHostInternal();
|
||||
Resolved = true;
|
||||
}
|
||||
|
||||
|
@ -410,30 +409,31 @@ namespace MediaBrowser.Common.Net
|
|||
/// <summary>
|
||||
/// Task that looks up a Host name and returns its IP addresses.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
|
||||
private async Task ResolveHostInternal()
|
||||
private void ResolveHostInternal()
|
||||
{
|
||||
if (!string.IsNullOrEmpty(HostName))
|
||||
var hostName = HostName;
|
||||
if (string.IsNullOrEmpty(hostName))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 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 };
|
||||
return;
|
||||
}
|
||||
|
||||
if (Uri.CheckHostName(HostName).Equals(UriHostNameType.Dns))
|
||||
if (Uri.CheckHostName(hostName) == UriHostNameType.Dns)
|
||||
{
|
||||
try
|
||||
{
|
||||
IPHostEntry ip = await Dns.GetHostEntryAsync(HostName).ConfigureAwait(false);
|
||||
_addresses = ip.AddressList;
|
||||
_addresses = Dns.GetHostEntry(hostName).AddressList;
|
||||
}
|
||||
catch (SocketException ex)
|
||||
{
|
||||
// Log and then ignore socket errors, as the result value will just be an empty array.
|
||||
Debug.WriteLine("GetHostEntryAsync failed with {Message}.", ex.Message);
|
||||
}
|
||||
Debug.WriteLine("GetHostAddresses failed with {Message}.", ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -47,10 +47,10 @@ namespace MediaBrowser.Common.Plugins
|
|||
var assemblyFilePath = assembly.Location;
|
||||
|
||||
var dataFolderPath = Path.Combine(ApplicationPaths.PluginsPath, Path.GetFileNameWithoutExtension(assemblyFilePath));
|
||||
if (!Directory.Exists(dataFolderPath) && Version != null)
|
||||
if (Version != null && !Directory.Exists(dataFolderPath))
|
||||
{
|
||||
// Try again with the version number appended to the folder name.
|
||||
dataFolderPath = dataFolderPath + "_" + Version.ToString();
|
||||
dataFolderPath += "_" + Version.ToString();
|
||||
}
|
||||
|
||||
SetAttributes(assemblyFilePath, dataFolderPath, assemblyName.Version);
|
||||
|
|
|
@ -18,7 +18,7 @@ namespace MediaBrowser.Common.Providers
|
|||
/// <param name="text">The text to parse.</param>
|
||||
/// <param name="imdbId">The parsed IMDb id.</param>
|
||||
/// <returns>True if parsing was successful, false otherwise.</returns>
|
||||
public static bool TryFindImdbId(ReadOnlySpan<char> text, [NotNullWhen(true)] out ReadOnlySpan<char> imdbId)
|
||||
public static bool TryFindImdbId(ReadOnlySpan<char> text, out ReadOnlySpan<char> imdbId)
|
||||
{
|
||||
// imdb id is at least 9 chars (tt + 7 numbers)
|
||||
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="tmdbId">The parsed TMDb id.</param>
|
||||
/// <returns>True if parsing was successful, false otherwise.</returns>
|
||||
public static bool TryFindTmdbMovieId(ReadOnlySpan<char> text, [NotNullWhen(true)] out ReadOnlySpan<char> tmdbId)
|
||||
public static bool TryFindTmdbMovieId(ReadOnlySpan<char> text, out ReadOnlySpan<char> tmdbId)
|
||||
=> TryFindProviderId(text, "themoviedb.org/movie/", out tmdbId);
|
||||
|
||||
/// <summary>
|
||||
|
@ -71,7 +71,7 @@ namespace MediaBrowser.Common.Providers
|
|||
/// <param name="text">The text with the url to parse.</param>
|
||||
/// <param name="tmdbId">The parsed TMDb id.</param>
|
||||
/// <returns>True if parsing was successful, false otherwise.</returns>
|
||||
public static bool TryFindTmdbSeriesId(ReadOnlySpan<char> text, [NotNullWhen(true)] out ReadOnlySpan<char> tmdbId)
|
||||
public static bool TryFindTmdbSeriesId(ReadOnlySpan<char> text, out ReadOnlySpan<char> tmdbId)
|
||||
=> TryFindProviderId(text, "themoviedb.org/tv/", out tmdbId);
|
||||
|
||||
/// <summary>
|
||||
|
@ -80,7 +80,7 @@ namespace MediaBrowser.Common.Providers
|
|||
/// <param name="text">The text with the url to parse.</param>
|
||||
/// <param name="tvdbId">The parsed TVDb id.</param>
|
||||
/// <returns>True if parsing was successful, false otherwise.</returns>
|
||||
public static bool TryFindTvdbId(ReadOnlySpan<char> text, [NotNullWhen(true)] out ReadOnlySpan<char> tvdbId)
|
||||
public static bool TryFindTvdbId(ReadOnlySpan<char> text, out ReadOnlySpan<char> tvdbId)
|
||||
=> TryFindProviderId(text, "thetvdb.com/?tab=series&id=", out tvdbId);
|
||||
|
||||
private static bool TryFindProviderId(ReadOnlySpan<char> text, ReadOnlySpan<char> searchString, [NotNullWhen(true)] out ReadOnlySpan<char> providerId)
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Jellyfin.Extensions;
|
||||
|
@ -16,7 +15,7 @@ namespace MediaBrowser.Controller.BaseItemManager
|
|||
{
|
||||
private readonly IServerConfigurationManager _serverConfigurationManager;
|
||||
|
||||
private int _metadataRefreshConcurrency = 0;
|
||||
private int _metadataRefreshConcurrency;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BaseItemManager"/> class.
|
||||
|
@ -101,7 +100,7 @@ namespace MediaBrowser.Controller.BaseItemManager
|
|||
/// Called when the configuration is updated.
|
||||
/// It will refresh the metadata throttler if the relevant config changed.
|
||||
/// </summary>
|
||||
private void OnConfigurationUpdated(object sender, EventArgs e)
|
||||
private void OnConfigurationUpdated(object? sender, EventArgs e)
|
||||
{
|
||||
int newMetadataRefreshConcurrency = GetMetadataRefreshConcurrency();
|
||||
if (_metadataRefreshConcurrency != newMetadataRefreshConcurrency)
|
||||
|
@ -114,6 +113,7 @@ namespace MediaBrowser.Controller.BaseItemManager
|
|||
/// <summary>
|
||||
/// Creates the metadata refresh throttler.
|
||||
/// </summary>
|
||||
[MemberNotNull(nameof(MetadataRefreshThrottler))]
|
||||
private void SetupMetadataThrottler()
|
||||
{
|
||||
MetadataRefreshThrottler = new SemaphoreSlim(_metadataRefreshConcurrency);
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System.Threading;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Model.Configuration;
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
#nullable disable
|
||||
|
||||
#pragma warning disable CA1002, CA2227, CS1591
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace MediaBrowser.Controller.Channels
|
||||
|
@ -10,10 +9,10 @@ namespace MediaBrowser.Controller.Channels
|
|||
{
|
||||
public ChannelItemResult()
|
||||
{
|
||||
Items = new List<ChannelItemInfo>();
|
||||
Items = Array.Empty<ChannelItemInfo>();
|
||||
}
|
||||
|
||||
public List<ChannelItemInfo> Items { get; set; }
|
||||
public IReadOnlyList<ChannelItemInfo> Items { get; set; }
|
||||
|
||||
public int? TotalRecordCount { get; set; }
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#nullable disable
|
||||
|
||||
#pragma warning disable CA2227, CS1591
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
|
@ -16,17 +14,17 @@ namespace MediaBrowser.Controller.Collections
|
|||
/// <summary>
|
||||
/// Occurs when [collection created].
|
||||
/// </summary>
|
||||
event EventHandler<CollectionCreatedEventArgs> CollectionCreated;
|
||||
event EventHandler<CollectionCreatedEventArgs>? CollectionCreated;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when [items added to collection].
|
||||
/// </summary>
|
||||
event EventHandler<CollectionModifiedEventArgs> ItemsAddedToCollection;
|
||||
event EventHandler<CollectionModifiedEventArgs>? ItemsAddedToCollection;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when [items removed from collection].
|
||||
/// </summary>
|
||||
event EventHandler<CollectionModifiedEventArgs> ItemsRemovedFromCollection;
|
||||
event EventHandler<CollectionModifiedEventArgs>? ItemsRemovedFromCollection;
|
||||
|
||||
/// <summary>
|
||||
/// Creates the collection.
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Model.Configuration;
|
||||
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
@ -22,7 +20,7 @@ namespace MediaBrowser.Controller.Dlna
|
|||
/// </summary>
|
||||
/// <param name="headers">The headers.</param>
|
||||
/// <returns>DeviceProfile.</returns>
|
||||
DeviceProfile GetProfile(IHeaderDictionary headers);
|
||||
DeviceProfile? GetProfile(IHeaderDictionary headers);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the default profile.
|
||||
|
@ -53,14 +51,14 @@ namespace MediaBrowser.Controller.Dlna
|
|||
/// </summary>
|
||||
/// <param name="id">The identifier.</param>
|
||||
/// <returns>DeviceProfile.</returns>
|
||||
DeviceProfile GetProfile(string id);
|
||||
DeviceProfile? GetProfile(string id);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the profile.
|
||||
/// </summary>
|
||||
/// <param name="deviceInfo">The device information.</param>
|
||||
/// <returns>DeviceProfile.</returns>
|
||||
DeviceProfile GetProfile(DeviceIdentification deviceInfo);
|
||||
DeviceProfile? GetProfile(DeviceIdentification deviceInfo);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the server description XML.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#nullable disable
|
||||
|
||||
#pragma warning disable CS1591
|
||||
#pragma warning disable CA1819, CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
|
@ -18,32 +18,23 @@ namespace MediaBrowser.Controller.Entities
|
|||
{
|
||||
/// <summary>
|
||||
/// 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>
|
||||
public class AggregateFolder : Folder
|
||||
{
|
||||
private bool _requiresRefresh;
|
||||
|
||||
public AggregateFolder()
|
||||
{
|
||||
PhysicalLocationsList = Array.Empty<string>();
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public override bool IsPhysicalRoot => true;
|
||||
|
||||
public override bool CanDelete()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public override bool SupportsPlayedStatus => false;
|
||||
private readonly object _childIdsLock = new object();
|
||||
|
||||
/// <summary>
|
||||
/// The _virtual children.
|
||||
/// </summary>
|
||||
private readonly ConcurrentBag<BaseItem> _virtualChildren = new ConcurrentBag<BaseItem>();
|
||||
private bool _requiresRefresh;
|
||||
private Guid[] _childrenIds = null;
|
||||
|
||||
public AggregateFolder()
|
||||
{
|
||||
PhysicalLocationsList = Array.Empty<string>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the virtual children.
|
||||
|
@ -51,19 +42,27 @@ namespace MediaBrowser.Controller.Entities
|
|||
/// <value>The virtual children.</value>
|
||||
public ConcurrentBag<BaseItem> VirtualChildren => _virtualChildren;
|
||||
|
||||
[JsonIgnore]
|
||||
public override bool IsPhysicalRoot => true;
|
||||
|
||||
[JsonIgnore]
|
||||
public override bool SupportsPlayedStatus => false;
|
||||
|
||||
[JsonIgnore]
|
||||
public override string[] PhysicalLocations => PhysicalLocationsList;
|
||||
|
||||
public string[] PhysicalLocationsList { get; set; }
|
||||
|
||||
public override bool CanDelete()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override FileSystemMetadata[] GetFileSystemChildren(IDirectoryService directoryService)
|
||||
{
|
||||
return CreateResolveArgs(directoryService, true).FileSystemChildren;
|
||||
}
|
||||
|
||||
private Guid[] _childrenIds = null;
|
||||
private readonly object _childIdsLock = new object();
|
||||
|
||||
protected override List<BaseItem> LoadChildren()
|
||||
{
|
||||
lock (_childIdsLock)
|
||||
|
@ -169,7 +168,7 @@ namespace MediaBrowser.Controller.Entities
|
|||
/// Adds the virtual child.
|
||||
/// </summary>
|
||||
/// <param name="child">The child.</param>
|
||||
/// <exception cref="ArgumentNullException"></exception>
|
||||
/// <exception cref="ArgumentNullException">Throws if child is null.</exception>
|
||||
public void AddVirtualChild(BaseItem child)
|
||||
{
|
||||
if (child == null)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#nullable disable
|
||||
|
||||
#pragma warning disable CS1591
|
||||
#pragma warning disable CA1002, CA1724, CA1826, CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
@ -25,6 +25,12 @@ namespace MediaBrowser.Controller.Entities.Audio
|
|||
IHasLookupInfo<SongInfo>,
|
||||
IHasMediaSources
|
||||
{
|
||||
public Audio()
|
||||
{
|
||||
Artists = Array.Empty<string>();
|
||||
AlbumArtists = Array.Empty<string>();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[JsonIgnore]
|
||||
public IReadOnlyList<string> Artists { get; set; }
|
||||
|
@ -33,17 +39,6 @@ namespace MediaBrowser.Controller.Entities.Audio
|
|||
[JsonIgnore]
|
||||
public IReadOnlyList<string> AlbumArtists { get; set; }
|
||||
|
||||
public Audio()
|
||||
{
|
||||
Artists = Array.Empty<string>();
|
||||
AlbumArtists = Array.Empty<string>();
|
||||
}
|
||||
|
||||
public override double GetDefaultPrimaryImageAspectRatio()
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public override bool SupportsPlayedStatus => true;
|
||||
|
||||
|
@ -62,11 +57,6 @@ namespace MediaBrowser.Controller.Entities.Audio
|
|||
[JsonIgnore]
|
||||
public override Folder LatestItemsIndexContainer => AlbumEntity;
|
||||
|
||||
public override bool CanDownload()
|
||||
{
|
||||
return IsFileProtocol;
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public MusicAlbum AlbumEntity => FindParent<MusicAlbum>();
|
||||
|
||||
|
@ -77,6 +67,16 @@ namespace MediaBrowser.Controller.Entities.Audio
|
|||
[JsonIgnore]
|
||||
public override string MediaType => Model.Entities.MediaType.Audio;
|
||||
|
||||
public override double GetDefaultPrimaryImageAspectRatio()
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
public override bool CanDownload()
|
||||
{
|
||||
return IsFileProtocol;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the name of the sort.
|
||||
/// </summary>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#nullable disable
|
||||
|
||||
#pragma warning disable CS1591
|
||||
#pragma warning disable CA1819, CS1591
|
||||
|
||||
namespace MediaBrowser.Controller.Entities.Audio
|
||||
{
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#nullable disable
|
||||
|
||||
#pragma warning disable CS1591
|
||||
#pragma warning disable CA1721, CA1826, CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
@ -23,18 +23,18 @@ namespace MediaBrowser.Controller.Entities.Audio
|
|||
/// </summary>
|
||||
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()
|
||||
{
|
||||
Artists = Array.Empty<string>();
|
||||
AlbumArtists = Array.Empty<string>();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyList<string> AlbumArtists { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyList<string> Artists { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public override bool SupportsAddingToPlaylist => true;
|
||||
|
||||
|
@ -44,6 +44,25 @@ namespace MediaBrowser.Controller.Entities.Audio
|
|||
[JsonIgnore]
|
||||
public MusicArtist MusicArtist => GetMusicArtist(new DtoOptions(true));
|
||||
|
||||
[JsonIgnore]
|
||||
public override bool SupportsPlayedStatus => false;
|
||||
|
||||
[JsonIgnore]
|
||||
public override bool SupportsCumulativeRunTimeTicks => true;
|
||||
|
||||
[JsonIgnore]
|
||||
public string AlbumArtist => AlbumArtists.FirstOrDefault();
|
||||
|
||||
[JsonIgnore]
|
||||
public override bool SupportsPeople => false;
|
||||
|
||||
/// <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)
|
||||
{
|
||||
var parents = GetParents();
|
||||
|
@ -64,25 +83,6 @@ namespace MediaBrowser.Controller.Entities.Audio
|
|||
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)
|
||||
{
|
||||
return Tracks;
|
||||
|
|
|
@ -44,6 +44,36 @@ namespace MediaBrowser.Controller.Entities.Audio
|
|||
[JsonIgnore]
|
||||
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()
|
||||
{
|
||||
return 1;
|
||||
|
@ -65,20 +95,6 @@ namespace MediaBrowser.Controller.Entities.Audio
|
|||
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)
|
||||
{
|
||||
return IsAccessedByName ? 0 : base.GetChildCount(user);
|
||||
|
@ -113,14 +129,6 @@ namespace MediaBrowser.Controller.Entities.Audio
|
|||
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>
|
||||
/// Gets the user data key.
|
||||
/// </summary>
|
||||
|
@ -167,14 +175,6 @@ namespace MediaBrowser.Controller.Entities.Audio
|
|||
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)
|
||||
{
|
||||
// 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>
|
||||
/// This is called before any metadata refresh and returns true or false indicating if changes were made.
|
||||
/// </summary>
|
||||
/// <param name="replaceAllMetadata">Option to replace metadata.</param>
|
||||
/// <returns>True if metadata changed.</returns>
|
||||
public override bool BeforeMetadataRefresh(bool replaceAllMetadata)
|
||||
{
|
||||
var hasChanges = base.BeforeMetadataRefresh(replaceAllMetadata);
|
||||
|
|
|
@ -15,19 +15,6 @@ namespace MediaBrowser.Controller.Entities.Audio
|
|||
/// </summary>
|
||||
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]
|
||||
public override bool SupportsAddingToPlaylist => true;
|
||||
|
||||
|
@ -45,6 +32,22 @@ namespace MediaBrowser.Controller.Entities.Audio
|
|||
[JsonIgnore]
|
||||
public override string ContainingFolderPath => Path;
|
||||
|
||||
[JsonIgnore]
|
||||
public override bool SupportsPeople => false;
|
||||
|
||||
public override List<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()
|
||||
{
|
||||
return 1;
|
||||
|
@ -60,9 +63,6 @@ namespace MediaBrowser.Controller.Entities.Audio
|
|||
return true;
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public override bool SupportsPeople => false;
|
||||
|
||||
public IList<BaseItem> GetTaggedItems(InternalItemsQuery query)
|
||||
{
|
||||
query.GenreIds = new[] { Id };
|
||||
|
@ -106,6 +106,8 @@ namespace MediaBrowser.Controller.Entities.Audio
|
|||
/// <summary>
|
||||
/// This is called before any metadata refresh and returns true or false indicating if changes were made.
|
||||
/// </summary>
|
||||
/// <param name="replaceAllMetadata">Option to replace metadata.</param>
|
||||
/// <returns>True if metadata changed.</returns>
|
||||
public override bool BeforeMetadataRefresh(bool replaceAllMetadata)
|
||||
{
|
||||
var hasChanges = base.BeforeMetadataRefresh(replaceAllMetadata);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#nullable disable
|
||||
|
||||
#pragma warning disable CS1591
|
||||
#pragma warning disable CA1724, CS1591
|
||||
|
||||
using System;
|
||||
using System.Text.Json.Serialization;
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -64,6 +64,8 @@ namespace MediaBrowser.Controller.Entities
|
|||
/// </summary>
|
||||
/// <param name="source">The source 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)
|
||||
where T : BaseItem
|
||||
where TU : BaseItem
|
||||
|
@ -109,6 +111,9 @@ namespace MediaBrowser.Controller.Entities
|
|||
/// Copies all properties on newly created object. Skips properties that do not exist.
|
||||
/// </summary>
|
||||
/// <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)
|
||||
where T : BaseItem
|
||||
where TU : BaseItem, new()
|
||||
|
|
|
@ -15,6 +15,12 @@ namespace MediaBrowser.Controller.Entities
|
|||
[JsonIgnore]
|
||||
public virtual string CollectionType => null;
|
||||
|
||||
[JsonIgnore]
|
||||
public override bool SupportsInheritedParentImages => false;
|
||||
|
||||
[JsonIgnore]
|
||||
public override bool SupportsPeople => false;
|
||||
|
||||
public override bool CanDelete()
|
||||
{
|
||||
return false;
|
||||
|
@ -24,11 +30,5 @@ namespace MediaBrowser.Controller.Entities
|
|||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public override bool SupportsInheritedParentImages => false;
|
||||
|
||||
[JsonIgnore]
|
||||
public override bool SupportsPeople => false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,6 +41,23 @@ namespace MediaBrowser.Controller.Entities
|
|||
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 IServerApplicationHost ApplicationHost { get; set; }
|
||||
|
@ -63,6 +80,9 @@ namespace MediaBrowser.Controller.Entities
|
|||
[JsonIgnore]
|
||||
public override IEnumerable<BaseItem> Children => GetActualChildren();
|
||||
|
||||
[JsonIgnore]
|
||||
public override bool SupportsPeople => false;
|
||||
|
||||
public override bool CanDelete()
|
||||
{
|
||||
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()
|
||||
{
|
||||
return true;
|
||||
|
@ -373,8 +376,5 @@ namespace MediaBrowser.Controller.Entities
|
|||
|
||||
return result;
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public override bool SupportsPeople => false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,6 +15,8 @@ namespace MediaBrowser.Controller.Entities
|
|||
/// <summary>
|
||||
/// Adds the trailer URL.
|
||||
/// </summary>
|
||||
/// <param name="item">Media item.</param>
|
||||
/// <param name="url">Trailer URL.</param>
|
||||
public static void AddTrailerUrl(this BaseItem item, string url)
|
||||
{
|
||||
if (string.IsNullOrEmpty(url))
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#nullable disable
|
||||
|
||||
#pragma warning disable CS1591
|
||||
#pragma warning disable CA1002, CA1721, CA1819, CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
@ -165,6 +165,8 @@ namespace MediaBrowser.Controller.Entities
|
|||
}
|
||||
}
|
||||
|
||||
public static ICollectionManager CollectionManager { get; set; }
|
||||
|
||||
public override bool CanDelete()
|
||||
{
|
||||
if (IsRoot)
|
||||
|
@ -258,6 +260,7 @@ namespace MediaBrowser.Controller.Entities
|
|||
/// Loads our children. Validation will occur externally.
|
||||
/// We want this synchronous.
|
||||
/// </summary>
|
||||
/// <returns>Returns children.</returns>
|
||||
protected virtual List<BaseItem> LoadChildren()
|
||||
{
|
||||
// logger.LogDebug("Loading children from {0} {1} {2}", GetType().Name, Id, Path);
|
||||
|
@ -642,6 +645,8 @@ namespace MediaBrowser.Controller.Entities
|
|||
/// Get the children of this folder from the actual file system.
|
||||
/// </summary>
|
||||
/// <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)
|
||||
{
|
||||
var collectionType = LibraryManager.GetContentType(this);
|
||||
|
@ -998,8 +1003,6 @@ namespace MediaBrowser.Controller.Entities
|
|||
return PostFilterAndSort(items, query, true);
|
||||
}
|
||||
|
||||
public static ICollectionManager CollectionManager { get; set; }
|
||||
|
||||
protected QueryResult<BaseItem> PostFilterAndSort(IEnumerable<BaseItem> items, InternalItemsQuery query, bool enableSorting)
|
||||
{
|
||||
var user = query.User;
|
||||
|
@ -1666,7 +1669,6 @@ namespace MediaBrowser.Controller.Entities
|
|||
/// <param name="user">The user.</param>
|
||||
/// <param name="datePlayed">The date played.</param>
|
||||
/// <param name="resetPosition">if set to <c>true</c> [reset position].</param>
|
||||
/// <returns>Task.</returns>
|
||||
public override void MarkPlayed(
|
||||
User user,
|
||||
DateTime? datePlayed,
|
||||
|
@ -1708,7 +1710,6 @@ namespace MediaBrowser.Controller.Entities
|
|||
/// Marks the unplayed.
|
||||
/// </summary>
|
||||
/// <param name="user">The user.</param>
|
||||
/// <returns>Task.</returns>
|
||||
public override void MarkUnplayed(User user)
|
||||
{
|
||||
var itemsResult = GetItemList(new InternalItemsQuery
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#nullable disable
|
||||
|
||||
#pragma warning disable CS1591
|
||||
#pragma warning disable CA1819, CS1591
|
||||
|
||||
using System;
|
||||
|
||||
|
|
|
@ -20,6 +20,8 @@ namespace MediaBrowser.Controller.Entities
|
|||
/// <summary>
|
||||
/// Gets the media sources.
|
||||
/// </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<MediaStream> GetMediaStreams();
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#nullable disable
|
||||
|
||||
#pragma warning disable CS1591
|
||||
#pragma warning disable CA1819, CS1591
|
||||
|
||||
namespace MediaBrowser.Controller.Entities
|
||||
{
|
||||
|
|
|
@ -39,6 +39,7 @@ namespace MediaBrowser.Controller.Entities
|
|||
/// <summary>
|
||||
/// Gets the trailer count.
|
||||
/// </summary>
|
||||
/// <param name="item">Media item.</param>
|
||||
/// <returns><see cref="IReadOnlyList{Guid}" />.</returns>
|
||||
public static int GetTrailerCount(this IHasTrailers item)
|
||||
=> item.LocalTrailerIds.Count + item.RemoteTrailerIds.Count;
|
||||
|
@ -46,6 +47,7 @@ namespace MediaBrowser.Controller.Entities
|
|||
/// <summary>
|
||||
/// Gets the trailer ids.
|
||||
/// </summary>
|
||||
/// <param name="item">Media item.</param>
|
||||
/// <returns><see cref="IReadOnlyList{Guid}" />.</returns>
|
||||
public static IReadOnlyList<Guid> GetTrailerIds(this IHasTrailers item)
|
||||
{
|
||||
|
@ -70,6 +72,7 @@ namespace MediaBrowser.Controller.Entities
|
|||
/// <summary>
|
||||
/// Gets the trailers.
|
||||
/// </summary>
|
||||
/// <param name="item">Media item.</param>
|
||||
/// <returns><see cref="IReadOnlyList{BaseItem}" />.</returns>
|
||||
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.Collections.Generic;
|
||||
|
@ -12,6 +12,55 @@ namespace MediaBrowser.Controller.Entities
|
|||
{
|
||||
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 int? StartIndex { get; set; }
|
||||
|
@ -186,23 +235,6 @@ namespace MediaBrowser.Controller.Entities
|
|||
|
||||
public Guid[] TopParentIds { get; set; }
|
||||
|
||||
public BaseItem? Parent
|
||||
{
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
ParentId = Guid.Empty;
|
||||
ParentType = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
ParentId = value.Id;
|
||||
ParentType = value.GetType().Name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string[] PresetViews { get; set; }
|
||||
|
||||
public TrailerType[] TrailerTypes { get; set; }
|
||||
|
@ -270,70 +302,21 @@ namespace MediaBrowser.Controller.Entities
|
|||
/// </summary>
|
||||
public bool? DisplayAlbumFolders { get; set; }
|
||||
|
||||
public InternalItemsQuery()
|
||||
public BaseItem? Parent
|
||||
{
|
||||
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>();
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
ParentId = Guid.Empty;
|
||||
ParentType = null;
|
||||
}
|
||||
|
||||
public InternalItemsQuery(User? user)
|
||||
: this()
|
||||
else
|
||||
{
|
||||
if (user != null)
|
||||
{
|
||||
SetUser(user);
|
||||
ParentId = value.Id;
|
||||
ParentType = value.GetType().Name;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public Dictionary<string, string>? HasAnyProviderId { get; set; }
|
||||
|
@ -361,5 +344,22 @@ namespace MediaBrowser.Controller.Entities
|
|||
public string? SearchTerm { get; set; }
|
||||
|
||||
public string? SeriesTimerId { get; set; }
|
||||
|
||||
public void SetUser(User user)
|
||||
{
|
||||
MaxParentalRating = user.MaxParentalAgeRating;
|
||||
|
||||
if (MaxParentalRating.HasValue)
|
||||
{
|
||||
string other = UnratedItem.Other.ToString();
|
||||
BlockUnratedItems = user.GetPreference(PreferenceKind.BlockUnratedItems)
|
||||
.Where(i => i != other)
|
||||
.Select(e => Enum.Parse<UnratedItem>(e, true)).ToArray();
|
||||
}
|
||||
|
||||
ExcludeInheritedTags = user.GetPreference(PreferenceKind.BlockedTags);
|
||||
|
||||
User = user;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#nullable disable
|
||||
|
||||
#pragma warning disable CS1591
|
||||
#pragma warning disable CA1721, CA1819, CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
@ -49,6 +49,30 @@ namespace MediaBrowser.Controller.Entities.Movies
|
|||
/// <value>The display order.</value>
|
||||
public string DisplayOrder { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
private bool IsLegacyBoxSet
|
||||
{
|
||||
get
|
||||
{
|
||||
if (string.IsNullOrEmpty(Path))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (LinkedChildren.Length > 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return !FileSystem.ContainsSubPath(ConfigurationManager.ApplicationPaths.DataPath, Path);
|
||||
}
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public override bool IsPreSorted => true;
|
||||
|
||||
public Guid[] LibraryFolderIds { get; set; }
|
||||
|
||||
protected override bool GetBlockUnratedValue(User user)
|
||||
{
|
||||
return user.GetPreferenceValues<UnratedItem>(PreferenceKind.BlockUnratedItems).Contains(UnratedItem.Movie);
|
||||
|
@ -83,28 +107,6 @@ namespace MediaBrowser.Controller.Entities.Movies
|
|||
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)
|
||||
{
|
||||
return true;
|
||||
|
@ -191,8 +193,6 @@ namespace MediaBrowser.Controller.Entities.Movies
|
|||
return IsVisible(user);
|
||||
}
|
||||
|
||||
public Guid[] LibraryFolderIds { get; set; }
|
||||
|
||||
private Guid[] GetLibraryFolderIds(User user)
|
||||
{
|
||||
return LibraryManager.GetUserRootFolder().GetChildren(user, true)
|
||||
|
|
|
@ -16,6 +16,26 @@ namespace MediaBrowser.Controller.Entities
|
|||
/// </summary>
|
||||
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()
|
||||
{
|
||||
var list = base.GetUserDataKeys();
|
||||
|
@ -49,14 +69,6 @@ namespace MediaBrowser.Controller.Entities
|
|||
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()
|
||||
{
|
||||
return false;
|
||||
|
@ -67,18 +79,6 @@ namespace MediaBrowser.Controller.Entities
|
|||
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)
|
||||
{
|
||||
return GetPath(name, true);
|
||||
|
@ -129,6 +129,8 @@ namespace MediaBrowser.Controller.Entities
|
|||
/// <summary>
|
||||
/// This is called before any metadata refresh and returns true or false indicating if changes were made.
|
||||
/// </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)
|
||||
{
|
||||
var hasChanges = base.BeforeMetadataRefresh(replaceAllMetadata);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#nullable disable
|
||||
|
||||
#pragma warning disable CS1591
|
||||
#pragma warning disable CA2227, CS1591
|
||||
|
||||
using System;
|
||||
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()
|
||||
{
|
||||
return true;
|
||||
|
@ -69,29 +93,5 @@ namespace MediaBrowser.Controller.Entities
|
|||
|
||||
return base.GetDefaultPrimaryImageAspectRatio();
|
||||
}
|
||||
|
||||
public string CameraMake { get; set; }
|
||||
|
||||
public string CameraModel { get; set; }
|
||||
|
||||
public string Software { get; set; }
|
||||
|
||||
public double? ExposureTime { get; set; }
|
||||
|
||||
public double? FocalLength { get; set; }
|
||||
|
||||
public ImageOrientation? Orientation { get; set; }
|
||||
|
||||
public double? Aperture { get; set; }
|
||||
|
||||
public double? ShutterSpeed { get; set; }
|
||||
|
||||
public double? Latitude { get; set; }
|
||||
|
||||
public double? Longitude { get; set; }
|
||||
|
||||
public double? Altitude { get; set; }
|
||||
|
||||
public int? IsoSpeedRating { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,19 +15,6 @@ namespace MediaBrowser.Controller.Entities
|
|||
/// </summary>
|
||||
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>
|
||||
/// Gets the folder containing the item.
|
||||
/// If the item is a folder, it returns the folder itself.
|
||||
|
@ -42,6 +29,22 @@ namespace MediaBrowser.Controller.Entities
|
|||
[JsonIgnore]
|
||||
public override bool SupportsAncestors => false;
|
||||
|
||||
[JsonIgnore]
|
||||
public override bool SupportsPeople => false;
|
||||
|
||||
public override List<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()
|
||||
{
|
||||
double value = 16;
|
||||
|
@ -67,9 +70,6 @@ namespace MediaBrowser.Controller.Entities
|
|||
return LibraryManager.GetItemList(query);
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public override bool SupportsPeople => false;
|
||||
|
||||
public static string GetPath(string name)
|
||||
{
|
||||
return GetPath(name, true);
|
||||
|
@ -105,6 +105,8 @@ namespace MediaBrowser.Controller.Entities
|
|||
/// <summary>
|
||||
/// This is called before any metadata refresh and returns true or false indicating if changes were made.
|
||||
/// </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)
|
||||
{
|
||||
var hasChanges = base.BeforeMetadataRefresh(replaceAllMetadata);
|
||||
|
|
|
@ -49,12 +49,6 @@ namespace MediaBrowser.Controller.Entities.TV
|
|||
/// <value>The index number.</value>
|
||||
public int? IndexNumberEnd { get; set; }
|
||||
|
||||
public string FindSeriesSortName()
|
||||
{
|
||||
var series = Series;
|
||||
return series == null ? SeriesName : series.SortName;
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
protected override bool SupportsOwnedItems => IsStacked || MediaSourceCount > 1;
|
||||
|
||||
|
@ -76,45 +70,6 @@ namespace MediaBrowser.Controller.Entities.TV
|
|||
[JsonIgnore]
|
||||
protected override bool EnableDefaultVideoUserDataKeys => false;
|
||||
|
||||
public override double GetDefaultPrimaryImageAspectRatio()
|
||||
{
|
||||
// hack for tv plugins
|
||||
if (SourceType == SourceType.Channel)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 16.0 / 9;
|
||||
}
|
||||
|
||||
public override List<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>
|
||||
/// Gets the Episode's Series Instance.
|
||||
/// </summary>
|
||||
|
@ -161,6 +116,74 @@ namespace MediaBrowser.Controller.Entities.TV
|
|||
[JsonIgnore]
|
||||
public string SeasonName { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public override bool SupportsRemoteImageDownloading
|
||||
{
|
||||
get
|
||||
{
|
||||
if (IsMissingEpisode)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public bool IsMissingEpisode => LocationType == LocationType.Virtual;
|
||||
|
||||
[JsonIgnore]
|
||||
public Guid SeasonId { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public Guid SeriesId { get; set; }
|
||||
|
||||
public string FindSeriesSortName()
|
||||
{
|
||||
var series = Series;
|
||||
return series == null ? SeriesName : series.SortName;
|
||||
}
|
||||
|
||||
public override double GetDefaultPrimaryImageAspectRatio()
|
||||
{
|
||||
// hack for tv plugins
|
||||
if (SourceType == SourceType.Channel)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 16.0 / 9;
|
||||
}
|
||||
|
||||
public override List<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()
|
||||
{
|
||||
var series = Series;
|
||||
|
@ -242,29 +265,6 @@ namespace MediaBrowser.Controller.Entities.TV
|
|||
return false;
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public override bool SupportsRemoteImageDownloading
|
||||
{
|
||||
get
|
||||
{
|
||||
if (IsMissingEpisode)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public bool IsMissingEpisode => LocationType == LocationType.Virtual;
|
||||
|
||||
[JsonIgnore]
|
||||
public Guid SeasonId { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public Guid SeriesId { get; set; }
|
||||
|
||||
public Guid FindSeriesId()
|
||||
{
|
||||
var series = FindParent<Series>();
|
||||
|
|
|
@ -38,6 +38,50 @@ namespace MediaBrowser.Controller.Entities.TV
|
|||
[JsonIgnore]
|
||||
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()
|
||||
{
|
||||
double value = 2;
|
||||
|
@ -80,41 +124,6 @@ namespace MediaBrowser.Controller.Entities.TV
|
|||
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()
|
||||
{
|
||||
if (IndexNumber.HasValue)
|
||||
|
@ -157,6 +166,9 @@ namespace MediaBrowser.Controller.Entities.TV
|
|||
/// <summary>
|
||||
/// Gets the episodes.
|
||||
/// </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)
|
||||
{
|
||||
return GetEpisodes(Series, user, options);
|
||||
|
@ -193,15 +205,6 @@ namespace MediaBrowser.Controller.Entities.TV
|
|||
return UnratedItem.Series;
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public string SeriesPresentationUniqueKey { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public string SeriesName { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public Guid SeriesId { get; set; }
|
||||
|
||||
public string FindSeriesPresentationUniqueKey()
|
||||
{
|
||||
var series = Series;
|
||||
|
@ -241,6 +244,7 @@ namespace MediaBrowser.Controller.Entities.TV
|
|||
/// <summary>
|
||||
/// This is called before any metadata refresh and returns true or false indicating if changes were made.
|
||||
/// </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>
|
||||
public override bool BeforeMetadataRefresh(bool replaceAllMetadata)
|
||||
{
|
||||
|
|
|
@ -72,6 +72,9 @@ namespace MediaBrowser.Controller.Entities.TV
|
|||
/// <value>The status.</value>
|
||||
public SeriesStatus? Status { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public override bool StopRefreshIfLocalMetadataFound => false;
|
||||
|
||||
public override double GetDefaultPrimaryImageAspectRatio()
|
||||
{
|
||||
double value = 2;
|
||||
|
@ -394,6 +397,10 @@ namespace MediaBrowser.Controller.Entities.TV
|
|||
/// <summary>
|
||||
/// Filters the episodes by season.
|
||||
/// </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)
|
||||
{
|
||||
var seasonNumber = parentSeason.IndexNumber;
|
||||
|
@ -424,6 +431,10 @@ namespace MediaBrowser.Controller.Entities.TV
|
|||
/// <summary>
|
||||
/// Filters the episodes by season.
|
||||
/// </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)
|
||||
{
|
||||
if (!includeSpecials || seasonNumber < 1)
|
||||
|
@ -499,8 +510,5 @@ namespace MediaBrowser.Controller.Entities.TV
|
|||
|
||||
return list;
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public override bool StopRefreshIfLocalMetadataFound => false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#nullable disable
|
||||
|
||||
#pragma warning disable CS1591
|
||||
#pragma warning disable CA1819, CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
@ -23,6 +23,9 @@ namespace MediaBrowser.Controller.Entities
|
|||
TrailerTypes = Array.Empty<TrailerType>();
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public override bool StopRefreshIfLocalMetadataFound => false;
|
||||
|
||||
public TrailerType[] TrailerTypes { get; set; }
|
||||
|
||||
public override double GetDefaultPrimaryImageAspectRatio()
|
||||
|
@ -97,8 +100,5 @@ namespace MediaBrowser.Controller.Entities
|
|||
|
||||
return list;
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public override bool StopRefreshIfLocalMetadataFound => false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,13 @@ namespace MediaBrowser.Controller.Entities
|
|||
/// </summary>
|
||||
public class UserItemData
|
||||
{
|
||||
public const double MinLikeValue = 6.5;
|
||||
|
||||
/// <summary>
|
||||
/// The _rating.
|
||||
/// </summary>
|
||||
private double? _rating;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the user id.
|
||||
/// </summary>
|
||||
|
@ -24,11 +31,6 @@ namespace MediaBrowser.Controller.Entities
|
|||
/// <value>The key.</value>
|
||||
public string Key { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The _rating.
|
||||
/// </summary>
|
||||
private double? _rating;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the users 0-10 rating.
|
||||
/// </summary>
|
||||
|
@ -93,8 +95,6 @@ namespace MediaBrowser.Controller.Entities
|
|||
/// <value>The index of the subtitle stream.</value>
|
||||
public int? SubtitleStreamIndex { get; set; }
|
||||
|
||||
public const double MinLikeValue = 6.5;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the item is liked or not.
|
||||
/// This should never be serialized.
|
||||
|
|
|
@ -21,8 +21,28 @@ namespace MediaBrowser.Controller.Entities
|
|||
/// </summary>
|
||||
public class UserRootFolder : Folder
|
||||
{
|
||||
private List<Guid> _childrenIds = null;
|
||||
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()
|
||||
{
|
||||
|
@ -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)
|
||||
{
|
||||
if (query.Recursive)
|
||||
|
@ -74,12 +80,6 @@ namespace MediaBrowser.Controller.Entities
|
|||
return GetChildren(user, true).Count;
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
protected override bool SupportsShortcutChildren => true;
|
||||
|
||||
[JsonIgnore]
|
||||
public override bool IsPreSorted => true;
|
||||
|
||||
protected override IEnumerable<BaseItem> GetEligibleChildrenForRecursiveChildren(User user)
|
||||
{
|
||||
var list = base.GetEligibleChildrenForRecursiveChildren(user).ToList();
|
||||
|
|
|
@ -28,6 +28,14 @@ namespace MediaBrowser.Controller.Entities
|
|||
ISupportsPlaceHolders,
|
||||
IHasMediaSources
|
||||
{
|
||||
public Video()
|
||||
{
|
||||
AdditionalParts = Array.Empty<string>();
|
||||
LocalAlternateVersions = Array.Empty<string>();
|
||||
SubtitleFiles = Array.Empty<string>();
|
||||
LinkedAlternateVersions = Array.Empty<LinkedChild>();
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public string PrimaryVersionId { get; set; }
|
||||
|
||||
|
@ -74,30 +82,6 @@ namespace MediaBrowser.Controller.Entities
|
|||
}
|
||||
}
|
||||
|
||||
public void SetPrimaryVersionId(string id)
|
||||
{
|
||||
if (string.IsNullOrEmpty(id))
|
||||
{
|
||||
PrimaryVersionId = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
PrimaryVersionId = id;
|
||||
}
|
||||
|
||||
PresentationUniqueKey = CreatePresentationUniqueKey();
|
||||
}
|
||||
|
||||
public override string CreatePresentationUniqueKey()
|
||||
{
|
||||
if (!string.IsNullOrEmpty(PrimaryVersionId))
|
||||
{
|
||||
return PrimaryVersionId;
|
||||
}
|
||||
|
||||
return base.CreatePresentationUniqueKey();
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public override bool SupportsThemeMedia => true;
|
||||
|
||||
|
@ -151,24 +135,6 @@ namespace MediaBrowser.Controller.Entities
|
|||
/// <value>The aspect ratio.</value>
|
||||
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]
|
||||
public override bool SupportsAddingToPlaylist => true;
|
||||
|
||||
|
@ -196,16 +162,6 @@ namespace MediaBrowser.Controller.Entities
|
|||
[JsonIgnore]
|
||||
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; }
|
||||
|
||||
[JsonIgnore]
|
||||
|
@ -222,21 +178,6 @@ namespace MediaBrowser.Controller.Entities
|
|||
}
|
||||
}
|
||||
|
||||
protected override bool IsActiveRecording()
|
||||
{
|
||||
return LiveTvManager.GetActiveRecordingInfo(Path) != null;
|
||||
}
|
||||
|
||||
public override bool CanDelete()
|
||||
{
|
||||
if (IsActiveRecording())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return base.CanDelete();
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public bool IsCompleteMedia
|
||||
{
|
||||
|
@ -254,80 +195,6 @@ namespace MediaBrowser.Controller.Entities
|
|||
[JsonIgnore]
|
||||
protected virtual bool EnableDefaultVideoUserDataKeys => true;
|
||||
|
||||
public override List<string> GetUserDataKeys()
|
||||
{
|
||||
var list = base.GetUserDataKeys();
|
||||
|
||||
if (EnableDefaultVideoUserDataKeys)
|
||||
{
|
||||
if (ExtraType.HasValue)
|
||||
{
|
||||
var key = this.GetProviderId(MetadataProvider.Tmdb);
|
||||
if (!string.IsNullOrEmpty(key))
|
||||
{
|
||||
list.Insert(0, GetUserDataKey(key));
|
||||
}
|
||||
|
||||
key = this.GetProviderId(MetadataProvider.Imdb);
|
||||
if (!string.IsNullOrEmpty(key))
|
||||
{
|
||||
list.Insert(0, GetUserDataKey(key));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var key = this.GetProviderId(MetadataProvider.Imdb);
|
||||
if (!string.IsNullOrEmpty(key))
|
||||
{
|
||||
list.Insert(0, key);
|
||||
}
|
||||
|
||||
key = this.GetProviderId(MetadataProvider.Tmdb);
|
||||
if (!string.IsNullOrEmpty(key))
|
||||
{
|
||||
list.Insert(0, key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
private string GetUserDataKey(string providerId)
|
||||
{
|
||||
var key = providerId + "-" + ExtraType.ToString().ToLowerInvariant();
|
||||
|
||||
// Make sure different trailers have their own data.
|
||||
if (RunTimeTicks.HasValue)
|
||||
{
|
||||
key += "-" + RunTimeTicks.Value.ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
public IEnumerable<Video> GetLinkedAlternateVersions()
|
||||
{
|
||||
return LinkedAlternateVersions
|
||||
.Select(GetLinkedChild)
|
||||
.Where(i => i != null)
|
||||
.OfType<Video>()
|
||||
.OrderBy(i => i.SortName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the additional parts.
|
||||
/// </summary>
|
||||
/// <returns>IEnumerable{Video}.</returns>
|
||||
public IOrderedEnumerable<Video> GetAdditionalParts()
|
||||
{
|
||||
return GetAdditionalPartIds()
|
||||
.Select(i => LibraryManager.GetItemById(i))
|
||||
.Where(i => i != null)
|
||||
.OfType<Video>()
|
||||
.OrderBy(i => i.SortName);
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public override string ContainingFolderPath
|
||||
{
|
||||
|
@ -369,6 +236,153 @@ namespace MediaBrowser.Controller.Entities
|
|||
}
|
||||
}
|
||||
|
||||
/// <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;
|
||||
|
||||
public override List<string> GetUserDataKeys()
|
||||
{
|
||||
var list = base.GetUserDataKeys();
|
||||
|
||||
if (EnableDefaultVideoUserDataKeys)
|
||||
{
|
||||
if (ExtraType.HasValue)
|
||||
{
|
||||
var key = this.GetProviderId(MetadataProvider.Tmdb);
|
||||
if (!string.IsNullOrEmpty(key))
|
||||
{
|
||||
list.Insert(0, GetUserDataKey(key));
|
||||
}
|
||||
|
||||
key = this.GetProviderId(MetadataProvider.Imdb);
|
||||
if (!string.IsNullOrEmpty(key))
|
||||
{
|
||||
list.Insert(0, GetUserDataKey(key));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var key = this.GetProviderId(MetadataProvider.Imdb);
|
||||
if (!string.IsNullOrEmpty(key))
|
||||
{
|
||||
list.Insert(0, key);
|
||||
}
|
||||
|
||||
key = this.GetProviderId(MetadataProvider.Tmdb);
|
||||
if (!string.IsNullOrEmpty(key))
|
||||
{
|
||||
list.Insert(0, key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
var key = providerId + "-" + ExtraType.ToString().ToLowerInvariant();
|
||||
|
||||
// Make sure different trailers have their own data.
|
||||
if (RunTimeTicks.HasValue)
|
||||
{
|
||||
key += "-" + RunTimeTicks.Value.ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
public IEnumerable<Video> GetLinkedAlternateVersions()
|
||||
{
|
||||
return LinkedAlternateVersions
|
||||
.Select(GetLinkedChild)
|
||||
.Where(i => i != null)
|
||||
.OfType<Video>()
|
||||
.OrderBy(i => i.SortName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the additional parts.
|
||||
/// </summary>
|
||||
/// <returns>IEnumerable{Video}.</returns>
|
||||
public IOrderedEnumerable<Video> GetAdditionalParts()
|
||||
{
|
||||
return GetAdditionalPartIds()
|
||||
.Select(i => LibraryManager.GetItemById(i))
|
||||
.Where(i => i != null)
|
||||
.OfType<Video>()
|
||||
.OrderBy(i => i.SortName);
|
||||
}
|
||||
|
||||
internal override ItemUpdateType UpdateFromResolvedItem(BaseItem newItem)
|
||||
{
|
||||
var updateType = base.UpdateFromResolvedItem(newItem);
|
||||
|
@ -397,20 +411,6 @@ namespace MediaBrowser.Controller.Entities
|
|||
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)
|
||||
{
|
||||
var hasChanges = await base.RefreshedOwnedItems(options, fileSystemChildren, cancellationToken).ConfigureAwait(false);
|
||||
|
|
|
@ -15,13 +15,11 @@ namespace MediaBrowser.Controller.Entities
|
|||
/// </summary>
|
||||
public class Year : BaseItem, IItemByName
|
||||
{
|
||||
public override List<string> GetUserDataKeys()
|
||||
{
|
||||
var list = base.GetUserDataKeys();
|
||||
[JsonIgnore]
|
||||
public override bool SupportsAncestors => false;
|
||||
|
||||
list.Insert(0, "Year-" + Name);
|
||||
return list;
|
||||
}
|
||||
[JsonIgnore]
|
||||
public override bool SupportsPeople => false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the folder containing the item.
|
||||
|
@ -31,6 +29,19 @@ namespace MediaBrowser.Controller.Entities
|
|||
[JsonIgnore]
|
||||
public override string ContainingFolderPath => Path;
|
||||
|
||||
public override bool CanDelete()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public override List<string> GetUserDataKeys()
|
||||
{
|
||||
var list = base.GetUserDataKeys();
|
||||
|
||||
list.Insert(0, "Year-" + Name);
|
||||
return list;
|
||||
}
|
||||
|
||||
public override double GetDefaultPrimaryImageAspectRatio()
|
||||
{
|
||||
double value = 2;
|
||||
|
@ -39,14 +50,6 @@ namespace MediaBrowser.Controller.Entities
|
|||
return value;
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public override bool SupportsAncestors => false;
|
||||
|
||||
public override bool CanDelete()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool IsSaveLocalMetadataEnabled()
|
||||
{
|
||||
return true;
|
||||
|
@ -76,9 +79,6 @@ namespace MediaBrowser.Controller.Entities
|
|||
return null;
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public override bool SupportsPeople => false;
|
||||
|
||||
public static string GetPath(string name)
|
||||
{
|
||||
return GetPath(name, true);
|
||||
|
|
|
@ -10,6 +10,15 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||
{
|
||||
public class BaseEncodingJobOptions
|
||||
{
|
||||
public BaseEncodingJobOptions()
|
||||
{
|
||||
EnableAutoStreamCopy = true;
|
||||
AllowVideoStreamCopy = true;
|
||||
AllowAudioStreamCopy = true;
|
||||
Context = EncodingContext.Streaming;
|
||||
StreamOptions = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the id.
|
||||
/// </summary>
|
||||
|
@ -191,14 +200,5 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||
|
||||
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.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
|
@ -16,9 +15,7 @@ using MediaBrowser.Model.Configuration;
|
|||
using MediaBrowser.Model.Dlna;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.MediaInfo;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace MediaBrowser.Controller.MediaEncoding
|
||||
{
|
||||
|
@ -161,6 +158,9 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||
/// <summary>
|
||||
/// Gets the name of the output video codec.
|
||||
/// </summary>
|
||||
/// <param name="state">Encording state.</param>
|
||||
/// <param name="encodingOptions">Encoding options.</param>
|
||||
/// <returns>Encoder string.</returns>
|
||||
public string GetVideoEncoder(EncodingJobInfo state, EncodingOptions encodingOptions)
|
||||
{
|
||||
var codec = state.OutputVideoCodec;
|
||||
|
@ -315,6 +315,11 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||
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)
|
||||
{
|
||||
// For these need to find out the ffmpeg names
|
||||
|
@ -344,6 +349,8 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||
/// <summary>
|
||||
/// Infers the audio codec based on the url.
|
||||
/// </summary>
|
||||
/// <param name="container">Container to use.</param>
|
||||
/// <returns>Codec string.</returns>
|
||||
public string InferAudioCodec(string container)
|
||||
{
|
||||
var ext = "." + (container ?? string.Empty);
|
||||
|
@ -489,6 +496,9 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||
/// <summary>
|
||||
/// Gets the input argument.
|
||||
/// </summary>
|
||||
/// <param name="state">Encoding state.</param>
|
||||
/// <param name="encodingOptions">Encoding options.</param>
|
||||
/// <returns>Input arguments.</returns>
|
||||
public string GetInputArgument(EncodingJobInfo state, EncodingOptions encodingOptions)
|
||||
{
|
||||
var arg = new StringBuilder();
|
||||
|
@ -965,6 +975,11 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||
/// <summary>
|
||||
/// Gets the video bitrate to specify on the command line.
|
||||
/// </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)
|
||||
{
|
||||
var param = string.Empty;
|
||||
|
@ -1966,8 +1981,12 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the graphical subtitle param.
|
||||
/// Gets the graphical subtitle parameter.
|
||||
/// </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(
|
||||
EncodingJobInfo state,
|
||||
EncodingOptions options,
|
||||
|
@ -2485,6 +2504,13 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||
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(
|
||||
EncodingJobInfo state,
|
||||
EncodingOptions options,
|
||||
|
@ -2495,8 +2521,13 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the output size parameter.
|
||||
/// If we're going to put a fixed size on the command line, this will calculate it.
|
||||
/// </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(
|
||||
EncodingJobInfo state,
|
||||
EncodingOptions options,
|
||||
|
@ -2908,6 +2939,10 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||
/// <summary>
|
||||
/// Gets the number of threads.
|
||||
/// </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
|
||||
public static int GetNumberOfThreads(EncodingJobInfo? state, EncodingOptions encodingOptions, string? outputVideoCodec)
|
||||
{
|
||||
|
@ -3551,6 +3586,11 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||
/// <summary>
|
||||
/// Gets a hw decoder name.
|
||||
/// </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)
|
||||
{
|
||||
var isCodecAvailable = _mediaEncoder.SupportsDecoder(decoder) && options.HardwareDecodingCodecs.Contains(videoCodec, StringComparer.OrdinalIgnoreCase);
|
||||
|
@ -3569,6 +3609,11 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||
/// <summary>
|
||||
/// Gets a hwaccel type to use as a hardware decoder(dxva/vaapi) depending on the system.
|
||||
/// </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)
|
||||
{
|
||||
var isWindows = OperatingSystem.IsWindows();
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#nullable disable
|
||||
|
||||
#pragma warning disable CS1591
|
||||
#pragma warning disable CS1591, SA1401
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
@ -20,6 +20,44 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||
// For now, a common base class until the API and MediaEncoding classes are unified
|
||||
public class EncodingJobInfo
|
||||
{
|
||||
public int? OutputAudioBitrate;
|
||||
public int? OutputAudioChannels;
|
||||
|
||||
private TranscodeReason[] _transcodeReasons = null;
|
||||
|
||||
public EncodingJobInfo(TranscodingJobType jobType)
|
||||
{
|
||||
TranscodingType = jobType;
|
||||
RemoteHttpHeaders = new Dictionary<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 VideoType VideoType { get; set; }
|
||||
|
@ -58,40 +96,6 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||
|
||||
public string MimeType { get; set; }
|
||||
|
||||
public string GetMimeType(string outputPath, bool enableStreamDefault = true)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(MimeType))
|
||||
{
|
||||
return MimeType;
|
||||
}
|
||||
|
||||
return MimeTypes.GetMimeType(outputPath, enableStreamDefault);
|
||||
}
|
||||
|
||||
private TranscodeReason[] _transcodeReasons = null;
|
||||
|
||||
public TranscodeReason[] TranscodeReasons
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_transcodeReasons == null)
|
||||
{
|
||||
if (BaseRequest.TranscodeReasons == null)
|
||||
{
|
||||
return Array.Empty<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 IgnoreInputIndex => MediaSource.IgnoreIndex;
|
||||
|
@ -144,196 +148,17 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||
|
||||
public BaseEncodingJobOptions BaseRequest { get; set; }
|
||||
|
||||
public long? StartTimeTicks => BaseRequest.StartTimeTicks;
|
||||
|
||||
public bool CopyTimestamps => BaseRequest.CopyTimestamps;
|
||||
|
||||
public int? OutputAudioBitrate;
|
||||
public int? OutputAudioChannels;
|
||||
|
||||
public bool DeInterlace(string videoCodec, bool forceDeinterlaceIfSourceIsInterlaced)
|
||||
{
|
||||
var videoStream = VideoStream;
|
||||
var isInputInterlaced = videoStream != null && videoStream.IsInterlaced;
|
||||
|
||||
if (!isInputInterlaced)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Support general param
|
||||
if (BaseRequest.DeInterlace)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(videoCodec))
|
||||
{
|
||||
if (string.Equals(BaseRequest.GetOption(videoCodec, "deinterlace"), "true", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return forceDeinterlaceIfSourceIsInterlaced && isInputInterlaced;
|
||||
}
|
||||
|
||||
public string[] GetRequestedProfiles(string codec)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(BaseRequest.Profile))
|
||||
{
|
||||
return BaseRequest.Profile.Split(new[] { '|', ',' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(codec))
|
||||
{
|
||||
var profile = BaseRequest.GetOption(codec, "profile");
|
||||
|
||||
if (!string.IsNullOrEmpty(profile))
|
||||
{
|
||||
return profile.Split(new[] { '|', ',' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
}
|
||||
}
|
||||
|
||||
return Array.Empty<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 TranscodingJobType TranscodingType { get; set; }
|
||||
|
||||
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 long? StartTimeTicks => BaseRequest.StartTimeTicks;
|
||||
|
||||
public bool CopyTimestamps => BaseRequest.CopyTimestamps;
|
||||
|
||||
public bool IsSegmentedLiveStream
|
||||
=> TranscodingType != TranscodingJobType.Progressive && !RunTimeTicks.HasValue;
|
||||
|
||||
public bool EnableBreakOnNonKeyFrames(string videoCodec)
|
||||
{
|
||||
if (TranscodingType != TranscodingJobType.Progressive)
|
||||
{
|
||||
if (IsSegmentedLiveStream)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return BaseRequest.BreakOnNonKeyFrames && EncodingHelper.IsCopyCodec(videoCodec);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public int? TotalOutputBitrate => (OutputAudioBitrate ?? 0) + (OutputVideoBitrate ?? 0);
|
||||
|
||||
public int? OutputWidth
|
||||
|
@ -682,6 +507,21 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||
|
||||
public int HlsListSize => 0;
|
||||
|
||||
public bool EnableBreakOnNonKeyFrames(string videoCodec)
|
||||
{
|
||||
if (TranscodingType != TranscodingJobType.Progressive)
|
||||
{
|
||||
if (IsSegmentedLiveStream)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return BaseRequest.BreakOnNonKeyFrames && EncodingHelper.IsCopyCodec(videoCodec);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private int? GetMediaStreamCount(MediaStreamType type, int limit)
|
||||
{
|
||||
var count = MediaSource.GetStreamCount(type);
|
||||
|
@ -694,7 +534,167 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||
return count;
|
||||
}
|
||||
|
||||
public IProgress<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)
|
||||
{
|
||||
|
|
|
@ -16,6 +16,13 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||
/// <summary>
|
||||
/// Refreshes the chapter images.
|
||||
/// </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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -71,13 +71,42 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||
/// <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="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);
|
||||
|
||||
/// <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);
|
||||
|
||||
/// <summary>
|
||||
/// Extracts the video images on interval.
|
||||
/// </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(
|
||||
string inputFile,
|
||||
string container,
|
||||
|
@ -122,10 +151,24 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||
/// <returns>System.String.</returns>
|
||||
string EscapeSubtitleFilterPath(string path);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the path to find FFmpeg.
|
||||
/// </summary>
|
||||
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);
|
||||
|
||||
/// <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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,6 +15,14 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||
/// <summary>
|
||||
/// Gets the subtitles.
|
||||
/// </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>
|
||||
Task<Stream> GetSubtitles(
|
||||
BaseItem item,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#nullable disable
|
||||
|
||||
#pragma warning disable CS1591
|
||||
#pragma warning disable CS1591, SA1306, SA1401
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
@ -30,6 +30,21 @@ namespace MediaBrowser.Controller.Net
|
|||
private readonly List<Tuple<IWebSocketConnection, CancellationTokenSource, TStateType>> _activeConnections =
|
||||
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>
|
||||
/// Gets the type used for the messages sent to the client.
|
||||
/// </summary>
|
||||
|
@ -54,21 +69,6 @@ namespace MediaBrowser.Controller.Net
|
|||
/// <returns>Task{`1}.</returns>
|
||||
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>
|
||||
/// Processes the message.
|
||||
/// </summary>
|
||||
|
|
|
@ -18,7 +18,6 @@ namespace MediaBrowser.Controller.Persistence
|
|||
/// <param name="key">The key.</param>
|
||||
/// <param name="userData">The user data.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task.</returns>
|
||||
void SaveUserData(long userId, string key, UserItemData userData, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -31,24 +31,18 @@ namespace MediaBrowser.Controller.Playlists
|
|||
".zpl"
|
||||
};
|
||||
|
||||
public Guid OwnerUserId { get; set; }
|
||||
|
||||
public Share[] Shares { get; set; }
|
||||
|
||||
public Playlist()
|
||||
{
|
||||
Shares = Array.Empty<Share>();
|
||||
}
|
||||
|
||||
public Guid OwnerUserId { get; set; }
|
||||
|
||||
public Share[] Shares { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public bool IsFile => IsPlaylistFile(Path);
|
||||
|
||||
public static bool IsPlaylistFile(string path)
|
||||
{
|
||||
// The path will sometimes be a directory and "Path.HasExtension" returns true if the name contains a '.' (dot).
|
||||
return System.IO.Path.HasExtension(path) && !Directory.Exists(path);
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public override string ContainingFolderPath
|
||||
{
|
||||
|
@ -80,6 +74,41 @@ namespace MediaBrowser.Controller.Playlists
|
|||
[JsonIgnore]
|
||||
public override bool SupportsCumulativeRunTimeTicks => true;
|
||||
|
||||
[JsonIgnore]
|
||||
public override bool IsPreSorted => true;
|
||||
|
||||
public string PlaylistMediaType { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public override string MediaType => PlaylistMediaType;
|
||||
|
||||
[JsonIgnore]
|
||||
private bool IsSharedItem
|
||||
{
|
||||
get
|
||||
{
|
||||
var path = Path;
|
||||
|
||||
if (string.IsNullOrEmpty(path))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return FileSystem.ContainsSubPath(ConfigurationManager.ApplicationPaths.DataPath, path);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsPlaylistFile(string path)
|
||||
{
|
||||
// The path will sometimes be a directory and "Path.HasExtension" returns true if the name contains a '.' (dot).
|
||||
return System.IO.Path.HasExtension(path) && !Directory.Exists(path);
|
||||
}
|
||||
|
||||
public void SetMediaType(string value)
|
||||
{
|
||||
PlaylistMediaType = value;
|
||||
}
|
||||
|
||||
public override double GetDefaultPrimaryImageAspectRatio()
|
||||
{
|
||||
return 1;
|
||||
|
@ -197,35 +226,6 @@ namespace MediaBrowser.Controller.Playlists
|
|||
return new[] { item };
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public override bool IsPreSorted => true;
|
||||
|
||||
public string PlaylistMediaType { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public override string MediaType => PlaylistMediaType;
|
||||
|
||||
public void SetMediaType(string value)
|
||||
{
|
||||
PlaylistMediaType = value;
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
private bool IsSharedItem
|
||||
{
|
||||
get
|
||||
{
|
||||
var path = Path;
|
||||
|
||||
if (string.IsNullOrEmpty(path))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return FileSystem.ContainsSubPath(ConfigurationManager.ApplicationPaths.DataPath, path);
|
||||
}
|
||||
}
|
||||
|
||||
public override bool IsVisible(User user)
|
||||
{
|
||||
if (!IsSharedItem)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#pragma warning disable CA1002, CS1591
|
||||
#pragma warning disable CA1002, CA1819, CS1591
|
||||
|
||||
using System.Collections.Generic;
|
||||
using MediaBrowser.Model.IO;
|
||||
|
|
|
@ -13,18 +13,18 @@ namespace MediaBrowser.Controller.Resolvers
|
|||
/// </summary>
|
||||
public interface IItemResolver
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the priority.
|
||||
/// </summary>
|
||||
/// <value>The priority.</value>
|
||||
ResolverPriority Priority { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Resolves the path.
|
||||
/// </summary>
|
||||
/// <param name="args">The args.</param>
|
||||
/// <returns>BaseItem.</returns>
|
||||
BaseItem ResolvePath(ItemResolveArgs args);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the priority.
|
||||
/// </summary>
|
||||
/// <value>The priority.</value>
|
||||
ResolverPriority Priority { get; }
|
||||
}
|
||||
|
||||
public interface IMultiItemResolver
|
||||
|
@ -38,14 +38,14 @@ namespace MediaBrowser.Controller.Resolvers
|
|||
|
||||
public class MultiItemResolverResult
|
||||
{
|
||||
public List<BaseItem> Items { get; set; }
|
||||
|
||||
public List<FileSystemMetadata> ExtraFiles { get; set; }
|
||||
|
||||
public MultiItemResolverResult()
|
||||
{
|
||||
Items = new List<BaseItem>();
|
||||
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…
Reference in New Issue
Block a user