Merge branch 'master' into tonemap-overlay

This commit is contained in:
Claus Vium 2021-08-30 20:02:31 +02:00 committed by GitHub
commit ae031fdd28
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
158 changed files with 2644 additions and 2112 deletions

View File

@ -212,4 +212,5 @@
- [Tim Hobbs](https://github.com/timhobbs) - [Tim Hobbs](https://github.com/timhobbs)
- [SvenVandenbrande](https://github.com/SvenVandenbrande) - [SvenVandenbrande](https://github.com/SvenVandenbrande)
- [olsh](https://github.com/olsh) - [olsh](https://github.com/olsh)
- [lbenini](https://github.com/lbenini)
- [gnuyent](https://github.com/gnuyent) - [gnuyent](https://github.com/gnuyent)

View File

@ -8,7 +8,7 @@ RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-
&& npm ci --no-audit --unsafe-perm \ && npm ci --no-audit --unsafe-perm \
&& mv dist /dist && mv dist /dist
FROM debian:buster-slim as app FROM debian:bullseye-slim as app
# https://askubuntu.com/questions/972516/debian-frontend-environment-variable # https://askubuntu.com/questions/972516/debian-frontend-environment-variable
ARG DEBIAN_FRONTEND="noninteractive" ARG DEBIAN_FRONTEND="noninteractive"

View File

@ -14,7 +14,7 @@ RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-
&& mv dist /dist && mv dist /dist
FROM multiarch/qemu-user-static:x86_64-arm as qemu FROM multiarch/qemu-user-static:x86_64-arm as qemu
FROM arm32v7/debian:buster-slim as app FROM arm32v7/debian:bullseye-slim as app
# https://askubuntu.com/questions/972516/debian-frontend-environment-variable # https://askubuntu.com/questions/972516/debian-frontend-environment-variable
ARG DEBIAN_FRONTEND="noninteractive" ARG DEBIAN_FRONTEND="noninteractive"

View File

@ -14,7 +14,7 @@ RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-
&& mv dist /dist && mv dist /dist
FROM multiarch/qemu-user-static:x86_64-aarch64 as qemu FROM multiarch/qemu-user-static:x86_64-aarch64 as qemu
FROM arm64v8/debian:buster-slim as app FROM arm64v8/debian:bullseye-slim as app
# https://askubuntu.com/questions/972516/debian-frontend-environment-variable # https://askubuntu.com/questions/972516/debian-frontend-environment-variable
ARG DEBIAN_FRONTEND="noninteractive" ARG DEBIAN_FRONTEND="noninteractive"

View File

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

View File

@ -1099,7 +1099,6 @@ namespace Emby.Server.Implementations
ServerName = FriendlyName, ServerName = FriendlyName,
LocalAddress = GetSmartApiUrl(source), LocalAddress = GetSmartApiUrl(source),
SupportsLibraryMonitor = true, SupportsLibraryMonitor = true,
EncoderLocation = _mediaEncoder.EncoderLocation,
SystemArchitecture = RuntimeInformation.OSArchitecture, SystemArchitecture = RuntimeInformation.OSArchitecture,
PackageName = _startupOptions.PackageName PackageName = _startupOptions.PackageName
}; };

View File

@ -880,7 +880,7 @@ namespace Emby.Server.Implementations.Channels
} }
} }
private async Task CacheResponse(object result, string path) private async Task CacheResponse(ChannelItemResult result, string path)
{ {
try try
{ {

View File

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

View File

@ -23,14 +23,15 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="DiscUtils.Udf" Version="0.16.4" />
<PackageReference Include="Jellyfin.XmlTv" Version="10.6.2" /> <PackageReference Include="Jellyfin.XmlTv" Version="10.6.2" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="5.0.2" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="5.0.2" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="5.0.0" /> <PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="5.0.0" /> <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="5.0.0" /> <PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="5.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="5.0.8" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="5.0.9" />
<PackageReference Include="Mono.Nat" Version="3.0.1" /> <PackageReference Include="Mono.Nat" Version="3.0.1" />
<PackageReference Include="prometheus-net.DotNetRuntime" Version="4.1.0" /> <PackageReference Include="prometheus-net.DotNetRuntime" Version="4.2.0" />
<PackageReference Include="sharpcompress" Version="0.28.3" /> <PackageReference Include="sharpcompress" Version="0.28.3" />
<PackageReference Include="SQLitePCL.pretty.netstandard" Version="3.1.0" /> <PackageReference Include="SQLitePCL.pretty.netstandard" Version="3.1.0" />
<PackageReference Include="DotNet.Glob" Version="3.1.2" /> <PackageReference Include="DotNet.Glob" Version="3.1.2" />

View File

@ -5,6 +5,7 @@
using System; using System;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using DiscUtils.Udf;
using Emby.Naming.Video; using Emby.Naming.Video;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
@ -201,6 +202,22 @@ namespace Emby.Server.Implementations.Library.Resolvers
{ {
video.IsoType = IsoType.BluRay; video.IsoType = IsoType.BluRay;
} }
else
{
// use disc-utils, both DVDs and BDs use UDF filesystem
using (var videoFileStream = File.Open(video.Path, FileMode.Open, FileAccess.Read))
{
UdfReader udfReader = new UdfReader(videoFileStream);
if (udfReader.DirectoryExists("VIDEO_TS"))
{
video.IsoType = IsoType.Dvd;
}
else if (udfReader.DirectoryExists("BDMV"))
{
video.IsoType = IsoType.BluRay;
}
}
}
} }
} }

View File

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

View File

@ -2,24 +2,24 @@
"Artists": "Kunstenare", "Artists": "Kunstenare",
"Channels": "Kanale", "Channels": "Kanale",
"Folders": "Lêergidse", "Folders": "Lêergidse",
"Favorites": "Gunstellinge", "Favorites": "Gunstelinge",
"HeaderFavoriteShows": "Gunsteling Vertonings", "HeaderFavoriteShows": "Gunsteling Vertonings",
"ValueSpecialEpisodeName": "Spesiale - {0}", "ValueSpecialEpisodeName": "Spesiale - {0}",
"HeaderAlbumArtists": "Album Kunstenaars", "HeaderAlbumArtists": "Kunstenaars se Album",
"Books": "Boeke", "Books": "Boeke",
"HeaderNextUp": "Volgende", "HeaderNextUp": "Volgende",
"Movies": "Flieks", "Movies": "Flieks",
"Shows": "Televisie Reekse", "Shows": "Televisie Reekse",
"HeaderContinueWatching": "Kyk Verder", "HeaderContinueWatching": "Kyk Verder",
"HeaderFavoriteEpisodes": "Gunsteling Episodes", "HeaderFavoriteEpisodes": "Gunsteling Episodes",
"Photos": "Fotos", "Photos": "Foto's",
"Playlists": "Snitlyste", "Playlists": "Snitlyste",
"HeaderFavoriteArtists": "Gunsteling Kunstenaars", "HeaderFavoriteArtists": "Gunsteling Kunstenaars",
"HeaderFavoriteAlbums": "Gunsteling Albums", "HeaderFavoriteAlbums": "Gunsteling Albums",
"Sync": "Sinkroniseer", "Sync": "Sinkroniseer",
"HeaderFavoriteSongs": "Gunsteling Liedjies", "HeaderFavoriteSongs": "Gunsteling Liedjies",
"Songs": "Liedjies", "Songs": "Liedjies",
"DeviceOnlineWithName": "{0} gekoppel is", "DeviceOnlineWithName": "{0} is gekoppel",
"DeviceOfflineWithName": "{0} is ontkoppel", "DeviceOfflineWithName": "{0} is ontkoppel",
"Collections": "Versamelings", "Collections": "Versamelings",
"Inherit": "Ontvang", "Inherit": "Ontvang",
@ -71,7 +71,7 @@
"NameSeasonUnknown": "Seisoen Onbekend", "NameSeasonUnknown": "Seisoen Onbekend",
"NameSeasonNumber": "Seisoen {0}", "NameSeasonNumber": "Seisoen {0}",
"NameInstallFailed": "{0} installering het misluk", "NameInstallFailed": "{0} installering het misluk",
"MusicVideos": "Musiek videos", "MusicVideos": "Musiek Videos",
"Music": "Musiek", "Music": "Musiek",
"MixedContent": "Gemengde inhoud", "MixedContent": "Gemengde inhoud",
"MessageServerConfigurationUpdated": "Bediener konfigurasie is opgedateer", "MessageServerConfigurationUpdated": "Bediener konfigurasie is opgedateer",
@ -79,15 +79,15 @@
"MessageApplicationUpdatedTo": "Jellyfin Bediener is opgedateer na {0}", "MessageApplicationUpdatedTo": "Jellyfin Bediener is opgedateer na {0}",
"MessageApplicationUpdated": "Jellyfin Bediener is opgedateer", "MessageApplicationUpdated": "Jellyfin Bediener is opgedateer",
"Latest": "Nuutste", "Latest": "Nuutste",
"LabelRunningTimeValue": "Lopende tyd: {0}", "LabelRunningTimeValue": "Werktyd: {0}",
"LabelIpAddressValue": "IP adres: {0}", "LabelIpAddressValue": "IP adres: {0}",
"ItemRemovedWithName": "{0} is uit versameling verwyder", "ItemRemovedWithName": "{0} is uit versameling verwyder",
"ItemAddedWithName": "{0} is in die versameling", "ItemAddedWithName": "{0} is by die versameling gevoeg",
"HomeVideos": "Tuis opnames", "HomeVideos": "Tuis Videos",
"HeaderRecordingGroups": "Groep Opnames", "HeaderRecordingGroups": "Groep Opnames",
"Genres": "Genres", "Genres": "Genres",
"FailedLoginAttemptWithUserName": "Mislukte aansluiting van {0}", "FailedLoginAttemptWithUserName": "Mislukte aansluiting van {0}",
"ChapterNameValue": "Hoofstuk", "ChapterNameValue": "Hoofstuk {0}",
"CameraImageUploadedFrom": "'n Nuwe kamera photo opgelaai van {0}", "CameraImageUploadedFrom": "'n Nuwe kamera photo opgelaai van {0}",
"AuthenticationSucceededWithUserName": "{0} suksesvol geverifieer", "AuthenticationSucceededWithUserName": "{0} suksesvol geverifieer",
"Albums": "Albums", "Albums": "Albums",
@ -117,5 +117,7 @@
"Forced": "Geforseer", "Forced": "Geforseer",
"Default": "Oorspronklik", "Default": "Oorspronklik",
"TaskCleanActivityLogDescription": "Verwyder aktiwiteitsaantekeninge ouer as die opgestelde ouderdom.", "TaskCleanActivityLogDescription": "Verwyder aktiwiteitsaantekeninge ouer as die opgestelde ouderdom.",
"TaskCleanActivityLog": "Maak Aktiwiteitsaantekeninge Skoon" "TaskCleanActivityLog": "Maak Aktiwiteitsaantekeninge Skoon",
"TaskOptimizeDatabaseDescription": "Komprimeer databasis en verkort vrye ruimte. As hierdie taak uitgevoer word nadat die media versameling geskandeer is of ander veranderings aangebring is wat databasisaanpassings impliseer, kan dit die prestasie verbeter.",
"TaskOptimizeDatabase": "Optimaliseer databasis"
} }

