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)
- [SvenVandenbrande](https://github.com/SvenVandenbrande)
- [olsh](https://github.com/olsh)
- [lbenini](https://github.com/lbenini)
- [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 \
&& mv dist /dist
FROM debian:buster-slim as app
FROM debian:bullseye-slim as app
# https://askubuntu.com/questions/972516/debian-frontend-environment-variable
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
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
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
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
ARG DEBIAN_FRONTEND="noninteractive"

View File

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

View File

@ -1099,7 +1099,6 @@ namespace Emby.Server.Implementations
ServerName = FriendlyName,
LocalAddress = GetSmartApiUrl(source),
SupportsLibraryMonitor = true,
EncoderLocation = _mediaEncoder.EncoderLocation,
SystemArchitecture = RuntimeInformation.OSArchitecture,
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
{

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -15,7 +15,7 @@
"Favorites": "Ulubione",
"Folders": "Foldery",
"Genres": "Gatunki",
"HeaderAlbumArtists": "Wykonawcy albumów",
"HeaderAlbumArtists": "Album artysty",
"HeaderContinueWatching": "Kontynuuj odtwarzanie",
"HeaderFavoriteAlbums": "Ulubione albumy",
"HeaderFavoriteArtists": "Ulubieni wykonawcy",
@ -25,7 +25,7 @@
"HeaderLiveTV": "Telewizja",
"HeaderNextUp": "Do obejrzenia",
"HeaderRecordingGroups": "Grupy nagrań",
"HomeVideos": "Nagrania prywatne",
"HomeVideos": "Nagrania domowe",
"Inherit": "Dziedzicz",
"ItemAddedWithName": "{0} zostało dodane do biblioteki",
"ItemRemovedWithName": "{0} zostało usunięte z biblioteki",
@ -119,5 +119,6 @@
"Undefined": "Nieustalony",
"Forced": "Wymuszony",
"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",
"Undefined": "Indefinido",
"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",
"Forced": "Forçado",
"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": "Эфир",
"HeaderNextUp": "Очередное",
"HeaderRecordingGroups": "Группы записей",
"HomeVideos": "Домашнее видео",
"HomeVideos": "Домашние видео",
"Inherit": "Наследуемое",
"ItemAddedWithName": "{0} - добавлено в медиатеку",
"ItemRemovedWithName": "{0} - изъято из медиатеки",

View File

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

View File

@ -15,7 +15,7 @@
"Favorites": "Favoriter",
"Folders": "Mappar",
"Genres": "Genrer",
"HeaderAlbumArtists": "Albumartister",
"HeaderAlbumArtists": "Artistens album",
"HeaderContinueWatching": "Fortsätt kolla",
"HeaderFavoriteAlbums": "Favoritalbum",
"HeaderFavoriteArtists": "Favoritartister",
@ -25,7 +25,7 @@
"HeaderLiveTV": "Live-TV",
"HeaderNextUp": "Nästa",
"HeaderRecordingGroups": "Inspelningsgrupper",
"HomeVideos": "Hemvideor",
"HomeVideos": "Hemmavideor",
"Inherit": "Ärv",
"ItemAddedWithName": "{0} lades till i biblioteket",
"ItemRemovedWithName": "{0} togs bort från biblioteket",
@ -118,5 +118,7 @@
"TaskCleanActivityLog": "Rensa Aktivitets Logg",
"Undefined": "odefinierad",
"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",
"Folders": "Thư Mục",
"Genres": "Thể Loại",
"HeaderAlbumArtists": "Tuyển Tập Nghệ sĩ",
"HeaderAlbumArtists": "Album Nghệ sĩ",
"HeaderContinueWatching": "Xem Tiếp",
"HeaderLiveTV": "TV Trực Tiếp",
"Movies": "Phim",
@ -82,7 +82,7 @@
"NameSeasonUnknown": "Không Rõ Mùa",
"NameSeasonNumber": "Phần {0}",
"NameInstallFailed": "{0} cài đặt thất bại",
"MusicVideos": "Video Nhạc",
"MusicVideos": "Videos Nhạc",
"Music": "Nhạc",
"MixedContent": "Nội dung hỗn hợp",
"MessageServerConfigurationUpdated": "Cấu hình máy chủ đã được cập nhật",

View File

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

View File

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

View File

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

View File

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

View File

@ -49,5 +49,10 @@ namespace Emby.Server.Implementations.Playlists
query.Parent = null;
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.Status404NotFound)]
[ProducesVideoFile]
public async Task<ActionResult> GetLiveRecordingFile([FromRoute, Required] string recordingId)
public ActionResult GetLiveRecordingFile([FromRoute, Required] string recordingId)
{
var path = _liveTvManager.GetEmbyTvActiveRecordingPath(recordingId);
@ -1181,11 +1181,8 @@ namespace Jellyfin.Api.Controllers
return NotFound();
}
await using var memoryStream = new MemoryStream();
await new ProgressiveFileCopier(path, null, _transcodingJobHelper, CancellationToken.None)
.WriteToAsync(memoryStream, CancellationToken.None)
.ConfigureAwait(false);
return File(memoryStream, MimeTypes.GetMimeType(path));
var stream = new ProgressiveFileStream(path, null, _transcodingJobHelper);
return new FileStreamResult(stream, MimeTypes.GetMimeType(path));
}
/// <summary>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
#nullable disable
#pragma warning disable CS1591
#pragma warning disable CA2227, CS1591
using System;
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()
{
return true;
@ -69,29 +93,5 @@ namespace MediaBrowser.Controller.Entities
return base.GetDefaultPrimaryImageAspectRatio();
}
public string CameraMake { get; set; }
public string CameraModel { get; set; }
public string Software { get; set; }
public double? ExposureTime { get; set; }
public double? FocalLength { get; set; }
public ImageOrientation? Orientation { get; set; }
public double? Aperture { get; set; }
public double? ShutterSpeed { get; set; }
public double? Latitude { get; set; }
public double? Longitude { get; set; }
public double? Altitude { get; set; }
public int? IsoSpeedRating { get; set; }
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -28,6 +28,14 @@ namespace MediaBrowser.Controller.Entities
ISupportsPlaceHolders,
IHasMediaSources
{
public Video()
{
AdditionalParts = Array.Empty<string>();
LocalAlternateVersions = Array.Empty<string>();
SubtitleFiles = Array.Empty<string>();
LinkedAlternateVersions = Array.Empty<LinkedChild>();
}
[JsonIgnore]
public string PrimaryVersionId { get; set; }
@ -74,30 +82,6 @@ namespace MediaBrowser.Controller.Entities
}
}
public void SetPrimaryVersionId(string id)
{
if (string.IsNullOrEmpty(id))
{
PrimaryVersionId = null;
}
else
{
PrimaryVersionId = id;
}
PresentationUniqueKey = CreatePresentationUniqueKey();
}
public override string CreatePresentationUniqueKey()
{
if (!string.IsNullOrEmpty(PrimaryVersionId))
{
return PrimaryVersionId;
}
return base.CreatePresentationUniqueKey();
}
[JsonIgnore]
public override bool SupportsThemeMedia => true;
@ -151,24 +135,6 @@ namespace MediaBrowser.Controller.Entities
/// <value>The aspect ratio.</value>
public string AspectRatio { get; set; }
public Video()
{
AdditionalParts = Array.Empty<string>();
LocalAlternateVersions = Array.Empty<string>();
SubtitleFiles = Array.Empty<string>();
LinkedAlternateVersions = Array.Empty<LinkedChild>();
}
public override bool CanDownload()
{
if (VideoType == VideoType.Dvd || VideoType == VideoType.BluRay)
{
return false;
}
return IsFileProtocol;
}
[JsonIgnore]
public override bool SupportsAddingToPlaylist => true;
@ -196,16 +162,6 @@ namespace MediaBrowser.Controller.Entities
[JsonIgnore]
public override bool HasLocalAlternateVersions => LocalAlternateVersions.Length > 0;
public IEnumerable<Guid> GetAdditionalPartIds()
{
return AdditionalParts.Select(i => LibraryManager.GetNewItemId(i, typeof(Video)));
}
public IEnumerable<Guid> GetLocalAlternateVersionIds()
{
return LocalAlternateVersions.Select(i => LibraryManager.GetNewItemId(i, typeof(Video)));
}
public static ILiveTvManager LiveTvManager { get; set; }
[JsonIgnore]
@ -222,21 +178,6 @@ namespace MediaBrowser.Controller.Entities
}
}
protected override bool IsActiveRecording()
{
return LiveTvManager.GetActiveRecordingInfo(Path) != null;
}
public override bool CanDelete()
{
if (IsActiveRecording())
{
return false;
}
return base.CanDelete();
}
[JsonIgnore]
public bool IsCompleteMedia
{
@ -254,80 +195,6 @@ namespace MediaBrowser.Controller.Entities
[JsonIgnore]
protected virtual bool EnableDefaultVideoUserDataKeys => true;
public override List<string> GetUserDataKeys()
{
var list = base.GetUserDataKeys();
if (EnableDefaultVideoUserDataKeys)
{
if (ExtraType.HasValue)
{
var key = this.GetProviderId(MetadataProvider.Tmdb);
if (!string.IsNullOrEmpty(key))
{
list.Insert(0, GetUserDataKey(key));
}
key = this.GetProviderId(MetadataProvider.Imdb);
if (!string.IsNullOrEmpty(key))
{
list.Insert(0, GetUserDataKey(key));
}
}
else
{
var key = this.GetProviderId(MetadataProvider.Imdb);
if (!string.IsNullOrEmpty(key))
{
list.Insert(0, key);
}
key = this.GetProviderId(MetadataProvider.Tmdb);
if (!string.IsNullOrEmpty(key))
{
list.Insert(0, key);
}
}
}
return list;
}
private string GetUserDataKey(string providerId)
{
var key = providerId + "-" + ExtraType.ToString().ToLowerInvariant();
// Make sure different trailers have their own data.
if (RunTimeTicks.HasValue)
{
key += "-" + RunTimeTicks.Value.ToString(CultureInfo.InvariantCulture);
}
return key;
}
public IEnumerable<Video> GetLinkedAlternateVersions()
{
return LinkedAlternateVersions
.Select(GetLinkedChild)
.Where(i => i != null)
.OfType<Video>()
.OrderBy(i => i.SortName);
}
/// <summary>
/// Gets the additional parts.
/// </summary>
/// <returns>IEnumerable{Video}.</returns>
public IOrderedEnumerable<Video> GetAdditionalParts()
{
return GetAdditionalPartIds()
.Select(i => LibraryManager.GetItemById(i))
.Where(i => i != null)
.OfType<Video>()
.OrderBy(i => i.SortName);
}
[JsonIgnore]
public override string ContainingFolderPath
{
@ -369,6 +236,153 @@ namespace MediaBrowser.Controller.Entities
}
}
/// <summary>
/// Gets a value indicating whether [is3 D].
/// </summary>
/// <value><c>true</c> if [is3 D]; otherwise, <c>false</c>.</value>
[JsonIgnore]
public bool Is3D => Video3DFormat.HasValue;
/// <summary>
/// Gets the type of the media.
/// </summary>
/// <value>The type of the media.</value>
[JsonIgnore]
public override string MediaType => Model.Entities.MediaType.Video;
public override List<string> GetUserDataKeys()
{
var list = base.GetUserDataKeys();
if (EnableDefaultVideoUserDataKeys)
{
if (ExtraType.HasValue)
{
var key = this.GetProviderId(MetadataProvider.Tmdb);
if (!string.IsNullOrEmpty(key))
{
list.Insert(0, GetUserDataKey(key));
}
key = this.GetProviderId(MetadataProvider.Imdb);
if (!string.IsNullOrEmpty(key))
{
list.Insert(0, GetUserDataKey(key));
}
}
else
{
var key = this.GetProviderId(MetadataProvider.Imdb);
if (!string.IsNullOrEmpty(key))
{
list.Insert(0, key);
}
key = this.GetProviderId(MetadataProvider.Tmdb);
if (!string.IsNullOrEmpty(key))
{
list.Insert(0, key);
}
}
}
return list;
}
public void SetPrimaryVersionId(string id)
{
if (string.IsNullOrEmpty(id))
{
PrimaryVersionId = null;
}
else
{
PrimaryVersionId = id;
}
PresentationUniqueKey = CreatePresentationUniqueKey();
}
public override string CreatePresentationUniqueKey()
{
if (!string.IsNullOrEmpty(PrimaryVersionId))
{
return PrimaryVersionId;
}
return base.CreatePresentationUniqueKey();
}
public override bool CanDownload()
{
if (VideoType == VideoType.Dvd || VideoType == VideoType.BluRay)
{
return false;
}
return IsFileProtocol;
}
protected override bool IsActiveRecording()
{
return LiveTvManager.GetActiveRecordingInfo(Path) != null;
}
public override bool CanDelete()
{
if (IsActiveRecording())
{
return false;
}
return base.CanDelete();
}
public IEnumerable<Guid> GetAdditionalPartIds()
{
return AdditionalParts.Select(i => LibraryManager.GetNewItemId(i, typeof(Video)));
}
public IEnumerable<Guid> GetLocalAlternateVersionIds()
{
return LocalAlternateVersions.Select(i => LibraryManager.GetNewItemId(i, typeof(Video)));
}
private string GetUserDataKey(string providerId)
{
var key = providerId + "-" + ExtraType.ToString().ToLowerInvariant();
// Make sure different trailers have their own data.
if (RunTimeTicks.HasValue)
{
key += "-" + RunTimeTicks.Value.ToString(CultureInfo.InvariantCulture);
}
return key;
}
public IEnumerable<Video> GetLinkedAlternateVersions()
{
return LinkedAlternateVersions
.Select(GetLinkedChild)
.Where(i => i != null)
.OfType<Video>()
.OrderBy(i => i.SortName);
}
/// <summary>
/// Gets the additional parts.
/// </summary>
/// <returns>IEnumerable{Video}.</returns>
public IOrderedEnumerable<Video> GetAdditionalParts()
{
return GetAdditionalPartIds()
.Select(i => LibraryManager.GetItemById(i))
.Where(i => i != null)
.OfType<Video>()
.OrderBy(i => i.SortName);
}
internal override ItemUpdateType UpdateFromResolvedItem(BaseItem newItem)
{
var updateType = base.UpdateFromResolvedItem(newItem);
@ -397,20 +411,6 @@ namespace MediaBrowser.Controller.Entities
return updateType;
}
/// <summary>
/// Gets a value indicating whether [is3 D].
/// </summary>
/// <value><c>true</c> if [is3 D]; otherwise, <c>false</c>.</value>
[JsonIgnore]
public bool Is3D => Video3DFormat.HasValue;
/// <summary>
/// Gets the type of the media.
/// </summary>
/// <value>The type of the media.</value>
[JsonIgnore]
public override string MediaType => Model.Entities.MediaType.Video;
protected override async Task<bool> RefreshedOwnedItems(MetadataRefreshOptions options, List<FileSystemMetadata> fileSystemChildren, CancellationToken cancellationToken)
{
var hasChanges = await base.RefreshedOwnedItems(options, fileSystemChildren, cancellationToken).ConfigureAwait(false);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -28,6 +28,11 @@ namespace MediaBrowser.Controller.Subtitles
/// <summary>
/// Searches the subtitles.
/// </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(
Video video,
string language,
@ -47,11 +52,20 @@ namespace MediaBrowser.Controller.Subtitles
/// <summary>
/// Downloads the subtitles.
/// </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);
/// <summary>
/// Downloads the subtitles.
/// </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);
/// <summary>
@ -73,11 +87,16 @@ namespace MediaBrowser.Controller.Subtitles
/// <summary>
/// Deletes the subtitles.
/// </summary>
/// <param name="item">Media item.</param>
/// <param name="index">Subtitle index.</param>
/// <returns>A task.</returns>
Task DeleteSubtitles(BaseItem item, int index);
/// <summary>
/// Gets the providers.
/// </summary>
/// <param name="item">The media item.</param>
/// <returns>Subtitles providers.</returns>
SubtitleProviderInfo[] GetSupportedProviders(BaseItem item);
}
}

