From 675a8a9ec91da47e37ace6161ba5a5a0e20a7839 Mon Sep 17 00:00:00 2001 From: Niels van Velzen Date: Sat, 7 Sep 2024 19:22:31 +0200 Subject: Remove left-over network path references (#12446) --- .../Library/LibraryManager.cs | 35 +--------------------- 1 file changed, 1 insertion(+), 34 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 48d24385e..28f7ed659 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -2725,33 +2725,9 @@ namespace Emby.Server.Implementations.Library public string GetPathAfterNetworkSubstitution(string path, BaseItem? ownerItem) { - string? newPath; - if (ownerItem is not null) - { - var libraryOptions = GetLibraryOptions(ownerItem); - if (libraryOptions is not null) - { - foreach (var pathInfo in libraryOptions.PathInfos) - { - if (path.TryReplaceSubPath(pathInfo.Path, pathInfo.NetworkPath, out newPath)) - { - return newPath; - } - } - } - } - - var metadataPath = _configurationManager.Configuration.MetadataPath; - var metadataNetworkPath = _configurationManager.Configuration.MetadataNetworkPath; - - if (path.TryReplaceSubPath(metadataPath, metadataNetworkPath, out newPath)) - { - return newPath; - } - foreach (var map in _configurationManager.Configuration.PathSubstitutions) { - if (path.TryReplaceSubPath(map.From, map.To, out newPath)) + if (path.TryReplaceSubPath(map.From, map.To, out var newPath)) { return newPath; } @@ -3070,15 +3046,6 @@ namespace Emby.Server.Implementations.Library SyncLibraryOptionsToLocations(virtualFolderPath, libraryOptions); - foreach (var originalPathInfo in libraryOptions.PathInfos) - { - if (string.Equals(mediaPath.Path, originalPathInfo.Path, StringComparison.Ordinal)) - { - originalPathInfo.NetworkPath = mediaPath.NetworkPath; - break; - } - } - CollectionFolder.SaveLibraryOptions(virtualFolderPath, libraryOptions); } -- cgit v1.2.3 From c56dbc1c4410e1b0ec31ca901809b6f627bbb6ed Mon Sep 17 00:00:00 2001 From: Tim Eisele Date: Sat, 7 Sep 2024 19:23:48 +0200 Subject: Enhance Trickplay (#11883) --- .../IO/ManagedFileSystem.cs | 38 +++-- .../Localization/Core/en-US.json | 4 +- Jellyfin.Api/Controllers/TrickplayController.cs | 7 +- .../Trickplay/TrickplayManager.cs | 183 +++++++++++++++++---- Jellyfin.Server/Migrations/MigrationRunner.cs | 1 + .../Migrations/Routines/MoveTrickplayFiles.cs | 73 ++++++++ .../Providers/MetadataRefreshOptions.cs | 7 + .../Trickplay/ITrickplayManager.cs | 34 +++- MediaBrowser.Model/Configuration/LibraryOptions.cs | 4 + MediaBrowser.Model/IO/IFileSystem.cs | 7 + .../Trickplay/TrickplayImagesTask.cs | 3 +- .../Trickplay/TrickplayMoveImagesTask.cs | 110 +++++++++++++ .../Trickplay/TrickplayProvider.cs | 6 +- 13 files changed, 423 insertions(+), 54 deletions(-) create mode 100644 Jellyfin.Server/Migrations/Routines/MoveTrickplayFiles.cs create mode 100644 MediaBrowser.Providers/Trickplay/TrickplayMoveImagesTask.cs (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/IO/ManagedFileSystem.cs b/Emby.Server.Implementations/IO/ManagedFileSystem.cs index 28bb29df8..4b68f21d5 100644 --- a/Emby.Server.Implementations/IO/ManagedFileSystem.cs +++ b/Emby.Server.Implementations/IO/ManagedFileSystem.cs @@ -149,6 +149,26 @@ namespace Emby.Server.Implementations.IO } } + /// + public void MoveDirectory(string source, string destination) + { + try + { + Directory.Move(source, destination); + } + catch (IOException) + { + // Cross device move requires a copy + Directory.CreateDirectory(destination); + foreach (string file in Directory.GetFiles(source)) + { + File.Copy(file, Path.Combine(destination, Path.GetFileName(file)), true); + } + + Directory.Delete(source, true); + } + } + /// /// Returns a object for the specified file or directory path. /// @@ -327,11 +347,7 @@ namespace Emby.Server.Implementations.IO } } - /// - /// Gets the creation time UTC. - /// - /// The path. - /// DateTime. + /// public virtual DateTime GetCreationTimeUtc(string path) { return GetCreationTimeUtc(GetFileSystemInfo(path)); @@ -368,11 +384,7 @@ namespace Emby.Server.Implementations.IO } } - /// - /// Gets the last write time UTC. - /// - /// The path. - /// DateTime. + /// public virtual DateTime GetLastWriteTimeUtc(string path) { return GetLastWriteTimeUtc(GetFileSystemInfo(path)); @@ -446,11 +458,7 @@ namespace Emby.Server.Implementations.IO File.SetAttributes(path, attributes); } - /// - /// Swaps the files. - /// - /// The file1. - /// The file2. + /// public virtual void SwapFiles(string file1, string file2) { ArgumentException.ThrowIfNullOrEmpty(file1); diff --git a/Emby.Server.Implementations/Localization/Core/en-US.json b/Emby.Server.Implementations/Localization/Core/en-US.json index d1410ef5e..d248fc303 100644 --- a/Emby.Server.Implementations/Localization/Core/en-US.json +++ b/Emby.Server.Implementations/Localization/Core/en-US.json @@ -131,5 +131,7 @@ "TaskKeyframeExtractor": "Keyframe Extractor", "TaskKeyframeExtractorDescription": "Extracts keyframes from video files to create more precise HLS playlists. This task may run for a long time.", "TaskCleanCollectionsAndPlaylists": "Clean up collections and playlists", - "TaskCleanCollectionsAndPlaylistsDescription": "Removes items from collections and playlists that no longer exist." + "TaskCleanCollectionsAndPlaylistsDescription": "Removes items from collections and playlists that no longer exist.", + "TaskMoveTrickplayImages": "Migrate Trickplay Image Location", + "TaskMoveTrickplayImagesDescription": "Moves existing trickplay files according to the library settings." } diff --git a/Jellyfin.Api/Controllers/TrickplayController.cs b/Jellyfin.Api/Controllers/TrickplayController.cs index 60d49af9e..c1ff0f340 100644 --- a/Jellyfin.Api/Controllers/TrickplayController.cs +++ b/Jellyfin.Api/Controllers/TrickplayController.cs @@ -80,7 +80,7 @@ public class TrickplayController : BaseJellyfinApiController [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesImageFile] - public ActionResult GetTrickplayTileImage( + public async Task GetTrickplayTileImageAsync( [FromRoute, Required] Guid itemId, [FromRoute, Required] int width, [FromRoute, Required] int index, @@ -92,8 +92,9 @@ public class TrickplayController : BaseJellyfinApiController return NotFound(); } - var path = _trickplayManager.GetTrickplayTilePath(item, width, index); - if (System.IO.File.Exists(path)) + var saveWithMedia = _libraryManager.GetLibraryOptions(item).SaveTrickplayWithMedia; + var path = await _trickplayManager.GetTrickplayTilePathAsync(item, width, index, saveWithMedia).ConfigureAwait(false); + if (!string.IsNullOrEmpty(path) && System.IO.File.Exists(path)) { Response.Headers.ContentDisposition = "attachment"; return PhysicalFile(path, MediaTypeNames.Image.Jpeg); diff --git a/Jellyfin.Server.Implementations/Trickplay/TrickplayManager.cs b/Jellyfin.Server.Implementations/Trickplay/TrickplayManager.cs index bb32b7c20..861037c1f 100644 --- a/Jellyfin.Server.Implementations/Trickplay/TrickplayManager.cs +++ b/Jellyfin.Server.Implementations/Trickplay/TrickplayManager.cs @@ -76,7 +76,65 @@ public class TrickplayManager : ITrickplayManager } /// - public async Task RefreshTrickplayDataAsync(Video video, bool replace, CancellationToken cancellationToken) + public async Task MoveGeneratedTrickplayDataAsync(Video video, LibraryOptions? libraryOptions, CancellationToken cancellationToken) + { + var options = _config.Configuration.TrickplayOptions; + if (!CanGenerateTrickplay(video, options.Interval)) + { + return; + } + + var existingTrickplayResolutions = await GetTrickplayResolutions(video.Id).ConfigureAwait(false); + foreach (var resolution in existingTrickplayResolutions) + { + cancellationToken.ThrowIfCancellationRequested(); + var existingResolution = resolution.Key; + var tileWidth = resolution.Value.TileWidth; + var tileHeight = resolution.Value.TileHeight; + var shouldBeSavedWithMedia = libraryOptions is null ? false : libraryOptions.SaveTrickplayWithMedia; + var localOutputDir = GetTrickplayDirectory(video, tileWidth, tileHeight, existingResolution, false); + var mediaOutputDir = GetTrickplayDirectory(video, tileWidth, tileHeight, existingResolution, true); + if (shouldBeSavedWithMedia && Directory.Exists(localOutputDir)) + { + var localDirFiles = Directory.GetFiles(localOutputDir); + var mediaDirExists = Directory.Exists(mediaOutputDir); + if (localDirFiles.Length > 0 && ((mediaDirExists && Directory.GetFiles(mediaOutputDir).Length == 0) || !mediaDirExists)) + { + // Move images from local dir to media dir + MoveContent(localOutputDir, mediaOutputDir); + _logger.LogInformation("Moved trickplay images for {ItemName} to {Location}", video.Name, mediaOutputDir); + } + } + else if (Directory.Exists(mediaOutputDir)) + { + var mediaDirFiles = Directory.GetFiles(mediaOutputDir); + var localDirExists = Directory.Exists(localOutputDir); + if (mediaDirFiles.Length > 0 && ((localDirExists && Directory.GetFiles(localOutputDir).Length == 0) || !localDirExists)) + { + // Move images from media dir to local dir + MoveContent(mediaOutputDir, localOutputDir); + _logger.LogInformation("Moved trickplay images for {ItemName} to {Location}", video.Name, localOutputDir); + } + } + } + } + + private void MoveContent(string sourceFolder, string destinationFolder) + { + _fileSystem.MoveDirectory(sourceFolder, destinationFolder); + var parent = Directory.GetParent(sourceFolder); + if (parent is not null) + { + var parentContent = Directory.GetDirectories(parent.FullName); + if (parentContent.Length == 0) + { + Directory.Delete(parent.FullName); + } + } + } + + /// + public async Task RefreshTrickplayDataAsync(Video video, bool replace, LibraryOptions? libraryOptions, CancellationToken cancellationToken) { _logger.LogDebug("Trickplay refresh for {ItemId} (replace existing: {Replace})", video.Id, replace); @@ -95,6 +153,7 @@ public class TrickplayManager : ITrickplayManager replace, width, options, + libraryOptions, cancellationToken).ConfigureAwait(false); } } @@ -104,6 +163,7 @@ public class TrickplayManager : ITrickplayManager bool replace, int width, TrickplayOptions options, + LibraryOptions? libraryOptions, CancellationToken cancellationToken) { if (!CanGenerateTrickplay(video, options.Interval)) @@ -144,14 +204,53 @@ public class TrickplayManager : ITrickplayManager actualWidth = 2 * ((int)mediaSource.VideoStream.Width / 2); } - var outputDir = GetTrickplayDirectory(video, actualWidth); + var tileWidth = options.TileWidth; + var tileHeight = options.TileHeight; + var saveWithMedia = libraryOptions is null ? false : libraryOptions.SaveTrickplayWithMedia; + var outputDir = GetTrickplayDirectory(video, tileWidth, tileHeight, actualWidth, saveWithMedia); - if (!replace && Directory.Exists(outputDir) && (await GetTrickplayResolutions(video.Id).ConfigureAwait(false)).ContainsKey(actualWidth)) + // Import existing trickplay tiles + if (!replace && Directory.Exists(outputDir)) { - _logger.LogDebug("Found existing trickplay files for {ItemId}. Exiting", video.Id); - return; + var existingFiles = Directory.GetFiles(outputDir); + if (existingFiles.Length > 0) + { + var hasTrickplayResolution = await HasTrickplayResolutionAsync(video.Id, actualWidth).ConfigureAwait(false); + if (hasTrickplayResolution) + { + _logger.LogDebug("Found existing trickplay files for {ItemId}.", video.Id); + return; + } + + // Import tiles + var localTrickplayInfo = new TrickplayInfo + { + ItemId = video.Id, + Width = width, + Interval = options.Interval, + TileWidth = options.TileWidth, + TileHeight = options.TileHeight, + ThumbnailCount = existingFiles.Length, + Height = 0, + Bandwidth = 0 + }; + + foreach (var tile in existingFiles) + { + var image = _imageEncoder.GetImageSize(tile); + localTrickplayInfo.Height = Math.Max(localTrickplayInfo.Height, image.Height); + var bitrate = (int)Math.Ceiling((decimal)new FileInfo(tile).Length * 8 / localTrickplayInfo.TileWidth / localTrickplayInfo.TileHeight / (localTrickplayInfo.Interval / 1000)); + localTrickplayInfo.Bandwidth = Math.Max(localTrickplayInfo.Bandwidth, bitrate); + } + + await SaveTrickplayInfo(localTrickplayInfo).ConfigureAwait(false); + + _logger.LogDebug("Imported existing trickplay files for {ItemId}.", video.Id); + return; + } } + // Generate trickplay tiles var mediaStream = mediaSource.VideoStream; var container = mediaSource.Container; @@ -224,7 +323,7 @@ public class TrickplayManager : ITrickplayManager } /// - public TrickplayInfo CreateTiles(List images, int width, TrickplayOptions options, string outputDir) + public TrickplayInfo CreateTiles(IReadOnlyList images, int width, TrickplayOptions options, string outputDir) { if (images.Count == 0) { @@ -264,7 +363,7 @@ public class TrickplayManager : ITrickplayManager var tilePath = Path.Combine(workDir, $"{i}.jpg"); imageOptions.OutputPath = tilePath; - imageOptions.InputPaths = images.GetRange(i * thumbnailsPerTile, Math.Min(thumbnailsPerTile, images.Count - (i * thumbnailsPerTile))); + imageOptions.InputPaths = images.Skip(i * thumbnailsPerTile).Take(Math.Min(thumbnailsPerTile, images.Count - (i * thumbnailsPerTile))).ToList(); // Generate image and use returned height for tiles info var height = _imageEncoder.CreateTrickplayTile(imageOptions, options.JpegQuality, trickplayInfo.Width, trickplayInfo.Height != 0 ? trickplayInfo.Height : null); @@ -289,7 +388,7 @@ public class TrickplayManager : ITrickplayManager Directory.Delete(outputDir, true); } - MoveDirectory(workDir, outputDir); + _fileSystem.MoveDirectory(workDir, outputDir); return trickplayInfo; } @@ -355,6 +454,24 @@ public class TrickplayManager : ITrickplayManager return trickplayResolutions; } + /// + public async Task> GetTrickplayItemsAsync() + { + List trickplayItems; + + var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false); + await using (dbContext.ConfigureAwait(false)) + { + trickplayItems = await dbContext.TrickplayInfos + .AsNoTracking() + .Select(i => i.ItemId) + .ToListAsync() + .ConfigureAwait(false); + } + + return trickplayItems; + } + /// public async Task SaveTrickplayInfo(TrickplayInfo info) { @@ -392,9 +509,15 @@ public class TrickplayManager : ITrickplayManager } /// - public string GetTrickplayTilePath(BaseItem item, int width, int index) + public async Task GetTrickplayTilePathAsync(BaseItem item, int width, int index, bool saveWithMedia) { - return Path.Combine(GetTrickplayDirectory(item, width), index + ".jpg"); + var trickplayResolutions = await GetTrickplayResolutions(item.Id).ConfigureAwait(false); + if (trickplayResolutions is not null && trickplayResolutions.TryGetValue(width, out var trickplayInfo)) + { + return Path.Combine(GetTrickplayDirectory(item, trickplayInfo.TileWidth, trickplayInfo.TileHeight, width, saveWithMedia), index + ".jpg"); + } + + return string.Empty; } /// @@ -470,29 +593,33 @@ public class TrickplayManager : ITrickplayManager return null; } - private string GetTrickplayDirectory(BaseItem item, int? width = null) + /// + public string GetTrickplayDirectory(BaseItem item, int tileWidth, int tileHeight, int width, bool saveWithMedia = false) { - var path = Path.Combine(item.GetInternalMetadataPath(), "trickplay"); - - return width.HasValue ? Path.Combine(path, width.Value.ToString(CultureInfo.InvariantCulture)) : path; + var path = saveWithMedia + ? Path.Combine(item.ContainingFolderPath, Path.ChangeExtension(item.Path, ".trickplay")) + : Path.Combine(item.GetInternalMetadataPath(), "trickplay"); + + var subdirectory = string.Format( + CultureInfo.InvariantCulture, + "{0} - {1}x{2}", + width.ToString(CultureInfo.InvariantCulture), + tileWidth.ToString(CultureInfo.InvariantCulture), + tileHeight.ToString(CultureInfo.InvariantCulture)); + + return Path.Combine(path, subdirectory); } - private void MoveDirectory(string source, string destination) + private async Task HasTrickplayResolutionAsync(Guid itemId, int width) { - try - { - Directory.Move(source, destination); - } - catch (IOException) + var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false); + await using (dbContext.ConfigureAwait(false)) { - // Cross device move requires a copy - Directory.CreateDirectory(destination); - foreach (string file in Directory.GetFiles(source)) - { - File.Copy(file, Path.Join(destination, Path.GetFileName(file)), true); - } - - Directory.Delete(source, true); + return await dbContext.TrickplayInfos + .AsNoTracking() + .Where(i => i.ItemId.Equals(itemId)) + .AnyAsync(i => i.Width == width) + .ConfigureAwait(false); } } } diff --git a/Jellyfin.Server/Migrations/MigrationRunner.cs b/Jellyfin.Server/Migrations/MigrationRunner.cs index 81fecc9a1..8682f28e0 100644 --- a/Jellyfin.Server/Migrations/MigrationRunner.cs +++ b/Jellyfin.Server/Migrations/MigrationRunner.cs @@ -46,6 +46,7 @@ namespace Jellyfin.Server.Migrations typeof(Routines.AddDefaultCastReceivers), typeof(Routines.UpdateDefaultPluginRepository), typeof(Routines.FixAudioData), + typeof(Routines.MoveTrickplayFiles) }; /// diff --git a/Jellyfin.Server/Migrations/Routines/MoveTrickplayFiles.cs b/Jellyfin.Server/Migrations/Routines/MoveTrickplayFiles.cs new file mode 100644 index 000000000..e8abee95a --- /dev/null +++ b/Jellyfin.Server/Migrations/Routines/MoveTrickplayFiles.cs @@ -0,0 +1,73 @@ +using System; +using System.Globalization; +using System.IO; +using DiscUtils; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Trickplay; + +namespace Jellyfin.Server.Migrations.Routines; + +/// +/// Migration to move trickplay files to the new directory. +/// +public class MoveTrickplayFiles : IMigrationRoutine +{ + private readonly ITrickplayManager _trickplayManager; + private readonly IFileSystem _fileSystem; + private readonly ILibraryManager _libraryManager; + + /// + /// Initializes a new instance of the class. + /// + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + public MoveTrickplayFiles(ITrickplayManager trickplayManager, IFileSystem fileSystem, ILibraryManager libraryManager) + { + _trickplayManager = trickplayManager; + _fileSystem = fileSystem; + _libraryManager = libraryManager; + } + + /// + public Guid Id => new("4EF123D5-8EFF-4B0B-869D-3AED07A60E1B"); + + /// + public string Name => "MoveTrickplayFiles"; + + /// + public bool PerformOnNewInstall => true; + + /// + public void Perform() + { + var trickplayItems = _trickplayManager.GetTrickplayItemsAsync().GetAwaiter().GetResult(); + foreach (var itemId in trickplayItems) + { + var resolutions = _trickplayManager.GetTrickplayResolutions(itemId).GetAwaiter().GetResult(); + var item = _libraryManager.GetItemById(itemId); + if (item is null) + { + continue; + } + + foreach (var resolution in resolutions) + { + var oldPath = GetOldTrickplayDirectory(item, resolution.Key); + var newPath = _trickplayManager.GetTrickplayDirectory(item, resolution.Value.TileWidth, resolution.Value.TileHeight, resolution.Value.Width, false); + if (_fileSystem.DirectoryExists(oldPath)) + { + _fileSystem.MoveDirectory(oldPath, newPath); + } + } + } + } + + private string GetOldTrickplayDirectory(BaseItem item, int? width = null) + { + var path = Path.Combine(item.GetInternalMetadataPath(), "trickplay"); + + return width.HasValue ? Path.Combine(path, width.Value.ToString(CultureInfo.InvariantCulture)) : path; + } +} diff --git a/MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs b/MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs index 9e91a8bcd..0bab2a6b9 100644 --- a/MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs +++ b/MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs @@ -29,6 +29,7 @@ namespace MediaBrowser.Controller.Providers IsAutomated = copy.IsAutomated; ImageRefreshMode = copy.ImageRefreshMode; ReplaceAllImages = copy.ReplaceAllImages; + RegenerateTrickplay = copy.RegenerateTrickplay; ReplaceImages = copy.ReplaceImages; SearchResult = copy.SearchResult; RemoveOldMetadata = copy.RemoveOldMetadata; @@ -47,6 +48,12 @@ namespace MediaBrowser.Controller.Providers /// public bool ReplaceAllMetadata { get; set; } + /// + /// Gets or sets a value indicating whether all existing trickplay images should be overwritten + /// when paired with MetadataRefreshMode=FullRefresh. + /// + public bool RegenerateTrickplay { get; set; } + public MetadataRefreshMode MetadataRefreshMode { get; set; } public RemoteSearchResult SearchResult { get; set; } diff --git a/MediaBrowser.Controller/Trickplay/ITrickplayManager.cs b/MediaBrowser.Controller/Trickplay/ITrickplayManager.cs index 0c41f3023..bda794aa6 100644 --- a/MediaBrowser.Controller/Trickplay/ITrickplayManager.cs +++ b/MediaBrowser.Controller/Trickplay/ITrickplayManager.cs @@ -18,9 +18,10 @@ public interface ITrickplayManager /// /// The video. /// Whether or not existing data should be replaced. + /// The library options. /// CancellationToken to use for operation. /// Task. - Task RefreshTrickplayDataAsync(Video video, bool replace, CancellationToken cancellationToken); + Task RefreshTrickplayDataAsync(Video video, bool replace, LibraryOptions? libraryOptions, CancellationToken cancellationToken); /// /// Creates trickplay tiles out of individual thumbnails. @@ -33,7 +34,7 @@ public interface ITrickplayManager /// /// The output directory will be DELETED and replaced if it already exists. /// - TrickplayInfo CreateTiles(List images, int width, TrickplayOptions options, string outputDir); + TrickplayInfo CreateTiles(IReadOnlyList images, int width, TrickplayOptions options, string outputDir); /// /// Get available trickplay resolutions and corresponding info. @@ -42,6 +43,12 @@ public interface ITrickplayManager /// Map of width resolutions to trickplay tiles info. Task> GetTrickplayResolutions(Guid itemId); + /// + /// Gets the item ids of all items with trickplay info. + /// + /// The list of item ids that have trickplay info. + public Task> GetTrickplayItemsAsync(); + /// /// Saves trickplay info. /// @@ -62,8 +69,29 @@ public interface ITrickplayManager /// The item. /// The width of a single thumbnail. /// The tile's index. + /// Whether or not the tile should be saved next to the media file. + /// The absolute path. + Task GetTrickplayTilePathAsync(BaseItem item, int width, int index, bool saveWithMedia); + + /// + /// Gets the path to a trickplay tile image. + /// + /// The item. + /// The amount of images for the tile width. + /// The amount of images for the tile height. + /// The width of a single thumbnail. + /// Whether or not the tile should be saved next to the media file. /// The absolute path. - string GetTrickplayTilePath(BaseItem item, int width, int index); + string GetTrickplayDirectory(BaseItem item, int tileWidth, int tileHeight, int width, bool saveWithMedia = false); + + /// + /// Migrates trickplay images between local and media directories. + /// + /// The video. + /// The library options. + /// CancellationToken to use for operation. + /// Task. + Task MoveGeneratedTrickplayDataAsync(Video video, LibraryOptions? libraryOptions, CancellationToken cancellationToken); /// /// Gets the trickplay HLS playlist. diff --git a/MediaBrowser.Model/Configuration/LibraryOptions.cs b/MediaBrowser.Model/Configuration/LibraryOptions.cs index b0f5c2a11..688a6418d 100644 --- a/MediaBrowser.Model/Configuration/LibraryOptions.cs +++ b/MediaBrowser.Model/Configuration/LibraryOptions.cs @@ -24,6 +24,7 @@ namespace MediaBrowser.Model.Configuration EnablePhotos = true; SaveSubtitlesWithMedia = true; SaveLyricsWithMedia = false; + SaveTrickplayWithMedia = false; PathInfos = Array.Empty(); EnableAutomaticSeriesGrouping = true; SeasonZeroDisplayName = "Specials"; @@ -99,6 +100,9 @@ namespace MediaBrowser.Model.Configuration [DefaultValue(false)] public bool SaveLyricsWithMedia { get; set; } + [DefaultValue(false)] + public bool SaveTrickplayWithMedia { get; set; } + public string[] DisabledLyricFetchers { get; set; } public string[] LyricFetcherOrder { get; set; } diff --git a/MediaBrowser.Model/IO/IFileSystem.cs b/MediaBrowser.Model/IO/IFileSystem.cs index ec381d423..2085328dd 100644 --- a/MediaBrowser.Model/IO/IFileSystem.cs +++ b/MediaBrowser.Model/IO/IFileSystem.cs @@ -33,6 +33,13 @@ namespace MediaBrowser.Model.IO string MakeAbsolutePath(string folderPath, string filePath); + /// + /// Moves a directory to a new location. + /// + /// Source directory. + /// Destination directory. + void MoveDirectory(string source, string destination); + /// /// Returns a object for the specified file or directory path. /// diff --git a/MediaBrowser.Providers/Trickplay/TrickplayImagesTask.cs b/MediaBrowser.Providers/Trickplay/TrickplayImagesTask.cs index 90c2ff8dd..31c0eeb31 100644 --- a/MediaBrowser.Providers/Trickplay/TrickplayImagesTask.cs +++ b/MediaBrowser.Providers/Trickplay/TrickplayImagesTask.cs @@ -98,7 +98,8 @@ public class TrickplayImagesTask : IScheduledTask try { - await _trickplayManager.RefreshTrickplayDataAsync(video, false, cancellationToken).ConfigureAwait(false); + var libraryOptions = _libraryManager.GetLibraryOptions(video); + await _trickplayManager.RefreshTrickplayDataAsync(video, false, libraryOptions, cancellationToken).ConfigureAwait(false); } catch (Exception ex) { diff --git a/MediaBrowser.Providers/Trickplay/TrickplayMoveImagesTask.cs b/MediaBrowser.Providers/Trickplay/TrickplayMoveImagesTask.cs new file mode 100644 index 000000000..c581fd26c --- /dev/null +++ b/MediaBrowser.Providers/Trickplay/TrickplayMoveImagesTask.cs @@ -0,0 +1,110 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Data.Enums; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Trickplay; +using MediaBrowser.Model.Globalization; +using MediaBrowser.Model.Tasks; +using Microsoft.Extensions.Logging; + +namespace MediaBrowser.Providers.Trickplay; + +/// +/// Class TrickplayMoveImagesTask. +/// +public class TrickplayMoveImagesTask : IScheduledTask +{ + private const int QueryPageLimit = 100; + + private readonly ILogger _logger; + private readonly ILibraryManager _libraryManager; + private readonly ILocalizationManager _localization; + private readonly ITrickplayManager _trickplayManager; + + /// + /// Initializes a new instance of the class. + /// + /// The logger. + /// The library manager. + /// The localization manager. + /// The trickplay manager. + public TrickplayMoveImagesTask( + ILogger logger, + ILibraryManager libraryManager, + ILocalizationManager localization, + ITrickplayManager trickplayManager) + { + _libraryManager = libraryManager; + _logger = logger; + _localization = localization; + _trickplayManager = trickplayManager; + } + + /// + public string Name => _localization.GetLocalizedString("TaskMoveTrickplayImages"); + + /// + public string Description => _localization.GetLocalizedString("TaskMoveTrickplayImagesDescription"); + + /// + public string Key => "MoveTrickplayImages"; + + /// + public string Category => _localization.GetLocalizedString("TasksMaintenanceCategory"); + + /// + public IEnumerable GetDefaultTriggers() => []; + + /// + public async Task ExecuteAsync(IProgress progress, CancellationToken cancellationToken) + { + var trickplayItems = await _trickplayManager.GetTrickplayItemsAsync().ConfigureAwait(false); + var query = new InternalItemsQuery + { + MediaTypes = [MediaType.Video], + SourceTypes = [SourceType.Library], + IsVirtualItem = false, + IsFolder = false, + Recursive = true, + Limit = QueryPageLimit + }; + + var numberOfVideos = _libraryManager.GetCount(query); + + var startIndex = 0; + var numComplete = 0; + + while (startIndex < numberOfVideos) + { + query.StartIndex = startIndex; + var videos = _libraryManager.GetItemList(query).OfType public class MediaSegmentManager : IMediaSegmentManager { + private readonly ILogger _logger; private readonly IDbContextFactory _dbProvider; + private readonly IMediaSegmentProvider[] _segmentProviders; + private readonly ILibraryManager _libraryManager; /// /// Initializes a new instance of the class. /// + /// Logger. /// EFCore Database factory. - public MediaSegmentManager(IDbContextFactory dbProvider) + /// List of all media segment providers. + /// Library manager. + public MediaSegmentManager( + ILogger logger, + IDbContextFactory dbProvider, + IEnumerable segmentProviders, + ILibraryManager libraryManager) { + _logger = logger; _dbProvider = dbProvider; + + _segmentProviders = segmentProviders + .OrderBy(i => i is IHasOrder hasOrder ? hasOrder.Order : 0) + .ToArray(); + _libraryManager = libraryManager; + } + + /// + public async Task RunSegmentPluginProviders(BaseItem baseItem, bool overwrite, CancellationToken cancellationToken) + { + var libraryOptions = _libraryManager.GetLibraryOptions(baseItem); + var providers = _segmentProviders + .Where(e => !libraryOptions.DisabledMediaSegmentProviders.Contains(GetProviderId(e.Name))) + .OrderBy(i => + { + var index = libraryOptions.MediaSegmentProvideOrder.IndexOf(i.Name); + return index == -1 ? int.MaxValue : index; + }) + .ToList(); + + _logger.LogInformation("Start media segment extraction from providers with {CountProviders} enabled", providers.Count); + using var db = await _dbProvider.CreateDbContextAsync(cancellationToken).ConfigureAwait(false); + + if (!overwrite && (await db.MediaSegments.AnyAsync(e => e.ItemId.Equals(baseItem.Id), cancellationToken).ConfigureAwait(false))) + { + _logger.LogInformation("Skip {MediaPath} as it already contains media segments", baseItem.Path); + return; + } + + _logger.LogInformation("Clear existing Segments for {MediaPath}", baseItem.Path); + + await db.MediaSegments.Where(e => e.ItemId.Equals(baseItem.Id)).ExecuteDeleteAsync(cancellationToken).ConfigureAwait(false); + + // no need to recreate the request object every time. + var requestItem = new MediaSegmentGenerationRequest() { ItemId = baseItem.Id }; + + foreach (var provider in providers) + { + if (!await provider.Supports(baseItem).ConfigureAwait(false)) + { + _logger.LogDebug("Media Segment provider {ProviderName} does not support item with path {Path}", provider.Name, baseItem.Path); + continue; + } + + _logger.LogDebug("Run Media Segment provider {ProviderName}", provider.Name); + try + { + var segments = await provider.GetMediaSegments(requestItem, cancellationToken) + .ConfigureAwait(false); + + _logger.LogInformation("Media Segment provider {ProviderName} found {CountSegments} for {MediaPath}", provider.Name, segments.Count, baseItem.Path); + var providerId = GetProviderId(provider.Name); + foreach (var segment in segments) + { + segment.ItemId = baseItem.Id; + await CreateSegmentAsync(segment, providerId).ConfigureAwait(false); + } + } + catch (Exception ex) + { + _logger.LogError(ex, "Provider {ProviderName} failed to extract segments from {MediaPath}", provider.Name, baseItem.Path); + } + } } /// @@ -103,4 +186,21 @@ public class MediaSegmentManager : IMediaSegmentManager { return baseItem.MediaType is Data.Enums.MediaType.Video or Data.Enums.MediaType.Audio; } + + /// + public IEnumerable<(string Name, string Id)> GetSupportedProviders(BaseItem item) + { + if (item is not (Video or Audio)) + { + return []; + } + + return _segmentProviders + .Select(p => (p.Name, GetProviderId(p.Name))); + } + + private string GetProviderId(string name) + => name.ToLowerInvariant() + .GetMD5() + .ToString("N", CultureInfo.InvariantCulture); } diff --git a/MediaBrowser.Controller/MediaSegements/IMediaSegmentManager.cs b/MediaBrowser.Controller/MediaSegements/IMediaSegmentManager.cs index 67384f6f6..010d7edb4 100644 --- a/MediaBrowser.Controller/MediaSegements/IMediaSegmentManager.cs +++ b/MediaBrowser.Controller/MediaSegements/IMediaSegmentManager.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; @@ -13,6 +14,15 @@ namespace MediaBrowser.Controller; /// public interface IMediaSegmentManager { + /// + /// Uses all segment providers enabled for the 's library to get the Media Segments. + /// + /// The Item to evaluate. + /// If set, will remove existing segments and replace it with new ones otherwise will check for existing segments and if found any, stops. + /// stop request token. + /// A task that indicates the Operation is finished. + Task RunSegmentPluginProviders(BaseItem baseItem, bool overwrite, CancellationToken cancellationToken); + /// /// Returns if this item supports media segments. /// @@ -50,4 +60,11 @@ public interface IMediaSegmentManager /// True if there are any segments stored for the item, otherwise false. /// TODO: this should be async but as the only caller BaseItem.GetVersionInfo isn't async, this is also not. Venson. bool HasSegments(Guid itemId); + + /// + /// Gets a list of all registered Segment Providers and their IDs. + /// + /// The media item that should be tested for providers. + /// A list of all providers for the tested item. + IEnumerable<(string Name, string Id)> GetSupportedProviders(BaseItem item); } diff --git a/MediaBrowser.Controller/MediaSegements/IMediaSegmentProvider.cs b/MediaBrowser.Controller/MediaSegements/IMediaSegmentProvider.cs new file mode 100644 index 000000000..39bb58bef --- /dev/null +++ b/MediaBrowser.Controller/MediaSegements/IMediaSegmentProvider.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Model; +using MediaBrowser.Model.MediaSegments; + +namespace MediaBrowser.Controller; + +/// +/// Provides methods for Obtaining the Media Segments from an Item. +/// +public interface IMediaSegmentProvider +{ + /// + /// Gets the provider name. + /// + string Name { get; } + + /// + /// Enumerates all Media Segments from an Media Item. + /// + /// Arguments to enumerate MediaSegments. + /// Abort token. + /// A list of all MediaSegments found from this provider. + Task> GetMediaSegments(MediaSegmentGenerationRequest request, CancellationToken cancellationToken); + + /// + /// Should return support state for the given item. + /// + /// The base item to extract segments from. + /// True if item is supported, otherwise false. + ValueTask Supports(BaseItem item); +} diff --git a/MediaBrowser.Model/Configuration/LibraryOptions.cs b/MediaBrowser.Model/Configuration/LibraryOptions.cs index 688a6418d..90ac377f4 100644 --- a/MediaBrowser.Model/Configuration/LibraryOptions.cs +++ b/MediaBrowser.Model/Configuration/LibraryOptions.cs @@ -11,6 +11,8 @@ namespace MediaBrowser.Model.Configuration { TypeOptions = Array.Empty(); DisabledSubtitleFetchers = Array.Empty(); + DisabledMediaSegmentProviders = Array.Empty(); + MediaSegmentProvideOrder = Array.Empty(); SubtitleFetcherOrder = Array.Empty(); DisabledLocalMetadataReaders = Array.Empty(); DisabledLyricFetchers = Array.Empty(); @@ -87,6 +89,10 @@ namespace MediaBrowser.Model.Configuration public string[] SubtitleFetcherOrder { get; set; } + public string[] DisabledMediaSegmentProviders { get; set; } + + public string[] MediaSegmentProvideOrder { get; set; } + public bool SkipSubtitlesIfEmbeddedSubtitlesPresent { get; set; } public bool SkipSubtitlesIfAudioTrackMatches { get; set; } diff --git a/MediaBrowser.Model/Configuration/MetadataPluginType.cs b/MediaBrowser.Model/Configuration/MetadataPluginType.cs index ef303726d..670d6e383 100644 --- a/MediaBrowser.Model/Configuration/MetadataPluginType.cs +++ b/MediaBrowser.Model/Configuration/MetadataPluginType.cs @@ -14,6 +14,7 @@ namespace MediaBrowser.Model.Configuration MetadataFetcher, MetadataSaver, SubtitleFetcher, - LyricFetcher + LyricFetcher, + MediaSegmentProvider } } diff --git a/MediaBrowser.Model/MediaSegments/MediaSegmentGenerationRequest.cs b/MediaBrowser.Model/MediaSegments/MediaSegmentGenerationRequest.cs new file mode 100644 index 000000000..8c1f44de8 --- /dev/null +++ b/MediaBrowser.Model/MediaSegments/MediaSegmentGenerationRequest.cs @@ -0,0 +1,14 @@ +using System; + +namespace MediaBrowser.Model; + +/// +/// Model containing the arguments for enumerating the requested media item. +/// +public record MediaSegmentGenerationRequest +{ + /// + /// Gets the Id to the BaseItem the segments should be extracted from. + /// + public Guid ItemId { get; init; } +} diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index 60d89a51b..81a9af68b 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -62,7 +62,7 @@ namespace MediaBrowser.Providers.Manager private readonly CancellationTokenSource _disposeCancellationTokenSource = new(); private readonly PriorityQueue<(Guid ItemId, MetadataRefreshOptions RefreshOptions), RefreshPriority> _refreshQueue = new(); private readonly IMemoryCache _memoryCache; - + private readonly IMediaSegmentManager _mediaSegmentManager; private readonly AsyncKeyedLocker _imageSaveLock = new(o => { o.PoolSize = 20; @@ -92,6 +92,7 @@ namespace MediaBrowser.Providers.Manager /// The BaseItem manager. /// The lyric manager. /// The memory cache. + /// The media segment manager. public ProviderManager( IHttpClientFactory httpClientFactory, ISubtitleManager subtitleManager, @@ -103,7 +104,8 @@ namespace MediaBrowser.Providers.Manager ILibraryManager libraryManager, IBaseItemManager baseItemManager, ILyricManager lyricManager, - IMemoryCache memoryCache) + IMemoryCache memoryCache, + IMediaSegmentManager mediaSegmentManager) { _logger = logger; _httpClientFactory = httpClientFactory; @@ -116,6 +118,7 @@ namespace MediaBrowser.Providers.Manager _baseItemManager = baseItemManager; _lyricManager = lyricManager; _memoryCache = memoryCache; + _mediaSegmentManager = mediaSegmentManager; } /// @@ -572,6 +575,14 @@ namespace MediaBrowser.Providers.Manager Type = MetadataPluginType.LyricFetcher })); + // Media segment providers + var mediaSegmentProviders = _mediaSegmentManager.GetSupportedProviders(dummy); + pluginList.AddRange(mediaSegmentProviders.Select(i => new MetadataPlugin + { + Name = i.Name, + Type = MetadataPluginType.MediaSegmentProvider + })); + summary.Plugins = pluginList.ToArray(); var supportedImageTypes = imageProviders.OfType() diff --git a/tests/Jellyfin.Providers.Tests/Manager/ProviderManagerTests.cs b/tests/Jellyfin.Providers.Tests/Manager/ProviderManagerTests.cs index cced2b1e2..c227883b5 100644 --- a/tests/Jellyfin.Providers.Tests/Manager/ProviderManagerTests.cs +++ b/tests/Jellyfin.Providers.Tests/Manager/ProviderManagerTests.cs @@ -574,7 +574,8 @@ namespace Jellyfin.Providers.Tests.Manager libraryManager.Object, baseItemManager!, Mock.Of(), - Mock.Of()); + Mock.Of(), + Mock.Of()); return providerManager; } -- cgit v1.2.3 From cfb19fa9fcadae91174ef13d4844e22e51f025a0 Mon Sep 17 00:00:00 2001 From: bene toffix Date: Sat, 7 Sep 2024 14:13:29 +0000 Subject: Translated using Weblate (Catalan) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ca/ --- Emby.Server.Implementations/Localization/Core/ca.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/ca.json b/Emby.Server.Implementations/Localization/Core/ca.json index 2998489b5..6b3b78fa1 100644 --- a/Emby.Server.Implementations/Localization/Core/ca.json +++ b/Emby.Server.Implementations/Localization/Core/ca.json @@ -130,5 +130,7 @@ "TaskCleanCollectionsAndPlaylistsDescription": "Esborra elements de col·leccions i llistes de reproducció que ja no existeixen.", "TaskCleanCollectionsAndPlaylists": "Neteja col·leccions i llistes de reproducció", "TaskAudioNormalization": "Normalització d'Àudio", - "TaskAudioNormalizationDescription": "Escaneja arxius per dades de normalització d'àudio." + "TaskAudioNormalizationDescription": "Escaneja arxius per dades de normalització d'àudio.", + "TaskDownloadMissingLyricsDescription": "Baixar lletres de les cançons", + "TaskDownloadMissingLyrics": "Baixar lletres que falten" } -- cgit v1.2.3 From 57b17b174f8c3677d33587373193cacaed54354b Mon Sep 17 00:00:00 2001 From: fabriciodeuner Date: Sat, 7 Sep 2024 17:21:29 +0000 Subject: Translated using Weblate (Portuguese (Brazil)) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/pt_BR/ --- Emby.Server.Implementations/Localization/Core/pt-BR.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/pt-BR.json b/Emby.Server.Implementations/Localization/Core/pt-BR.json index d9867f5e0..0c9f4c171 100644 --- a/Emby.Server.Implementations/Localization/Core/pt-BR.json +++ b/Emby.Server.Implementations/Localization/Core/pt-BR.json @@ -130,5 +130,7 @@ "TaskCleanCollectionsAndPlaylists": "Limpe coleções e playlists", "TaskCleanCollectionsAndPlaylistsDescription": "Remove itens de coleções e playlists que não existem mais.", "TaskAudioNormalization": "Normalização de áudio", - "TaskAudioNormalizationDescription": "Examina os ficheiros em busca de dados de normalização de áudio." + "TaskAudioNormalizationDescription": "Examina os ficheiros em busca de dados de normalização de áudio.", + "TaskDownloadMissingLyricsDescription": "Baixar letras para músicas", + "TaskDownloadMissingLyrics": "Baixar letra faltante" } -- cgit v1.2.3 From ae1dd5b1fcd1b9bc69c81591ca19c9a0a8e5589c Mon Sep 17 00:00:00 2001 From: fabriciodeuner Date: Sat, 7 Sep 2024 16:32:38 +0000 Subject: Translated using Weblate (Portuguese) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/pt/ --- Emby.Server.Implementations/Localization/Core/pt.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/pt.json b/Emby.Server.Implementations/Localization/Core/pt.json index ff9a0d4f4..d157547de 100644 --- a/Emby.Server.Implementations/Localization/Core/pt.json +++ b/Emby.Server.Implementations/Localization/Core/pt.json @@ -129,5 +129,7 @@ "TaskCleanCollectionsAndPlaylistsDescription": "Remove itens de coleções e listas de reprodução que já não existem.", "TaskCleanCollectionsAndPlaylists": "Limpar coleções e listas de reprodução", "TaskAudioNormalizationDescription": "Analisa os ficheiros para obter dados de normalização de áudio.", - "TaskAudioNormalization": "Normalização de áudio" + "TaskAudioNormalization": "Normalização de áudio", + "TaskDownloadMissingLyrics": "Baixar letras faltantes", + "TaskDownloadMissingLyricsDescription": "Baixa letras para músicas" } -- cgit v1.2.3 From d93eb9a87ed53633c47feef9ac424fc47bb89722 Mon Sep 17 00:00:00 2001 From: Lukáš Kucharczyk Date: Mon, 9 Sep 2024 06:51:11 +0000 Subject: Translated using Weblate (Czech) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/cs/ --- Emby.Server.Implementations/Localization/Core/cs.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/cs.json b/Emby.Server.Implementations/Localization/Core/cs.json index ad9e555a3..ba2e2700d 100644 --- a/Emby.Server.Implementations/Localization/Core/cs.json +++ b/Emby.Server.Implementations/Localization/Core/cs.json @@ -132,5 +132,9 @@ "TaskAudioNormalization": "Normalizace zvuku", "TaskAudioNormalizationDescription": "Skenovat soubory za účelem normalizace zvuku.", "TaskDownloadMissingLyrics": "Stáhnout chybějící texty k písni", - "TaskDownloadMissingLyricsDescription": "Stáhne texty k písni" + "TaskDownloadMissingLyricsDescription": "Stáhne texty k písni", + "TaskExtractMediaSegments": "Skenování segmentů médií", + "TaskExtractMediaSegmentsDescription": "Extrahuje či získá segmenty médií pomocí zásuvných modulů MediaSegment.", + "TaskMoveTrickplayImages": "Přesunout úložiště obrázků Trickplay", + "TaskMoveTrickplayImagesDescription": "Přesune existující soubory Trickplay podle nastavení knihovny." } -- cgit v1.2.3 From 98ea585a0f93dd8eda7d671beaf724525233b799 Mon Sep 17 00:00:00 2001 From: Lea3D Date: Mon, 9 Sep 2024 11:07:45 +0000 Subject: Translated using Weblate (German) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/de/ --- Emby.Server.Implementations/Localization/Core/de.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/de.json b/Emby.Server.Implementations/Localization/Core/de.json index bbb162c77..51c9e87d5 100644 --- a/Emby.Server.Implementations/Localization/Core/de.json +++ b/Emby.Server.Implementations/Localization/Core/de.json @@ -132,5 +132,9 @@ "TaskAudioNormalization": "Audio Normalisierung", "TaskAudioNormalizationDescription": "Durchsucht Dateien nach Audionormalisierungsdaten.", "TaskDownloadMissingLyricsDescription": "Lädt Songtexte herunter", - "TaskDownloadMissingLyrics": "Fehlende Songtexte herunterladen" + "TaskDownloadMissingLyrics": "Fehlende Songtexte herunterladen", + "TaskExtractMediaSegments": "Scanne Mediensegmente", + "TaskExtractMediaSegmentsDescription": "Extrahiert oder empfängt Mediensegmente von Plugins die Mediensegmente nutzen.", + "TaskMoveTrickplayImages": "Verzeichnis für Trickplay-Bilder migrieren", + "TaskMoveTrickplayImagesDescription": "Trickplay-Bilder werden entsprechend der Bibliothekseinstellungen verschoben." } -- cgit v1.2.3 From 0003a55c1dc8e27339db3d0a78a32970e09dac6b Mon Sep 17 00:00:00 2001 From: Federico Abella Date: Sun, 8 Sep 2024 23:53:24 +0000 Subject: Translated using Weblate (Spanish (Argentina)) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/es_AR/ --- Emby.Server.Implementations/Localization/Core/es-AR.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/es-AR.json b/Emby.Server.Implementations/Localization/Core/es-AR.json index b926d9d30..f2f657b04 100644 --- a/Emby.Server.Implementations/Localization/Core/es-AR.json +++ b/Emby.Server.Implementations/Localization/Core/es-AR.json @@ -132,5 +132,9 @@ "TaskCleanCollectionsAndPlaylists": "Limpiar colecciones y listas de reproducción", "TaskCleanCollectionsAndPlaylistsDescription": "Elimina elementos de colecciones y listas de reproducción que ya no existen.", "TaskDownloadMissingLyrics": "Descargar letra faltante", - "TaskDownloadMissingLyricsDescription": "Descarga letras de canciones" + "TaskDownloadMissingLyricsDescription": "Descarga letras de canciones", + "TaskExtractMediaSegments": "Escanear Segmentos de Media", + "TaskExtractMediaSegmentsDescription": "Extrae u obtiene segmentos de medio de plugins habilitados para MediaSegment.", + "TaskMoveTrickplayImagesDescription": "Mueve archivos existentes de trickplay de acuerdo a la configuración de la biblioteca.", + "TaskMoveTrickplayImages": "Migrar Ubicación de Imagen de Trickplay" } -- cgit v1.2.3 From 9bbb3b61642d9b9500b96ba694f624cae0a60e75 Mon Sep 17 00:00:00 2001 From: Bas <44002186+854562@users.noreply.github.com> Date: Sun, 8 Sep 2024 14:45:25 +0000 Subject: Translated using Weblate (Dutch) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/nl/ --- Emby.Server.Implementations/Localization/Core/nl.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/nl.json b/Emby.Server.Implementations/Localization/Core/nl.json index 39e7cd546..1522720dc 100644 --- a/Emby.Server.Implementations/Localization/Core/nl.json +++ b/Emby.Server.Implementations/Localization/Core/nl.json @@ -132,5 +132,9 @@ "TaskAudioNormalization": "Geluidsnormalisatie", "TaskAudioNormalizationDescription": "Scant bestanden op gegevens voor geluidsnormalisatie.", "TaskDownloadMissingLyrics": "Ontbrekende liedteksten downloaden", - "TaskDownloadMissingLyricsDescription": "Downloadt liedteksten" + "TaskDownloadMissingLyricsDescription": "Downloadt liedteksten", + "TaskExtractMediaSegmentsDescription": "Verkrijgt mediasegmenten vanuit plug-ins met MediaSegment-ondersteuning.", + "TaskMoveTrickplayImages": "Locatie trickplay-afbeeldingen migreren", + "TaskMoveTrickplayImagesDescription": "Verplaatst bestaande trickplay-bestanden op basis van de bibliotheekinstellingen.", + "TaskExtractMediaSegments": "Scannen op mediasegmenten" } -- cgit v1.2.3 From 34323ae811283ea93f31babf19ee12c58837705d Mon Sep 17 00:00:00 2001 From: Kityn Date: Mon, 9 Sep 2024 05:40:06 +0000 Subject: Translated using Weblate (Polish) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/pl/ --- Emby.Server.Implementations/Localization/Core/pl.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/pl.json b/Emby.Server.Implementations/Localization/Core/pl.json index a24a837ab..33b0bb7e1 100644 --- a/Emby.Server.Implementations/Localization/Core/pl.json +++ b/Emby.Server.Implementations/Localization/Core/pl.json @@ -132,5 +132,9 @@ "TaskAudioNormalization": "Normalizacja dźwięku", "TaskAudioNormalizationDescription": "Skanuje pliki w poszukiwaniu danych normalizacji dźwięku.", "TaskDownloadMissingLyrics": "Pobierz brakujące słowa", - "TaskDownloadMissingLyricsDescription": "Pobierz słowa piosenek" + "TaskDownloadMissingLyricsDescription": "Pobierz słowa piosenek", + "TaskExtractMediaSegments": "Skanowanie segmentów mediów", + "TaskMoveTrickplayImages": "Migruj lokalizację obrazu Trickplay", + "TaskExtractMediaSegmentsDescription": "Wyodrębnia lub pobiera segmenty mediów z wtyczek obsługujących MediaSegment.", + "TaskMoveTrickplayImagesDescription": "Przenosi istniejące pliki Trickplay zgodnie z ustawieniami biblioteki." } -- cgit v1.2.3 From 43861f0ce112873514d3f97f3c4320525d642b39 Mon Sep 17 00:00:00 2001 From: Jolter Date: Sun, 8 Sep 2024 19:38:23 +0000 Subject: Translated using Weblate (Swedish) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/sv/ --- Emby.Server.Implementations/Localization/Core/sv.json | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/sv.json b/Emby.Server.Implementations/Localization/Core/sv.json index a4e2302d1..5cf54522b 100644 --- a/Emby.Server.Implementations/Localization/Core/sv.json +++ b/Emby.Server.Implementations/Localization/Core/sv.json @@ -9,7 +9,7 @@ "Channels": "Kanaler", "ChapterNameValue": "Kapitel {0}", "Collections": "Samlingar", - "DeviceOfflineWithName": "{0} har avbrutit uppkopplingen", + "DeviceOfflineWithName": "{0} har kopplat ned", "DeviceOnlineWithName": "{0} är ansluten", "FailedLoginAttemptWithUserName": "Misslyckat inloggningsförsök från {0}", "Favorites": "Favoriter", @@ -121,7 +121,7 @@ "Default": "Standard", "TaskOptimizeDatabase": "Optimera databasen", "TaskOptimizeDatabaseDescription": "Komprimerar databasen och trunkerar ledigt utrymme. Prestandan kan förbättras genom att köra denna aktivitet efter att du har skannat biblioteket eller gjort andra förändringar som indikerar att databasen har modifierats.", - "TaskKeyframeExtractorDescription": "Exporterar nyckelbildrutor från videofiler för att skapa mer exakta HLS-spellistor. Denna rutin kan ta lång tid.", + "TaskKeyframeExtractorDescription": "Exporterar nyckelbildrutor från videofiler för att skapa mer exakta HLS-spellistor. Denna körning kan ta lång tid.", "TaskKeyframeExtractor": "Extraktor för nyckelbildrutor", "External": "Extern", "HearingImpaired": "Hörselskadad", @@ -132,5 +132,9 @@ "TaskCleanCollectionsAndPlaylistsDescription": "Tar bort objekt från samlingar och spellistor som inte längre finns.", "TaskAudioNormalizationDescription": "Skannar filer för ljudnormaliseringsdata.", "TaskDownloadMissingLyrics": "Ladda ner saknad låttext", - "TaskDownloadMissingLyricsDescription": "Laddar ner låttexter" + "TaskDownloadMissingLyricsDescription": "Laddar ner låttexter", + "TaskExtractMediaSegments": "Skanning av mediesegment", + "TaskExtractMediaSegmentsDescription": "Extraherar eller hämtar ut mediesegmen från tillägg som stöder MediaSegment.", + "TaskMoveTrickplayImages": "Migrera platsen för Trickplay-bilder", + "TaskMoveTrickplayImagesDescription": "Flyttar befintliga trickplay-filer enligt bibliotekets inställningar." } -- cgit v1.2.3 From 987dbe98c8ab55c5c8eb563820e54453c835cdde Mon Sep 17 00:00:00 2001 From: gnattu Date: Tue, 10 Sep 2024 03:17:10 +0800 Subject: cli: add option to disable network change detection (#11253) --- Emby.Server.Implementations/ConfigurationOptions.cs | 3 ++- Jellyfin.Server/StartupOptions.cs | 11 +++++++++++ MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs | 5 +++++ src/Jellyfin.Networking/Manager/NetworkManager.cs | 9 +++++++-- 4 files changed, 25 insertions(+), 3 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/ConfigurationOptions.cs b/Emby.Server.Implementations/ConfigurationOptions.cs index e86010513..702707297 100644 --- a/Emby.Server.Implementations/ConfigurationOptions.cs +++ b/Emby.Server.Implementations/ConfigurationOptions.cs @@ -20,7 +20,8 @@ namespace Emby.Server.Implementations { PlaylistsAllowDuplicatesKey, bool.FalseString }, { BindToUnixSocketKey, bool.FalseString }, { SqliteCacheSizeKey, "20000" }, - { FfmpegSkipValidationKey, bool.FalseString } + { FfmpegSkipValidationKey, bool.FalseString }, + { DetectNetworkChangeKey, bool.TrueString } }; } } diff --git a/Jellyfin.Server/StartupOptions.cs b/Jellyfin.Server/StartupOptions.cs index c3989751c..91ac827ca 100644 --- a/Jellyfin.Server/StartupOptions.cs +++ b/Jellyfin.Server/StartupOptions.cs @@ -67,6 +67,12 @@ namespace Jellyfin.Server [Option("published-server-url", Required = false, HelpText = "Jellyfin Server URL to publish via auto discover process")] public string? PublishedServerUrl { get; set; } + /// + /// Gets or sets a value indicating whether the server should not detect network status change. + /// + [Option("nonetchange", Required = false, HelpText = "Indicates that the server should not detect network status change.")] + public bool NoDetectNetworkChange { get; set; } + /// /// Gets the command line options as a dictionary that can be used in the .NET configuration system. /// @@ -90,6 +96,11 @@ namespace Jellyfin.Server config.Add(FfmpegPathKey, FFmpegPath); } + if (NoDetectNetworkChange) + { + config.Add(DetectNetworkChangeKey, bool.FalseString); + } + return config; } } diff --git a/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs b/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs index 0aaf4fcd9..7ca508426 100644 --- a/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs +++ b/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs @@ -69,6 +69,11 @@ namespace MediaBrowser.Controller.Extensions /// public const string SqliteCacheSizeKey = "sqlite:cacheSize"; + /// + /// The key for a setting that indicates whether the application should detect network status change. + /// + public const string DetectNetworkChangeKey = "DetectNetworkChange"; + /// /// Gets a value indicating whether the application should host static web content from the . /// diff --git a/src/Jellyfin.Networking/Manager/NetworkManager.cs b/src/Jellyfin.Networking/Manager/NetworkManager.cs index cf6a2cc55..b285b836b 100644 --- a/src/Jellyfin.Networking/Manager/NetworkManager.cs +++ b/src/Jellyfin.Networking/Manager/NetworkManager.cs @@ -97,10 +97,15 @@ public class NetworkManager : INetworkManager, IDisposable _networkEventLock = new object(); _remoteAddressFilter = new List(); + _ = bool.TryParse(startupConfig[DetectNetworkChangeKey], out var detectNetworkChange); + UpdateSettings(_configurationManager.GetNetworkConfiguration()); - NetworkChange.NetworkAddressChanged += OnNetworkAddressChanged; - NetworkChange.NetworkAvailabilityChanged += OnNetworkAvailabilityChanged; + if (detectNetworkChange) + { + NetworkChange.NetworkAddressChanged += OnNetworkAddressChanged; + NetworkChange.NetworkAvailabilityChanged += OnNetworkAvailabilityChanged; + } _configurationManager.NamedConfigurationUpdated += ConfigurationUpdated; } -- cgit v1.2.3 From c14b5306928f489cba15562f13421026f5aee561 Mon Sep 17 00:00:00 2001 From: Hoomaane79 Date: Mon, 9 Sep 2024 18:55:47 +0000 Subject: Translated using Weblate (Persian) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/fa/ --- Emby.Server.Implementations/Localization/Core/fa.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/fa.json b/Emby.Server.Implementations/Localization/Core/fa.json index b0ddec104..ff14c1367 100644 --- a/Emby.Server.Implementations/Localization/Core/fa.json +++ b/Emby.Server.Implementations/Localization/Core/fa.json @@ -132,5 +132,9 @@ "TaskAudioNormalizationDescription": "بررسی فایل برای داده‌های نرمال کردن صدا.", "TaskDownloadMissingLyrics": "دانلود متن‌های ناموجود", "TaskDownloadMissingLyricsDescription": "دانلود متن شعر‌ها", - "TaskAudioNormalization": "نرمال کردن صدا" + "TaskAudioNormalization": "نرمال کردن صدا", + "TaskExtractMediaSegments": "بررسی بخش محتوا", + "TaskExtractMediaSegmentsDescription": "بخش‌های محتوا را از افزونه‌های مربوط استخراح می‌کند.", + "TaskMoveTrickplayImages": "جابه‌جایی عکس‌های Trickplay", + "TaskMoveTrickplayImagesDescription": "داده‌های Trickplay را با توجه به تنظیمات کتاب‌خانه جابه‌جا می‌کند." } -- cgit v1.2.3 From 7d2a498f68c758347fc0437e142c476353aed783 Mon Sep 17 00:00:00 2001 From: 無情天 Date: Mon, 9 Sep 2024 16:46:17 +0000 Subject: Translated using Weblate (Chinese (Simplified)) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/zh_Hans/ --- Emby.Server.Implementations/Localization/Core/zh-CN.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/zh-CN.json b/Emby.Server.Implementations/Localization/Core/zh-CN.json index 4bec590fb..a2337e069 100644 --- a/Emby.Server.Implementations/Localization/Core/zh-CN.json +++ b/Emby.Server.Implementations/Localization/Core/zh-CN.json @@ -132,5 +132,9 @@ "TaskAudioNormalization": "音频标准化", "TaskAudioNormalizationDescription": "扫描文件以寻找音频标准化数据。", "TaskDownloadMissingLyrics": "下载缺失的歌词", - "TaskDownloadMissingLyricsDescription": "下载歌曲歌词" + "TaskDownloadMissingLyricsDescription": "下载歌曲歌词", + "TaskMoveTrickplayImages": "迁移 特技播放 图像位置", + "TaskExtractMediaSegments": "媒体片段扫描", + "TaskExtractMediaSegmentsDescription": "从启用 MediaSegment 的插件中提取或获取媒体片段。", + "TaskMoveTrickplayImagesDescription": "根据媒体库设置移动现有的特技播放文件。" } -- cgit v1.2.3 From c67b78bc68317c66f3324230b2fc36918b2b9d70 Mon Sep 17 00:00:00 2001 From: stanol Date: Tue, 10 Sep 2024 14:51:15 +0000 Subject: Translated using Weblate (Ukrainian) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/uk/ --- Emby.Server.Implementations/Localization/Core/uk.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/uk.json b/Emby.Server.Implementations/Localization/Core/uk.json index 97bad4532..3fddc2e78 100644 --- a/Emby.Server.Implementations/Localization/Core/uk.json +++ b/Emby.Server.Implementations/Localization/Core/uk.json @@ -131,5 +131,9 @@ "TaskAudioNormalizationDescription": "Сканує файли на наявність даних для нормалізації звуку.", "TaskAudioNormalization": "Нормалізація аудіо", "TaskDownloadMissingLyrics": "Завантажити відсутні тексти пісень", - "TaskDownloadMissingLyricsDescription": "Завантаження текстів пісень" + "TaskDownloadMissingLyricsDescription": "Завантаження текстів пісень", + "TaskMoveTrickplayImagesDescription": "Переміщує наявні Trickplay-зображення відповідно до налаштувань медіатеки.", + "TaskExtractMediaSegments": "Сканування медіа-сегментів", + "TaskMoveTrickplayImages": "Змінити місце розташування Trickplay-зображень", + "TaskExtractMediaSegmentsDescription": "Витягує або отримує медіа-сегменти з плагінів з підтримкою MediaSegment." } -- cgit v1.2.3 From 624800a1c79b4e272ade89a73790579f6ecbe115 Mon Sep 17 00:00:00 2001 From: Andi Chandler Date: Tue, 10 Sep 2024 20:28:58 +0000 Subject: Translated using Weblate (English (United Kingdom)) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/en_GB/ --- Emby.Server.Implementations/Localization/Core/en-GB.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/en-GB.json b/Emby.Server.Implementations/Localization/Core/en-GB.json index 65df1e45b..ca52ffb14 100644 --- a/Emby.Server.Implementations/Localization/Core/en-GB.json +++ b/Emby.Server.Implementations/Localization/Core/en-GB.json @@ -132,5 +132,9 @@ "TaskAudioNormalization": "Audio Normalisation", "TaskAudioNormalizationDescription": "Scans files for audio normalisation data.", "TaskDownloadMissingLyrics": "Download missing lyrics", - "TaskDownloadMissingLyricsDescription": "Downloads lyrics for songs" + "TaskDownloadMissingLyricsDescription": "Downloads lyrics for songs", + "TaskExtractMediaSegments": "Media Segment Scan", + "TaskExtractMediaSegmentsDescription": "Extracts or obtains media segments from MediaSegment enabled plugins.", + "TaskMoveTrickplayImages": "Migrate Trickplay Image Location", + "TaskMoveTrickplayImagesDescription": "Moves existing trickplay files according to the library settings." } -- cgit v1.2.3 From 23590bb962c553758194c06e4585c5ee64189540 Mon Sep 17 00:00:00 2001 From: nextlooper42 Date: Wed, 11 Sep 2024 09:34:29 +0000 Subject: Translated using Weblate (Slovak) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/sk/ --- Emby.Server.Implementations/Localization/Core/sk.json | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/sk.json b/Emby.Server.Implementations/Localization/Core/sk.json index a9b6fbeef..66d8bf899 100644 --- a/Emby.Server.Implementations/Localization/Core/sk.json +++ b/Emby.Server.Implementations/Localization/Core/sk.json @@ -130,5 +130,11 @@ "TaskCleanCollectionsAndPlaylists": "Vyčistiť kolekcie a playlisty", "TaskCleanCollectionsAndPlaylistsDescription": "Odstráni položky z kolekcií a playlistov, ktoré už neexistujú.", "TaskAudioNormalization": "Normalizácia zvuku", - "TaskAudioNormalizationDescription": "Skenovať súbory za účelom normalizácie zvuku." + "TaskAudioNormalizationDescription": "Skenovať súbory za účelom normalizácie zvuku.", + "TaskExtractMediaSegments": "Skenovanie segmentov médií", + "TaskExtractMediaSegmentsDescription": "Extrahuje alebo získava segmenty médií zo zásuvných modulov s povolenou funkciou MediaSegment.", + "TaskMoveTrickplayImages": "Presunúť umiestnenie obrázkov Trickplay", + "TaskMoveTrickplayImagesDescription": "Presunie existujúce súbory Trickplay podľa nastavení knižnice.", + "TaskDownloadMissingLyrics": "Stiahnuť chýbajúce texty piesní", + "TaskDownloadMissingLyricsDescription": "Stiahne texty pre piesne" } -- cgit v1.2.3 From fdb3f3c7b760a8e0319645b34fcafce58e75e9f9 Mon Sep 17 00:00:00 2001 From: queeup Date: Tue, 10 Sep 2024 21:14:02 +0000 Subject: Translated using Weblate (Turkish) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/tr/ --- Emby.Server.Implementations/Localization/Core/tr.json | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/tr.json b/Emby.Server.Implementations/Localization/Core/tr.json index 1dceadc61..a3cf78fcb 100644 --- a/Emby.Server.Implementations/Localization/Core/tr.json +++ b/Emby.Server.Implementations/Localization/Core/tr.json @@ -130,5 +130,11 @@ "TaskCleanCollectionsAndPlaylistsDescription": "Artık var olmayan koleksiyon ve çalma listelerindeki ögeleri kaldırır.", "TaskCleanCollectionsAndPlaylists": "Koleksiyonları ve çalma listelerini temizleyin", "TaskAudioNormalizationDescription": "Ses normalleştirme verileri için dosyaları tarar.", - "TaskAudioNormalization": "Ses Normalleştirme" + "TaskAudioNormalization": "Ses Normalleştirme", + "TaskExtractMediaSegments": "Medya Segmenti Tarama", + "TaskMoveTrickplayImages": "Trickplay Görsel Konumunu Taşıma", + "TaskMoveTrickplayImagesDescription": "Mevcut trickplay dosyalarını kütüphane ayarlarına göre taşır.", + "TaskDownloadMissingLyrics": "Eksik şarkı sözlerini indir", + "TaskDownloadMissingLyricsDescription": "Şarkı sözlerini indirir", + "TaskExtractMediaSegmentsDescription": "MediaSegment özelliği etkin olan eklentilerden medya segmentlerini çıkarır veya alır." } -- cgit v1.2.3 From 074d9aa5d5cb191e7baca669dfe86314d229ca5a Mon Sep 17 00:00:00 2001 From: Nyanmisaka <799610810@qq.com> Date: Wed, 11 Sep 2024 18:55:22 +0000 Subject: Translated using Weblate (Chinese (Simplified)) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/zh_Hans/ --- Emby.Server.Implementations/Localization/Core/zh-CN.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/zh-CN.json b/Emby.Server.Implementations/Localization/Core/zh-CN.json index a2337e069..cbec0979a 100644 --- a/Emby.Server.Implementations/Localization/Core/zh-CN.json +++ b/Emby.Server.Implementations/Localization/Core/zh-CN.json @@ -133,8 +133,8 @@ "TaskAudioNormalizationDescription": "扫描文件以寻找音频标准化数据。", "TaskDownloadMissingLyrics": "下载缺失的歌词", "TaskDownloadMissingLyricsDescription": "下载歌曲歌词", - "TaskMoveTrickplayImages": "迁移 特技播放 图像位置", + "TaskMoveTrickplayImages": "迁移时间轴缩略图的存储位置", "TaskExtractMediaSegments": "媒体片段扫描", - "TaskExtractMediaSegmentsDescription": "从启用 MediaSegment 的插件中提取或获取媒体片段。", + "TaskExtractMediaSegmentsDescription": "从支持 MediaSegment 的插件中提取或获取媒体片段。", "TaskMoveTrickplayImagesDescription": "根据媒体库设置移动现有的特技播放文件。" } -- cgit v1.2.3 From 751e12e5b5c59f9df4494251166f47c8cf9ebfe1 Mon Sep 17 00:00:00 2001 From: felix920506 Date: Tue, 10 Sep 2024 22:07:04 +0000 Subject: Translated using Weblate (Chinese (Traditional)) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/zh_Hant/ --- Emby.Server.Implementations/Localization/Core/zh-TW.json | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/zh-TW.json b/Emby.Server.Implementations/Localization/Core/zh-TW.json index f06bbc591..81d5b83d6 100644 --- a/Emby.Server.Implementations/Localization/Core/zh-TW.json +++ b/Emby.Server.Implementations/Localization/Core/zh-TW.json @@ -129,5 +129,11 @@ "TaskCleanCollectionsAndPlaylists": "清理系列和播放清單", "TaskCleanCollectionsAndPlaylistsDescription": "清理系列和播放清單中已不存在的項目。", "TaskAudioNormalization": "音量標準化", - "TaskAudioNormalizationDescription": "掃描文件以找出音量標準化資料。" + "TaskAudioNormalizationDescription": "掃描文件以找出音量標準化資料。", + "TaskDownloadMissingLyrics": "下載缺少的歌詞", + "TaskDownloadMissingLyricsDescription": "卡在歌曲歌詞", + "TaskExtractMediaSegments": "掃描媒體片段", + "TaskExtractMediaSegmentsDescription": "從使用媒體片段的擴充功能取得媒體片段。", + "TaskMoveTrickplayImages": "遷移快轉縮圖位置", + "TaskMoveTrickplayImagesDescription": "根據媒體庫的設定遷移快轉縮圖的檔案。" } -- cgit v1.2.3 From 6b646e24eabdfdd150f1ff72f837d37b43f3b985 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Thu, 12 Sep 2024 21:44:57 +0200 Subject: Don't extract chapter images if chapters are <1s long on average (#11832) --- .../MediaEncoder/EncodingManager.cs | 30 ++++++++++++++++++++++ 1 file changed, 30 insertions(+) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs b/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs index 896f47923..784bac5d0 100644 --- a/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs +++ b/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs @@ -91,8 +91,30 @@ namespace Emby.Server.Implementations.MediaEncoder return video.DefaultVideoStreamIndex.HasValue; } + private long GetAverageDurationBetweenChapters(IReadOnlyList chapters) + { + if (chapters.Count < 2) + { + return 0; + } + + long sum = 0; + for (int i = 1; i < chapters.Count; i++) + { + sum += chapters[i].StartPositionTicks - chapters[i - 1].StartPositionTicks; + } + + return sum / chapters.Count; + } + + public async Task RefreshChapterImages(Video video, IDirectoryService directoryService, IReadOnlyList chapters, bool extractImages, bool saveChapters, CancellationToken cancellationToken) { + if (chapters.Count == 0) + { + return true; + } + var libraryOptions = _libraryManager.GetLibraryOptions(video); if (!IsEligibleForChapterImageExtraction(video, libraryOptions)) @@ -100,6 +122,14 @@ namespace Emby.Server.Implementations.MediaEncoder extractImages = false; } + var averageChapterDuration = GetAverageDurationBetweenChapters(chapters); + var threshold = TimeSpan.FromSeconds(1).Ticks; + if (averageChapterDuration < threshold) + { + _logger.LogInformation("Skipping chapter image extraction for {Video} as the average chapter duration {AverageDuration} was lower than the minimum threshold {Threshold}", video.Name, averageChapterDuration, threshold); + extractImages = false; + } + var success = true; var changesMade = false; -- cgit v1.2.3 From ac55682260706f894536b09e5946e79cb14a9672 Mon Sep 17 00:00:00 2001 From: BromTeque Date: Thu, 12 Sep 2024 13:24:45 +0000 Subject: Translated using Weblate (Norwegian Bokmål) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/nb_NO/ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Emby.Server.Implementations/Localization/Core/nb.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/nb.json b/Emby.Server.Implementations/Localization/Core/nb.json index 747652538..6d644976d 100644 --- a/Emby.Server.Implementations/Localization/Core/nb.json +++ b/Emby.Server.Implementations/Localization/Core/nb.json @@ -132,5 +132,6 @@ "TaskAudioNormalizationDescription": "Skan filer for lyd normaliserende data", "TaskCleanCollectionsAndPlaylistsDescription": "Fjerner elementer fra kolleksjoner og spillelister som ikke lengere finnes", "TaskDownloadMissingLyrics": "Last ned manglende tekster", - "TaskDownloadMissingLyricsDescription": "Last ned sangtekster" + "TaskDownloadMissingLyricsDescription": "Last ned sangtekster", + "TaskExtractMediaSegments": "Skann mediasegment" } -- cgit v1.2.3 From acbe4082a8356806dc7ce8d45e6be40908b822e7 Mon Sep 17 00:00:00 2001 From: Ruben Teixeira Date: Thu, 12 Sep 2024 19:24:10 +0000 Subject: Translated using Weblate (Portuguese) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/pt/ --- Emby.Server.Implementations/Localization/Core/pt.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/pt.json b/Emby.Server.Implementations/Localization/Core/pt.json index d157547de..7e9be76e5 100644 --- a/Emby.Server.Implementations/Localization/Core/pt.json +++ b/Emby.Server.Implementations/Localization/Core/pt.json @@ -131,5 +131,9 @@ "TaskAudioNormalizationDescription": "Analisa os ficheiros para obter dados de normalização de áudio.", "TaskAudioNormalization": "Normalização de áudio", "TaskDownloadMissingLyrics": "Baixar letras faltantes", - "TaskDownloadMissingLyricsDescription": "Baixa letras para músicas" + "TaskDownloadMissingLyricsDescription": "Baixa letras para músicas", + "TaskMoveTrickplayImagesDescription": "Transfere ficheiros de miniatura de vídeo, conforme as definições da biblioteca.", + "TaskExtractMediaSegments": "Varrimento de segmentos da média", + "TaskExtractMediaSegmentsDescription": "Extrai ou obtém segmentos de média de extensões com suporte a MediaSegment.", + "TaskMoveTrickplayImages": "Migração de miniaturas de vídeo" } -- cgit v1.2.3 From a529edaad17fa048d654fcd90dd71a7454fe3174 Mon Sep 17 00:00:00 2001 From: Josh Hood Date: Wed, 11 Sep 2024 21:47:22 +0000 Subject: Translated using Weblate (Cornish) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/kw/ --- Emby.Server.Implementations/Localization/Core/kw.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/kw.json b/Emby.Server.Implementations/Localization/Core/kw.json index ffb4345c8..336d286fc 100644 --- a/Emby.Server.Implementations/Localization/Core/kw.json +++ b/Emby.Server.Implementations/Localization/Core/kw.json @@ -131,5 +131,9 @@ "TaskCleanCollectionsAndPlaylists": "Glanhe kuntellow ha rolyow-gwari", "TaskKeyframeExtractor": "Estennell Framalhwedh", "TaskCleanCollectionsAndPlaylistsDescription": "Y hwra dilea taklow a-dhyworth kuntellow ha rolyow-gwari na vos na moy.", - "TaskKeyframeExtractorDescription": "Y hwra kuntel framyowalhwedh a-dhyworth restrennow gwydhyowyow rag gul rolyow-gwari HLS moy poran. Martesen y hwra an oberen ma ow ponya rag termyn hir." + "TaskKeyframeExtractorDescription": "Y hwra kuntel framyowalhwedh a-dhyworth restrennow gwydhyowyow rag gul rolyow-gwari HLS moy poran. Martesen y hwra an oberen ma ow ponya rag termyn hir.", + "TaskExtractMediaSegments": "Arhwilas Rann Media", + "TaskExtractMediaSegmentsDescription": "Kavos rannow media a-dhyworth ystynansow gallosegys MediaSegment.", + "TaskMoveTrickplayImages": "Divroa Tyller Imach TrickPlay", + "TaskMoveTrickplayImagesDescription": "Y hwra movya restrennow a-lemmyn trickplay herwydh settyansow lyverva." } -- cgit v1.2.3 From 3c64e1d33fecc4815a9f491d08833310e7a906a2 Mon Sep 17 00:00:00 2001 From: gnattu Date: Fri, 13 Sep 2024 10:07:10 +0800 Subject: Remove redundant newline to fix CI Signed-off-by: gnattu --- Emby.Server.Implementations/MediaEncoder/EncodingManager.cs | 1 - 1 file changed, 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs b/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs index 784bac5d0..eb55e32c5 100644 --- a/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs +++ b/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs @@ -107,7 +107,6 @@ namespace Emby.Server.Implementations.MediaEncoder return sum / chapters.Count; } - public async Task RefreshChapterImages(Video video, IDirectoryService directoryService, IReadOnlyList chapters, bool extractImages, bool saveChapters, CancellationToken cancellationToken) { if (chapters.Count == 0) -- cgit v1.2.3 From 195142861c195131dfb13b86f3b5cbbbe640356d Mon Sep 17 00:00:00 2001 From: NonameMissingNo Date: Sat, 14 Sep 2024 12:34:21 +0000 Subject: Translated using Weblate (Hungarian) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/hu/ --- Emby.Server.Implementations/Localization/Core/hu.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/hu.json b/Emby.Server.Implementations/Localization/Core/hu.json index 31d6aaedb..2c8533ac6 100644 --- a/Emby.Server.Implementations/Localization/Core/hu.json +++ b/Emby.Server.Implementations/Localization/Core/hu.json @@ -130,5 +130,8 @@ "TaskAudioNormalization": "Hangerő Normalizáció", "TaskCleanCollectionsAndPlaylistsDescription": "Nem létező elemek törlése a gyűjteményekből és lejátszási listákról.", "TaskAudioNormalizationDescription": "Hangerő normalizációs adatok keresése.", - "TaskCleanCollectionsAndPlaylists": "Gyűjtemények és lejátszási listák optimalizálása" + "TaskCleanCollectionsAndPlaylists": "Gyűjtemények és lejátszási listák optimalizálása", + "TaskExtractMediaSegments": "Média szegmens felismerése", + "TaskDownloadMissingLyrics": "Hiányzó szöveg letöltése", + "TaskDownloadMissingLyricsDescription": "Zenék szövegének letöltése" } -- cgit v1.2.3 From 7df938674e08cf0387965866735dd398cbc4cb6c Mon Sep 17 00:00:00 2001 From: Sebastião josé Date: Fri, 13 Sep 2024 23:36:03 +0000 Subject: Translated using Weblate (Portuguese (Brazil)) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/pt_BR/ --- Emby.Server.Implementations/Localization/Core/pt-BR.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/pt-BR.json b/Emby.Server.Implementations/Localization/Core/pt-BR.json index 0c9f4c171..9bc012ae5 100644 --- a/Emby.Server.Implementations/Localization/Core/pt-BR.json +++ b/Emby.Server.Implementations/Localization/Core/pt-BR.json @@ -132,5 +132,9 @@ "TaskAudioNormalization": "Normalização de áudio", "TaskAudioNormalizationDescription": "Examina os ficheiros em busca de dados de normalização de áudio.", "TaskDownloadMissingLyricsDescription": "Baixar letras para músicas", - "TaskDownloadMissingLyrics": "Baixar letra faltante" + "TaskDownloadMissingLyrics": "Baixar letra faltante", + "TaskMoveTrickplayImagesDescription": "Move os arquivos do trickplay de acordo com as configurações da biblioteca.", + "TaskExtractMediaSegments": "Varredura do segmento de mídia", + "TaskExtractMediaSegmentsDescription": "Extrai ou obtém segmentos de mídia de plug-ins habilitados para MediaSegment.", + "TaskMoveTrickplayImages": "Migrar o local da imagem do Trickplay" } -- cgit v1.2.3 From 66bfb2b4f81b0a3a5fb8671254e5a658be715091 Mon Sep 17 00:00:00 2001 From: xwr Date: Fri, 13 Sep 2024 17:36:21 +0000 Subject: Translated using Weblate (Galician) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/gl/ --- Emby.Server.Implementations/Localization/Core/gl.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/gl.json b/Emby.Server.Implementations/Localization/Core/gl.json index 76a98aa54..3ba3e6679 100644 --- a/Emby.Server.Implementations/Localization/Core/gl.json +++ b/Emby.Server.Implementations/Localization/Core/gl.json @@ -1,7 +1,7 @@ { "Albums": "Álbumes", - "Collections": "Colecións", - "ChapterNameValue": "Capítulos {0}", + "Collections": "Coleccións", + "ChapterNameValue": "Capítulo {0}", "Channels": "Canles", "CameraImageUploadedFrom": "Cargouse unha nova imaxe da cámara desde {0}", "Books": "Libros", -- cgit v1.2.3 From 523e0c927eeac074604cb625331423e801039d75 Mon Sep 17 00:00:00 2001 From: Filipe Motta Date: Sun, 15 Sep 2024 22:57:49 +0000 Subject: Translated using Weblate (Portuguese (Brazil)) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/pt_BR/ --- Emby.Server.Implementations/Localization/Core/pt-BR.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/pt-BR.json b/Emby.Server.Implementations/Localization/Core/pt-BR.json index 9bc012ae5..9f4f58cb6 100644 --- a/Emby.Server.Implementations/Localization/Core/pt-BR.json +++ b/Emby.Server.Implementations/Localization/Core/pt-BR.json @@ -8,7 +8,7 @@ "CameraImageUploadedFrom": "Uma nova imagem da câmera foi enviada de {0}", "Channels": "Canais", "ChapterNameValue": "Capítulo {0}", - "Collections": "Coletâneas", + "Collections": "Coleções", "DeviceOfflineWithName": "{0} se desconectou", "DeviceOnlineWithName": "{0} se conectou", "FailedLoginAttemptWithUserName": "Falha na tentativa de login de {0}", -- cgit v1.2.3 From 2a6f7c1a40af972a6b5101f11e258eba459d331c Mon Sep 17 00:00:00 2001 From: sand14 Date: Sun, 15 Sep 2024 00:34:54 +0000 Subject: Translated using Weblate (Romanian) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ro/ --- Emby.Server.Implementations/Localization/Core/ro.json | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/ro.json b/Emby.Server.Implementations/Localization/Core/ro.json index 2f52aafa3..bf59e1583 100644 --- a/Emby.Server.Implementations/Localization/Core/ro.json +++ b/Emby.Server.Implementations/Localization/Core/ro.json @@ -129,5 +129,11 @@ "TaskAudioNormalizationDescription": "Scanează fișiere pentru date necesare normalizării sunetului.", "TaskAudioNormalization": "Normalizare sunet", "TaskCleanCollectionsAndPlaylists": "Curăță colecțiile și listele de redare", - "TaskCleanCollectionsAndPlaylistsDescription": "Elimină elementele care nu mai există din colecții și liste de redare." + "TaskCleanCollectionsAndPlaylistsDescription": "Elimină elementele care nu mai există din colecții și liste de redare.", + "TaskExtractMediaSegments": "Scanează segmentele media", + "TaskMoveTrickplayImagesDescription": "Mută fișierele trickplay existente conform setărilor librăriei.", + "TaskExtractMediaSegmentsDescription": "Extrage sau obține segmentele media de la pluginurile MediaSegment activate.", + "TaskMoveTrickplayImages": "Migrează locația imaginii Trickplay", + "TaskDownloadMissingLyrics": "Descarcă versurile lipsă", + "TaskDownloadMissingLyricsDescription": "Descarcă versuri pentru melodii" } -- cgit v1.2.3 From b92fc7ea9dbf86437a981c3f0477a7b457977b9a Mon Sep 17 00:00:00 2001 From: gnattu Date: Tue, 17 Sep 2024 00:47:12 +0800 Subject: Don't resolve trickplay folder during media scanning (#12652) --- Emby.Server.Implementations/Library/IgnorePatterns.cs | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Library/IgnorePatterns.cs b/Emby.Server.Implementations/Library/IgnorePatterns.cs index a2301c8ae..bb45dd87e 100644 --- a/Emby.Server.Implementations/Library/IgnorePatterns.cs +++ b/Emby.Server.Implementations/Library/IgnorePatterns.cs @@ -50,6 +50,10 @@ namespace Emby.Server.Implementations.Library "**/lost+found/**", "**/lost+found", + // Trickplay files + "**/*.trickplay", + "**/*.trickplay/**", + // WMC temp recording directories that will constantly be written to "**/TempRec/**", "**/TempRec", -- cgit v1.2.3 From 0f9a8d8ee113d2e7aaae8a0687938fba9245229b Mon Sep 17 00:00:00 2001 From: Chris Lee Date: Tue, 17 Sep 2024 07:40:26 +0000 Subject: Translated using Weblate (Norwegian Bokmål) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/nb_NO/ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Emby.Server.Implementations/Localization/Core/nb.json | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/nb.json b/Emby.Server.Implementations/Localization/Core/nb.json index 6d644976d..b90d06c7b 100644 --- a/Emby.Server.Implementations/Localization/Core/nb.json +++ b/Emby.Server.Implementations/Localization/Core/nb.json @@ -129,9 +129,12 @@ "TaskRefreshTrickplayImagesDescription": "Oppretter trickplay-forhåndsvisninger for videoer i aktiverte biblioteker.", "TaskCleanCollectionsAndPlaylists": "Rydd kolleksjoner og spillelister", "TaskAudioNormalization": "Lyd Normalisering", - "TaskAudioNormalizationDescription": "Skan filer for lyd normaliserende data", - "TaskCleanCollectionsAndPlaylistsDescription": "Fjerner elementer fra kolleksjoner og spillelister som ikke lengere finnes", + "TaskAudioNormalizationDescription": "Skan filer for lyd normaliserende data.", + "TaskCleanCollectionsAndPlaylistsDescription": "Fjerner elementer fra kolleksjoner og spillelister som ikke lengere finnes.", "TaskDownloadMissingLyrics": "Last ned manglende tekster", "TaskDownloadMissingLyricsDescription": "Last ned sangtekster", - "TaskExtractMediaSegments": "Skann mediasegment" + "TaskExtractMediaSegments": "Skann mediasegment", + "TaskMoveTrickplayImages": "Migrer bildeplassering for Trickplay", + "TaskMoveTrickplayImagesDescription": "Flytter eksisterende Trickplay-filer i henhold til bibliotekseinstillingene.", + "TaskExtractMediaSegmentsDescription": "Trekker ut eller henter mediasegmenter fra plugins som støtter MediaSegment." } -- cgit v1.2.3 From 7a2427bf07f9036d62c88a75855cd6dc7e8e3064 Mon Sep 17 00:00:00 2001 From: Shadowghost Date: Thu, 5 Sep 2024 12:55:15 +0200 Subject: Add SessionInfoDto, DeviceInfoDto and implement JsonDelimitedArrayConverter.Write --- .../Session/SessionManager.cs | 137 ++++++++++++++- Jellyfin.Api/Controllers/DevicesController.cs | 10 +- Jellyfin.Api/Controllers/SessionController.cs | 85 ++-------- .../Models/SessionDtos/ClientCapabilitiesDto.cs | 83 --------- Jellyfin.Data/Dtos/DeviceOptionsDto.cs | 33 ++-- .../Devices/DeviceManager.cs | 85 ++++++++-- .../Authentication/AuthenticationResult.cs | 33 ++-- MediaBrowser.Controller/Devices/IDeviceManager.cs | 150 ++++++++++------- .../AuthenticationResultEventArgs.cs | 3 +- .../WebSocketMessages/Outbound/SessionsMessage.cs | 5 +- MediaBrowser.Controller/Session/ISessionManager.cs | 11 ++ MediaBrowser.Controller/Session/SessionInfo.cs | 120 +++++++++++-- MediaBrowser.Model/Devices/DeviceInfo.cs | 119 +++++++------ MediaBrowser.Model/Dto/ClientCapabilitiesDto.cs | 69 ++++++++ MediaBrowser.Model/Dto/DeviceInfoDto.cs | 83 +++++++++ MediaBrowser.Model/Dto/SessionInfoDto.cs | 186 +++++++++++++++++++++ .../Json/Converters/JsonDelimitedArrayConverter.cs | 65 ++++--- .../Converters/JsonCommaDelimitedArrayTests.cs | 16 +- 18 files changed, 919 insertions(+), 374 deletions(-) delete mode 100644 Jellyfin.Api/Models/SessionDtos/ClientCapabilitiesDto.cs create mode 100644 MediaBrowser.Model/Dto/ClientCapabilitiesDto.cs create mode 100644 MediaBrowser.Model/Dto/DeviceInfoDto.cs create mode 100644 MediaBrowser.Model/Dto/SessionInfoDto.cs (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 72e164b52..6bcbe3ceb 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1,7 +1,5 @@ #nullable disable -#pragma warning disable CS1591 - using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -68,13 +66,29 @@ namespace Emby.Server.Implementations.Session private Timer _inactiveTimer; private DtoOptions _itemInfoDtoOptions; - private bool _disposed = false; + private bool _disposed; + /// + /// Initializes a new instance of the class. + /// + /// Instance of interface. + /// Instance of interface. + /// Instance of interface. + /// Instance of interface. + /// Instance of interface. + /// Instance of interface. + /// Instance of interface. + /// Instance of interface. + /// Instance of interface. + /// Instance of interface. + /// Instance of interface. + /// Instance of interface. + /// Instance of interface. public SessionManager( ILogger logger, IEventManager eventManager, IUserDataManager userDataManager, - IServerConfigurationManager config, + IServerConfigurationManager serverConfigurationManager, ILibraryManager libraryManager, IUserManager userManager, IMusicManager musicManager, @@ -88,7 +102,7 @@ namespace Emby.Server.Implementations.Session _logger = logger; _eventManager = eventManager; _userDataManager = userDataManager; - _config = config; + _config = serverConfigurationManager; _libraryManager = libraryManager; _userManager = userManager; _musicManager = musicManager; @@ -508,7 +522,10 @@ namespace Emby.Server.Implementations.Session deviceName = "Network Device"; } - var deviceOptions = _deviceManager.GetDeviceOptions(deviceId); + var deviceOptions = _deviceManager.GetDeviceOptions(deviceId) ?? new() + { + DeviceId = deviceId + }; if (string.IsNullOrEmpty(deviceOptions.CustomName)) { sessionInfo.DeviceName = deviceName; @@ -1076,6 +1093,42 @@ namespace Emby.Server.Implementations.Session return session; } + private SessionInfoDto ToSessionInfoDto(SessionInfo sessionInfo) + { + return new SessionInfoDto + { + PlayState = sessionInfo.PlayState, + AdditionalUsers = sessionInfo.AdditionalUsers, + Capabilities = _deviceManager.ToClientCapabilitiesDto(sessionInfo.Capabilities), + RemoteEndPoint = sessionInfo.RemoteEndPoint, + PlayableMediaTypes = sessionInfo.PlayableMediaTypes, + Id = sessionInfo.Id, + UserId = sessionInfo.UserId, + UserName = sessionInfo.UserName, + Client = sessionInfo.Client, + LastActivityDate = sessionInfo.LastActivityDate, + LastPlaybackCheckIn = sessionInfo.LastPlaybackCheckIn, + LastPausedDate = sessionInfo.LastPausedDate, + DeviceName = sessionInfo.DeviceName, + DeviceType = sessionInfo.DeviceType, + NowPlayingItem = sessionInfo.NowPlayingItem, + NowViewingItem = sessionInfo.NowViewingItem, + DeviceId = sessionInfo.DeviceId, + ApplicationVersion = sessionInfo.ApplicationVersion, + TranscodingInfo = sessionInfo.TranscodingInfo, + IsActive = sessionInfo.IsActive, + SupportsMediaControl = sessionInfo.SupportsMediaControl, + SupportsRemoteControl = sessionInfo.SupportsRemoteControl, + NowPlayingQueue = sessionInfo.NowPlayingQueue, + NowPlayingQueueFullItems = sessionInfo.NowPlayingQueueFullItems, + HasCustomDeviceName = sessionInfo.HasCustomDeviceName, + PlaylistItemId = sessionInfo.PlaylistItemId, + ServerId = sessionInfo.ServerId, + UserPrimaryImageTag = sessionInfo.UserPrimaryImageTag, + SupportedCommands = sessionInfo.SupportedCommands + }; + } + /// public Task SendMessageCommand(string controllingSessionId, string sessionId, MessageCommand command, CancellationToken cancellationToken) { @@ -1393,7 +1446,7 @@ namespace Emby.Server.Implementations.Session UserName = user.Username }; - session.AdditionalUsers = [..session.AdditionalUsers, newUser]; + session.AdditionalUsers = [.. session.AdditionalUsers, newUser]; } } @@ -1505,7 +1558,7 @@ namespace Emby.Server.Implementations.Session var returnResult = new AuthenticationResult { User = _userManager.GetUserDto(user, request.RemoteEndPoint), - SessionInfo = session, + SessionInfo = ToSessionInfoDto(session), AccessToken = token, ServerId = _appHost.SystemId }; @@ -1800,6 +1853,74 @@ namespace Emby.Server.Implementations.Session return await GetSessionByAuthenticationToken(items[0], deviceId, remoteEndpoint, null).ConfigureAwait(false); } + /// + public IReadOnlyList GetSessions( + Guid userId, + string deviceId, + int? activeWithinSeconds, + Guid? controllableUserToCheck) + { + var result = Sessions; + var user = _userManager.GetUserById(userId); + if (!string.IsNullOrEmpty(deviceId)) + { + result = result.Where(i => string.Equals(i.DeviceId, deviceId, StringComparison.OrdinalIgnoreCase)); + } + + if (!controllableUserToCheck.IsNullOrEmpty()) + { + result = result.Where(i => i.SupportsRemoteControl); + + var controlledUser = _userManager.GetUserById(controllableUserToCheck.Value); + if (controlledUser is null) + { + return []; + } + + if (!controlledUser.HasPermission(PermissionKind.EnableSharedDeviceControl)) + { + // Controlled user has device sharing disabled + result = result.Where(i => !i.UserId.IsEmpty()); + } + + if (!user.HasPermission(PermissionKind.EnableRemoteControlOfOtherUsers)) + { + // User cannot control other user's sessions, validate user id. + result = result.Where(i => i.UserId.IsEmpty() || i.ContainsUser(controllableUserToCheck.Value)); + } + + result = result.Where(i => + { + if (!string.IsNullOrWhiteSpace(i.DeviceId) && !_deviceManager.CanAccessDevice(user, i.DeviceId)) + { + return false; + } + + return true; + }); + } + else if (!user.HasPermission(PermissionKind.IsAdministrator)) + { + // Request isn't from administrator, limit to "own" sessions. + result = result.Where(i => i.UserId.IsEmpty() || i.ContainsUser(userId)); + + // Don't report acceleration type for non-admin users. + result = result.Select(r => + { + r.TranscodingInfo.HardwareAccelerationType = HardwareAccelerationType.none; + return r; + }); + } + + if (activeWithinSeconds.HasValue && activeWithinSeconds.Value > 0) + { + var minActiveDate = DateTime.UtcNow.AddSeconds(0 - activeWithinSeconds.Value); + result = result.Where(i => i.LastActivityDate >= minActiveDate); + } + + return result.Select(ToSessionInfoDto).ToList(); + } + /// public Task SendMessageToAdminSessions(SessionMessageType name, T data, CancellationToken cancellationToken) { diff --git a/Jellyfin.Api/Controllers/DevicesController.cs b/Jellyfin.Api/Controllers/DevicesController.cs index 2a2ab4ad1..50050262f 100644 --- a/Jellyfin.Api/Controllers/DevicesController.cs +++ b/Jellyfin.Api/Controllers/DevicesController.cs @@ -1,15 +1,13 @@ using System; using System.ComponentModel.DataAnnotations; using System.Threading.Tasks; -using Jellyfin.Api.Constants; using Jellyfin.Api.Helpers; using Jellyfin.Data.Dtos; -using Jellyfin.Data.Entities.Security; using Jellyfin.Data.Queries; using MediaBrowser.Common.Api; using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Session; -using MediaBrowser.Model.Devices; +using MediaBrowser.Model.Dto; using MediaBrowser.Model.Querying; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; @@ -47,7 +45,7 @@ public class DevicesController : BaseJellyfinApiController /// An containing the list of devices. [HttpGet] [ProducesResponseType(StatusCodes.Status200OK)] - public ActionResult> GetDevices([FromQuery] Guid? userId) + public ActionResult> GetDevices([FromQuery] Guid? userId) { userId = RequestHelpers.GetUserId(User, userId); return _deviceManager.GetDevicesForUser(userId); @@ -63,7 +61,7 @@ public class DevicesController : BaseJellyfinApiController [HttpGet("Info")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public ActionResult GetDeviceInfo([FromQuery, Required] string id) + public ActionResult GetDeviceInfo([FromQuery, Required] string id) { var deviceInfo = _deviceManager.GetDevice(id); if (deviceInfo is null) @@ -84,7 +82,7 @@ public class DevicesController : BaseJellyfinApiController [HttpGet("Options")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public ActionResult GetDeviceOptions([FromQuery, Required] string id) + public ActionResult GetDeviceOptions([FromQuery, Required] string id) { var deviceInfo = _deviceManager.GetDeviceOptions(id); if (deviceInfo is null) diff --git a/Jellyfin.Api/Controllers/SessionController.cs b/Jellyfin.Api/Controllers/SessionController.cs index 942bdeb9e..91a879b8e 100644 --- a/Jellyfin.Api/Controllers/SessionController.cs +++ b/Jellyfin.Api/Controllers/SessionController.cs @@ -1,18 +1,13 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; -using System.Linq; using System.Threading; using System.Threading.Tasks; -using Jellyfin.Api.Constants; using Jellyfin.Api.Extensions; using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; -using Jellyfin.Api.Models.SessionDtos; using Jellyfin.Data.Enums; -using Jellyfin.Extensions; using MediaBrowser.Common.Api; -using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Dto; @@ -32,22 +27,18 @@ public class SessionController : BaseJellyfinApiController { private readonly ISessionManager _sessionManager; private readonly IUserManager _userManager; - private readonly IDeviceManager _deviceManager; /// /// Initializes a new instance of the class. /// /// Instance of interface. /// Instance of interface. - /// Instance of interface. public SessionController( ISessionManager sessionManager, - IUserManager userManager, - IDeviceManager deviceManager) + IUserManager userManager) { _sessionManager = sessionManager; _userManager = userManager; - _deviceManager = deviceManager; } /// @@ -57,77 +48,25 @@ public class SessionController : BaseJellyfinApiController /// Filter by device Id. /// Optional. Filter by sessions that were active in the last n seconds. /// List of sessions returned. - /// An with the available sessions. + /// An with the available sessions. [HttpGet("Sessions")] [Authorize] [ProducesResponseType(StatusCodes.Status200OK)] - public ActionResult> GetSessions( + public ActionResult> GetSessions( [FromQuery] Guid? controllableByUserId, [FromQuery] string? deviceId, [FromQuery] int? activeWithinSeconds) { - var result = _sessionManager.Sessions; - var isRequestingFromAdmin = User.IsInRole(UserRoles.Administrator); - - if (!string.IsNullOrEmpty(deviceId)) - { - result = result.Where(i => string.Equals(i.DeviceId, deviceId, StringComparison.OrdinalIgnoreCase)); - } - - if (!controllableByUserId.IsNullOrEmpty()) + Guid? controllableUserToCheck = controllableByUserId is null ? null : RequestHelpers.GetUserId(User, controllableByUserId); + var result = _sessionManager.GetSessions( + User.GetUserId(), + deviceId, + activeWithinSeconds, + controllableUserToCheck); + + if (result.Count == 0) { - result = result.Where(i => i.SupportsRemoteControl); - - var user = _userManager.GetUserById(controllableByUserId.Value); - if (user is null) - { - return NotFound(); - } - - if (!user.HasPermission(PermissionKind.EnableRemoteControlOfOtherUsers)) - { - // User cannot control other user's sessions, validate user id. - result = result.Where(i => i.UserId.IsEmpty() || i.ContainsUser(RequestHelpers.GetUserId(User, controllableByUserId))); - } - - if (!user.HasPermission(PermissionKind.EnableSharedDeviceControl)) - { - result = result.Where(i => !i.UserId.IsEmpty()); - } - - result = result.Where(i => - { - if (!string.IsNullOrWhiteSpace(i.DeviceId)) - { - if (!_deviceManager.CanAccessDevice(user, i.DeviceId)) - { - return false; - } - } - - return true; - }); - } - else if (!isRequestingFromAdmin) - { - // Request isn't from administrator, limit to "own" sessions. - result = result.Where(i => i.UserId.IsEmpty() || i.ContainsUser(User.GetUserId())); - } - - if (activeWithinSeconds.HasValue && activeWithinSeconds.Value > 0) - { - var minActiveDate = DateTime.UtcNow.AddSeconds(0 - activeWithinSeconds.Value); - result = result.Where(i => i.LastActivityDate >= minActiveDate); - } - - // Request isn't from administrator, don't report acceleration type. - if (!isRequestingFromAdmin) - { - result = result.Select(r => - { - r.TranscodingInfo.HardwareAccelerationType = HardwareAccelerationType.none; - return r; - }); + return NotFound(); } return Ok(result); diff --git a/Jellyfin.Api/Models/SessionDtos/ClientCapabilitiesDto.cs b/Jellyfin.Api/Models/SessionDtos/ClientCapabilitiesDto.cs deleted file mode 100644 index c699c469d..000000000 --- a/Jellyfin.Api/Models/SessionDtos/ClientCapabilitiesDto.cs +++ /dev/null @@ -1,83 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Text.Json.Serialization; -using Jellyfin.Data.Enums; -using Jellyfin.Extensions.Json.Converters; -using MediaBrowser.Model.Dlna; -using MediaBrowser.Model.Session; - -namespace Jellyfin.Api.Models.SessionDtos; - -/// -/// Client capabilities dto. -/// -public class ClientCapabilitiesDto -{ - /// - /// Gets or sets the list of playable media types. - /// - [JsonConverter(typeof(JsonCommaDelimitedArrayConverterFactory))] - public IReadOnlyList PlayableMediaTypes { get; set; } = Array.Empty(); - - /// - /// Gets or sets the list of supported commands. - /// - [JsonConverter(typeof(JsonCommaDelimitedArrayConverterFactory))] - public IReadOnlyList SupportedCommands { get; set; } = Array.Empty(); - - /// - /// Gets or sets a value indicating whether session supports media control. - /// - public bool SupportsMediaControl { get; set; } - - /// - /// Gets or sets a value indicating whether session supports a persistent identifier. - /// - public bool SupportsPersistentIdentifier { get; set; } - - /// - /// Gets or sets the device profile. - /// - public DeviceProfile? DeviceProfile { get; set; } - - /// - /// Gets or sets the app store url. - /// - public string? AppStoreUrl { get; set; } - - /// - /// Gets or sets the icon url. - /// - public string? IconUrl { get; set; } - -#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member - // TODO: Remove after 10.9 - [Obsolete("Unused")] - [DefaultValue(false)] - public bool? SupportsContentUploading { get; set; } = false; - - // TODO: Remove after 10.9 - [Obsolete("Unused")] - [DefaultValue(false)] - public bool? SupportsSync { get; set; } = false; -#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member - - /// - /// Convert the dto to the full model. - /// - /// The converted model. - public ClientCapabilities ToClientCapabilities() - { - return new ClientCapabilities - { - PlayableMediaTypes = PlayableMediaTypes, - SupportedCommands = SupportedCommands, - SupportsMediaControl = SupportsMediaControl, - SupportsPersistentIdentifier = SupportsPersistentIdentifier, - DeviceProfile = DeviceProfile, - AppStoreUrl = AppStoreUrl, - IconUrl = IconUrl - }; - } -} diff --git a/Jellyfin.Data/Dtos/DeviceOptionsDto.cs b/Jellyfin.Data/Dtos/DeviceOptionsDto.cs index 392ef5ff4..aad578709 100644 --- a/Jellyfin.Data/Dtos/DeviceOptionsDto.cs +++ b/Jellyfin.Data/Dtos/DeviceOptionsDto.cs @@ -1,23 +1,22 @@ -namespace Jellyfin.Data.Dtos +namespace Jellyfin.Data.Dtos; + +/// +/// A dto representing custom options for a device. +/// +public class DeviceOptionsDto { /// - /// A dto representing custom options for a device. + /// Gets or sets the id. /// - public class DeviceOptionsDto - { - /// - /// Gets or sets the id. - /// - public int Id { get; set; } + public int Id { get; set; } - /// - /// Gets or sets the device id. - /// - public string? DeviceId { get; set; } + /// + /// Gets or sets the device id. + /// + public string? DeviceId { get; set; } - /// - /// Gets or sets the custom name. - /// - public string? CustomName { get; set; } - } + /// + /// Gets or sets the custom name. + /// + public string? CustomName { get; set; } } diff --git a/Jellyfin.Server.Implementations/Devices/DeviceManager.cs b/Jellyfin.Server.Implementations/Devices/DeviceManager.cs index 415c04bbf..d3bff2936 100644 --- a/Jellyfin.Server.Implementations/Devices/DeviceManager.cs +++ b/Jellyfin.Server.Implementations/Devices/DeviceManager.cs @@ -3,6 +3,7 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Jellyfin.Data.Dtos; using Jellyfin.Data.Entities; using Jellyfin.Data.Entities.Security; using Jellyfin.Data.Enums; @@ -13,6 +14,7 @@ using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Devices; +using MediaBrowser.Model.Dto; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Session; using Microsoft.EntityFrameworkCore; @@ -68,7 +70,7 @@ namespace Jellyfin.Server.Implementations.Devices } /// - public async Task UpdateDeviceOptions(string deviceId, string deviceName) + public async Task UpdateDeviceOptions(string deviceId, string? deviceName) { DeviceOptions? deviceOptions; var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false); @@ -105,29 +107,37 @@ namespace Jellyfin.Server.Implementations.Devices } /// - public DeviceOptions GetDeviceOptions(string deviceId) + public DeviceOptionsDto? GetDeviceOptions(string deviceId) { - _deviceOptions.TryGetValue(deviceId, out var deviceOptions); + if (_deviceOptions.TryGetValue(deviceId, out var deviceOptions)) + { + return ToDeviceOptionsDto(deviceOptions); + } - return deviceOptions ?? new DeviceOptions(deviceId); + return null; } /// - public ClientCapabilities GetCapabilities(string deviceId) + public ClientCapabilities GetCapabilities(string? deviceId) { + if (deviceId is null) + { + return new(); + } + return _capabilitiesMap.TryGetValue(deviceId, out ClientCapabilities? result) ? result - : new ClientCapabilities(); + : new(); } /// - public DeviceInfo? GetDevice(string id) + public DeviceInfoDto? GetDevice(string id) { var device = _devices.Values.Where(d => d.DeviceId == id).OrderByDescending(d => d.DateLastActivity).FirstOrDefault(); _deviceOptions.TryGetValue(id, out var deviceOption); var deviceInfo = device is null ? null : ToDeviceInfo(device, deviceOption); - return deviceInfo; + return deviceInfo is null ? null : ToDeviceInfoDto(deviceInfo); } /// @@ -166,7 +176,7 @@ namespace Jellyfin.Server.Implementations.Devices } /// - public QueryResult GetDevicesForUser(Guid? userId) + public QueryResult GetDevicesForUser(Guid? userId) { IEnumerable devices = _devices.Values .OrderByDescending(d => d.DateLastActivity) @@ -187,9 +197,11 @@ namespace Jellyfin.Server.Implementations.Devices { _deviceOptions.TryGetValue(device.DeviceId, out var option); return ToDeviceInfo(device, option); - }).ToArray(); + }) + .Select(ToDeviceInfoDto) + .ToArray(); - return new QueryResult(array); + return new QueryResult(array); } /// @@ -235,13 +247,9 @@ namespace Jellyfin.Server.Implementations.Devices private DeviceInfo ToDeviceInfo(Device authInfo, DeviceOptions? options = null) { var caps = GetCapabilities(authInfo.DeviceId); - var user = _userManager.GetUserById(authInfo.UserId); - if (user is null) - { - throw new ResourceNotFoundException("User with UserId " + authInfo.UserId + " not found"); - } + var user = _userManager.GetUserById(authInfo.UserId) ?? throw new ResourceNotFoundException("User with UserId " + authInfo.UserId + " not found"); - return new DeviceInfo + return new() { AppName = authInfo.AppName, AppVersion = authInfo.AppVersion, @@ -254,5 +262,48 @@ namespace Jellyfin.Server.Implementations.Devices CustomName = options?.CustomName, }; } + + private DeviceOptionsDto ToDeviceOptionsDto(DeviceOptions options) + { + return new() + { + Id = options.Id, + DeviceId = options.DeviceId, + CustomName = options.CustomName, + }; + } + + private DeviceInfoDto ToDeviceInfoDto(DeviceInfo info) + { + return new() + { + Name = info.Name, + CustomName = info.CustomName, + AccessToken = info.AccessToken, + Id = info.Id, + LastUserName = info.LastUserName, + AppName = info.AppName, + AppVersion = info.AppVersion, + LastUserId = info.LastUserId, + DateLastActivity = info.DateLastActivity, + Capabilities = ToClientCapabilitiesDto(info.Capabilities), + IconUrl = info.IconUrl + }; + } + + /// + public ClientCapabilitiesDto ToClientCapabilitiesDto(ClientCapabilities capabilities) + { + return new() + { + PlayableMediaTypes = capabilities.PlayableMediaTypes, + SupportedCommands = capabilities.SupportedCommands, + SupportsMediaControl = capabilities.SupportsMediaControl, + SupportsPersistentIdentifier = capabilities.SupportsPersistentIdentifier, + DeviceProfile = capabilities.DeviceProfile, + AppStoreUrl = capabilities.AppStoreUrl, + IconUrl = capabilities.IconUrl + }; + } } } diff --git a/MediaBrowser.Controller/Authentication/AuthenticationResult.cs b/MediaBrowser.Controller/Authentication/AuthenticationResult.cs index 635e4eb3d..daf4d9631 100644 --- a/MediaBrowser.Controller/Authentication/AuthenticationResult.cs +++ b/MediaBrowser.Controller/Authentication/AuthenticationResult.cs @@ -1,20 +1,31 @@ #nullable disable -#pragma warning disable CS1591 - -using MediaBrowser.Controller.Session; using MediaBrowser.Model.Dto; -namespace MediaBrowser.Controller.Authentication +namespace MediaBrowser.Controller.Authentication; + +/// +/// A class representing an authentication result. +/// +public class AuthenticationResult { - public class AuthenticationResult - { - public UserDto User { get; set; } + /// + /// Gets or sets the user. + /// + public UserDto User { get; set; } - public SessionInfo SessionInfo { get; set; } + /// + /// Gets or sets the session info. + /// + public SessionInfoDto SessionInfo { get; set; } - public string AccessToken { get; set; } + /// + /// Gets or sets the access token. + /// + public string AccessToken { get; set; } - public string ServerId { get; set; } - } + /// + /// Gets or sets the server id. + /// + public string ServerId { get; set; } } diff --git a/MediaBrowser.Controller/Devices/IDeviceManager.cs b/MediaBrowser.Controller/Devices/IDeviceManager.cs index 5566421cb..cade53d99 100644 --- a/MediaBrowser.Controller/Devices/IDeviceManager.cs +++ b/MediaBrowser.Controller/Devices/IDeviceManager.cs @@ -1,81 +1,117 @@ -#nullable disable - -#pragma warning disable CS1591 - using System; using System.Threading.Tasks; +using Jellyfin.Data.Dtos; using Jellyfin.Data.Entities; using Jellyfin.Data.Entities.Security; using Jellyfin.Data.Events; using Jellyfin.Data.Queries; using MediaBrowser.Model.Devices; +using MediaBrowser.Model.Dto; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Session; -namespace MediaBrowser.Controller.Devices +namespace MediaBrowser.Controller.Devices; + +/// +/// Device manager interface. +/// +public interface IDeviceManager { - public interface IDeviceManager - { - event EventHandler>> DeviceOptionsUpdated; + /// + /// Event handler for updated device options. + /// + event EventHandler>> DeviceOptionsUpdated; + + /// + /// Creates a new device. + /// + /// The device to create. + /// A representing the creation of the device. + Task CreateDevice(Device device); - /// - /// Creates a new device. - /// - /// The device to create. - /// A representing the creation of the device. - Task CreateDevice(Device device); + /// + /// Saves the capabilities. + /// + /// The device id. + /// The capabilities. + void SaveCapabilities(string deviceId, ClientCapabilities capabilities); - /// - /// Saves the capabilities. - /// - /// The device id. - /// The capabilities. - void SaveCapabilities(string deviceId, ClientCapabilities capabilities); + /// + /// Gets the capabilities. + /// + /// The device id. + /// ClientCapabilities. + ClientCapabilities GetCapabilities(string? deviceId); - /// - /// Gets the capabilities. - /// - /// The device id. - /// ClientCapabilities. - ClientCapabilities GetCapabilities(string deviceId); + /// + /// Gets the device information. + /// + /// The identifier. + /// DeviceInfoDto. + DeviceInfoDto? GetDevice(string id); - /// - /// Gets the device information. - /// - /// The identifier. - /// DeviceInfo. - DeviceInfo GetDevice(string id); + /// + /// Gets devices based on the provided query. + /// + /// The device query. + /// A representing the retrieval of the devices. + QueryResult GetDevices(DeviceQuery query); - /// - /// Gets devices based on the provided query. - /// - /// The device query. - /// A representing the retrieval of the devices. - QueryResult GetDevices(DeviceQuery query); + /// + /// Gets device infromation based on the provided query. + /// + /// The device query. + /// A representing the retrieval of the device information. + QueryResult GetDeviceInfos(DeviceQuery query); - QueryResult GetDeviceInfos(DeviceQuery query); + /// + /// Gets the device information. + /// + /// The user's id, or null. + /// IEnumerable<DeviceInfoDto>. + QueryResult GetDevicesForUser(Guid? userId); - /// - /// Gets the devices. - /// - /// The user's id, or null. - /// IEnumerable<DeviceInfo>. - QueryResult GetDevicesForUser(Guid? userId); + /// + /// Deletes a device. + /// + /// The device. + /// A representing the deletion of the device. + Task DeleteDevice(Device device); - Task DeleteDevice(Device device); + /// + /// Updates a device. + /// + /// The device. + /// A representing the update of the device. + Task UpdateDevice(Device device); - Task UpdateDevice(Device device); + /// + /// Determines whether this instance [can access device] the specified user identifier. + /// + /// The user to test. + /// The device id to test. + /// Whether the user can access the device. + bool CanAccessDevice(User user, string deviceId); - /// - /// Determines whether this instance [can access device] the specified user identifier. - /// - /// The user to test. - /// The device id to test. - /// Whether the user can access the device. - bool CanAccessDevice(User user, string deviceId); + /// + /// Updates the options of a device. + /// + /// The device id. + /// The device name. + /// A representing the update of the device options. + Task UpdateDeviceOptions(string deviceId, string? deviceName); - Task UpdateDeviceOptions(string deviceId, string deviceName); + /// + /// Gets the options of a device. + /// + /// The device id. + /// of the device. + DeviceOptionsDto? GetDeviceOptions(string deviceId); - DeviceOptions GetDeviceOptions(string deviceId); - } + /// + /// Gets the dto for client capabilites. + /// + /// The client capabilities. + /// of the device. + ClientCapabilitiesDto ToClientCapabilitiesDto(ClientCapabilities capabilities); } diff --git a/MediaBrowser.Controller/Events/Authentication/AuthenticationResultEventArgs.cs b/MediaBrowser.Controller/Events/Authentication/AuthenticationResultEventArgs.cs index 357ef9406..1542c58b3 100644 --- a/MediaBrowser.Controller/Events/Authentication/AuthenticationResultEventArgs.cs +++ b/MediaBrowser.Controller/Events/Authentication/AuthenticationResultEventArgs.cs @@ -1,6 +1,5 @@ using System; using MediaBrowser.Controller.Authentication; -using MediaBrowser.Controller.Session; using MediaBrowser.Model.Dto; namespace MediaBrowser.Controller.Events.Authentication; @@ -29,7 +28,7 @@ public class AuthenticationResultEventArgs : EventArgs /// /// Gets or sets the session information. /// - public SessionInfo? SessionInfo { get; set; } + public SessionInfoDto? SessionInfo { get; set; } /// /// Gets or sets the server id. diff --git a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SessionsMessage.cs b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SessionsMessage.cs index 3504831b8..833074541 100644 --- a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SessionsMessage.cs +++ b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SessionsMessage.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.ComponentModel; using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Dto; using MediaBrowser.Model.Session; namespace MediaBrowser.Controller.Net.WebSocketMessages.Outbound; @@ -8,13 +9,13 @@ namespace MediaBrowser.Controller.Net.WebSocketMessages.Outbound; /// /// Sessions message. /// -public class SessionsMessage : OutboundWebSocketMessage> +public class SessionsMessage : OutboundWebSocketMessage> { /// /// Initializes a new instance of the class. /// /// Session info. - public SessionsMessage(IReadOnlyList data) + public SessionsMessage(IReadOnlyList data) : base(data) { } diff --git a/MediaBrowser.Controller/Session/ISessionManager.cs b/MediaBrowser.Controller/Session/ISessionManager.cs index 5a47236f9..f2e98dd78 100644 --- a/MediaBrowser.Controller/Session/ISessionManager.cs +++ b/MediaBrowser.Controller/Session/ISessionManager.cs @@ -9,6 +9,7 @@ using System.Threading.Tasks; using Jellyfin.Data.Entities.Security; using MediaBrowser.Controller.Authentication; using MediaBrowser.Controller.Library; +using MediaBrowser.Model.Dto; using MediaBrowser.Model.Session; using MediaBrowser.Model.SyncPlay; @@ -292,6 +293,16 @@ namespace MediaBrowser.Controller.Session /// SessionInfo. SessionInfo GetSession(string deviceId, string client, string version); + /// + /// Gets all sessions available to a user. + /// + /// The session identifier. + /// The device id. + /// Active within session limit. + /// Filter for sessions remote controllable for this user. + /// IReadOnlyList{SessionInfoDto}. + IReadOnlyList GetSessions(Guid userId, string deviceId, int? activeWithinSeconds, Guid? controllableUserToCheck); + /// /// Gets the session by authentication token. /// diff --git a/MediaBrowser.Controller/Session/SessionInfo.cs b/MediaBrowser.Controller/Session/SessionInfo.cs index 9e3358818..3ba1bfce4 100644 --- a/MediaBrowser.Controller/Session/SessionInfo.cs +++ b/MediaBrowser.Controller/Session/SessionInfo.cs @@ -1,7 +1,5 @@ #nullable disable -#pragma warning disable CS1591 - using System; using System.Collections.Generic; using System.Linq; @@ -27,28 +25,45 @@ namespace MediaBrowser.Controller.Session private readonly ISessionManager _sessionManager; private readonly ILogger _logger; - private readonly object _progressLock = new object(); + private readonly object _progressLock = new(); private Timer _progressTimer; private PlaybackProgressInfo _lastProgressInfo; - private bool _disposed = false; + private bool _disposed; + /// + /// Initializes a new instance of the class. + /// + /// Instance of interface. + /// Instance of interface. public SessionInfo(ISessionManager sessionManager, ILogger logger) { _sessionManager = sessionManager; _logger = logger; - AdditionalUsers = Array.Empty(); + AdditionalUsers = []; PlayState = new PlayerStateInfo(); - SessionControllers = Array.Empty(); - NowPlayingQueue = Array.Empty(); - NowPlayingQueueFullItems = Array.Empty(); + SessionControllers = []; + NowPlayingQueue = []; + NowPlayingQueueFullItems = []; } + /// + /// Gets or sets the play state. + /// + /// The play state. public PlayerStateInfo PlayState { get; set; } - public SessionUserInfo[] AdditionalUsers { get; set; } + /// + /// Gets or sets the additional users. + /// + /// The additional users. + public IReadOnlyList AdditionalUsers { get; set; } + /// + /// Gets or sets the client capabilities. + /// + /// The client capabilities. public ClientCapabilities Capabilities { get; set; } /// @@ -67,7 +82,7 @@ namespace MediaBrowser.Controller.Session { if (Capabilities is null) { - return Array.Empty(); + return []; } return Capabilities.PlayableMediaTypes; @@ -134,9 +149,17 @@ namespace MediaBrowser.Controller.Session /// The now playing item. public BaseItemDto NowPlayingItem { get; set; } + /// + /// Gets or sets the now playing queue full items. + /// + /// The now playing queue full items. [JsonIgnore] public BaseItem FullNowPlayingItem { get; set; } + /// + /// Gets or sets the now viewing item. + /// + /// The now viewing item. public BaseItemDto NowViewingItem { get; set; } /// @@ -156,8 +179,12 @@ namespace MediaBrowser.Controller.Session /// /// The session controller. [JsonIgnore] - public ISessionController[] SessionControllers { get; set; } + public IReadOnlyList SessionControllers { get; set; } + /// + /// Gets or sets the transcoding info. + /// + /// The transcoding info. public TranscodingInfo TranscodingInfo { get; set; } /// @@ -177,7 +204,7 @@ namespace MediaBrowser.Controller.Session } } - if (controllers.Length > 0) + if (controllers.Count > 0) { return false; } @@ -186,6 +213,10 @@ namespace MediaBrowser.Controller.Session } } + /// + /// Gets a value indicating whether the session supports media control. + /// + /// true if this session supports media control; otherwise, false. public bool SupportsMediaControl { get @@ -208,6 +239,10 @@ namespace MediaBrowser.Controller.Session } } + /// + /// Gets a value indicating whether the session supports remote control. + /// + /// true if this session supports remote control; otherwise, false. public bool SupportsRemoteControl { get @@ -230,16 +265,40 @@ namespace MediaBrowser.Controller.Session } } + /// + /// Gets or sets the now playing queue. + /// + /// The now playing queue. public IReadOnlyList NowPlayingQueue { get; set; } + /// + /// Gets or sets the now playing queue full items. + /// + /// The now playing queue full items. public IReadOnlyList NowPlayingQueueFullItems { get; set; } + /// + /// Gets or sets a value indicating whether the session has a custom device name. + /// + /// true if this session has a custom device name; otherwise, false. public bool HasCustomDeviceName { get; set; } + /// + /// Gets or sets the playlist item id. + /// + /// The splaylist item id. public string PlaylistItemId { get; set; } + /// + /// Gets or sets the server id. + /// + /// The server id. public string ServerId { get; set; } + /// + /// Gets or sets the user primary image tag. + /// + /// The user primary image tag. public string UserPrimaryImageTag { get; set; } /// @@ -247,8 +306,14 @@ namespace MediaBrowser.Controller.Session /// /// The supported commands. public IReadOnlyList SupportedCommands - => Capabilities is null ? Array.Empty() : Capabilities.SupportedCommands; + => Capabilities is null ? [] : Capabilities.SupportedCommands; + /// + /// Ensures a controller of type exists. + /// + /// Class to register. + /// The factory. + /// Tuple{ISessionController, bool}. public Tuple EnsureController(Func factory) { var controllers = SessionControllers.ToList(); @@ -261,18 +326,27 @@ namespace MediaBrowser.Controller.Session } var newController = factory(this); - _logger.LogDebug("Creating new {0}", newController.GetType().Name); + _logger.LogDebug("Creating new {Factory}", newController.GetType().Name); controllers.Add(newController); - SessionControllers = controllers.ToArray(); + SessionControllers = [.. controllers]; return new Tuple(newController, true); } + /// + /// Adds a controller to the session. + /// + /// The controller. public void AddController(ISessionController controller) { - SessionControllers = [..SessionControllers, controller]; + SessionControllers = [.. SessionControllers, controller]; } + /// + /// Gets a value indicating whether the session contains a user. + /// + /// The user id to check. + /// true if this session contains the user; otherwise, false. public bool ContainsUser(Guid userId) { if (UserId.Equals(userId)) @@ -291,6 +365,11 @@ namespace MediaBrowser.Controller.Session return false; } + /// + /// Starts automatic progressing. + /// + /// The playback progress info. + /// The supported commands. public void StartAutomaticProgress(PlaybackProgressInfo progressInfo) { if (_disposed) @@ -359,6 +438,9 @@ namespace MediaBrowser.Controller.Session } } + /// + /// Stops automatic progressing. + /// public void StopAutomaticProgress() { lock (_progressLock) @@ -373,6 +455,10 @@ namespace MediaBrowser.Controller.Session } } + /// + /// Disposes the instance async. + /// + /// ValueTask. public async ValueTask DisposeAsync() { _disposed = true; @@ -380,7 +466,7 @@ namespace MediaBrowser.Controller.Session StopAutomaticProgress(); var controllers = SessionControllers.ToList(); - SessionControllers = Array.Empty(); + SessionControllers = []; foreach (var controller in controllers) { diff --git a/MediaBrowser.Model/Devices/DeviceInfo.cs b/MediaBrowser.Model/Devices/DeviceInfo.cs index 4962992a0..115598613 100644 --- a/MediaBrowser.Model/Devices/DeviceInfo.cs +++ b/MediaBrowser.Model/Devices/DeviceInfo.cs @@ -1,69 +1,84 @@ -#nullable disable -#pragma warning disable CS1591 - using System; using MediaBrowser.Model.Session; -namespace MediaBrowser.Model.Devices +namespace MediaBrowser.Model.Devices; + +/// +/// A class for device Information. +/// +public class DeviceInfo { - public class DeviceInfo + /// + /// Initializes a new instance of the class. + /// + public DeviceInfo() { - public DeviceInfo() - { - Capabilities = new ClientCapabilities(); - } + Capabilities = new ClientCapabilities(); + } - public string Name { get; set; } + /// + /// Gets or sets the name. + /// + /// The name. + public string? Name { get; set; } - public string CustomName { get; set; } + /// + /// Gets or sets the custom name. + /// + /// The custom name. + public string? CustomName { get; set; } - /// - /// Gets or sets the access token. - /// - public string AccessToken { get; set; } + /// + /// Gets or sets the access token. + /// + /// The access token. + public string? AccessToken { get; set; } - /// - /// Gets or sets the identifier. - /// - /// The identifier. - public string Id { get; set; } + /// + /// Gets or sets the identifier. + /// + /// The identifier. + public string? Id { get; set; } - /// - /// Gets or sets the last name of the user. - /// - /// The last name of the user. - public string LastUserName { get; set; } + /// + /// Gets or sets the last name of the user. + /// + /// The last name of the user. + public string? LastUserName { get; set; } - /// - /// Gets or sets the name of the application. - /// - /// The name of the application. - public string AppName { get; set; } + /// + /// Gets or sets the name of the application. + /// + /// The name of the application. + public string? AppName { get; set; } - /// - /// Gets or sets the application version. - /// - /// The application version. - public string AppVersion { get; set; } + /// + /// Gets or sets the application version. + /// + /// The application version. + public string? AppVersion { get; set; } - /// - /// Gets or sets the last user identifier. - /// - /// The last user identifier. - public Guid LastUserId { get; set; } + /// + /// Gets or sets the last user identifier. + /// + /// The last user identifier. + public Guid? LastUserId { get; set; } - /// - /// Gets or sets the date last modified. - /// - /// The date last modified. - public DateTime DateLastActivity { get; set; } + /// + /// Gets or sets the date last modified. + /// + /// The date last modified. + public DateTime? DateLastActivity { get; set; } - /// - /// Gets or sets the capabilities. - /// - /// The capabilities. - public ClientCapabilities Capabilities { get; set; } + /// + /// Gets or sets the capabilities. + /// + /// The capabilities. + public ClientCapabilities Capabilities { get; set; } - public string IconUrl { get; set; } - } + /// + /// Gets or sets the icon URL. + /// + /// The icon URL. + public string? IconUrl { get; set; } } diff --git a/MediaBrowser.Model/Dto/ClientCapabilitiesDto.cs b/MediaBrowser.Model/Dto/ClientCapabilitiesDto.cs new file mode 100644 index 000000000..5963ed270 --- /dev/null +++ b/MediaBrowser.Model/Dto/ClientCapabilitiesDto.cs @@ -0,0 +1,69 @@ +using System.Collections.Generic; +using System.Text.Json.Serialization; +using Jellyfin.Data.Enums; +using Jellyfin.Extensions.Json.Converters; +using MediaBrowser.Model.Dlna; +using MediaBrowser.Model.Session; + +namespace MediaBrowser.Model.Dto; + +/// +/// Client capabilities dto. +/// +public class ClientCapabilitiesDto +{ + /// + /// Gets or sets the list of playable media types. + /// + [JsonConverter(typeof(JsonCommaDelimitedArrayConverterFactory))] + public IReadOnlyList PlayableMediaTypes { get; set; } = []; + + /// + /// Gets or sets the list of supported commands. + /// + [JsonConverter(typeof(JsonCommaDelimitedArrayConverterFactory))] + public IReadOnlyList SupportedCommands { get; set; } = []; + + /// + /// Gets or sets a value indicating whether session supports media control. + /// + public bool SupportsMediaControl { get; set; } + + /// + /// Gets or sets a value indicating whether session supports a persistent identifier. + /// + public bool SupportsPersistentIdentifier { get; set; } + + /// + /// Gets or sets the device profile. + /// + public DeviceProfile? DeviceProfile { get; set; } + + /// + /// Gets or sets the app store url. + /// + public string? AppStoreUrl { get; set; } + + /// + /// Gets or sets the icon url. + /// + public string? IconUrl { get; set; } + + /// + /// Convert the dto to the full model. + /// + /// The converted model. + public ClientCapabilities ToClientCapabilities() + { + return new ClientCapabilities + { + PlayableMediaTypes = PlayableMediaTypes, + SupportedCommands = SupportedCommands, + SupportsMediaControl = SupportsMediaControl, + SupportsPersistentIdentifier = SupportsPersistentIdentifier, + DeviceProfile = DeviceProfile, + AppStoreUrl = AppStoreUrl, + IconUrl = IconUrl + }; + } +} diff --git a/MediaBrowser.Model/Dto/DeviceInfoDto.cs b/MediaBrowser.Model/Dto/DeviceInfoDto.cs new file mode 100644 index 000000000..ac7a731a9 --- /dev/null +++ b/MediaBrowser.Model/Dto/DeviceInfoDto.cs @@ -0,0 +1,83 @@ +using System; + +namespace MediaBrowser.Model.Dto; + +/// +/// A DTO representing device information. +/// +public class DeviceInfoDto +{ + /// + /// Initializes a new instance of the class. + /// + public DeviceInfoDto() + { + Capabilities = new ClientCapabilitiesDto(); + } + + /// + /// Gets or sets the name. + /// + /// The name. + public string? Name { get; set; } + + /// + /// Gets or sets the custom name. + /// + /// The custom name. + public string? CustomName { get; set; } + + /// + /// Gets or sets the access token. + /// + /// The access token. + public string? AccessToken { get; set; } + + /// + /// Gets or sets the identifier. + /// + /// The identifier. + public string? Id { get; set; } + + /// + /// Gets or sets the last name of the user. + /// + /// The last name of the user. + public string? LastUserName { get; set; } + + /// + /// Gets or sets the name of the application. + /// + /// The name of the application. + public string? AppName { get; set; } + + /// + /// Gets or sets the application version. + /// + /// The application version. + public string? AppVersion { get; set; } + + /// + /// Gets or sets the last user identifier. + /// + /// The last user identifier. + public Guid? LastUserId { get; set; } + + /// + /// Gets or sets the date last modified. + /// + /// The date last modified. + public DateTime? DateLastActivity { get; set; } + + /// + /// Gets or sets the capabilities. + /// + /// The capabilities. + public ClientCapabilitiesDto Capabilities { get; set; } + + /// + /// Gets or sets the icon URL. + /// + /// The icon URL. + public string? IconUrl { get; set; } +} diff --git a/MediaBrowser.Model/Dto/SessionInfoDto.cs b/MediaBrowser.Model/Dto/SessionInfoDto.cs new file mode 100644 index 000000000..2496c933a --- /dev/null +++ b/MediaBrowser.Model/Dto/SessionInfoDto.cs @@ -0,0 +1,186 @@ +using System; +using System.Collections.Generic; +using Jellyfin.Data.Enums; +using MediaBrowser.Model.Session; + +namespace MediaBrowser.Model.Dto; + +/// +/// Session info DTO. +/// +public class SessionInfoDto +{ + /// + /// Gets or sets the play state. + /// + /// The play state. + public PlayerStateInfo? PlayState { get; set; } + + /// + /// Gets or sets the additional users. + /// + /// The additional users. + public IReadOnlyList? AdditionalUsers { get; set; } + + /// + /// Gets or sets the client capabilities. + /// + /// The client capabilities. + public ClientCapabilitiesDto? Capabilities { get; set; } + + /// + /// Gets or sets the remote end point. + /// + /// The remote end point. + public string? RemoteEndPoint { get; set; } + + /// + /// Gets or sets the playable media types. + /// + /// The playable media types. + public IReadOnlyList PlayableMediaTypes { get; set; } = []; + + /// + /// Gets or sets the id. + /// + /// The id. + public string? Id { get; set; } + + /// + /// Gets or sets the user id. + /// + /// The user id. + public Guid UserId { get; set; } + + /// + /// Gets or sets the username. + /// + /// The username. + public string? UserName { get; set; } + + /// + /// Gets or sets the type of the client. + /// + /// The type of the client. + public string? Client { get; set; } + + /// + /// Gets or sets the last activity date. + /// + /// The last activity date. + public DateTime LastActivityDate { get; set; } + + /// + /// Gets or sets the last playback check in. + /// + /// The last playback check in. + public DateTime LastPlaybackCheckIn { get; set; } + + /// + /// Gets or sets the last paused date. + /// + /// The last paused date. + public DateTime? LastPausedDate { get; set; } + + /// + /// Gets or sets the name of the device. + /// + /// The name of the device. + public string? DeviceName { get; set; } + + /// + /// Gets or sets the type of the device. + /// + /// The type of the device. + public string? DeviceType { get; set; } + + /// + /// Gets or sets the now playing item. + /// + /// The now playing item. + public BaseItemDto? NowPlayingItem { get; set; } + + /// + /// Gets or sets the now viewing item. + /// + /// The now viewing item. + public BaseItemDto? NowViewingItem { get; set; } + + /// + /// Gets or sets the device id. + /// + /// The device id. + public string? DeviceId { get; set; } + + /// + /// Gets or sets the application version. + /// + /// The application version. + public string? ApplicationVersion { get; set; } + + /// + /// Gets or sets the transcoding info. + /// + /// The transcoding info. + public TranscodingInfo? TranscodingInfo { get; set; } + + /// + /// Gets or sets a value indicating whether this session is active. + /// + /// true if this session is active; otherwise, false. + public bool IsActive { get; set; } + + /// + /// Gets or sets a value indicating whether the session supports media control. + /// + /// true if this session supports media control; otherwise, false. + public bool SupportsMediaControl { get; set; } + + /// + /// Gets or sets a value indicating whether the session supports remote control. + /// + /// true if this session supports remote control; otherwise, false. + public bool SupportsRemoteControl { get; set; } + + /// + /// Gets or sets the now playing queue. + /// + /// The now playing queue. + public IReadOnlyList? NowPlayingQueue { get; set; } + + /// + /// Gets or sets the now playing queue full items. + /// + /// The now playing queue full items. + public IReadOnlyList? NowPlayingQueueFullItems { get; set; } + + /// + /// Gets or sets a value indicating whether the session has a custom device name. + /// + /// true if this session has a custom device name; otherwise, false. + public bool HasCustomDeviceName { get; set; } + + /// + /// Gets or sets the playlist item id. + /// + /// The splaylist item id. + public string? PlaylistItemId { get; set; } + + /// + /// Gets or sets the server id. + /// + /// The server id. + public string? ServerId { get; set; } + + /// + /// Gets or sets the user primary image tag. + /// + /// The user primary image tag. + public string? UserPrimaryImageTag { get; set; } + + /// + /// Gets or sets the supported commands. + /// + /// The supported commands. + public IReadOnlyList SupportedCommands { get; set; } = []; +} diff --git a/src/Jellyfin.Extensions/Json/Converters/JsonDelimitedArrayConverter.cs b/src/Jellyfin.Extensions/Json/Converters/JsonDelimitedArrayConverter.cs index 1466d3a71..b9477ce6b 100644 --- a/src/Jellyfin.Extensions/Json/Converters/JsonDelimitedArrayConverter.cs +++ b/src/Jellyfin.Extensions/Json/Converters/JsonDelimitedArrayConverter.cs @@ -1,5 +1,7 @@ using System; +using System.Collections.Generic; using System.ComponentModel; +using System.Linq; using System.Text.Json; using System.Text.Json.Serialization; @@ -35,38 +37,27 @@ namespace Jellyfin.Extensions.Json.Converters var stringEntries = reader.GetString()!.Split(Delimiter, StringSplitOptions.RemoveEmptyEntries); if (stringEntries.Length == 0) { - return Array.Empty(); + return []; } - var parsedValues = new object[stringEntries.Length]; - var convertedCount = 0; + var typedValues = new List(); for (var i = 0; i < stringEntries.Length; i++) { try { - parsedValues[i] = _typeConverter.ConvertFromInvariantString(stringEntries[i].Trim()) ?? throw new FormatException(); - convertedCount++; + var parsedValue = _typeConverter.ConvertFromInvariantString(stringEntries[i].Trim()); + if (parsedValue is not null) + { + typedValues.Add((T)parsedValue); + } } catch (FormatException) { - // TODO log when upgraded to .Net6 - // https://github.com/dotnet/runtime/issues/42975 - // _logger.LogDebug(e, "Error converting value."); + // Ignore unconvertable inputs } } - var typedValues = new T[convertedCount]; - var typedValueIndex = 0; - for (var i = 0; i < stringEntries.Length; i++) - { - if (parsedValues[i] is not null) - { - typedValues.SetValue(parsedValues[i], typedValueIndex); - typedValueIndex++; - } - } - - return typedValues; + return [.. typedValues]; } return JsonSerializer.Deserialize(ref reader, options); @@ -75,7 +66,39 @@ namespace Jellyfin.Extensions.Json.Converters /// public override void Write(Utf8JsonWriter writer, T[]? value, JsonSerializerOptions options) { - throw new NotImplementedException(); + if (value is not null) + { + writer.WriteStartArray(); + if (value.Length > 0) + { + var toWrite = value.Length - 1; + foreach (var it in value) + { + var wrote = false; + if (it is not null) + { + writer.WriteStringValue(it.ToString()); + wrote = true; + } + + if (toWrite > 0) + { + if (wrote) + { + writer.WriteStringValue(Delimiter.ToString()); + } + + toWrite--; + } + } + } + + writer.WriteEndArray(); + } + else + { + writer.WriteNullValue(); + } } } } diff --git a/tests/Jellyfin.Extensions.Tests/Json/Converters/JsonCommaDelimitedArrayTests.cs b/tests/Jellyfin.Extensions.Tests/Json/Converters/JsonCommaDelimitedArrayTests.cs index 61105b42b..9fc015823 100644 --- a/tests/Jellyfin.Extensions.Tests/Json/Converters/JsonCommaDelimitedArrayTests.cs +++ b/tests/Jellyfin.Extensions.Tests/Json/Converters/JsonCommaDelimitedArrayTests.cs @@ -41,7 +41,7 @@ namespace Jellyfin.Extensions.Tests.Json.Converters { var desiredValue = new GenericBodyArrayModel { - Value = new[] { "a", "b", "c" } + Value = ["a", "b", "c"] }; var value = JsonSerializer.Deserialize>(@"{ ""Value"": ""a,b,c"" }", _jsonOptions); @@ -53,7 +53,7 @@ namespace Jellyfin.Extensions.Tests.Json.Converters { var desiredValue = new GenericBodyArrayModel { - Value = new[] { "a", "b", "c" } + Value = ["a", "b", "c"] }; var value = JsonSerializer.Deserialize>(@"{ ""Value"": ""a, b, c"" }", _jsonOptions); @@ -65,7 +65,7 @@ namespace Jellyfin.Extensions.Tests.Json.Converters { var desiredValue = new GenericBodyArrayModel { - Value = new[] { GeneralCommandType.MoveUp, GeneralCommandType.MoveDown } + Value = [GeneralCommandType.MoveUp, GeneralCommandType.MoveDown] }; var value = JsonSerializer.Deserialize>(@"{ ""Value"": ""MoveUp,MoveDown"" }", _jsonOptions); @@ -77,7 +77,7 @@ namespace Jellyfin.Extensions.Tests.Json.Converters { var desiredValue = new GenericBodyArrayModel { - Value = new[] { GeneralCommandType.MoveUp, GeneralCommandType.MoveDown } + Value = [GeneralCommandType.MoveUp, GeneralCommandType.MoveDown] }; var value = JsonSerializer.Deserialize>(@"{ ""Value"": ""MoveUp,,MoveDown"" }", _jsonOptions); @@ -89,7 +89,7 @@ namespace Jellyfin.Extensions.Tests.Json.Converters { var desiredValue = new GenericBodyArrayModel { - Value = new[] { GeneralCommandType.MoveUp, GeneralCommandType.MoveDown } + Value = [GeneralCommandType.MoveUp, GeneralCommandType.MoveDown] }; var value = JsonSerializer.Deserialize>(@"{ ""Value"": ""MoveUp,TotallyNotAVallidCommand,MoveDown"" }", _jsonOptions); @@ -101,7 +101,7 @@ namespace Jellyfin.Extensions.Tests.Json.Converters { var desiredValue = new GenericBodyArrayModel { - Value = new[] { GeneralCommandType.MoveUp, GeneralCommandType.MoveDown } + Value = [GeneralCommandType.MoveUp, GeneralCommandType.MoveDown] }; var value = JsonSerializer.Deserialize>(@"{ ""Value"": ""MoveUp, MoveDown"" }", _jsonOptions); @@ -113,7 +113,7 @@ namespace Jellyfin.Extensions.Tests.Json.Converters { var desiredValue = new GenericBodyArrayModel { - Value = new[] { "a", "b", "c" } + Value = ["a", "b", "c"] }; var value = JsonSerializer.Deserialize>(@"{ ""Value"": [""a"",""b"",""c""] }", _jsonOptions); @@ -125,7 +125,7 @@ namespace Jellyfin.Extensions.Tests.Json.Converters { var desiredValue = new GenericBodyArrayModel { - Value = new[] { GeneralCommandType.MoveUp, GeneralCommandType.MoveDown } + Value = [GeneralCommandType.MoveUp, GeneralCommandType.MoveDown] }; var value = JsonSerializer.Deserialize>(@"{ ""Value"": [""MoveUp"", ""MoveDown""] }", _jsonOptions); -- cgit v1.2.3 From ffa1c370fd4b92df15609cd3706b8ebcff930e0d Mon Sep 17 00:00:00 2001 From: Shadowghost Date: Wed, 18 Sep 2024 16:10:13 +0200 Subject: Fix permission checks --- Emby.Server.Implementations/Session/SessionManager.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 6bcbe3ceb..55e485669 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1886,7 +1886,7 @@ namespace Emby.Server.Implementations.Session if (!user.HasPermission(PermissionKind.EnableRemoteControlOfOtherUsers)) { // User cannot control other user's sessions, validate user id. - result = result.Where(i => i.UserId.IsEmpty() || i.ContainsUser(controllableUserToCheck.Value)); + result = result.Where(i => i.UserId.IsEmpty() || i.ContainsUser(user.Id)); } result = result.Where(i => @@ -1903,7 +1903,10 @@ namespace Emby.Server.Implementations.Session { // Request isn't from administrator, limit to "own" sessions. result = result.Where(i => i.UserId.IsEmpty() || i.ContainsUser(userId)); + } + if (!user.HasPermission(PermissionKind.IsAdministrator)) + { // Don't report acceleration type for non-admin users. result = result.Select(r => { -- cgit v1.2.3 From 9c4bf48b4ea85aac30d96d70a198247e56a6323d Mon Sep 17 00:00:00 2001 From: Balázs Meskó Date: Wed, 18 Sep 2024 15:29:42 +0000 Subject: Translated using Weblate (Hungarian) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/hu/ --- .../Localization/Core/hu.json | 49 ++++++++++++---------- 1 file changed, 26 insertions(+), 23 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/hu.json b/Emby.Server.Implementations/Localization/Core/hu.json index 2c8533ac6..f205e8b64 100644 --- a/Emby.Server.Implementations/Localization/Core/hu.json +++ b/Emby.Server.Implementations/Localization/Core/hu.json @@ -1,13 +1,13 @@ { "Albums": "Albumok", - "AppDeviceValues": "Program: {0}, Eszköz: {1}", + "AppDeviceValues": "Program: {0}, eszköz: {1}", "Application": "Alkalmazás", "Artists": "Előadók", "AuthenticationSucceededWithUserName": "{0} sikeresen hitelesítve", "Books": "Könyvek", "CameraImageUploadedFrom": "Új kamerakép lett feltöltve innen: {0}", "Channels": "Csatornák", - "ChapterNameValue": "Jelenet {0}", + "ChapterNameValue": "{0}. jelenet", "Collections": "Gyűjtemények", "DeviceOfflineWithName": "{0} kijelentkezett", "DeviceOnlineWithName": "{0} belépett", @@ -15,31 +15,31 @@ "Favorites": "Kedvencek", "Folders": "Könyvtárak", "Genres": "Műfajok", - "HeaderAlbumArtists": "Album előadók", + "HeaderAlbumArtists": "Albumelőadók", "HeaderContinueWatching": "Megtekintés folytatása", - "HeaderFavoriteAlbums": "Kedvenc Albumok", - "HeaderFavoriteArtists": "Kedvenc Előadók", - "HeaderFavoriteEpisodes": "Kedvenc Epizódok", - "HeaderFavoriteShows": "Kedvenc Sorozatok", - "HeaderFavoriteSongs": "Kedvenc Dalok", + "HeaderFavoriteAlbums": "Kedvenc albumok", + "HeaderFavoriteArtists": "Kedvenc előadók", + "HeaderFavoriteEpisodes": "Kedvenc epizódok", + "HeaderFavoriteShows": "Kedvenc sorozatok", + "HeaderFavoriteSongs": "Kedvenc számok", "HeaderLiveTV": "Élő TV", "HeaderNextUp": "Következik", - "HeaderRecordingGroups": "Felvevő Csoportok", - "HomeVideos": "Otthoni Videók", - "Inherit": "Örökölt", - "ItemAddedWithName": "{0} hozzáadva a könyvtárhoz", - "ItemRemovedWithName": "{0} eltávolítva a könyvtárból", + "HeaderRecordingGroups": "Felvételi csoportok", + "HomeVideos": "Otthoni videók", + "Inherit": "Öröklés", + "ItemAddedWithName": "{0} hozzáadva a médiatárhoz", + "ItemRemovedWithName": "{0} eltávolítva a médiatárból", "LabelIpAddressValue": "IP-cím: {0}", "LabelRunningTimeValue": "Lejátszási idő: {0}", "Latest": "Legújabb", "MessageApplicationUpdated": "A Jellyfin kiszolgáló frissítve lett", "MessageApplicationUpdatedTo": "A Jellyfin kiszolgáló frissítve lett a következőre: {0}", "MessageNamedServerConfigurationUpdatedWithValue": "A kiszolgálókonfigurációs rész frissítve lett: {0}", - "MessageServerConfigurationUpdated": "Kiszolgálókonfiguráció frissítve lett", + "MessageServerConfigurationUpdated": "A kiszolgálókonfiguráció frissítve lett", "MixedContent": "Vegyes tartalom", "Movies": "Filmek", "Music": "Zenék", - "MusicVideos": "Zenei videóklippek", + "MusicVideos": "Zenei videóklipek", "NameInstallFailed": "{0} sikertelen telepítés", "NameSeasonNumber": "{0}. évad", "NameSeasonUnknown": "Ismeretlen évad", @@ -56,7 +56,7 @@ "NotificationOptionPluginUninstalled": "Bővítmény eltávolítva", "NotificationOptionPluginUpdateInstalled": "Bővítményfrissítés telepítve", "NotificationOptionServerRestartRequired": "A kiszolgáló újraindítása szükséges", - "NotificationOptionTaskFailed": "Ütemezett feladat hiba", + "NotificationOptionTaskFailed": "Hiba az ütemezett feladatban", "NotificationOptionUserLockedOut": "Felhasználó tiltva", "NotificationOptionVideoPlayback": "Videólejátszás elkezdve", "NotificationOptionVideoPlaybackStopped": "Videólejátszás leállítva", @@ -107,7 +107,7 @@ "TaskCleanCache": "Gyorsítótár könyvtárának ürítése", "TasksChannelsCategory": "Internetes csatornák", "TasksApplicationCategory": "Alkalmazás", - "TasksLibraryCategory": "Könyvtár", + "TasksLibraryCategory": "Médiatár", "TasksMaintenanceCategory": "Karbantartás", "TaskDownloadMissingSubtitlesDescription": "A metaadat-konfiguráció alapján ellenőrzi és letölti a hiányzó feliratokat az internetről.", "TaskDownloadMissingSubtitles": "Hiányzó feliratok letöltése", @@ -119,19 +119,22 @@ "Undefined": "Meghatározatlan", "Forced": "Kényszerített", "Default": "Alapértelmezett", - "TaskOptimizeDatabaseDescription": "Tömöríti az adatbázist és csonkolja a szabad helyet. A feladat futtatása a könyvtár beolvasása után, vagy egyéb, adatbázis-módosítást igénylő változtatások végrehajtása javíthatja a teljesítményt.", + "TaskOptimizeDatabaseDescription": "Tömöríti az adatbázist és csonkolja a szabad helyet. A feladat futtatása a médiatár beolvasása, vagy egyéb adatbázis-módosítást igénylő változtatás végrehajtása után, javíthatja a teljesítményt.", "TaskOptimizeDatabase": "Adatbázis optimalizálása", "TaskKeyframeExtractor": "Kulcsképkockák kibontása", "TaskKeyframeExtractorDescription": "Kibontja a kulcsképkockákat a videófájlokból, hogy pontosabb HLS lejátszási listákat hozzon létre. Ez a feladat hosszú ideig tarthat.", "External": "Külső", "HearingImpaired": "Hallássérült", - "TaskRefreshTrickplayImages": "Trickplay képek generálása", + "TaskRefreshTrickplayImages": "Trickplay képek előállítása", "TaskRefreshTrickplayImagesDescription": "Trickplay előnézetet készít az engedélyezett könyvtárakban lévő videókhoz.", - "TaskAudioNormalization": "Hangerő Normalizáció", + "TaskAudioNormalization": "Hangerő-normalizálás", "TaskCleanCollectionsAndPlaylistsDescription": "Nem létező elemek törlése a gyűjteményekből és lejátszási listákról.", - "TaskAudioNormalizationDescription": "Hangerő normalizációs adatok keresése.", + "TaskAudioNormalizationDescription": "Hangerő-normalizálási adatok keresése.", "TaskCleanCollectionsAndPlaylists": "Gyűjtemények és lejátszási listák optimalizálása", - "TaskExtractMediaSegments": "Média szegmens felismerése", + "TaskExtractMediaSegments": "Médiaszegmens felismerése", "TaskDownloadMissingLyrics": "Hiányzó szöveg letöltése", - "TaskDownloadMissingLyricsDescription": "Zenék szövegének letöltése" + "TaskDownloadMissingLyricsDescription": "Zenék szövegének letöltése", + "TaskMoveTrickplayImages": "Trickplay képek helyének átköltöztetése", + "TaskMoveTrickplayImagesDescription": "A médiatár-beállításoknak megfelelően áthelyezi a meglévő trickplay fájlokat.", + "TaskExtractMediaSegmentsDescription": "Kinyeri vagy megszerzi a médiaszegmenseket a MediaSegment támogatással rendelkező bővítményekből." } -- cgit v1.2.3 From 03aa37731b39135e3b3be3f8751f3018d7427d10 Mon Sep 17 00:00:00 2001 From: Brian Howe <30811239+bhowe34@users.noreply.github.com> Date: Thu, 19 Sep 2024 08:12:32 -0500 Subject: Watch library directories with perm errors (#10684) --- Emby.Server.Implementations/IO/LibraryMonitor.cs | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/IO/LibraryMonitor.cs b/Emby.Server.Implementations/IO/LibraryMonitor.cs index 31617d1a5..6af2a553d 100644 --- a/Emby.Server.Implementations/IO/LibraryMonitor.cs +++ b/Emby.Server.Implementations/IO/LibraryMonitor.cs @@ -314,6 +314,12 @@ namespace Emby.Server.Implementations.IO var ex = e.GetException(); var dw = (FileSystemWatcher)sender; + if (ex is UnauthorizedAccessException unauthorizedAccessException) + { + _logger.LogError(unauthorizedAccessException, "Permission error for Directory watcher: {Path}", dw.Path); + return; + } + _logger.LogError(ex, "Error in Directory watcher for: {Path}", dw.Path); DisposeWatcher(dw, true); -- cgit v1.2.3 From 2c0520b5401620bc121e1e4424e3b4a3f08656f2 Mon Sep 17 00:00:00 2001 From: Bas <44002186+854562@users.noreply.github.com> Date: Thu, 19 Sep 2024 13:41:47 +0000 Subject: Translated using Weblate (Dutch) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/nl/ --- .../Localization/Core/nl.json | 24 +++++++++++----------- 1 file changed, 12 insertions(+), 12 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/nl.json b/Emby.Server.Implementations/Localization/Core/nl.json index 1522720dc..7d101195b 100644 --- a/Emby.Server.Implementations/Localization/Core/nl.json +++ b/Emby.Server.Implementations/Localization/Core/nl.json @@ -16,13 +16,13 @@ "Folders": "Mappen", "Genres": "Genres", "HeaderAlbumArtists": "Albumartiesten", - "HeaderContinueWatching": "Kijken hervatten", + "HeaderContinueWatching": "Verderkijken", "HeaderFavoriteAlbums": "Favoriete albums", "HeaderFavoriteArtists": "Favoriete artiesten", "HeaderFavoriteEpisodes": "Favoriete afleveringen", - "HeaderFavoriteShows": "Favoriete shows", + "HeaderFavoriteShows": "Favoriete series", "HeaderFavoriteSongs": "Favoriete nummers", - "HeaderLiveTV": "Live TV", + "HeaderLiveTV": "Live-tv", "HeaderNextUp": "Volgende", "HeaderRecordingGroups": "Opnamegroepen", "HomeVideos": "Homevideo's", @@ -34,8 +34,8 @@ "Latest": "Nieuwste", "MessageApplicationUpdated": "Jellyfin Server is bijgewerkt", "MessageApplicationUpdatedTo": "Jellyfin Server is bijgewerkt naar {0}", - "MessageNamedServerConfigurationUpdatedWithValue": "Sectie {0} van de server configuratie is bijgewerkt", - "MessageServerConfigurationUpdated": "Server configuratie is bijgewerkt", + "MessageNamedServerConfigurationUpdatedWithValue": "Sectie {0} van de serverconfiguratie is bijgewerkt", + "MessageServerConfigurationUpdated": "Serverconfiguratie is bijgewerkt", "MixedContent": "Gemengde inhoud", "Movies": "Films", "Music": "Muziek", @@ -50,12 +50,12 @@ "NotificationOptionAudioPlaybackStopped": "Muziek gestopt", "NotificationOptionCameraImageUploaded": "Camera-afbeelding geüpload", "NotificationOptionInstallationFailed": "Installatie mislukt", - "NotificationOptionNewLibraryContent": "Nieuwe content toegevoegd", - "NotificationOptionPluginError": "Plug-in fout", + "NotificationOptionNewLibraryContent": "Nieuwe inhoud toegevoegd", + "NotificationOptionPluginError": "Plug-in-fout", "NotificationOptionPluginInstalled": "Plug-in geïnstalleerd", "NotificationOptionPluginUninstalled": "Plug-in verwijderd", "NotificationOptionPluginUpdateInstalled": "Plug-in-update geïnstalleerd", - "NotificationOptionServerRestartRequired": "Server herstart nodig", + "NotificationOptionServerRestartRequired": "Herstarten server vereist", "NotificationOptionTaskFailed": "Geplande taak mislukt", "NotificationOptionUserLockedOut": "Gebruiker is vergrendeld", "NotificationOptionVideoPlayback": "Afspelen van video gestart", @@ -72,16 +72,16 @@ "ServerNameNeedsToBeRestarted": "{0} moet herstart worden", "Shows": "Series", "Songs": "Nummers", - "StartupEmbyServerIsLoading": "Jellyfin Server is aan het laden, probeer het later opnieuw.", + "StartupEmbyServerIsLoading": "Jellyfin Server is aan het laden. Probeer het later opnieuw.", "SubtitleDownloadFailureForItem": "Downloaden van ondertiteling voor {0} is mislukt", - "SubtitleDownloadFailureFromForItem": "Ondertitels konden niet gedownload worden van {0} voor {1}", + "SubtitleDownloadFailureFromForItem": "Ondertiteling kon niet gedownload worden van {0} voor {1}", "Sync": "Synchronisatie", "System": "Systeem", "TvShows": "TV-series", "User": "Gebruiker", "UserCreatedWithName": "Gebruiker {0} is aangemaakt", "UserDeletedWithName": "Gebruiker {0} is verwijderd", - "UserDownloadingItemWithValues": "{0} download {1}", + "UserDownloadingItemWithValues": "{0} downloadt {1}", "UserLockedOutWithName": "Gebruikersaccount {0} is vergrendeld", "UserOfflineFromDevice": "Verbinding van {0} met {1} is verbroken", "UserOnlineFromDevice": "{0} heeft verbinding met {1}", @@ -90,7 +90,7 @@ "UserStartedPlayingItemWithValues": "{0} speelt {1} af op {2}", "UserStoppedPlayingItemWithValues": "{0} heeft afspelen van {1} gestopt op {2}", "ValueHasBeenAddedToLibrary": "{0} is toegevoegd aan je mediabibliotheek", - "ValueSpecialEpisodeName": "Speciaal - {0}", + "ValueSpecialEpisodeName": "Special - {0}", "VersionNumber": "Versie {0}", "TaskDownloadMissingSubtitlesDescription": "Zoekt op het internet naar ontbrekende ondertiteling gebaseerd op metadataconfiguratie.", "TaskDownloadMissingSubtitles": "Ontbrekende ondertiteling downloaden", -- cgit v1.2.3 From 78638c72fb2fd15e7c9277bb37f5a6a93b9cc0a9 Mon Sep 17 00:00:00 2001 From: l00d3r Date: Sat, 21 Sep 2024 11:52:24 +0000 Subject: Translated using Weblate (Estonian) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/et/ --- Emby.Server.Implementations/Localization/Core/et.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/et.json b/Emby.Server.Implementations/Localization/Core/et.json index 075bcc9a4..fcb12718a 100644 --- a/Emby.Server.Implementations/Localization/Core/et.json +++ b/Emby.Server.Implementations/Localization/Core/et.json @@ -102,7 +102,7 @@ "Forced": "Sunnitud", "Folders": "Kaustad", "Favorites": "Lemmikud", - "FailedLoginAttemptWithUserName": "{0} - sisselogimine nurjus", + "FailedLoginAttemptWithUserName": "Sisselogimine nurjus aadressilt {0}", "DeviceOnlineWithName": "{0} on ühendatud", "DeviceOfflineWithName": "{0} katkestas ühenduse", "Default": "Vaikimisi", -- cgit v1.2.3 From 092e9e29f1d94a8b6a88868d56c5b4801cbe5e35 Mon Sep 17 00:00:00 2001 From: l00d3r Date: Sat, 21 Sep 2024 12:32:53 +0000 Subject: Translated using Weblate (Estonian) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/et/ --- Emby.Server.Implementations/Localization/Core/et.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/et.json b/Emby.Server.Implementations/Localization/Core/et.json index fcb12718a..7ca4e431d 100644 --- a/Emby.Server.Implementations/Localization/Core/et.json +++ b/Emby.Server.Implementations/Localization/Core/et.json @@ -129,5 +129,7 @@ "TaskAudioNormalization": "Heli Normaliseerimine", "TaskAudioNormalizationDescription": "Skaneerib faile heli normaliseerimise andmete jaoks.", "TaskCleanCollectionsAndPlaylistsDescription": "Eemaldab kogumikest ja esitusloenditest asjad, mida enam ei eksisteeri.", - "TaskCleanCollectionsAndPlaylists": "Puhasta kogumikud ja esitusloendid" + "TaskCleanCollectionsAndPlaylists": "Puhasta kogumikud ja esitusloendid", + "TaskDownloadMissingLyrics": "Lae alla puuduolev lüürika", + "TaskDownloadMissingLyricsDescription": "Lae lauludele alla lüürika" } -- cgit v1.2.3 From 38f80edc8011783d6544a9c1bd6401e3fe13ff57 Mon Sep 17 00:00:00 2001 From: l00d3r Date: Sat, 21 Sep 2024 12:37:52 +0000 Subject: Translated using Weblate (Estonian) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/et/ --- Emby.Server.Implementations/Localization/Core/et.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/et.json b/Emby.Server.Implementations/Localization/Core/et.json index 7ca4e431d..3b2bb70a9 100644 --- a/Emby.Server.Implementations/Localization/Core/et.json +++ b/Emby.Server.Implementations/Localization/Core/et.json @@ -131,5 +131,9 @@ "TaskCleanCollectionsAndPlaylistsDescription": "Eemaldab kogumikest ja esitusloenditest asjad, mida enam ei eksisteeri.", "TaskCleanCollectionsAndPlaylists": "Puhasta kogumikud ja esitusloendid", "TaskDownloadMissingLyrics": "Lae alla puuduolev lüürika", - "TaskDownloadMissingLyricsDescription": "Lae lauludele alla lüürika" + "TaskDownloadMissingLyricsDescription": "Lae lauludele alla lüürika", + "TaskMoveTrickplayImagesDescription": "Liigutab trickplay pildid meediakogu sätete kohaselt.", + "TaskExtractMediaSegments": "Meediasegmentide skaneerimine", + "TaskExtractMediaSegmentsDescription": "Eraldab või võtab meediasegmendid MediaSegment'i lubavatest pluginatest.", + "TaskMoveTrickplayImages": "Migreeri trickplay piltide asukoht" } -- cgit v1.2.3 From 62606e46b538138d2d8c5b901344cdecc069c5c6 Mon Sep 17 00:00:00 2001 From: Nyanmisaka <799610810@qq.com> Date: Sun, 22 Sep 2024 09:56:44 +0000 Subject: Translated using Weblate (Chinese (Simplified Han script)) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/zh_Hans/ --- Emby.Server.Implementations/Localization/Core/zh-CN.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/zh-CN.json b/Emby.Server.Implementations/Localization/Core/zh-CN.json index cbec0979a..a406a73b7 100644 --- a/Emby.Server.Implementations/Localization/Core/zh-CN.json +++ b/Emby.Server.Implementations/Localization/Core/zh-CN.json @@ -125,15 +125,15 @@ "TaskKeyframeExtractor": "关键帧提取器", "External": "外部", "HearingImpaired": "听力障碍", - "TaskRefreshTrickplayImages": "生成时间轴缩略图", - "TaskRefreshTrickplayImagesDescription": "为启用的媒体库中的视频生成时间轴缩略图。", + "TaskRefreshTrickplayImages": "生成特技播放预览图", + "TaskRefreshTrickplayImagesDescription": "为启用的媒体库中的视频生成特技播放预览图。", "TaskCleanCollectionsAndPlaylists": "清理合集和播放列表", "TaskCleanCollectionsAndPlaylistsDescription": "清理合集和播放列表中已不存在的项目。", "TaskAudioNormalization": "音频标准化", "TaskAudioNormalizationDescription": "扫描文件以寻找音频标准化数据。", "TaskDownloadMissingLyrics": "下载缺失的歌词", "TaskDownloadMissingLyricsDescription": "下载歌曲歌词", - "TaskMoveTrickplayImages": "迁移时间轴缩略图的存储位置", + "TaskMoveTrickplayImages": "迁移特技播放预览图的存储位置", "TaskExtractMediaSegments": "媒体片段扫描", "TaskExtractMediaSegmentsDescription": "从支持 MediaSegment 的插件中提取或获取媒体片段。", "TaskMoveTrickplayImagesDescription": "根据媒体库设置移动现有的特技播放文件。" -- cgit v1.2.3 From e21592f473845a587312dc08935f623d4d8f745b Mon Sep 17 00:00:00 2001 From: hoanghuy309 Date: Sun, 22 Sep 2024 18:06:29 +0000 Subject: Translated using Weblate (Vietnamese) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/vi/ --- Emby.Server.Implementations/Localization/Core/vi.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/vi.json b/Emby.Server.Implementations/Localization/Core/vi.json index 32e2f4bab..f890ea74d 100644 --- a/Emby.Server.Implementations/Localization/Core/vi.json +++ b/Emby.Server.Implementations/Localization/Core/vi.json @@ -131,5 +131,9 @@ "TaskAudioNormalization": "Chuẩn Hóa Âm Thanh", "TaskAudioNormalizationDescription": "Quét tập tin để tìm dữ liệu chuẩn hóa âm thanh.", "TaskDownloadMissingLyricsDescription": "Tải xuống lời cho bài hát", - "TaskDownloadMissingLyrics": "Tải xuống lời bị thiếu" + "TaskDownloadMissingLyrics": "Tải xuống lời bị thiếu", + "TaskExtractMediaSegmentsDescription": "Trích xuất hoặc lấy các phân đoạn phương tiện từ các plugin hỗ trợ MediaSegment.", + "TaskMoveTrickplayImages": "Di chuyển vị trí hình ảnh Trickplay", + "TaskMoveTrickplayImagesDescription": "Di chuyển các tập tin trickplay hiện có theo cài đặt thư viện.", + "TaskExtractMediaSegments": "Quét Phân Đoạn Phương Tiện" } -- cgit v1.2.3 From c108e5c4854e0b0a83361b05c39827186c545eb4 Mon Sep 17 00:00:00 2001 From: Nyanmisaka <799610810@qq.com> Date: Sun, 22 Sep 2024 19:49:50 +0000 Subject: Translated using Weblate (Chinese (Simplified Han script)) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/zh_Hans/ --- Emby.Server.Implementations/Localization/Core/zh-CN.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/zh-CN.json b/Emby.Server.Implementations/Localization/Core/zh-CN.json index a406a73b7..3256569e0 100644 --- a/Emby.Server.Implementations/Localization/Core/zh-CN.json +++ b/Emby.Server.Implementations/Localization/Core/zh-CN.json @@ -125,16 +125,16 @@ "TaskKeyframeExtractor": "关键帧提取器", "External": "外部", "HearingImpaired": "听力障碍", - "TaskRefreshTrickplayImages": "生成特技播放预览图", - "TaskRefreshTrickplayImagesDescription": "为启用的媒体库中的视频生成特技播放预览图。", + "TaskRefreshTrickplayImages": "生成进度条预览图", + "TaskRefreshTrickplayImagesDescription": "为启用的媒体库中的视频生成进度条预览图。", "TaskCleanCollectionsAndPlaylists": "清理合集和播放列表", "TaskCleanCollectionsAndPlaylistsDescription": "清理合集和播放列表中已不存在的项目。", "TaskAudioNormalization": "音频标准化", "TaskAudioNormalizationDescription": "扫描文件以寻找音频标准化数据。", "TaskDownloadMissingLyrics": "下载缺失的歌词", "TaskDownloadMissingLyricsDescription": "下载歌曲歌词", - "TaskMoveTrickplayImages": "迁移特技播放预览图的存储位置", + "TaskMoveTrickplayImages": "迁移进度条预览图的存储位置", "TaskExtractMediaSegments": "媒体片段扫描", "TaskExtractMediaSegmentsDescription": "从支持 MediaSegment 的插件中提取或获取媒体片段。", - "TaskMoveTrickplayImagesDescription": "根据媒体库设置移动现有的特技播放文件。" + "TaskMoveTrickplayImagesDescription": "根据媒体库设置移动现有的进度条预览图文件。" } -- cgit v1.2.3 From dafd1864104afe8d9bb8f20cf8bf40931a0417f1 Mon Sep 17 00:00:00 2001 From: elfalem Date: Mon, 23 Sep 2024 18:52:18 -0400 Subject: Ensure user's own playlists are accessible regardless of allowed tags --- Emby.Server.Implementations/Data/SqliteItemRepository.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 60f5ee47a..5598c81dc 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -4203,6 +4203,15 @@ namespace Emby.Server.Implementations.Data OR (select CleanValue from ItemValues where ItemId=ParentId and Type=6 and CleanValue in ({includedTags})) is not null) """); } + + // A playlist should be accessible to its owner regardless of allowed tags. + else if (includeTypes.Length == 1 && includeTypes.FirstOrDefault() is BaseItemKind.Playlist) + { + whereClauses.Add($""" + ((select CleanValue from ItemValues where ItemId=Guid and Type=6 and CleanValue in ({includedTags})) is not null + OR data like @PlaylistOwnerUserId) + """); + } else { whereClauses.Add("((select CleanValue from ItemValues where ItemId=Guid and Type=6 and cleanvalue in (" + includedTags + ")) is not null)"); @@ -4214,6 +4223,11 @@ namespace Emby.Server.Implementations.Data { statement.TryBind(paramName + index, GetCleanValue(query.IncludeInheritedTags[index])); } + + if (query.User != null) + { + statement.TryBind("@PlaylistOwnerUserId", $"""%"OwnerUserId":"{query.User.Id.ToString("N")}"%"""); + } } } -- cgit v1.2.3 From 75bbd3029613829a9b55ac01e27093583fc8cf52 Mon Sep 17 00:00:00 2001 From: gnattu Date: Tue, 24 Sep 2024 22:15:53 +0800 Subject: Fix get sessions with api key (#12696) --- .../Session/SessionManager.cs | 44 ++++++++++++++++++---- Jellyfin.Api/Controllers/SessionController.cs | 3 +- MediaBrowser.Controller/Session/ISessionManager.cs | 3 +- 3 files changed, 40 insertions(+), 10 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 55e485669..6a8ad2bdc 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1858,15 +1858,38 @@ namespace Emby.Server.Implementations.Session Guid userId, string deviceId, int? activeWithinSeconds, - Guid? controllableUserToCheck) + Guid? controllableUserToCheck, + bool isApiKey) { var result = Sessions; - var user = _userManager.GetUserById(userId); if (!string.IsNullOrEmpty(deviceId)) { result = result.Where(i => string.Equals(i.DeviceId, deviceId, StringComparison.OrdinalIgnoreCase)); } + var userCanControlOthers = false; + var userIsAdmin = false; + User user = null; + + if (isApiKey) + { + userCanControlOthers = true; + userIsAdmin = true; + } + else if (!userId.IsEmpty()) + { + user = _userManager.GetUserById(userId); + if (user is not null) + { + userCanControlOthers = user.HasPermission(PermissionKind.EnableRemoteControlOfOtherUsers); + userIsAdmin = user.HasPermission(PermissionKind.IsAdministrator); + } + else + { + return []; + } + } + if (!controllableUserToCheck.IsNullOrEmpty()) { result = result.Where(i => i.SupportsRemoteControl); @@ -1883,29 +1906,34 @@ namespace Emby.Server.Implementations.Session result = result.Where(i => !i.UserId.IsEmpty()); } - if (!user.HasPermission(PermissionKind.EnableRemoteControlOfOtherUsers)) + if (!userCanControlOthers) { // User cannot control other user's sessions, validate user id. - result = result.Where(i => i.UserId.IsEmpty() || i.ContainsUser(user.Id)); + result = result.Where(i => i.UserId.IsEmpty() || i.ContainsUser(userId)); } result = result.Where(i => { - if (!string.IsNullOrWhiteSpace(i.DeviceId) && !_deviceManager.CanAccessDevice(user, i.DeviceId)) + if (isApiKey) + { + return true; + } + + if (user is null) { return false; } - return true; + return string.IsNullOrWhiteSpace(i.DeviceId) || _deviceManager.CanAccessDevice(user, i.DeviceId); }); } - else if (!user.HasPermission(PermissionKind.IsAdministrator)) + else if (!userIsAdmin) { // Request isn't from administrator, limit to "own" sessions. result = result.Where(i => i.UserId.IsEmpty() || i.ContainsUser(userId)); } - if (!user.HasPermission(PermissionKind.IsAdministrator)) + if (!userIsAdmin) { // Don't report acceleration type for non-admin users. result = result.Select(r => diff --git a/Jellyfin.Api/Controllers/SessionController.cs b/Jellyfin.Api/Controllers/SessionController.cs index 72eb93eff..2f9e9f091 100644 --- a/Jellyfin.Api/Controllers/SessionController.cs +++ b/Jellyfin.Api/Controllers/SessionController.cs @@ -62,7 +62,8 @@ public class SessionController : BaseJellyfinApiController User.GetUserId(), deviceId, activeWithinSeconds, - controllableUserToCheck); + controllableUserToCheck, + User.GetIsApiKey()); return Ok(result); } diff --git a/MediaBrowser.Controller/Session/ISessionManager.cs b/MediaBrowser.Controller/Session/ISessionManager.cs index f2e98dd78..462a62455 100644 --- a/MediaBrowser.Controller/Session/ISessionManager.cs +++ b/MediaBrowser.Controller/Session/ISessionManager.cs @@ -300,8 +300,9 @@ namespace MediaBrowser.Controller.Session /// The device id. /// Active within session limit. /// Filter for sessions remote controllable for this user. + /// Is the request authenticated with API key. /// IReadOnlyList{SessionInfoDto}. - IReadOnlyList GetSessions(Guid userId, string deviceId, int? activeWithinSeconds, Guid? controllableUserToCheck); + IReadOnlyList GetSessions(Guid userId, string deviceId, int? activeWithinSeconds, Guid? controllableUserToCheck, bool isApiKey); /// /// Gets the session by authentication token. -- cgit v1.2.3 From 8499be23ccfbe1b7eed6768d3ea634a6eed05217 Mon Sep 17 00:00:00 2001 From: elfalem Date: Wed, 25 Sep 2024 20:05:00 -0400 Subject: Update SqliteItemRepository.cs - incorporate review suggestion Co-authored-by: Bond-009 --- Emby.Server.Implementations/Data/SqliteItemRepository.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 5598c81dc..3da925f93 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -4224,7 +4224,7 @@ namespace Emby.Server.Implementations.Data statement.TryBind(paramName + index, GetCleanValue(query.IncludeInheritedTags[index])); } - if (query.User != null) + if (query.User is not null) { statement.TryBind("@PlaylistOwnerUserId", $"""%"OwnerUserId":"{query.User.Id.ToString("N")}"%"""); } -- cgit v1.2.3 From b7093b0d407dd43a40d819b05ada7ed4a836c712 Mon Sep 17 00:00:00 2001 From: Tim Wang Date: Thu, 26 Sep 2024 05:28:06 +0000 Subject: Translated using Weblate (Japanese) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ja/ --- Emby.Server.Implementations/Localization/Core/ja.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/ja.json b/Emby.Server.Implementations/Localization/Core/ja.json index c8ed7d0fb..10f4aee25 100644 --- a/Emby.Server.Implementations/Localization/Core/ja.json +++ b/Emby.Server.Implementations/Localization/Core/ja.json @@ -129,5 +129,10 @@ "TaskCleanCollectionsAndPlaylists": "コレクションとプレイリストをクリーンアップ", "TaskAudioNormalization": "音声の正規化", "TaskAudioNormalizationDescription": "音声の正規化データのためにファイルをスキャンします。", - "TaskCleanCollectionsAndPlaylistsDescription": "在しなくなったコレクションやプレイリストからアイテムを削除します。" + "TaskCleanCollectionsAndPlaylistsDescription": "在しなくなったコレクションやプレイリストからアイテムを削除します。", + "TaskDownloadMissingLyricsDescription": "歌詞をダウンロード", + "TaskExtractMediaSegments": "メディアセグメントを読み取る", + "TaskMoveTrickplayImages": "Trickplayの画像を移動", + "TaskMoveTrickplayImagesDescription": "ライブラリ設定によりTrickplayのファイルを移動。", + "TaskDownloadMissingLyrics": "記録されていない歌詞をダウンロード" } -- cgit v1.2.3 From 2ccb8b7380262b1bdfc9ca377125e374040b5d1a Mon Sep 17 00:00:00 2001 From: BromTeque Date: Thu, 26 Sep 2024 20:51:51 +0000 Subject: Translated using Weblate (Norwegian Bokmål) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/nb_NO/ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Emby.Server.Implementations/Localization/Core/nb.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/nb.json b/Emby.Server.Implementations/Localization/Core/nb.json index b90d06c7b..b1b6e96ea 100644 --- a/Emby.Server.Implementations/Localization/Core/nb.json +++ b/Emby.Server.Implementations/Localization/Core/nb.json @@ -128,8 +128,8 @@ "TaskRefreshTrickplayImages": "Generer Trickplay bilder", "TaskRefreshTrickplayImagesDescription": "Oppretter trickplay-forhåndsvisninger for videoer i aktiverte biblioteker.", "TaskCleanCollectionsAndPlaylists": "Rydd kolleksjoner og spillelister", - "TaskAudioNormalization": "Lyd Normalisering", - "TaskAudioNormalizationDescription": "Skan filer for lyd normaliserende data.", + "TaskAudioNormalization": "Lydnormalisering", + "TaskAudioNormalizationDescription": "Skan filer for lydnormaliserende data.", "TaskCleanCollectionsAndPlaylistsDescription": "Fjerner elementer fra kolleksjoner og spillelister som ikke lengere finnes.", "TaskDownloadMissingLyrics": "Last ned manglende tekster", "TaskDownloadMissingLyricsDescription": "Last ned sangtekster", -- cgit v1.2.3 From 0ef72683bba5e1ce2dfaf0d6aae49466c7a7d0c6 Mon Sep 17 00:00:00 2001 From: Tim Eisele Date: Mon, 30 Sep 2024 04:21:24 +0200 Subject: Do not consider tags in search (#12741) --- .../Data/SqliteItemRepository.cs | 34 +++++++++++++++------- 1 file changed, 24 insertions(+), 10 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 3da925f93..3477194cf 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -1,7 +1,5 @@ #nullable disable -#pragma warning disable CS1591 - using System; using System.Collections.Generic; using System.Diagnostics; @@ -547,6 +545,7 @@ namespace Emby.Server.Implementations.Data } } + /// public void SaveImages(BaseItem item) { ArgumentNullException.ThrowIfNull(item); @@ -2340,9 +2339,6 @@ namespace Emby.Server.Implementations.Data if (query.SearchTerm.Length > 1) { builder.Append("+ ((CleanName like @SearchTermContains or (OriginalTitle not null and OriginalTitle like @SearchTermContains)) * 10)"); - builder.Append("+ (SELECT COUNT(1) * 1 from ItemValues where ItemId=Guid and CleanValue like @SearchTermContains)"); - builder.Append("+ (SELECT COUNT(1) * 2 from ItemValues where ItemId=Guid and CleanValue like @SearchTermStartsWith)"); - builder.Append("+ (SELECT COUNT(1) * 10 from ItemValues where ItemId=Guid and CleanValue like @SearchTermEquals)"); } builder.Append(") as SearchScore"); @@ -2373,11 +2369,6 @@ namespace Emby.Server.Implementations.Data { statement.TryBind("@SearchTermContains", "%" + searchTerm + "%"); } - - if (commandText.Contains("@SearchTermEquals", StringComparison.OrdinalIgnoreCase)) - { - statement.TryBind("@SearchTermEquals", searchTerm); - } } private void BindSimilarParams(InternalItemsQuery query, SqliteCommand statement) @@ -2443,6 +2434,7 @@ namespace Emby.Server.Implementations.Data return string.Empty; } + /// public int GetCount(InternalItemsQuery query) { ArgumentNullException.ThrowIfNull(query); @@ -2490,6 +2482,7 @@ namespace Emby.Server.Implementations.Data } } + /// public List GetItemList(InternalItemsQuery query) { ArgumentNullException.ThrowIfNull(query); @@ -2643,6 +2636,7 @@ namespace Emby.Server.Implementations.Data items.Add(newItem); } + /// public QueryResult GetItems(InternalItemsQuery query) { ArgumentNullException.ThrowIfNull(query); @@ -2887,6 +2881,7 @@ namespace Emby.Server.Implementations.Data }; } + /// public List GetItemIdsList(InternalItemsQuery query) { ArgumentNullException.ThrowIfNull(query); @@ -4445,6 +4440,7 @@ namespace Emby.Server.Implementations.Data || query.IncludeItemTypes.Contains(BaseItemKind.Season); } + /// public void UpdateInheritedValues() { const string Statements = """ @@ -4461,6 +4457,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type transaction.Commit(); } + /// public void DeleteItem(Guid id) { if (id.IsEmpty()) @@ -4503,6 +4500,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type } } + /// public List GetPeopleNames(InternalPeopleQuery query) { ArgumentNullException.ThrowIfNull(query); @@ -4541,6 +4539,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type return list; } + /// public List GetPeople(InternalPeopleQuery query) { ArgumentNullException.ThrowIfNull(query); @@ -4700,46 +4699,55 @@ AND Type = @InternalPersonType)"); } } + /// public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetAllArtists(InternalItemsQuery query) { return GetItemValues(query, new[] { 0, 1 }, typeof(MusicArtist).FullName); } + /// public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetArtists(InternalItemsQuery query) { return GetItemValues(query, new[] { 0 }, typeof(MusicArtist).FullName); } + /// public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetAlbumArtists(InternalItemsQuery query) { return GetItemValues(query, new[] { 1 }, typeof(MusicArtist).FullName); } + /// public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetStudios(InternalItemsQuery query) { return GetItemValues(query, new[] { 3 }, typeof(Studio).FullName); } + /// public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetGenres(InternalItemsQuery query) { return GetItemValues(query, new[] { 2 }, typeof(Genre).FullName); } + /// public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetMusicGenres(InternalItemsQuery query) { return GetItemValues(query, new[] { 2 }, typeof(MusicGenre).FullName); } + /// public List GetStudioNames() { return GetItemValueNames(new[] { 3 }, Array.Empty(), Array.Empty()); } + /// public List GetAllArtistNames() { return GetItemValueNames(new[] { 0, 1 }, Array.Empty(), Array.Empty()); } + /// public List GetMusicGenreNames() { return GetItemValueNames( @@ -4754,6 +4762,7 @@ AND Type = @InternalPersonType)"); Array.Empty()); } + /// public List GetGenreNames() { return GetItemValueNames( @@ -5231,6 +5240,7 @@ AND Type = @InternalPersonType)"); } } + /// public void UpdatePeople(Guid itemId, List people) { if (itemId.IsEmpty()) @@ -5332,6 +5342,7 @@ AND Type = @InternalPersonType)"); return item; } + /// public List GetMediaStreams(MediaStreamQuery query) { CheckDisposed(); @@ -5380,6 +5391,7 @@ AND Type = @InternalPersonType)"); } } + /// public void SaveMediaStreams(Guid id, IReadOnlyList streams, CancellationToken cancellationToken) { CheckDisposed(); @@ -5734,6 +5746,7 @@ AND Type = @InternalPersonType)"); return item; } + /// public List GetMediaAttachments(MediaAttachmentQuery query) { CheckDisposed(); @@ -5769,6 +5782,7 @@ AND Type = @InternalPersonType)"); return list; } + /// public void SaveMediaAttachments( Guid id, IReadOnlyList attachments, -- cgit v1.2.3 From 9ef7ccfc125d00c0a0ce789626db258a4cb51f2f Mon Sep 17 00:00:00 2001 From: gnattu Date: Mon, 30 Sep 2024 10:21:46 +0800 Subject: Add perf tradeoff mode to image extractor (#12744) --- Emby.Server.Implementations/ConfigurationOptions.cs | 1 + .../Extensions/ConfigurationExtensions.cs | 13 +++++++++++++ MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 9 ++++++++- 3 files changed, 22 insertions(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/ConfigurationOptions.cs b/Emby.Server.Implementations/ConfigurationOptions.cs index 702707297..91791a1c8 100644 --- a/Emby.Server.Implementations/ConfigurationOptions.cs +++ b/Emby.Server.Implementations/ConfigurationOptions.cs @@ -21,6 +21,7 @@ namespace Emby.Server.Implementations { BindToUnixSocketKey, bool.FalseString }, { SqliteCacheSizeKey, "20000" }, { FfmpegSkipValidationKey, bool.FalseString }, + { FfmpegImgExtractPerfTradeoffKey, bool.FalseString }, { DetectNetworkChangeKey, bool.TrueString } }; } diff --git a/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs b/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs index 7ca508426..f8049cd48 100644 --- a/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs +++ b/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs @@ -39,6 +39,11 @@ namespace MediaBrowser.Controller.Extensions /// public const string FfmpegAnalyzeDurationKey = "FFmpeg:analyzeduration"; + /// + /// The key for the FFmpeg image extraction performance tradeoff option. + /// + public const string FfmpegImgExtractPerfTradeoffKey = "FFmpeg:imgExtractPerfTradeoff"; + /// /// The key for the FFmpeg path option. /// @@ -107,6 +112,14 @@ namespace MediaBrowser.Controller.Extensions public static bool GetFFmpegSkipValidation(this IConfiguration configuration) => configuration.GetValue(FfmpegSkipValidationKey); + /// + /// Gets a value indicating whether the server should trade off for performance during FFmpeg image extraction. + /// + /// The configuration to read the setting from. + /// true if the server should trade off for performance during FFmpeg image extraction, otherwise false. + public static bool GetFFmpegImgExtractPerfTradeoff(this IConfiguration configuration) + => configuration.GetValue(FfmpegImgExtractPerfTradeoffKey); + /// /// Gets a value indicating whether playlists should allow duplicate entries from the . /// diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 7ae1fbbb1..ace8bda19 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -650,6 +650,8 @@ namespace MediaBrowser.MediaEncoding.Encoder { ArgumentException.ThrowIfNullOrEmpty(inputPath); + var useTradeoff = _config.GetFFmpegImgExtractPerfTradeoff(); + var outputExtension = targetFormat?.GetExtension() ?? ".jpg"; var tempExtractPath = Path.Combine(_configurationManager.ApplicationPaths.TempDirectory, Guid.NewGuid() + outputExtension); @@ -684,7 +686,7 @@ namespace MediaBrowser.MediaEncoding.Encoder // Use ffmpeg to sample 100 (we can drop this if required using thumbnail=50 for 50 frames) frames and pick the best thumbnail. Have a fall back just in case. // mpegts need larger batch size otherwise the corrupted thumbnail will be created. Larger batch size will lower the processing speed. - var enableThumbnail = useIFrame && !string.Equals("wtv", container, StringComparison.OrdinalIgnoreCase); + var enableThumbnail = !useTradeoff && useIFrame && !string.Equals("wtv", container, StringComparison.OrdinalIgnoreCase); if (enableThumbnail) { var useLargerBatchSize = string.Equals("mpegts", container, StringComparison.OrdinalIgnoreCase); @@ -718,6 +720,11 @@ namespace MediaBrowser.MediaEncoding.Encoder args = string.Format(CultureInfo.InvariantCulture, "-ss {0} ", GetTimeParameter(offset.Value)) + args; } + if (useIFrame && useTradeoff) + { + args = "-skip_frame nokey " + args; + } + if (!string.IsNullOrWhiteSpace(container)) { var inputFormat = EncodingHelper.GetInputFormat(container); -- cgit v1.2.3 From 4314b451d934c7df27b35f943c300a365a61a3a7 Mon Sep 17 00:00:00 2001 From: Léon Date: Tue, 1 Oct 2024 03:33:38 +0000 Subject: Translated using Weblate (French) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/fr/ --- Emby.Server.Implementations/Localization/Core/fr.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/fr.json b/Emby.Server.Implementations/Localization/Core/fr.json index 1dba78add..3caf8b547 100644 --- a/Emby.Server.Implementations/Localization/Core/fr.json +++ b/Emby.Server.Implementations/Localization/Core/fr.json @@ -132,5 +132,9 @@ "TaskAudioNormalization": "Normalisation audio", "TaskAudioNormalizationDescription": "Analyse les fichiers à la recherche de données de normalisation audio.", "TaskDownloadMissingLyricsDescription": "Téléchargement des paroles des chansons", - "TaskDownloadMissingLyrics": "Télécharger les paroles des chansons manquantes" + "TaskDownloadMissingLyrics": "Télécharger les paroles des chansons manquantes", + "TaskExtractMediaSegments": "Analyse des segments de média", + "TaskMoveTrickplayImages": "Changer l'emplacement des images Trickplay", + "TaskExtractMediaSegmentsDescription": "Extrait ou obtient des segments de média à partir des plugins compatibles avec MediaSegment.", + "TaskMoveTrickplayImagesDescription": "Déplace les fichiers trickplay existants en fonction des paramètres de la bibliothèque." } -- cgit v1.2.3 From ee542317151d9c7d74feb9cbb22f3a149c15e7b6 Mon Sep 17 00:00:00 2001 From: Nyanmisaka <799610810@qq.com> Date: Tue, 1 Oct 2024 10:48:41 +0000 Subject: Translated using Weblate (Chinese (Simplified Han script)) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/zh_Hans/ --- Emby.Server.Implementations/Localization/Core/zh-CN.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/zh-CN.json b/Emby.Server.Implementations/Localization/Core/zh-CN.json index 3256569e0..9a0e2115e 100644 --- a/Emby.Server.Implementations/Localization/Core/zh-CN.json +++ b/Emby.Server.Implementations/Localization/Core/zh-CN.json @@ -93,7 +93,7 @@ "ValueSpecialEpisodeName": "特典 - {0}", "VersionNumber": "版本 {0}", "TaskUpdatePluginsDescription": "为已设置为自动更新的插件下载和安装更新。", - "TaskRefreshPeople": "刷新人员", + "TaskRefreshPeople": "刷新演职人员", "TasksChannelsCategory": "互联网频道", "TasksLibraryCategory": "媒体库", "TaskDownloadMissingSubtitlesDescription": "根据元数据设置在互联网上搜索缺少的字幕。", @@ -122,7 +122,7 @@ "TaskOptimizeDatabaseDescription": "压缩数据库并优化可用空间,在扫描库或执行其他数据库修改后运行此任务可能会提高性能。", "TaskOptimizeDatabase": "优化数据库", "TaskKeyframeExtractorDescription": "从视频文件中提取关键帧以创建更准确的 HLS 播放列表。这项任务可能需要很长时间。", - "TaskKeyframeExtractor": "关键帧提取器", + "TaskKeyframeExtractor": "关键帧提取", "External": "外部", "HearingImpaired": "听力障碍", "TaskRefreshTrickplayImages": "生成进度条预览图", -- cgit v1.2.3 From abcd6cd10d125738bc29d023b3393c96e4f6ad1b Mon Sep 17 00:00:00 2001 From: Roi Gabay Date: Tue, 1 Oct 2024 20:44:03 +0000 Subject: Translated using Weblate (Hebrew) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/he/ --- Emby.Server.Implementations/Localization/Core/he.json | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/he.json b/Emby.Server.Implementations/Localization/Core/he.json index c8e036424..2f9fdc270 100644 --- a/Emby.Server.Implementations/Localization/Core/he.json +++ b/Emby.Server.Implementations/Localization/Core/he.json @@ -130,5 +130,11 @@ "TaskAudioNormalization": "נרמול שמע", "TaskCleanCollectionsAndPlaylistsDescription": "מנקה פריטים לא קיימים מאוספים ורשימות השמעה.", "TaskAudioNormalizationDescription": "מחפש קבצי נורמליזציה של שמע.", - "TaskCleanCollectionsAndPlaylists": "מנקה אוספים ורשימות השמעה" + "TaskCleanCollectionsAndPlaylists": "מנקה אוספים ורשימות השמעה", + "TaskDownloadMissingLyrics": "הורדת מילים חסרות", + "TaskDownloadMissingLyricsDescription": "הורדת מילים לשירים", + "TaskMoveTrickplayImages": "מעביר את מיקום תמונות Trickplay", + "TaskExtractMediaSegments": "סריקת מדיה", + "TaskExtractMediaSegmentsDescription": "מחלץ חלקי מדיה מתוספים המאפשרים זאת.", + "TaskMoveTrickplayImagesDescription": "מזיז קבצי trickplay קיימים בהתאם להגדרות הספרייה." } -- cgit v1.2.3 From 973eaf5cafd27f1378a2ddb2000e6b65c3e2670a Mon Sep 17 00:00:00 2001 From: Filip S Date: Tue, 1 Oct 2024 21:46:52 +0000 Subject: Translated using Weblate (Macedonian) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/mk/ --- Emby.Server.Implementations/Localization/Core/mk.json | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/mk.json b/Emby.Server.Implementations/Localization/Core/mk.json index 7ef907918..e149f8adf 100644 --- a/Emby.Server.Implementations/Localization/Core/mk.json +++ b/Emby.Server.Implementations/Localization/Core/mk.json @@ -55,7 +55,7 @@ "Genres": "Жанрови", "Folders": "Папки", "Favorites": "Омилени", - "FailedLoginAttemptWithUserName": "Неуспешно поврзување од {0}", + "FailedLoginAttemptWithUserName": "Неуспешен обид за најавување од {0}", "DeviceOnlineWithName": "{0} е приклучен", "DeviceOfflineWithName": "{0} се исклучи", "Collections": "Колекции", @@ -123,5 +123,13 @@ "TaskCleanActivityLogDescription": "Избришува логови на активности постари од определеното време.", "TaskCleanActivityLog": "Избриши Лог на Активности", "External": "Надворешен", - "HearingImpaired": "Оштетен слух" + "HearingImpaired": "Оштетен слух", + "TaskCleanCollectionsAndPlaylists": "Исчисти ги колекциите и плејлистите", + "TaskAudioNormalizationDescription": "Скенирање датотеки за податоци за нормализација на звукот.", + "TaskDownloadMissingLyrics": "Преземи стихови кои недостасуваат", + "TaskDownloadMissingLyricsDescription": "Преземи стихови/текстови за песни", + "TaskRefreshTrickplayImages": "Генерирај слики за прегледување (Trickplay)", + "TaskAudioNormalization": "Нормализација на звукот", + "TaskRefreshTrickplayImagesDescription": "Креира трикплеј прегледи за видеа во овозможените библиотеки.", + "TaskCleanCollectionsAndPlaylistsDescription": "Отстранува ставки од колекциите и плејлистите што веќе не постојат." } -- cgit v1.2.3 From eb56475651f147790698151efe2f6c6220baa9b0 Mon Sep 17 00:00:00 2001 From: millallo Date: Fri, 4 Oct 2024 06:36:48 +0000 Subject: Translated using Weblate (Italian) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/it/ --- Emby.Server.Implementations/Localization/Core/it.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/it.json b/Emby.Server.Implementations/Localization/Core/it.json index 961d1a0df..6b0cfb359 100644 --- a/Emby.Server.Implementations/Localization/Core/it.json +++ b/Emby.Server.Implementations/Localization/Core/it.json @@ -132,5 +132,7 @@ "TaskAudioNormalization": "Normalizzazione dell'audio", "TaskAudioNormalizationDescription": "Scansiona i file alla ricerca dei dati per la normalizzazione dell'audio.", "TaskDownloadMissingLyricsDescription": "Scarica testi per le canzoni", - "TaskDownloadMissingLyrics": "Scarica testi mancanti" + "TaskDownloadMissingLyrics": "Scarica testi mancanti", + "TaskMoveTrickplayImages": "Sposta le immagini Trickplay", + "TaskMoveTrickplayImagesDescription": "Sposta le immagini Trickplay esistenti secondo la configurazione della libreria." } -- cgit v1.2.3 From b5d89a67e8ef9acac7195515587e4ae4e4cf7e45 Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 3 Oct 2024 18:10:12 +0000 Subject: Translated using Weblate (Finnish) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/fi/ --- Emby.Server.Implementations/Localization/Core/fi.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/fi.json b/Emby.Server.Implementations/Localization/Core/fi.json index dced61c5e..8a88cf28e 100644 --- a/Emby.Server.Implementations/Localization/Core/fi.json +++ b/Emby.Server.Implementations/Localization/Core/fi.json @@ -129,5 +129,6 @@ "TaskCleanCollectionsAndPlaylistsDescription": "Poistaa kohteet kokoelmista ja soittolistoista joita ei ole enää olemassa.", "TaskCleanCollectionsAndPlaylists": "Puhdista kokoelmat ja soittolistat", "TaskAudioNormalization": "Äänenvoimakkuuden normalisointi", - "TaskAudioNormalizationDescription": "Etsii tiedostoista äänenvoimakkuuden normalisointitietoja." + "TaskAudioNormalizationDescription": "Etsii tiedostoista äänenvoimakkuuden normalisointitietoja.", + "TaskDownloadMissingLyrics": "Lataa puuttuva lyriikka" } -- cgit v1.2.3 From 62755c7312021ca2b75987d173daa3e02c24c76c Mon Sep 17 00:00:00 2001 From: Roi Gabay Date: Fri, 4 Oct 2024 21:46:22 +0000 Subject: Translated using Weblate (Hebrew) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/he/ --- Emby.Server.Implementations/Localization/Core/he.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/he.json b/Emby.Server.Implementations/Localization/Core/he.json index 2f9fdc270..af57b1693 100644 --- a/Emby.Server.Implementations/Localization/Core/he.json +++ b/Emby.Server.Implementations/Localization/Core/he.json @@ -60,7 +60,7 @@ "NotificationOptionUserLockedOut": "משתמש ננעל", "NotificationOptionVideoPlayback": "ניגון וידאו החל", "NotificationOptionVideoPlaybackStopped": "ניגון וידאו הופסק", - "Photos": "תמונות", + "Photos": "צילומים", "Playlists": "רשימות נגינה", "Plugin": "תוסף", "PluginInstalledWithName": "{0} הותקן", -- cgit v1.2.3 From b988b989d5b2c58f245eaf5f752ed643d6f04258 Mon Sep 17 00:00:00 2001 From: Yon Ploj Date: Sat, 5 Oct 2024 10:36:58 +0000 Subject: Translated using Weblate (Slovenian) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/sl/ --- Emby.Server.Implementations/Localization/Core/sl-SI.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/sl-SI.json b/Emby.Server.Implementations/Localization/Core/sl-SI.json index 110af11b7..19be1a23e 100644 --- a/Emby.Server.Implementations/Localization/Core/sl-SI.json +++ b/Emby.Server.Implementations/Localization/Core/sl-SI.json @@ -3,7 +3,7 @@ "AppDeviceValues": "Aplikacija: {0}, Naprava: {1}", "Application": "Aplikacija", "Artists": "Izvajalci", - "AuthenticationSucceededWithUserName": "{0} se je uspešno prijavil", + "AuthenticationSucceededWithUserName": "{0} se je uspešno prijavil/a", "Books": "Knjige", "CameraImageUploadedFrom": "Nova fotografija je bila naložena iz {0}", "Channels": "Kanali", -- cgit v1.2.3 From aaf20592bb0bbdf4f0f0d99fed091758e68ae850 Mon Sep 17 00:00:00 2001 From: rushmash Date: Sun, 6 Oct 2024 18:45:33 +0000 Subject: Translated using Weblate (Belarusian) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/be/ --- Emby.Server.Implementations/Localization/Core/be.json | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/be.json b/Emby.Server.Implementations/Localization/Core/be.json index 9172af516..97aa0ca58 100644 --- a/Emby.Server.Implementations/Localization/Core/be.json +++ b/Emby.Server.Implementations/Localization/Core/be.json @@ -129,5 +129,11 @@ "TaskCleanCollectionsAndPlaylists": "Ачысціце калекцыі і спісы прайгравання", "TaskCleanCollectionsAndPlaylistsDescription": "Выдаляе элементы з калекцый і спісаў прайгравання, якія больш не існуюць.", "TaskAudioNormalizationDescription": "Сканіруе файлы на прадмет нармалізацыі гуку.", - "TaskAudioNormalization": "Нармалізацыя гуку" + "TaskAudioNormalization": "Нармалізацыя гуку", + "TaskExtractMediaSegmentsDescription": "Выдае або атрымлівае медыясегменты з убудоў з падтрымкай MediaSegment.", + "TaskMoveTrickplayImagesDescription": "Перамяшчае існуючыя файлы trickplay у адпаведнасці з наладамі бібліятэкі.", + "TaskDownloadMissingLyrics": "Спампаваць зніклыя тэксты песень", + "TaskDownloadMissingLyricsDescription": "Спампоўвае тэксты для песень", + "TaskExtractMediaSegments": "Сканіраванне медыя-сегмента", + "TaskMoveTrickplayImages": "Перанесці месцазнаходжанне выявы Trickplay" } -- cgit v1.2.3