View File

@ -5,7 +5,7 @@
"Artists": "Artistes", "Artists": "Artistes",
"AuthenticationSucceededWithUserName": "{0} s'ha autenticat correctament", "AuthenticationSucceededWithUserName": "{0} s'ha autenticat correctament",
"Books": "Llibres", "Books": "Llibres",
"CameraImageUploadedFrom": "Una nova imatge de la càmera ha estat pujada des de {0}", "CameraImageUploadedFrom": "S'ha pujat una nova imatge des de la camera desde {0}",
"Channels": "Canals", "Channels": "Canals",
"ChapterNameValue": "Capítol {0}", "ChapterNameValue": "Capítol {0}",
"Collections": "Col·leccions", "Collections": "Col·leccions",

View File

@ -15,7 +15,7 @@
"Favorites": "Oblíbené", "Favorites": "Oblíbené",
"Folders": "Složky", "Folders": "Složky",
"Genres": "Žánry", "Genres": "Žánry",
"HeaderAlbumArtists": "Umělci alba", "HeaderAlbumArtists": "Album umělce",
"HeaderContinueWatching": "Pokračovat ve sledování", "HeaderContinueWatching": "Pokračovat ve sledování",
"HeaderFavoriteAlbums": "Oblíbená alba", "HeaderFavoriteAlbums": "Oblíbená alba",
"HeaderFavoriteArtists": "Oblíbení interpreti", "HeaderFavoriteArtists": "Oblíbení interpreti",

View File

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

View File

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

View File

@ -15,7 +15,7 @@
"Favorites": "Favoritos", "Favorites": "Favoritos",
"Folders": "Carpetas", "Folders": "Carpetas",
"Genres": "Géneros", "Genres": "Géneros",
"HeaderAlbumArtists": "Artistas del álbum", "HeaderAlbumArtists": "Artistas del Álbum",
"HeaderContinueWatching": "Continuar viendo", "HeaderContinueWatching": "Continuar viendo",
"HeaderFavoriteAlbums": "Álbumes favoritos", "HeaderFavoriteAlbums": "Álbumes favoritos",
"HeaderFavoriteArtists": "Artistas favoritos", "HeaderFavoriteArtists": "Artistas favoritos",
@ -25,7 +25,7 @@
"HeaderLiveTV": "TV en vivo", "HeaderLiveTV": "TV en vivo",
"HeaderNextUp": "A continuación", "HeaderNextUp": "A continuación",
"HeaderRecordingGroups": "Grupos de grabación", "HeaderRecordingGroups": "Grupos de grabación",
"HomeVideos": "Videos caseros", "HomeVideos": "Videos Caseros",
"Inherit": "Heredar", "Inherit": "Heredar",
"ItemAddedWithName": "{0} fue agregado a la biblioteca", "ItemAddedWithName": "{0} fue agregado a la biblioteca",
"ItemRemovedWithName": "{0} fue removido de la biblioteca", "ItemRemovedWithName": "{0} fue removido de la biblioteca",
@ -39,7 +39,7 @@
"MixedContent": "Contenido mezclado", "MixedContent": "Contenido mezclado",
"Movies": "Películas", "Movies": "Películas",
"Music": "Música", "Music": "Música",
"MusicVideos": "Videos musicales", "MusicVideos": "Videos Musicales",
"NameInstallFailed": "Falló la instalación de {0}", "NameInstallFailed": "Falló la instalación de {0}",
"NameSeasonNumber": "Temporada {0}", "NameSeasonNumber": "Temporada {0}",
"NameSeasonUnknown": "Temporada desconocida", "NameSeasonUnknown": "Temporada desconocida",
@ -49,7 +49,7 @@
"NotificationOptionAudioPlayback": "Reproducción de audio iniciada", "NotificationOptionAudioPlayback": "Reproducción de audio iniciada",
"NotificationOptionAudioPlaybackStopped": "Reproducción de audio detenida", "NotificationOptionAudioPlaybackStopped": "Reproducción de audio detenida",
"NotificationOptionCameraImageUploaded": "Imagen de la cámara subida", "NotificationOptionCameraImageUploaded": "Imagen de la cámara subida",
"NotificationOptionInstallationFailed": "Falla de instalación", "NotificationOptionInstallationFailed": "Fallo en la instalación",
"NotificationOptionNewLibraryContent": "Nuevo contenido agregado", "NotificationOptionNewLibraryContent": "Nuevo contenido agregado",
"NotificationOptionPluginError": "Falla de complemento", "NotificationOptionPluginError": "Falla de complemento",
"NotificationOptionPluginInstalled": "Complemento instalado", "NotificationOptionPluginInstalled": "Complemento instalado",
@ -69,7 +69,7 @@
"ProviderValue": "Proveedor: {0}", "ProviderValue": "Proveedor: {0}",
"ScheduledTaskFailedWithName": "{0} falló", "ScheduledTaskFailedWithName": "{0} falló",
"ScheduledTaskStartedWithName": "{0} iniciado", "ScheduledTaskStartedWithName": "{0} iniciado",
"ServerNameNeedsToBeRestarted": "{0} debe ser reiniciado", "ServerNameNeedsToBeRestarted": "{0} necesita ser reiniciado",
"Shows": "Programas", "Shows": "Programas",
"Songs": "Canciones", "Songs": "Canciones",
"StartupEmbyServerIsLoading": "El servidor Jellyfin está cargando. Por favor, intente de nuevo pronto.", "StartupEmbyServerIsLoading": "El servidor Jellyfin está cargando. Por favor, intente de nuevo pronto.",
@ -94,9 +94,9 @@
"VersionNumber": "Versión {0}", "VersionNumber": "Versión {0}",
"TaskDownloadMissingSubtitlesDescription": "Busca subtítulos faltantes en Internet basándose en la configuración de metadatos.", "TaskDownloadMissingSubtitlesDescription": "Busca subtítulos faltantes en Internet basándose en la configuración de metadatos.",
"TaskDownloadMissingSubtitles": "Descargar subtítulos faltantes", "TaskDownloadMissingSubtitles": "Descargar subtítulos faltantes",
"TaskRefreshChannelsDescription": "Actualiza la información de canales de Internet.", "TaskRefreshChannelsDescription": "Actualiza la información de los canales de Internet.",
"TaskRefreshChannels": "Actualizar canales", "TaskRefreshChannels": "Actualizar canales",
"TaskCleanTranscodeDescription": "Elimina archivos transcodificados que tengan más de un día.", "TaskCleanTranscodeDescription": "Elimina archivos transcodificados que tengan más de un día de antigüedad.",
"TaskCleanTranscode": "Limpiar directorio de transcodificado", "TaskCleanTranscode": "Limpiar directorio de transcodificado",
"TaskUpdatePluginsDescription": "Descarga e instala actualizaciones para complementos que están configurados para actualizarse automáticamente.", "TaskUpdatePluginsDescription": "Descarga e instala actualizaciones para complementos que están configurados para actualizarse automáticamente.",
"TaskUpdatePlugins": "Actualizar complementos", "TaskUpdatePlugins": "Actualizar complementos",
@ -118,5 +118,7 @@
"TaskCleanActivityLog": "Limpiar registro de actividades", "TaskCleanActivityLog": "Limpiar registro de actividades",
"Undefined": "Sin definir", "Undefined": "Sin definir",
"Forced": "Forzado", "Forced": "Forzado",
"Default": "Predeterminado" "Default": "Predeterminado",
"TaskOptimizeDatabase": "Optimizar base de datos",
"TaskOptimizeDatabaseDescription": "Compacta la base de datos y trunca el espacio libre. Puede mejorar el rendimiento si se realiza esta tarea después de escanear la biblioteca o después de realizar otros cambios que impliquen modificar la base de datos."
} }

View File

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

View File

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

View File

@ -15,7 +15,7 @@
"Favorites": "Preferiti", "Favorites": "Preferiti",
"Folders": "Cartelle", "Folders": "Cartelle",
"Genres": "Generi", "Genres": "Generi",
"HeaderAlbumArtists": "Artisti degli Album", "HeaderAlbumArtists": "Artisti dell'Album",
"HeaderContinueWatching": "Continua a guardare", "HeaderContinueWatching": "Continua a guardare",
"HeaderFavoriteAlbums": "Album Preferiti", "HeaderFavoriteAlbums": "Album Preferiti",
"HeaderFavoriteArtists": "Artisti Preferiti", "HeaderFavoriteArtists": "Artisti Preferiti",
@ -25,7 +25,7 @@
"HeaderLiveTV": "Diretta TV", "HeaderLiveTV": "Diretta TV",
"HeaderNextUp": "Prossimo", "HeaderNextUp": "Prossimo",
"HeaderRecordingGroups": "Gruppi di Registrazione", "HeaderRecordingGroups": "Gruppi di Registrazione",
"HomeVideos": "Video personali", "HomeVideos": "Video Personali",
"Inherit": "Eredita", "Inherit": "Eredita",
"ItemAddedWithName": "{0} è stato aggiunto alla libreria", "ItemAddedWithName": "{0} è stato aggiunto alla libreria",
"ItemRemovedWithName": "{0} è stato rimosso dalla libreria", "ItemRemovedWithName": "{0} è stato rimosso dalla libreria",
@ -39,7 +39,7 @@
"MixedContent": "Contenuto misto", "MixedContent": "Contenuto misto",
"Movies": "Film", "Movies": "Film",
"Music": "Musica", "Music": "Musica",
"MusicVideos": "Video musicali", "MusicVideos": "Video Musicali",
"NameInstallFailed": "{0} installazione fallita", "NameInstallFailed": "{0} installazione fallita",
"NameSeasonNumber": "Stagione {0}", "NameSeasonNumber": "Stagione {0}",
"NameSeasonUnknown": "Stagione sconosciuta", "NameSeasonUnknown": "Stagione sconosciuta",