View File

@ -11,6 +11,15 @@ namespace MediaBrowser.Controller.Subtitles
{
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 TwoLetterISOLanguageName { get; set; }
@ -42,14 +51,5 @@ namespace MediaBrowser.Controller.Subtitles
public string[] DisabledSubtitleFetchers { get; set; }
public string[] SubtitleFetcherOrder { get; set; }
public SubtitleSearchRequest()
{
SearchAllProviders = true;
ProviderIds = new Dictionary<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
{
private const string DefaultEncoderPath = "ffmpeg";
private static readonly string[] _requiredDecoders = new[]
{
"h264",
@ -124,7 +122,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
private readonly string _encoderPath;
public EncoderValidator(ILogger logger, string encoderPath = DefaultEncoderPath)
public EncoderValidator(ILogger logger, string encoderPath)
{
_logger = logger;
_encoderPath = encoderPath;

View File

@ -23,7 +23,6 @@ using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.MediaInfo;
using MediaBrowser.Model.System;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
@ -72,7 +71,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
private Version _ffmpegVersion = null;
private string _ffmpegPath = string.Empty;
private string _ffprobePath;
private int threads;
private int _threads;
public MediaEncoder(
ILogger<MediaEncoder> logger,
@ -92,9 +91,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
/// <inheritdoc />
public string EncoderPath => _ffmpegPath;
/// <inheritdoc />
public FFmpegLocation EncoderLocation { get; private set; }
/// <summary>
/// Run at startup or if the user removes a Custom path from transcode page.
/// Sets global variables FFmpegPath.
@ -103,19 +99,22 @@ namespace MediaBrowser.MediaEncoding.Encoder
public void SetFFmpegPath()
{
// 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
if (!ValidatePath(_startupOptionFFmpegPath, FFmpegLocation.SetByArgument))
ffmpegPath = _startupOptionFFmpegPath;
if (string.IsNullOrEmpty(ffmpegPath))
{
// 3) Search system $PATH environment variable for valid FFmpeg
if (!ValidatePath(ExistsOnSystemPath("ffmpeg"), FFmpegLocation.System))
// 3) Check "ffmpeg"
ffmpegPath = "ffmpeg";
}
}
if (!ValidatePath(ffmpegPath))
{
EncoderLocation = FFmpegLocation.NotFound;
_ffmpegPath = null;
}
}
}
// Write the FFmpeg path to the config/encoding.xml file as <EncoderAppPathDisplay> so it appears in UI
var config = _configurationManager.GetEncodingOptions();
@ -138,10 +137,10 @@ namespace MediaBrowser.MediaEncoding.Encoder
SetAvailableHwaccels(validator.GetHwaccels());
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>
@ -160,15 +159,12 @@ namespace MediaBrowser.MediaEncoding.Encoder
{
throw new ArgumentException("Unexpected pathType value");
}
else if (string.IsNullOrWhiteSpace(path))
if (string.IsNullOrWhiteSpace(path))
{
// User had cleared the custom path in UI
newPath = string.Empty;
}
else if (File.Exists(path))
{
newPath = path;
}
else if (Directory.Exists(path))
{
// Given path is directory, so resolve down to filename
@ -176,7 +172,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
}
else
{
throw new ResourceNotFoundException();
newPath = path;
}
// Write the new ffmpeg path to the xml as <EncoderAppPath>
@ -191,38 +187,27 @@ namespace MediaBrowser.MediaEncoding.Encoder
/// <summary>
/// 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>
/// <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>
private bool ValidatePath(string path, FFmpegLocation location)
private bool ValidatePath(string path)
{
bool rc = false;
if (!string.IsNullOrEmpty(path))
{
if (File.Exists(path))
{
rc = new EncoderValidator(_logger, path).ValidateVersion();
if (string.IsNullOrEmpty(path))
{
return false;
}
bool rc = new EncoderValidator(_logger, path).ValidateVersion();
if (!rc)
{
_logger.LogWarning("FFmpeg: {Location}: Failed version check: {Path}", location, path);
_logger.LogWarning("FFmpeg: Failed version check: {Path}", path);
return false;
}
_ffmpegPath = path;
EncoderLocation = location;
return true;
}
else
{
_logger.LogWarning("FFmpeg: {Location}: File not found: {Path}", location, path);
}
}
return rc;
}
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)
{
_encoders = list.ToList();
@ -425,7 +382,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
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_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
{
@ -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)
{
@ -759,7 +716,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
Directory.CreateDirectory(targetDirectory);
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))
{

View File

@ -1,4 +1,3 @@
#nullable disable
using System.Collections.Generic;
using System.Globalization;
using MediaBrowser.Model.Entities;
@ -56,19 +55,11 @@ namespace MediaBrowser.Model.Globalization
/// <returns><see cref="IEnumerable{LocalizatonOption}" />.</returns>
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>
/// Returns the correct <see cref="CultureInfo" /> for the given language.
/// </summary>
/// <param name="language">The language.</param>
/// <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")]
public bool HasUpdateAvailable { get; set; }
[Obsolete("This isn't set correctly anymore")]
public FFmpegLocation EncoderLocation { get; set; }
public Architecture SystemArchitecture { get; set; }

View File

@ -59,9 +59,9 @@ namespace MediaBrowser.Providers.BoxSets
}
/// <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();
@ -69,10 +69,10 @@ namespace MediaBrowser.Providers.BoxSets
if (itemLibraryFolderIds == null || !libraryFolderIds.SequenceEqual(itemLibraryFolderIds))
{
item.LibraryFolderIds = libraryFolderIds;
updateType |= ItemUpdateType.MetadataImport;
updatedType |= ItemUpdateType.MetadataImport;
}
return updateType;
return updatedType;
}
}
}

View File

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

View File

@ -505,6 +505,11 @@ namespace MediaBrowser.Providers.Manager
/// <summary>
/// Gets the providers.
/// </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>
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