View File

@ -15,7 +15,7 @@
"Favorites": "お気に入り", "Favorites": "お気に入り",
"Folders": "フォルダー", "Folders": "フォルダー",
"Genres": "ジャンル", "Genres": "ジャンル",
"HeaderAlbumArtists": "アルバムアーティスト", "HeaderAlbumArtists": "アーティストのアルバム",
"HeaderContinueWatching": "視聴を続ける", "HeaderContinueWatching": "視聴を続ける",
"HeaderFavoriteAlbums": "お気に入りのアルバム", "HeaderFavoriteAlbums": "お気に入りのアルバム",
"HeaderFavoriteArtists": "お気に入りのアーティスト", "HeaderFavoriteArtists": "お気に入りのアーティスト",

View File

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

View File

@ -15,7 +15,7 @@
"Favorites": "즐겨찾기", "Favorites": "즐겨찾기",
"Folders": "폴더", "Folders": "폴더",
"Genres": "장르", "Genres": "장르",
"HeaderAlbumArtists": "앨범 아티스트", "HeaderAlbumArtists": "아티스트의 앨범",
"HeaderContinueWatching": "계속 시청하기", "HeaderContinueWatching": "계속 시청하기",
"HeaderFavoriteAlbums": "즐겨찾는 앨범", "HeaderFavoriteAlbums": "즐겨찾는 앨범",
"HeaderFavoriteArtists": "즐겨찾는 아티스트", "HeaderFavoriteArtists": "즐겨찾는 아티스트",

View File

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

View File

@ -15,7 +15,7 @@
"Favorites": "Ulubione", "Favorites": "Ulubione",
"Folders": "Foldery", "Folders": "Foldery",
"Genres": "Gatunki", "Genres": "Gatunki",
"HeaderAlbumArtists": "Wykonawcy albumów", "HeaderAlbumArtists": "Album artysty",
"HeaderContinueWatching": "Kontynuuj odtwarzanie", "HeaderContinueWatching": "Kontynuuj odtwarzanie",
"HeaderFavoriteAlbums": "Ulubione albumy", "HeaderFavoriteAlbums": "Ulubione albumy",
"HeaderFavoriteArtists": "Ulubieni wykonawcy", "HeaderFavoriteArtists": "Ulubieni wykonawcy",
@ -25,7 +25,7 @@
"HeaderLiveTV": "Telewizja", "HeaderLiveTV": "Telewizja",
"HeaderNextUp": "Do obejrzenia", "HeaderNextUp": "Do obejrzenia",
"HeaderRecordingGroups": "Grupy nagrań", "HeaderRecordingGroups": "Grupy nagrań",
"HomeVideos": "Nagrania prywatne", "HomeVideos": "Nagrania domowe",
"Inherit": "Dziedzicz", "Inherit": "Dziedzicz",
"ItemAddedWithName": "{0} zostało dodane do biblioteki", "ItemAddedWithName": "{0} zostało dodane do biblioteki",
"ItemRemovedWithName": "{0} zostało usunięte z biblioteki", "ItemRemovedWithName": "{0} zostało usunięte z biblioteki",
@ -119,5 +119,6 @@
"Undefined": "Nieustalony", "Undefined": "Nieustalony",
"Forced": "Wymuszony", "Forced": "Wymuszony",
"Default": "Domyślne", "Default": "Domyślne",
"TaskOptimizeDatabase": "Optymalizuj bazę danych" "TaskOptimizeDatabase": "Optymalizuj bazę danych",
"TaskOptimizeDatabaseDescription": "Kompaktuje bazę danych i obcina wolne miejsce. Uruchomienie tego zadania po przeskanowaniu biblioteki lub dokonaniu innych zmian, które pociągają za sobą modyfikacje bazy danych, może poprawić wydajność."
} }

View File

@ -0,0 +1 @@
{}

View File

@ -118,5 +118,7 @@
"TaskCleanActivityLog": "Limpar Registro de Atividades", "TaskCleanActivityLog": "Limpar Registro de Atividades",
"Undefined": "Indefinido", "Undefined": "Indefinido",
"Forced": "Forçado", "Forced": "Forçado",
"Default": "Padrão" "Default": "Padrão",
"TaskOptimizeDatabaseDescription": "Compactar base de dados e liberar espaço livre. Executar esta tarefa após realizar mudanças que impliquem em modificações da base de dados pode trazer melhorias de desempenho.",
"TaskOptimizeDatabase": "Otimizar base de dados"
} }

View File

@ -117,5 +117,6 @@
"Undefined": "Indefinido", "Undefined": "Indefinido",
"Forced": "Forçado", "Forced": "Forçado",
"Default": "Predefinição", "Default": "Predefinição",
"TaskCleanActivityLogDescription": "Apaga itens no registro com idade acima do que é configurado." "TaskCleanActivityLogDescription": "Apaga itens no registro com idade acima do que é configurado.",
"TaskOptimizeDatabase": "Otimizar base de dados"
} }

View File

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

View File

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

View File

@ -15,7 +15,7 @@
"Favorites": "Favoriter", "Favorites": "Favoriter",
"Folders": "Mappar", "Folders": "Mappar",
"Genres": "Genrer", "Genres": "Genrer",
"HeaderAlbumArtists": "Albumartister", "HeaderAlbumArtists": "Artistens album",
"HeaderContinueWatching": "Fortsätt kolla", "HeaderContinueWatching": "Fortsätt kolla",
"HeaderFavoriteAlbums": "Favoritalbum", "HeaderFavoriteAlbums": "Favoritalbum",
"HeaderFavoriteArtists": "Favoritartister", "HeaderFavoriteArtists": "Favoritartister",
@ -25,7 +25,7 @@
"HeaderLiveTV": "Live-TV", "HeaderLiveTV": "Live-TV",
"HeaderNextUp": "Nästa", "HeaderNextUp": "Nästa",
"HeaderRecordingGroups": "Inspelningsgrupper", "HeaderRecordingGroups": "Inspelningsgrupper",
"HomeVideos": "Hemvideor", "HomeVideos": "Hemmavideor",
"Inherit": "Ärv", "Inherit": "Ärv",
"ItemAddedWithName": "{0} lades till i biblioteket", "ItemAddedWithName": "{0} lades till i biblioteket",
"ItemRemovedWithName": "{0} togs bort från biblioteket", "ItemRemovedWithName": "{0} togs bort från biblioteket",
@ -118,5 +118,7 @@
"TaskCleanActivityLog": "Rensa Aktivitets Logg", "TaskCleanActivityLog": "Rensa Aktivitets Logg",
"Undefined": "odefinierad", "Undefined": "odefinierad",
"Forced": "Tvingad", "Forced": "Tvingad",
"Default": "Standard" "Default": "Standard",
"TaskOptimizeDatabase": "Optimera databasen",
"TaskOptimizeDatabaseDescription": "Komprimerar databasen och trunkerar ledigt utrymme. Prestandan kan förbättras genom att köra denna task efter att du har skannat biblioteket eller gjort andra förändringar som indikerar att databasen har modifierats."
} }

View File

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

View File

@ -7,7 +7,7 @@
"Books": "书籍", "Books": "书籍",
"CameraImageUploadedFrom": "新的相机图像已从 {0} 上传", "CameraImageUploadedFrom": "新的相机图像已从 {0} 上传",
"Channels": "频道", "Channels": "频道",
"ChapterNameValue": "第 {0} 集", "ChapterNameValue": "章节 {0}",
"Collections": "合集", "Collections": "合集",
"DeviceOfflineWithName": "{0} 已断开", "DeviceOfflineWithName": "{0} 已断开",
"DeviceOnlineWithName": "{0} 已连接", "DeviceOnlineWithName": "{0} 已连接",
@ -15,8 +15,8 @@
"Favorites": "我的最爱", "Favorites": "我的最爱",
"Folders": "文件夹", "Folders": "文件夹",
"Genres": "风格", "Genres": "风格",
"HeaderAlbumArtists": "专辑家", "HeaderAlbumArtists": "专辑艺术家",
"HeaderContinueWatching": "继续观", "HeaderContinueWatching": "继续观",
"HeaderFavoriteAlbums": "收藏的专辑", "HeaderFavoriteAlbums": "收藏的专辑",
"HeaderFavoriteArtists": "最爱的艺术家", "HeaderFavoriteArtists": "最爱的艺术家",
"HeaderFavoriteEpisodes": "最爱的剧集", "HeaderFavoriteEpisodes": "最爱的剧集",
@ -108,8 +108,8 @@
"TaskCleanLogs": "清理日志目录", "TaskCleanLogs": "清理日志目录",
"TaskRefreshLibraryDescription": "扫描你的媒体库以获取新文件并刷新元数据。", "TaskRefreshLibraryDescription": "扫描你的媒体库以获取新文件并刷新元数据。",
"TaskRefreshLibrary": "扫描媒体库", "TaskRefreshLibrary": "扫描媒体库",
"TaskRefreshChapterImagesDescription": "为包含剧集的视频提取缩略图。", "TaskRefreshChapterImagesDescription": "为包含章节的视频提取缩略图。",
"TaskRefreshChapterImages": "提取剧集图片", "TaskRefreshChapterImages": "提取章节图片",
"TaskCleanCacheDescription": "删除系统不再需要的缓存文件。", "TaskCleanCacheDescription": "删除系统不再需要的缓存文件。",
"TaskCleanCache": "清理缓存目录", "TaskCleanCache": "清理缓存目录",
"TasksApplicationCategory": "应用程序", "TasksApplicationCategory": "应用程序",

View File

@ -13,7 +13,7 @@
"DeviceOnlineWithName": "{0} 已經連接", "DeviceOnlineWithName": "{0} 已經連接",
"FailedLoginAttemptWithUserName": "來自 {0} 的登入失敗", "FailedLoginAttemptWithUserName": "來自 {0} 的登入失敗",
"Favorites": "我的最愛", "Favorites": "我的最愛",
"Folders": "檔案夾", "Folders": "資料夾",
"Genres": "風格", "Genres": "風格",
"HeaderAlbumArtists": "專輯藝人", "HeaderAlbumArtists": "專輯藝人",
"HeaderContinueWatching": "繼續觀看", "HeaderContinueWatching": "繼續觀看",
@ -39,7 +39,7 @@
"MixedContent": "混合內容", "MixedContent": "混合內容",
"Movies": "電影", "Movies": "電影",
"Music": "音樂", "Music": "音樂",
"MusicVideos": "音樂視頻", "MusicVideos": "音樂影片",
"NameInstallFailed": "{0} 安裝失敗", "NameInstallFailed": "{0} 安裝失敗",
"NameSeasonNumber": "第 {0} 季", "NameSeasonNumber": "第 {0} 季",
"NameSeasonUnknown": "未知季數", "NameSeasonUnknown": "未知季數",
@ -117,5 +117,8 @@
"TaskCleanActivityLog": "清理活動記錄", "TaskCleanActivityLog": "清理活動記錄",
"Undefined": "未定義", "Undefined": "未定義",
"Forced": "強制", "Forced": "強制",
"Default": "預設" "Default": "預設",
"TaskOptimizeDatabaseDescription": "壓縮數據庫並截斷可用空間。在掃描媒體庫或執行其他數據庫的修改後運行此任務可能會提高性能。",
"TaskOptimizeDatabase": "最佳化數據庫",
"TaskCleanActivityLogDescription": "刪除早於設定時間的日誌記錄。"
} }

View File

@ -24,7 +24,7 @@
"HeaderFavoriteSongs": "最愛歌曲", "HeaderFavoriteSongs": "最愛歌曲",
"HeaderLiveTV": "電視直播", "HeaderLiveTV": "電視直播",
"HeaderNextUp": "接下來", "HeaderNextUp": "接下來",
"HomeVideos": "自製影片", "HomeVideos": "家庭影片",
"ItemAddedWithName": "{0} 已新增至媒體庫", "ItemAddedWithName": "{0} 已新增至媒體庫",
"ItemRemovedWithName": "{0} 已從媒體庫移除", "ItemRemovedWithName": "{0} 已從媒體庫移除",
"LabelIpAddressValue": "IP 位址:{0}", "LabelIpAddressValue": "IP 位址:{0}",
@ -117,5 +117,7 @@
"TaskCleanActivityLog": "清除活動紀錄", "TaskCleanActivityLog": "清除活動紀錄",
"Undefined": "未定義的", "Undefined": "未定義的",
"Forced": "強制", "Forced": "強制",
"Default": "原本" "Default": "原本",
"TaskOptimizeDatabaseDescription": "縮小資料庫並釋放可用空間。在掃描資料庫或進行資料庫相關的更動後使用此功能會增加效能。",
"TaskOptimizeDatabase": "最佳化資料庫"
} }

View File

@ -1,5 +1,3 @@
#nullable disable
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
@ -38,10 +36,10 @@ namespace Emby.Server.Implementations.Localization
private readonly ConcurrentDictionary<string, Dictionary<string, string>> _dictionaries = private readonly ConcurrentDictionary<string, Dictionary<string, string>> _dictionaries =
new ConcurrentDictionary<string, Dictionary<string, string>>(StringComparer.OrdinalIgnoreCase); new ConcurrentDictionary<string, Dictionary<string, string>>(StringComparer.OrdinalIgnoreCase);
private List<CultureDto> _cultures;
private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options; private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
private List<CultureDto> _cultures = new List<CultureDto>();
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="LocalizationManager" /> class. /// Initializes a new instance of the <see cref="LocalizationManager" /> class.
/// </summary> /// </summary>
@ -72,8 +70,8 @@ namespace Emby.Server.Implementations.Localization
string countryCode = resource.Substring(RatingsPath.Length, 2); string countryCode = resource.Substring(RatingsPath.Length, 2);
var dict = new Dictionary<string, ParentalRating>(StringComparer.OrdinalIgnoreCase); var dict = new Dictionary<string, ParentalRating>(StringComparer.OrdinalIgnoreCase);
await using var str = _assembly.GetManifestResourceStream(resource); await using var stream = _assembly.GetManifestResourceStream(resource);
using var reader = new StreamReader(str); using var reader = new StreamReader(stream!); // shouldn't be null here, we just got the resource path from Assembly.GetManifestResourceNames()
await foreach (var line in reader.ReadAllLinesAsync().ConfigureAwait(false)) await foreach (var line in reader.ReadAllLinesAsync().ConfigureAwait(false))
{ {
if (string.IsNullOrWhiteSpace(line)) if (string.IsNullOrWhiteSpace(line))
@ -113,7 +111,8 @@ namespace Emby.Server.Implementations.Localization
{ {
List<CultureDto> list = new List<CultureDto>(); List<CultureDto> list = new List<CultureDto>();
await using var stream = _assembly.GetManifestResourceStream(CulturesPath); await using var stream = _assembly.GetManifestResourceStream(CulturesPath)
?? throw new InvalidOperationException($"Invalid resource path: '{CulturesPath}'");
using var reader = new StreamReader(stream); using var reader = new StreamReader(stream);
await foreach (var line in reader.ReadAllLinesAsync().ConfigureAwait(false)) await foreach (var line in reader.ReadAllLinesAsync().ConfigureAwait(false))
{ {
@ -162,7 +161,7 @@ namespace Emby.Server.Implementations.Localization
} }
/// <inheritdoc /> /// <inheritdoc />
public CultureDto FindLanguageInfo(string language) public CultureDto? FindLanguageInfo(string language)
{ {
// TODO language should ideally be a ReadOnlySpan but moq cannot mock ref structs // TODO language should ideally be a ReadOnlySpan but moq cannot mock ref structs
for (var i = 0; i < _cultures.Count; i++) for (var i = 0; i < _cultures.Count; i++)
@ -183,9 +182,10 @@ namespace Emby.Server.Implementations.Localization
/// <inheritdoc /> /// <inheritdoc />
public IEnumerable<CountryInfo> GetCountries() public IEnumerable<CountryInfo> GetCountries()
{ {
using StreamReader reader = new StreamReader(_assembly.GetManifestResourceStream(CountriesPath)); using StreamReader reader = new StreamReader(
_assembly.GetManifestResourceStream(CountriesPath) ?? throw new InvalidOperationException($"Invalid resource path: '{CountriesPath}'"));
return JsonSerializer.Deserialize<IEnumerable<CountryInfo>>(reader.ReadToEnd(), _jsonOptions); return JsonSerializer.Deserialize<IEnumerable<CountryInfo>>(reader.ReadToEnd(), _jsonOptions)
?? throw new InvalidOperationException($"Resource contains invalid data: '{CountriesPath}'");
} }
/// <inheritdoc /> /// <inheritdoc />
@ -205,7 +205,9 @@ namespace Emby.Server.Implementations.Localization
countryCode = "us"; countryCode = "us";
} }
return GetRatings(countryCode) ?? GetRatings("us"); return GetRatings(countryCode)
?? GetRatings("us")
?? throw new InvalidOperationException($"Invalid resource path: '{CountriesPath}'");
} }
/// <summary> /// <summary>
@ -213,7 +215,7 @@ namespace Emby.Server.Implementations.Localization
/// </summary> /// </summary>
/// <param name="countryCode">The country code.</param> /// <param name="countryCode">The country code.</param>
/// <returns>The ratings.</returns> /// <returns>The ratings.</returns>
private Dictionary<string, ParentalRating> GetRatings(string countryCode) private Dictionary<string, ParentalRating>? GetRatings(string countryCode)
{ {
_allParentalRatings.TryGetValue(countryCode, out var value); _allParentalRatings.TryGetValue(countryCode, out var value);
@ -238,7 +240,7 @@ namespace Emby.Server.Implementations.Localization
var ratingsDictionary = GetParentalRatingsDictionary(); var ratingsDictionary = GetParentalRatingsDictionary();
if (ratingsDictionary.TryGetValue(rating, out ParentalRating value)) if (ratingsDictionary.TryGetValue(rating, out ParentalRating? value))
{ {
return value.Value; return value.Value;
} }
@ -268,20 +270,6 @@ namespace Emby.Server.Implementations.Localization
return null; return null;
} }
/// <inheritdoc />
public bool HasUnicodeCategory(string value, UnicodeCategory category)
{
foreach (var chr in value)
{
if (char.GetUnicodeCategory(chr) == category)
{
return true;
}
}
return false;
}
/// <inheritdoc /> /// <inheritdoc />
public string GetLocalizedString(string phrase) public string GetLocalizedString(string phrase)
{ {
@ -347,18 +335,21 @@ namespace Emby.Server.Implementations.Localization
{ {
await using var stream = _assembly.GetManifestResourceStream(resourcePath); await using var stream = _assembly.GetManifestResourceStream(resourcePath);
// If a Culture doesn't have a translation the stream will be null and it defaults to en-us further up the chain // If a Culture doesn't have a translation the stream will be null and it defaults to en-us further up the chain
if (stream != null) if (stream == null)
{
var dict = await JsonSerializer.DeserializeAsync<Dictionary<string, string>>(stream, _jsonOptions).ConfigureAwait(false);
foreach (var key in dict.Keys)
{
dictionary[key] = dict[key];
}
}
else
{ {
_logger.LogError("Missing translation/culture resource: {ResourcePath}", resourcePath); _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];
} }
} }

View File

@ -49,5 +49,10 @@ namespace Emby.Server.Implementations.Playlists
query.Parent = null; query.Parent = null;
return LibraryManager.GetItemsResult(query); return LibraryManager.GetItemsResult(query);
} }
public override string GetClientTypeName()
{
return "ManualPlaylistsFolder";
}
} }
} }

View File

@ -1172,7 +1172,7 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesVideoFile] [ProducesVideoFile]
public async Task<ActionResult> GetLiveRecordingFile([FromRoute, Required] string recordingId) public ActionResult GetLiveRecordingFile([FromRoute, Required] string recordingId)
{ {
var path = _liveTvManager.GetEmbyTvActiveRecordingPath(recordingId); var path = _liveTvManager.GetEmbyTvActiveRecordingPath(recordingId);
@ -1181,11 +1181,8 @@ namespace Jellyfin.Api.Controllers
return NotFound(); return NotFound();
} }
await using var memoryStream = new MemoryStream(); var stream = new ProgressiveFileStream(path, null, _transcodingJobHelper);
await new ProgressiveFileCopier(path, null, _transcodingJobHelper, CancellationToken.None) return new FileStreamResult(stream, MimeTypes.GetMimeType(path));
.WriteToAsync(memoryStream, CancellationToken.None)
.ConfigureAwait(false);
return File(memoryStream, MimeTypes.GetMimeType(path));
} }
/// <summary> /// <summary>

View File

@ -1,4 +1,5 @@
using System; using System;
using System.Diagnostics;
using System.IO; using System.IO;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Threading; using System.Threading;
@ -16,6 +17,7 @@ namespace Jellyfin.Api.Helpers
private readonly FileStream _fileStream; private readonly FileStream _fileStream;
private readonly TranscodingJobDto? _job; private readonly TranscodingJobDto? _job;
private readonly TranscodingJobHelper _transcodingJobHelper; private readonly TranscodingJobHelper _transcodingJobHelper;
private readonly int _timeoutMs;
private readonly bool _allowAsyncFileRead; private readonly bool _allowAsyncFileRead;
private int _bytesWritten; private int _bytesWritten;
private bool _disposed; private bool _disposed;
@ -26,10 +28,12 @@ namespace Jellyfin.Api.Helpers
/// <param name="filePath">The path to the transcoded file.</param> /// <param name="filePath">The path to the transcoded file.</param>
/// <param name="job">The transcoding job information.</param> /// <param name="job">The transcoding job information.</param>
/// <param name="transcodingJobHelper">The transcoding job helper.</param> /// <param name="transcodingJobHelper">The transcoding job helper.</param>
public ProgressiveFileStream(string filePath, TranscodingJobDto? job, TranscodingJobHelper transcodingJobHelper) /// <param name="timeoutMs">The timeout duration in milliseconds.</param>
public ProgressiveFileStream(string filePath, TranscodingJobDto? job, TranscodingJobHelper transcodingJobHelper, int timeoutMs = 30000)
{ {
_job = job; _job = job;
_transcodingJobHelper = transcodingJobHelper; _transcodingJobHelper = transcodingJobHelper;
_timeoutMs = timeoutMs;
_bytesWritten = 0; _bytesWritten = 0;
var fileOptions = FileOptions.SequentialScan; var fileOptions = FileOptions.SequentialScan;
@ -81,6 +85,7 @@ namespace Jellyfin.Api.Helpers
{ {
int totalBytesRead = 0; int totalBytesRead = 0;
int remainingBytesToRead = count; int remainingBytesToRead = count;
var stopwatch = Stopwatch.StartNew();
int newOffset = offset; int newOffset = offset;
while (remainingBytesToRead > 0) while (remainingBytesToRead > 0)
@ -111,8 +116,8 @@ namespace Jellyfin.Api.Helpers
} }
else else
{ {
// If the job is null it's a live stream and will require user action to close // If the job is null it's a live stream and will require user action to close, but don't keep it open indefinitely
if (_job?.HasExited ?? false) if (_job?.HasExited ?? stopwatch.ElapsedMilliseconds > _timeoutMs)
{ {
break; break;
} }

View File

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

View File

@ -78,6 +78,16 @@
/// </summary> /// </summary>
Movie, Movie,
/// <summary>
/// Item is a live tv channel.
/// </summary>
LiveTvChannel,
/// <summary>
/// Item is a live tv program.
/// </summary>
LiveTvProgram,
/// <summary> /// <summary>
/// Item is music album. /// Item is music album.
/// </summary> /// </summary>
@ -118,6 +128,11 @@
/// </summary> /// </summary>
Playlist, Playlist,
/// <summary>
/// Item is playlist folder.
/// </summary>
PlaylistsFolder,
/// <summary> /// <summary>
/// Item is program /// Item is program
/// </summary> /// </summary>
@ -187,4 +202,4 @@
/// </summary> /// </summary>
Year Year
} }
} }

View File

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

View File

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

View File

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

View File

@ -47,10 +47,10 @@ namespace MediaBrowser.Common.Plugins
var assemblyFilePath = assembly.Location; var assemblyFilePath = assembly.Location;
var dataFolderPath = Path.Combine(ApplicationPaths.PluginsPath, Path.GetFileNameWithoutExtension(assemblyFilePath)); var dataFolderPath = Path.Combine(ApplicationPaths.PluginsPath, Path.GetFileNameWithoutExtension(assemblyFilePath));
if (!Directory.Exists(dataFolderPath) && Version != null) if (Version != null && !Directory.Exists(dataFolderPath))
{ {
// Try again with the version number appended to the folder name. // Try again with the version number appended to the folder name.
dataFolderPath = dataFolderPath + "_" + Version.ToString(); dataFolderPath += "_" + Version.ToString();
} }
SetAttributes(assemblyFilePath, dataFolderPath, assemblyName.Version); SetAttributes(assemblyFilePath, dataFolderPath, assemblyName.Version);

View File

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

View File

@ -1,5 +1,3 @@
#nullable disable
using System.Threading; using System.Threading;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Configuration;
@ -34,4 +32,4 @@ namespace MediaBrowser.Controller.BaseItemManager
/// <returns><c>true</c> if image fetcher is enabled, else false.</returns> /// <returns><c>true</c> if image fetcher is enabled, else false.</returns>
bool IsImageFetcherEnabled(BaseItem baseItem, LibraryOptions libraryOptions, string name); bool IsImageFetcherEnabled(BaseItem baseItem, LibraryOptions libraryOptions, string name);
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
#nullable disable #nullable disable
#pragma warning disable CS1591 #pragma warning disable CA1721, CA1819, CS1591
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@ -49,6 +49,30 @@ namespace MediaBrowser.Controller.Entities.Movies
/// <value>The display order.</value> /// <value>The display order.</value>
public string DisplayOrder { get; set; } public string DisplayOrder { get; set; }
[JsonIgnore]
private bool IsLegacyBoxSet
{
get
{
if (string.IsNullOrEmpty(Path))
{
return false;
}
if (LinkedChildren.Length > 0)
{
return false;
}
return !FileSystem.ContainsSubPath(ConfigurationManager.ApplicationPaths.DataPath, Path);
}
}
[JsonIgnore]
public override bool IsPreSorted => true;
public Guid[] LibraryFolderIds { get; set; }
protected override bool GetBlockUnratedValue(User user) protected override bool GetBlockUnratedValue(User user)
{ {
return user.GetPreferenceValues<UnratedItem>(PreferenceKind.BlockUnratedItems).Contains(UnratedItem.Movie); return user.GetPreferenceValues<UnratedItem>(PreferenceKind.BlockUnratedItems).Contains(UnratedItem.Movie);
@ -83,28 +107,6 @@ namespace MediaBrowser.Controller.Entities.Movies
return new List<BaseItem>(); return new List<BaseItem>();
} }
[JsonIgnore]
private bool IsLegacyBoxSet
{
get
{
if (string.IsNullOrEmpty(Path))
{
return false;
}
if (LinkedChildren.Length > 0)
{
return false;
}
return !FileSystem.ContainsSubPath(ConfigurationManager.ApplicationPaths.DataPath, Path);
}
}
[JsonIgnore]
public override bool IsPreSorted => true;
public override bool IsAuthorizedToDelete(User user, List<Folder> allCollectionFolders) public override bool IsAuthorizedToDelete(User user, List<Folder> allCollectionFolders)
{ {
return true; return true;
@ -191,8 +193,6 @@ namespace MediaBrowser.Controller.Entities.Movies
return IsVisible(user); return IsVisible(user);
} }
public Guid[] LibraryFolderIds { get; set; }
private Guid[] GetLibraryFolderIds(User user) private Guid[] GetLibraryFolderIds(User user)
{ {
return LibraryManager.GetUserRootFolder().GetChildren(user, true) return LibraryManager.GetUserRootFolder().GetChildren(user, true)

View File

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

View File

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

View File

@ -36,6 +36,30 @@ namespace MediaBrowser.Controller.Entities
} }
} }
public string CameraMake { get; set; }
public string CameraModel { get; set; }
public string Software { get; set; }
public double? ExposureTime { get; set; }
public double? FocalLength { get; set; }
public ImageOrientation? Orientation { get; set; }
public double? Aperture { get; set; }
public double? ShutterSpeed { get; set; }
public double? Latitude { get; set; }
public double? Longitude { get; set; }
public double? Altitude { get; set; }
public int? IsoSpeedRating { get; set; }
public override bool CanDownload() public override bool CanDownload()
{ {
return true; return true;
@ -69,29 +93,5 @@ namespace MediaBrowser.Controller.Entities
return base.GetDefaultPrimaryImageAspectRatio(); return base.GetDefaultPrimaryImageAspectRatio();
} }
public string CameraMake { get; set; }
public string CameraModel { get; set; }
public string Software { get; set; }
public double? ExposureTime { get; set; }
public double? FocalLength { get; set; }
public ImageOrientation? Orientation { get; set; }
public double? Aperture { get; set; }
public double? ShutterSpeed { get; set; }
public double? Latitude { get; set; }
public double? Longitude { get; set; }
public double? Altitude { get; set; }
public int? IsoSpeedRating { get; set; }
} }
} }

View File

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

View File

@ -49,12 +49,6 @@ namespace MediaBrowser.Controller.Entities.TV
/// <value>The index number.</value> /// <value>The index number.</value>
public int? IndexNumberEnd { get; set; } public int? IndexNumberEnd { get; set; }
public string FindSeriesSortName()
{
var series = Series;
return series == null ? SeriesName : series.SortName;
}
[JsonIgnore] [JsonIgnore]
protected override bool SupportsOwnedItems => IsStacked || MediaSourceCount > 1; protected override bool SupportsOwnedItems => IsStacked || MediaSourceCount > 1;
@ -76,45 +70,6 @@ namespace MediaBrowser.Controller.Entities.TV
[JsonIgnore] [JsonIgnore]
protected override bool EnableDefaultVideoUserDataKeys => false; protected override bool EnableDefaultVideoUserDataKeys => false;
public override double GetDefaultPrimaryImageAspectRatio()
{
// hack for tv plugins
if (SourceType == SourceType.Channel)
{
return 0;
}
return 16.0 / 9;
}
public override List<string> GetUserDataKeys()
{
var list = base.GetUserDataKeys();
var series = Series;
if (series != null && ParentIndexNumber.HasValue && IndexNumber.HasValue)
{
var seriesUserDataKeys = series.GetUserDataKeys();
var take = seriesUserDataKeys.Count;
if (seriesUserDataKeys.Count > 1)
{
take--;
}
var newList = seriesUserDataKeys.GetRange(0, take);
var suffix = ParentIndexNumber.Value.ToString("000", CultureInfo.InvariantCulture) + IndexNumber.Value.ToString("000", CultureInfo.InvariantCulture);
for (int i = 0; i < take; i++)
{
newList[i] = newList[i] + suffix;
}
newList.AddRange(list);
list = newList;
}
return list;
}
/// <summary> /// <summary>
/// Gets the Episode's Series Instance. /// Gets the Episode's Series Instance.
/// </summary> /// </summary>
@ -161,6 +116,74 @@ namespace MediaBrowser.Controller.Entities.TV
[JsonIgnore] [JsonIgnore]
public string SeasonName { get; set; } public string SeasonName { get; set; }
[JsonIgnore]
public override bool SupportsRemoteImageDownloading
{
get
{
if (IsMissingEpisode)
{
return false;
}
return true;
}
}
[JsonIgnore]
public bool IsMissingEpisode => LocationType == LocationType.Virtual;
[JsonIgnore]
public Guid SeasonId { get; set; }
[JsonIgnore]
public Guid SeriesId { get; set; }
public string FindSeriesSortName()
{
var series = Series;
return series == null ? SeriesName : series.SortName;
}
public override double GetDefaultPrimaryImageAspectRatio()
{
// hack for tv plugins
if (SourceType == SourceType.Channel)
{
return 0;
}
return 16.0 / 9;
}
public override List<string> GetUserDataKeys()
{
var list = base.GetUserDataKeys();
var series = Series;
if (series != null && ParentIndexNumber.HasValue && IndexNumber.HasValue)
{
var seriesUserDataKeys = series.GetUserDataKeys();
var take = seriesUserDataKeys.Count;
if (seriesUserDataKeys.Count > 1)
{
take--;
}
var newList = seriesUserDataKeys.GetRange(0, take);
var suffix = ParentIndexNumber.Value.ToString("000", CultureInfo.InvariantCulture) + IndexNumber.Value.ToString("000", CultureInfo.InvariantCulture);
for (int i = 0; i < take; i++)
{
newList[i] = newList[i] + suffix;
}
newList.AddRange(list);
list = newList;
}
return list;
}
public string FindSeriesPresentationUniqueKey() public string FindSeriesPresentationUniqueKey()
{ {
var series = Series; var series = Series;
@ -242,29 +265,6 @@ namespace MediaBrowser.Controller.Entities.TV
return false; return false;
} }
[JsonIgnore]
public override bool SupportsRemoteImageDownloading
{
get
{
if (IsMissingEpisode)
{
return false;
}
return true;
}
}
[JsonIgnore]
public bool IsMissingEpisode => LocationType == LocationType.Virtual;
[JsonIgnore]
public Guid SeasonId { get; set; }
[JsonIgnore]
public Guid SeriesId { get; set; }
public Guid FindSeriesId() public Guid FindSeriesId()
{ {
var series = FindParent<Series>(); var series = FindParent<Series>();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -28,6 +28,14 @@ namespace MediaBrowser.Controller.Entities
ISupportsPlaceHolders, ISupportsPlaceHolders,
IHasMediaSources IHasMediaSources
{ {
public Video()
{
AdditionalParts = Array.Empty<string>();
LocalAlternateVersions = Array.Empty<string>();
SubtitleFiles = Array.Empty<string>();
LinkedAlternateVersions = Array.Empty<LinkedChild>();
}
[JsonIgnore] [JsonIgnore]
public string PrimaryVersionId { get; set; } public string PrimaryVersionId { get; set; }
@ -74,30 +82,6 @@ namespace MediaBrowser.Controller.Entities
} }
} }
public void SetPrimaryVersionId(string id)
{
if (string.IsNullOrEmpty(id))
{
PrimaryVersionId = null;
}
else
{
PrimaryVersionId = id;
}
PresentationUniqueKey = CreatePresentationUniqueKey();
}
public override string CreatePresentationUniqueKey()
{
if (!string.IsNullOrEmpty(PrimaryVersionId))
{
return PrimaryVersionId;
}
return base.CreatePresentationUniqueKey();
}
[JsonIgnore] [JsonIgnore]
public override bool SupportsThemeMedia => true; public override bool SupportsThemeMedia => true;
@ -151,24 +135,6 @@ namespace MediaBrowser.Controller.Entities
/// <value>The aspect ratio.</value> /// <value>The aspect ratio.</value>
public string AspectRatio { get; set; } public string AspectRatio { get; set; }
public Video()
{
AdditionalParts = Array.Empty<string>();
LocalAlternateVersions = Array.Empty<string>();
SubtitleFiles = Array.Empty<string>();
LinkedAlternateVersions = Array.Empty<LinkedChild>();
}
public override bool CanDownload()
{
if (VideoType == VideoType.Dvd || VideoType == VideoType.BluRay)
{
return false;
}
return IsFileProtocol;
}
[JsonIgnore] [JsonIgnore]
public override bool SupportsAddingToPlaylist => true; public override bool SupportsAddingToPlaylist => true;
@ -196,16 +162,6 @@ namespace MediaBrowser.Controller.Entities
[JsonIgnore] [JsonIgnore]
public override bool HasLocalAlternateVersions => LocalAlternateVersions.Length > 0; public override bool HasLocalAlternateVersions => LocalAlternateVersions.Length > 0;
public IEnumerable<Guid> GetAdditionalPartIds()
{
return AdditionalParts.Select(i => LibraryManager.GetNewItemId(i, typeof(Video)));
}
public IEnumerable<Guid> GetLocalAlternateVersionIds()
{
return LocalAlternateVersions.Select(i => LibraryManager.GetNewItemId(i, typeof(Video)));
}
public static ILiveTvManager LiveTvManager { get; set; } public static ILiveTvManager LiveTvManager { get; set; }
[JsonIgnore] [JsonIgnore]
@ -222,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] [JsonIgnore]
public bool IsCompleteMedia public bool IsCompleteMedia
{ {
@ -254,80 +195,6 @@ namespace MediaBrowser.Controller.Entities
[JsonIgnore] [JsonIgnore]
protected virtual bool EnableDefaultVideoUserDataKeys => true; 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] [JsonIgnore]
public override string ContainingFolderPath 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) internal override ItemUpdateType UpdateFromResolvedItem(BaseItem newItem)
{ {
var updateType = base.UpdateFromResolvedItem(newItem); var updateType = base.UpdateFromResolvedItem(newItem);
@ -397,20 +411,6 @@ namespace MediaBrowser.Controller.Entities
return updateType; return updateType;
} }
/// <summary>
/// Gets a value indicating whether [is3 D].
/// </summary>
/// <value><c>true</c> if [is3 D]; otherwise, <c>false</c>.</value>
[JsonIgnore]
public bool Is3D => Video3DFormat.HasValue;
/// <summary>
/// Gets the type of the media.
/// </summary>
/// <value>The type of the media.</value>
[JsonIgnore]
public override string MediaType => Model.Entities.MediaType.Video;
protected override async Task<bool> RefreshedOwnedItems(MetadataRefreshOptions options, List<FileSystemMetadata> fileSystemChildren, CancellationToken cancellationToken) protected override async Task<bool> RefreshedOwnedItems(MetadataRefreshOptions options, List<FileSystemMetadata> fileSystemChildren, CancellationToken cancellationToken)
{ {
var hasChanges = await base.RefreshedOwnedItems(options, fileSystemChildren, cancellationToken).ConfigureAwait(false); var hasChanges = await base.RefreshedOwnedItems(options, fileSystemChildren, cancellationToken).ConfigureAwait(false);

View File

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

View File

@ -7,7 +7,6 @@ using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Runtime.InteropServices;
using System.Text; using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading; using System.Threading;
@ -16,9 +15,7 @@ using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Dto; using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.MediaInfo;
using Microsoft.Extensions.Configuration;
namespace MediaBrowser.Controller.MediaEncoding namespace MediaBrowser.Controller.MediaEncoding
{ {

View File

@ -10,7 +10,6 @@ using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Dto; using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.MediaInfo;
using MediaBrowser.Model.System;
namespace MediaBrowser.Controller.MediaEncoding namespace MediaBrowser.Controller.MediaEncoding
{ {
@ -19,11 +18,6 @@ namespace MediaBrowser.Controller.MediaEncoding
/// </summary> /// </summary>
public interface IMediaEncoder : ITranscoderSupport public interface IMediaEncoder : ITranscoderSupport
{ {
/// <summary>
/// Gets location of the discovered FFmpeg tool.
/// </summary>
FFmpegLocation EncoderLocation { get; }
/// <summary> /// <summary>
/// Gets the encoder path. /// Gets the encoder path.
/// </summary> /// </summary>

View File

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

View File

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

View File

@ -31,24 +31,18 @@ namespace MediaBrowser.Controller.Playlists
".zpl" ".zpl"
}; };
public Guid OwnerUserId { get; set; }
public Share[] Shares { get; set; }
public Playlist() public Playlist()
{ {
Shares = Array.Empty<Share>(); Shares = Array.Empty<Share>();
} }
public Guid OwnerUserId { get; set; }
public Share[] Shares { get; set; }
[JsonIgnore] [JsonIgnore]
public bool IsFile => IsPlaylistFile(Path); public bool IsFile => IsPlaylistFile(Path);
public static bool IsPlaylistFile(string path)
{
// The path will sometimes be a directory and "Path.HasExtension" returns true if the name contains a '.' (dot).
return System.IO.Path.HasExtension(path) && !Directory.Exists(path);
}
[JsonIgnore] [JsonIgnore]
public override string ContainingFolderPath public override string ContainingFolderPath
{ {
@ -80,6 +74,41 @@ namespace MediaBrowser.Controller.Playlists
[JsonIgnore] [JsonIgnore]
public override bool SupportsCumulativeRunTimeTicks => true; public override bool SupportsCumulativeRunTimeTicks => true;
[JsonIgnore]
public override bool IsPreSorted => true;
public string PlaylistMediaType { get; set; }
[JsonIgnore]
public override string MediaType => PlaylistMediaType;
[JsonIgnore]
private bool IsSharedItem
{
get
{
var path = Path;
if (string.IsNullOrEmpty(path))
{
return false;
}
return FileSystem.ContainsSubPath(ConfigurationManager.ApplicationPaths.DataPath, path);
}
}
public static bool IsPlaylistFile(string path)
{
// The path will sometimes be a directory and "Path.HasExtension" returns true if the name contains a '.' (dot).
return System.IO.Path.HasExtension(path) && !Directory.Exists(path);
}
public void SetMediaType(string value)
{
PlaylistMediaType = value;
}
public override double GetDefaultPrimaryImageAspectRatio() public override double GetDefaultPrimaryImageAspectRatio()
{ {
return 1; return 1;
@ -197,35 +226,6 @@ namespace MediaBrowser.Controller.Playlists
return new[] { item }; return new[] { item };
} }
[JsonIgnore]
public override bool IsPreSorted => true;
public string PlaylistMediaType { get; set; }
[JsonIgnore]
public override string MediaType => PlaylistMediaType;
public void SetMediaType(string value)
{
PlaylistMediaType = value;
}
[JsonIgnore]
private bool IsSharedItem
{
get
{
var path = Path;
if (string.IsNullOrEmpty(path))
{
return false;
}
return FileSystem.ContainsSubPath(ConfigurationManager.ApplicationPaths.DataPath, path);
}
}
public override bool IsVisible(User user) public override bool IsVisible(User user)
{ {
if (!IsSharedItem) if (!IsSharedItem)

View File

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

View File

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

View File

@ -28,6 +28,11 @@ namespace MediaBrowser.Controller.Subtitles
/// <summary> /// <summary>
/// Searches the subtitles. /// Searches the subtitles.
/// </summary> /// </summary>
/// <param name="video">The video.</param>
/// <param name="language">Subtitle language.</param>
/// <param name="isPerfectMatch">Require perfect match.</param>
/// <param name="cancellationToken">CancellationToken to use for the operation.</param>
/// <returns>Subtitles, wrapped in task.</returns>
Task<RemoteSubtitleInfo[]> SearchSubtitles( Task<RemoteSubtitleInfo[]> SearchSubtitles(
Video video, Video video,
string language, string language,
@ -47,11 +52,20 @@ namespace MediaBrowser.Controller.Subtitles
/// <summary> /// <summary>
/// Downloads the subtitles. /// Downloads the subtitles.
/// </summary> /// </summary>
/// <param name="video">The video.</param>
/// <param name="subtitleId">Subtitle ID.</param>
/// <param name="cancellationToken">CancellationToken to use for the operation.</param>
/// <returns>A task.</returns>
Task DownloadSubtitles(Video video, string subtitleId, CancellationToken cancellationToken); Task DownloadSubtitles(Video video, string subtitleId, CancellationToken cancellationToken);
/// <summary> /// <summary>
/// Downloads the subtitles. /// Downloads the subtitles.
/// </summary> /// </summary>
/// <param name="video">The video.</param>
/// <param name="libraryOptions">Library options to use.</param>
/// <param name="subtitleId">Subtitle ID.</param>
/// <param name="cancellationToken">CancellationToken to use for the operation.</param>
/// <returns>A task.</returns>
Task DownloadSubtitles(Video video, LibraryOptions libraryOptions, string subtitleId, CancellationToken cancellationToken); Task DownloadSubtitles(Video video, LibraryOptions libraryOptions, string subtitleId, CancellationToken cancellationToken);
/// <summary> /// <summary>
@ -73,11 +87,16 @@ namespace MediaBrowser.Controller.Subtitles
/// <summary> /// <summary>
/// Deletes the subtitles. /// Deletes the subtitles.
/// </summary> /// </summary>
/// <param name="item">Media item.</param>
/// <param name="index">Subtitle index.</param>
/// <returns>A task.</returns>
Task DeleteSubtitles(BaseItem item, int index); Task DeleteSubtitles(BaseItem item, int index);
/// <summary> /// <summary>
/// Gets the providers. /// Gets the providers.
/// </summary> /// </summary>
/// <param name="item">The media item.</param>
/// <returns>Subtitles providers.</returns>
SubtitleProviderInfo[] GetSupportedProviders(BaseItem item); SubtitleProviderInfo[] GetSupportedProviders(BaseItem item);
} }
} }

View File

@ -11,6 +11,15 @@ namespace MediaBrowser.Controller.Subtitles
{ {
public class SubtitleSearchRequest : IHasProviderIds public class SubtitleSearchRequest : IHasProviderIds
{ {
public SubtitleSearchRequest()
{
SearchAllProviders = true;
ProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
DisabledSubtitleFetchers = Array.Empty<string>();
SubtitleFetcherOrder = Array.Empty<string>();
}
public string Language { get; set; } public string Language { get; set; }
public string TwoLetterISOLanguageName { get; set; } public string TwoLetterISOLanguageName { get; set; }
@ -42,14 +51,5 @@ namespace MediaBrowser.Controller.Subtitles
public string[] DisabledSubtitleFetchers { get; set; } public string[] DisabledSubtitleFetchers { get; set; }
public string[] SubtitleFetcherOrder { get; set; } public string[] SubtitleFetcherOrder { get; set; }
public SubtitleSearchRequest()
{
SearchAllProviders = true;
ProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
DisabledSubtitleFetchers = Array.Empty<string>();
SubtitleFetcherOrder = Array.Empty<string>();
}
} }
} }

View File

@ -12,8 +12,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
{ {
public class EncoderValidator public class EncoderValidator
{ {
private const string DefaultEncoderPath = "ffmpeg";
private static readonly string[] _requiredDecoders = new[] private static readonly string[] _requiredDecoders = new[]
{ {
"h264", "h264",
@ -124,7 +122,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
private readonly string _encoderPath; private readonly string _encoderPath;
public EncoderValidator(ILogger logger, string encoderPath = DefaultEncoderPath) public EncoderValidator(ILogger logger, string encoderPath)
{ {
_logger = logger; _logger = logger;
_encoderPath = encoderPath; _encoderPath = encoderPath;

View File

@ -23,7 +23,6 @@ using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.MediaInfo;
using MediaBrowser.Model.System;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -72,7 +71,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
private Version _ffmpegVersion = null; private Version _ffmpegVersion = null;
private string _ffmpegPath = string.Empty; private string _ffmpegPath = string.Empty;
private string _ffprobePath; private string _ffprobePath;
private int threads; private int _threads;
public MediaEncoder( public MediaEncoder(
ILogger<MediaEncoder> logger, ILogger<MediaEncoder> logger,
@ -92,9 +91,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
/// <inheritdoc /> /// <inheritdoc />
public string EncoderPath => _ffmpegPath; public string EncoderPath => _ffmpegPath;
/// <inheritdoc />
public FFmpegLocation EncoderLocation { get; private set; }
/// <summary> /// <summary>
/// Run at startup or if the user removes a Custom path from transcode page. /// Run at startup or if the user removes a Custom path from transcode page.
/// Sets global variables FFmpegPath. /// Sets global variables FFmpegPath.
@ -103,20 +99,23 @@ namespace MediaBrowser.MediaEncoding.Encoder
public void SetFFmpegPath() public void SetFFmpegPath()
{ {
// 1) Custom path stored in config/encoding xml file under tag <EncoderAppPath> takes precedence // 1) Custom path stored in config/encoding xml file under tag <EncoderAppPath> takes precedence
if (!ValidatePath(_configurationManager.GetEncodingOptions().EncoderAppPath, FFmpegLocation.Custom)) var ffmpegPath = _configurationManager.GetEncodingOptions().EncoderAppPath;
if (string.IsNullOrEmpty(ffmpegPath))
{ {
// 2) Check if the --ffmpeg CLI switch has been given // 2) Check if the --ffmpeg CLI switch has been given
if (!ValidatePath(_startupOptionFFmpegPath, FFmpegLocation.SetByArgument)) ffmpegPath = _startupOptionFFmpegPath;
if (string.IsNullOrEmpty(ffmpegPath))
{ {
// 3) Search system $PATH environment variable for valid FFmpeg // 3) Check "ffmpeg"
if (!ValidatePath(ExistsOnSystemPath("ffmpeg"), FFmpegLocation.System)) ffmpegPath = "ffmpeg";
{
EncoderLocation = FFmpegLocation.NotFound;
_ffmpegPath = null;
}
} }
} }
if (!ValidatePath(ffmpegPath))
{
_ffmpegPath = null;
}
// Write the FFmpeg path to the config/encoding.xml file as <EncoderAppPathDisplay> so it appears in UI // Write the FFmpeg path to the config/encoding.xml file as <EncoderAppPathDisplay> so it appears in UI
var config = _configurationManager.GetEncodingOptions(); var config = _configurationManager.GetEncodingOptions();
config.EncoderAppPathDisplay = _ffmpegPath ?? string.Empty; config.EncoderAppPathDisplay = _ffmpegPath ?? string.Empty;
@ -138,10 +137,10 @@ namespace MediaBrowser.MediaEncoding.Encoder
SetAvailableHwaccels(validator.GetHwaccels()); SetAvailableHwaccels(validator.GetHwaccels());
SetMediaEncoderVersion(validator); SetMediaEncoderVersion(validator);
threads = EncodingHelper.GetNumberOfThreads(null, _configurationManager.GetEncodingOptions(), null); _threads = EncodingHelper.GetNumberOfThreads(null, _configurationManager.GetEncodingOptions(), null);
} }
_logger.LogInformation("FFmpeg: {EncoderLocation}: {FfmpegPath}", EncoderLocation, _ffmpegPath ?? string.Empty); _logger.LogInformation("FFmpeg: {FfmpegPath}", _ffmpegPath ?? string.Empty);
} }
/// <summary> /// <summary>
@ -160,15 +159,12 @@ namespace MediaBrowser.MediaEncoding.Encoder
{ {
throw new ArgumentException("Unexpected pathType value"); throw new ArgumentException("Unexpected pathType value");
} }
else if (string.IsNullOrWhiteSpace(path))
if (string.IsNullOrWhiteSpace(path))
{ {
// User had cleared the custom path in UI // User had cleared the custom path in UI
newPath = string.Empty; newPath = string.Empty;
} }
else if (File.Exists(path))
{
newPath = path;
}
else if (Directory.Exists(path)) else if (Directory.Exists(path))
{ {
// Given path is directory, so resolve down to filename // Given path is directory, so resolve down to filename
@ -176,7 +172,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
} }
else else
{ {
throw new ResourceNotFoundException(); newPath = path;
} }
// Write the new ffmpeg path to the xml as <EncoderAppPath> // Write the new ffmpeg path to the xml as <EncoderAppPath>
@ -191,37 +187,26 @@ namespace MediaBrowser.MediaEncoding.Encoder
/// <summary> /// <summary>
/// Validates the supplied FQPN to ensure it is a ffmpeg utility. /// Validates the supplied FQPN to ensure it is a ffmpeg utility.
/// If checks pass, global variable FFmpegPath and EncoderLocation are updated. /// If checks pass, global variable FFmpegPath is updated.
/// </summary> /// </summary>
/// <param name="path">FQPN to test.</param> /// <param name="path">FQPN to test.</param>
/// <param name="location">Location (External, Custom, System) of tool.</param>
/// <returns><c>true</c> if the version validation succeeded; otherwise, <c>false</c>.</returns> /// <returns><c>true</c> if the version validation succeeded; otherwise, <c>false</c>.</returns>
private bool ValidatePath(string path, FFmpegLocation location) private bool ValidatePath(string path)
{ {
bool rc = false; if (string.IsNullOrEmpty(path))
if (!string.IsNullOrEmpty(path))
{ {
if (File.Exists(path)) return false;
{
rc = new EncoderValidator(_logger, path).ValidateVersion();
if (!rc)
{
_logger.LogWarning("FFmpeg: {Location}: Failed version check: {Path}", location, path);
}
_ffmpegPath = path;
EncoderLocation = location;
return true;
}
else
{
_logger.LogWarning("FFmpeg: {Location}: File not found: {Path}", location, path);
}
} }
return rc; bool rc = new EncoderValidator(_logger, path).ValidateVersion();
if (!rc)
{
_logger.LogWarning("FFmpeg: Failed version check: {Path}", path);
return false;
}
_ffmpegPath = path;
return true;
} }
private string GetEncoderPathFromDirectory(string path, string filename, bool recursive = false) private string GetEncoderPathFromDirectory(string path, string filename, bool recursive = false)
@ -242,34 +227,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
} }
} }
/// <summary>
/// Search the system $PATH environment variable looking for given filename.
/// </summary>
/// <param name="fileName">The filename.</param>
/// <returns>The full path to the file.</returns>
private string ExistsOnSystemPath(string fileName)
{
var inJellyfinPath = GetEncoderPathFromDirectory(AppContext.BaseDirectory, fileName, recursive: true);
if (!string.IsNullOrEmpty(inJellyfinPath))
{
return inJellyfinPath;
}
var values = Environment.GetEnvironmentVariable("PATH");
foreach (var path in values.Split(Path.PathSeparator))
{
var candidatePath = GetEncoderPathFromDirectory(path, fileName);
if (!string.IsNullOrEmpty(candidatePath))
{
return candidatePath;
}
}
return null;
}
public void SetAvailableEncoders(IEnumerable<string> list) public void SetAvailableEncoders(IEnumerable<string> list)
{ {
_encoders = list.ToList(); _encoders = list.ToList();
@ -425,7 +382,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
var args = extractChapters var args = extractChapters
? "{0} -i {1} -threads {2} -v warning -print_format json -show_streams -show_chapters -show_format" ? "{0} -i {1} -threads {2} -v warning -print_format json -show_streams -show_chapters -show_format"
: "{0} -i {1} -threads {2} -v warning -print_format json -show_streams -show_format"; : "{0} -i {1} -threads {2} -v warning -print_format json -show_streams -show_format";
args = string.Format(CultureInfo.InvariantCulture, args, probeSizeArgument, inputPath, threads).Trim(); args = string.Format(CultureInfo.InvariantCulture, args, probeSizeArgument, inputPath, _threads).Trim();
var process = new Process var process = new Process
{ {
@ -646,7 +603,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
} }
} }
var args = string.Format(CultureInfo.InvariantCulture, "-i {0}{3} -threads {4} -v quiet -vframes 1 {2} -f image2 \"{1}\"", inputPath, tempExtractPath, vf, mapArg, threads); var args = string.Format(CultureInfo.InvariantCulture, "-i {0}{3} -threads {4} -v quiet -vframes 1 {2} -f image2 \"{1}\"", inputPath, tempExtractPath, vf, mapArg, _threads);
if (offset.HasValue) if (offset.HasValue)
{ {
@ -759,7 +716,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
Directory.CreateDirectory(targetDirectory); Directory.CreateDirectory(targetDirectory);
var outputPath = Path.Combine(targetDirectory, filenamePrefix + "%05d.jpg"); var outputPath = Path.Combine(targetDirectory, filenamePrefix + "%05d.jpg");
var args = string.Format(CultureInfo.InvariantCulture, "-i {0} -threads {3} -v quiet {2} -f image2 \"{1}\"", inputArgument, outputPath, vf, threads); var args = string.Format(CultureInfo.InvariantCulture, "-i {0} -threads {3} -v quiet {2} -f image2 \"{1}\"", inputArgument, outputPath, vf, _threads);
if (!string.IsNullOrWhiteSpace(container)) if (!string.IsNullOrWhiteSpace(container))
{ {

View File

@ -1,4 +1,3 @@
#nullable disable
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
@ -56,19 +55,11 @@ namespace MediaBrowser.Model.Globalization
/// <returns><see cref="IEnumerable{LocalizatonOption}" />.</returns> /// <returns><see cref="IEnumerable{LocalizatonOption}" />.</returns>
IEnumerable<LocalizationOption> GetLocalizationOptions(); IEnumerable<LocalizationOption> GetLocalizationOptions();
/// <summary>
/// Checks if the string contains a character with the specified unicode category.
/// </summary>
/// <param name="value">The string.</param>
/// <param name="category">The unicode category.</param>
/// <returns>Wether or not the string contains a character with the specified unicode category.</returns>
bool HasUnicodeCategory(string value, UnicodeCategory category);
/// <summary> /// <summary>
/// Returns the correct <see cref="CultureInfo" /> for the given language. /// Returns the correct <see cref="CultureInfo" /> for the given language.
/// </summary> /// </summary>
/// <param name="language">The language.</param> /// <param name="language">The language.</param>
/// <returns>The correct <see cref="CultureInfo" /> for the given language.</returns> /// <returns>The correct <see cref="CultureInfo" /> for the given language.</returns>
CultureDto FindLanguageInfo(string language); CultureDto? FindLanguageInfo(string language);
} }
} }

View File

@ -133,6 +133,7 @@ namespace MediaBrowser.Model.System
[Obsolete("This should be handled by the package manager")] [Obsolete("This should be handled by the package manager")]
public bool HasUpdateAvailable { get; set; } public bool HasUpdateAvailable { get; set; }
[Obsolete("This isn't set correctly anymore")]
public FFmpegLocation EncoderLocation { get; set; } public FFmpegLocation EncoderLocation { get; set; }
public Architecture SystemArchitecture { get; set; } public Architecture SystemArchitecture { get; set; }

View File

@ -59,9 +59,9 @@ namespace MediaBrowser.Providers.BoxSets
} }
/// <inheritdoc /> /// <inheritdoc />
protected override ItemUpdateType BeforeSaveInternal(BoxSet item, bool isFullRefresh, ItemUpdateType currentUpdateType) protected override ItemUpdateType BeforeSaveInternal(BoxSet item, bool isFullRefresh, ItemUpdateType updateType)
{ {
var updateType = base.BeforeSaveInternal(item, isFullRefresh, currentUpdateType); var updatedType = base.BeforeSaveInternal(item, isFullRefresh, updateType);
var libraryFolderIds = item.GetLibraryFolderIds(); var libraryFolderIds = item.GetLibraryFolderIds();
@ -69,10 +69,10 @@ namespace MediaBrowser.Providers.BoxSets
if (itemLibraryFolderIds == null || !libraryFolderIds.SequenceEqual(itemLibraryFolderIds)) if (itemLibraryFolderIds == null || !libraryFolderIds.SequenceEqual(itemLibraryFolderIds))
{ {
item.LibraryFolderIds = libraryFolderIds; item.LibraryFolderIds = libraryFolderIds;
updateType |= ItemUpdateType.MetadataImport; updatedType |= ItemUpdateType.MetadataImport;
} }
return updateType; return updatedType;
} }
} }
} }

View File

@ -1,7 +1,8 @@
#pragma warning disable CS1591 #pragma warning disable CA1002, CS1591
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
@ -536,6 +537,7 @@ namespace MediaBrowser.Providers.Manager
return true; return true;
} }
} }
// We always want to use prefetched images // We always want to use prefetched images
return false; return false;
} }

View File

@ -505,6 +505,11 @@ namespace MediaBrowser.Providers.Manager
/// <summary> /// <summary>
/// Gets the providers. /// Gets the providers.
/// </summary> /// </summary>
/// <param name="item">A media item.</param>
/// <param name="libraryOptions">The LibraryOptions to use.</param>
/// <param name="options">The MetadataRefreshOptions to use.</param>
/// <param name="isFirstRefresh">Specifies first refresh mode.</param>
/// <param name="requiresRefresh">Specifies refresh mode.</param>
/// <returns>IEnumerable{`0}.</returns> /// <returns>IEnumerable{`0}.</returns>
protected IEnumerable<IMetadataProvider> GetProviders(BaseItem item, LibraryOptions libraryOptions, MetadataRefreshOptions options, bool isFirstRefresh, bool requiresRefresh) protected IEnumerable<IMetadataProvider> GetProviders(BaseItem item, LibraryOptions libraryOptions, MetadataRefreshOptions options, bool isFirstRefresh, bool requiresRefresh)
{ {

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