aboutsummaryrefslogtreecommitdiff
path: root/Jellyfin.Api/Controllers
diff options
context:
space:
mode:
Diffstat (limited to 'Jellyfin.Api/Controllers')
-rw-r--r--Jellyfin.Api/Controllers/ArtistsController.cs71
-rw-r--r--Jellyfin.Api/Controllers/AudioController.cs26
-rw-r--r--Jellyfin.Api/Controllers/BackupController.cs127
-rw-r--r--Jellyfin.Api/Controllers/BrandingController.cs13
-rw-r--r--Jellyfin.Api/Controllers/ChannelsController.cs15
-rw-r--r--Jellyfin.Api/Controllers/CollectionController.cs6
-rw-r--r--Jellyfin.Api/Controllers/ConfigurationController.cs25
-rw-r--r--Jellyfin.Api/Controllers/DisplayPreferencesController.cs4
-rw-r--r--Jellyfin.Api/Controllers/DynamicHlsController.cs102
-rw-r--r--Jellyfin.Api/Controllers/FilterController.cs6
-rw-r--r--Jellyfin.Api/Controllers/GenresController.cs15
-rw-r--r--Jellyfin.Api/Controllers/ImageController.cs5
-rw-r--r--Jellyfin.Api/Controllers/InstantMixController.cs50
-rw-r--r--Jellyfin.Api/Controllers/ItemUpdateController.cs8
-rw-r--r--Jellyfin.Api/Controllers/ItemsController.cs158
-rw-r--r--Jellyfin.Api/Controllers/LibraryController.cs25
-rw-r--r--Jellyfin.Api/Controllers/LibraryStructureController.cs5
-rw-r--r--Jellyfin.Api/Controllers/LiveTvController.cs42
-rw-r--r--Jellyfin.Api/Controllers/LocalizationController.cs5
-rw-r--r--Jellyfin.Api/Controllers/MediaSegmentsController.cs7
-rw-r--r--Jellyfin.Api/Controllers/MoviesController.cs8
-rw-r--r--Jellyfin.Api/Controllers/MusicGenresController.cs15
-rw-r--r--Jellyfin.Api/Controllers/PersonsController.cs12
-rw-r--r--Jellyfin.Api/Controllers/PlaylistsController.cs49
-rw-r--r--Jellyfin.Api/Controllers/PlaystateController.cs15
-rw-r--r--Jellyfin.Api/Controllers/SearchController.cs6
-rw-r--r--Jellyfin.Api/Controllers/SessionController.cs6
-rw-r--r--Jellyfin.Api/Controllers/StartupController.cs6
-rw-r--r--Jellyfin.Api/Controllers/StudiosController.cs10
-rw-r--r--Jellyfin.Api/Controllers/SubtitleController.cs2
-rw-r--r--Jellyfin.Api/Controllers/SuggestionsController.cs11
-rw-r--r--Jellyfin.Api/Controllers/SyncPlayController.cs28
-rw-r--r--Jellyfin.Api/Controllers/SystemController.cs31
-rw-r--r--Jellyfin.Api/Controllers/TrailersController.cs61
-rw-r--r--Jellyfin.Api/Controllers/TvShowsController.cs21
-rw-r--r--Jellyfin.Api/Controllers/UniversalAudioController.cs8
-rw-r--r--Jellyfin.Api/Controllers/UserController.cs3
-rw-r--r--Jellyfin.Api/Controllers/UserLibraryController.cs48
-rw-r--r--Jellyfin.Api/Controllers/UserViewsController.cs4
-rw-r--r--Jellyfin.Api/Controllers/VideosController.cs26
-rw-r--r--Jellyfin.Api/Controllers/YearsController.cs26
41 files changed, 667 insertions, 444 deletions
diff --git a/Jellyfin.Api/Controllers/ArtistsController.cs b/Jellyfin.Api/Controllers/ArtistsController.cs
index 8b931f162..7ba75dc24 100644
--- a/Jellyfin.Api/Controllers/ArtistsController.cs
+++ b/Jellyfin.Api/Controllers/ArtistsController.cs
@@ -4,8 +4,9 @@ using System.Linq;
using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders;
-using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
+using Jellyfin.Database.Implementations.Entities;
+using Jellyfin.Database.Implementations.Enums;
using Jellyfin.Extensions;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
@@ -91,31 +92,31 @@ public class ArtistsController : BaseJellyfinApiController
[FromQuery] int? limit,
[FromQuery] string? searchTerm,
[FromQuery] Guid? parentId,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] excludeItemTypes,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] excludeItemTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] includeItemTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFilter[] filters,
[FromQuery] bool? isFavorite,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] MediaType[] mediaTypes,
- [FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] genres,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] genreIds,
- [FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] officialRatings,
- [FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] tags,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] int[] years,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] MediaType[] mediaTypes,
+ [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] genres,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] genreIds,
+ [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] officialRatings,
+ [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] tags,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] int[] years,
[FromQuery] bool? enableUserData,
[FromQuery] int? imageTypeLimit,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes,
[FromQuery] string? person,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] personIds,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] personTypes,
- [FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] studios,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] studioIds,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] personIds,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] string[] personTypes,
+ [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] studios,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] studioIds,
[FromQuery] Guid? userId,
[FromQuery] string? nameStartsWithOrGreater,
[FromQuery] string? nameStartsWith,
[FromQuery] string? nameLessThan,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemSortBy[] sortBy,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SortOrder[] sortOrder,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemSortBy[] sortBy,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] SortOrder[] sortOrder,
[FromQuery] bool? enableImages = true,
[FromQuery] bool enableTotalRecordCount = true)
{
@@ -295,31 +296,31 @@ public class ArtistsController : BaseJellyfinApiController
[FromQuery] int? limit,
[FromQuery] string? searchTerm,
[FromQuery] Guid? parentId,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] excludeItemTypes,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] excludeItemTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] includeItemTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFilter[] filters,
[FromQuery] bool? isFavorite,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] MediaType[] mediaTypes,
- [FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] genres,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] genreIds,
- [FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] officialRatings,
- [FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] tags,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] int[] years,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] MediaType[] mediaTypes,
+ [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] genres,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] genreIds,
+ [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] officialRatings,
+ [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] tags,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] int[] years,
[FromQuery] bool? enableUserData,
[FromQuery] int? imageTypeLimit,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes,
[FromQuery] string? person,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] personIds,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] personTypes,
- [FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] studios,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] studioIds,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] personIds,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] string[] personTypes,
+ [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] studios,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] studioIds,
[FromQuery] Guid? userId,
[FromQuery] string? nameStartsWithOrGreater,
[FromQuery] string? nameStartsWith,
[FromQuery] string? nameLessThan,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemSortBy[] sortBy,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SortOrder[] sortOrder,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemSortBy[] sortBy,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] SortOrder[] sortOrder,
[FromQuery] bool? enableImages = true,
[FromQuery] bool enableTotalRecordCount = true)
{
diff --git a/Jellyfin.Api/Controllers/AudioController.cs b/Jellyfin.Api/Controllers/AudioController.cs
index a47c60473..e334e1264 100644
--- a/Jellyfin.Api/Controllers/AudioController.cs
+++ b/Jellyfin.Api/Controllers/AudioController.cs
@@ -92,18 +92,18 @@ public class AudioController : BaseJellyfinApiController
[ProducesAudioFile]
public async Task<ActionResult> GetAudioStream(
[FromRoute, Required] Guid itemId,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? container,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? container,
[FromQuery] bool? @static,
[FromQuery] string? @params,
[FromQuery] string? tag,
[FromQuery, ParameterObsolete] string? deviceProfileId,
[FromQuery] string? playSessionId,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? segmentContainer,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? segmentContainer,
[FromQuery] int? segmentLength,
[FromQuery] int? minSegments,
[FromQuery] string? mediaSourceId,
[FromQuery] string? deviceId,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? audioCodec,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? audioCodec,
[FromQuery] bool? enableAutoStreamCopy,
[FromQuery] bool? allowVideoStreamCopy,
[FromQuery] bool? allowAudioStreamCopy,
@@ -114,7 +114,7 @@ public class AudioController : BaseJellyfinApiController
[FromQuery] int? audioChannels,
[FromQuery] int? maxAudioChannels,
[FromQuery] string? profile,
- [FromQuery] string? level,
+ [FromQuery] [RegularExpression(EncodingHelper.LevelValidationRegex)] string? level,
[FromQuery] float? framerate,
[FromQuery] float? maxFramerate,
[FromQuery] bool? copyTimestamps,
@@ -133,8 +133,8 @@ public class AudioController : BaseJellyfinApiController
[FromQuery] int? cpuCoreLimit,
[FromQuery] string? liveStreamId,
[FromQuery] bool? enableMpegtsM2TsMode,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? videoCodec,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? subtitleCodec,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? videoCodec,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? subtitleCodec,
[FromQuery] string? transcodeReasons,
[FromQuery] int? audioStreamIndex,
[FromQuery] int? videoStreamIndex,
@@ -238,7 +238,7 @@ public class AudioController : BaseJellyfinApiController
/// <param name="maxVideoBitDepth">Optional. The maximum video bit depth.</param>
/// <param name="requireAvc">Optional. Whether to require avc.</param>
/// <param name="deInterlace">Optional. Whether to deinterlace the video.</param>
- /// <param name="requireNonAnamorphic">Optional. Whether to require a non anamporphic stream.</param>
+ /// <param name="requireNonAnamorphic">Optional. Whether to require a non anamorphic stream.</param>
/// <param name="transcodingMaxAudioChannels">Optional. The maximum number of audio channels to transcode.</param>
/// <param name="cpuCoreLimit">Optional. The limit of how many cpu cores to use.</param>
/// <param name="liveStreamId">The live stream id.</param>
@@ -259,18 +259,18 @@ public class AudioController : BaseJellyfinApiController
[ProducesAudioFile]
public async Task<ActionResult> GetAudioStreamByContainer(
[FromRoute, Required] Guid itemId,
- [FromRoute, Required] string container,
+ [FromRoute, Required] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string container,
[FromQuery] bool? @static,
[FromQuery] string? @params,
[FromQuery] string? tag,
[FromQuery, ParameterObsolete] string? deviceProfileId,
[FromQuery] string? playSessionId,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? segmentContainer,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? segmentContainer,
[FromQuery] int? segmentLength,
[FromQuery] int? minSegments,
[FromQuery] string? mediaSourceId,
[FromQuery] string? deviceId,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? audioCodec,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? audioCodec,
[FromQuery] bool? enableAutoStreamCopy,
[FromQuery] bool? allowVideoStreamCopy,
[FromQuery] bool? allowAudioStreamCopy,
@@ -281,7 +281,7 @@ public class AudioController : BaseJellyfinApiController
[FromQuery] int? audioChannels,
[FromQuery] int? maxAudioChannels,
[FromQuery] string? profile,
- [FromQuery] string? level,
+ [FromQuery] [RegularExpression(EncodingHelper.LevelValidationRegex)] string? level,
[FromQuery] float? framerate,
[FromQuery] float? maxFramerate,
[FromQuery] bool? copyTimestamps,
@@ -300,8 +300,8 @@ public class AudioController : BaseJellyfinApiController
[FromQuery] int? cpuCoreLimit,
[FromQuery] string? liveStreamId,
[FromQuery] bool? enableMpegtsM2TsMode,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? videoCodec,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? subtitleCodec,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? videoCodec,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? subtitleCodec,
[FromQuery] string? transcodeReasons,
[FromQuery] int? audioStreamIndex,
[FromQuery] int? videoStreamIndex,
diff --git a/Jellyfin.Api/Controllers/BackupController.cs b/Jellyfin.Api/Controllers/BackupController.cs
new file mode 100644
index 000000000..aa908ee30
--- /dev/null
+++ b/Jellyfin.Api/Controllers/BackupController.cs
@@ -0,0 +1,127 @@
+using System.IO;
+using System.Threading.Tasks;
+using Jellyfin.Server.Implementations.SystemBackupService;
+using MediaBrowser.Common.Api;
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Controller.SystemBackupService;
+using Microsoft.AspNetCore.Authentication.OAuth.Claims;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.ModelBinding;
+
+namespace Jellyfin.Api.Controllers;
+
+/// <summary>
+/// The backup controller.
+/// </summary>
+[Authorize(Policy = Policies.RequiresElevation)]
+public class BackupController : BaseJellyfinApiController
+{
+ private readonly IBackupService _backupService;
+ private readonly IApplicationPaths _applicationPaths;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="BackupController"/> class.
+ /// </summary>
+ /// <param name="backupService">Instance of the <see cref="IBackupService"/> interface.</param>
+ /// <param name="applicationPaths">Instance of the <see cref="IApplicationPaths"/> interface.</param>
+ public BackupController(IBackupService backupService, IApplicationPaths applicationPaths)
+ {
+ _backupService = backupService;
+ _applicationPaths = applicationPaths;
+ }
+
+ /// <summary>
+ /// Creates a new Backup.
+ /// </summary>
+ /// <param name="backupOptions">The backup options.</param>
+ /// <response code="200">Backup created.</response>
+ /// <response code="403">User does not have permission to retrieve information.</response>
+ /// <returns>The created backup manifest.</returns>
+ [HttpPost("Create")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status403Forbidden)]
+ public async Task<ActionResult<BackupManifestDto>> CreateBackup([FromBody] BackupOptionsDto backupOptions)
+ {
+ return Ok(await _backupService.CreateBackupAsync(backupOptions ?? new()).ConfigureAwait(false));
+ }
+
+ /// <summary>
+ /// Restores to a backup by restarting the server and applying the backup.
+ /// </summary>
+ /// <param name="archiveRestoreDto">The data to start a restore process.</param>
+ /// <response code="204">Backup restore started.</response>
+ /// <response code="403">User does not have permission to retrieve information.</response>
+ /// <returns>No-Content.</returns>
+ [HttpPost("Restore")]
+ [ProducesResponseType(StatusCodes.Status204NoContent)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ [ProducesResponseType(StatusCodes.Status403Forbidden)]
+ public IActionResult StartRestoreBackup([FromBody, BindRequired] BackupRestoreRequestDto archiveRestoreDto)
+ {
+ var archivePath = SanitizePath(archiveRestoreDto.ArchiveFileName);
+ if (!System.IO.File.Exists(archivePath))
+ {
+ return NotFound();
+ }
+
+ _backupService.ScheduleRestoreAndRestartServer(archivePath);
+ return NoContent();
+ }
+
+ /// <summary>
+ /// Gets a list of all currently present backups in the backup directory.
+ /// </summary>
+ /// <response code="200">Backups available.</response>
+ /// <response code="403">User does not have permission to retrieve information.</response>
+ /// <returns>The list of backups.</returns>
+ [HttpGet]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status403Forbidden)]
+ public async Task<ActionResult<BackupManifestDto[]>> ListBackups()
+ {
+ return Ok(await _backupService.EnumerateBackups().ConfigureAwait(false));
+ }
+
+ /// <summary>
+ /// Gets the descriptor from an existing archive is present.
+ /// </summary>
+ /// <param name="path">The data to start a restore process.</param>
+ /// <response code="200">Backup archive manifest.</response>
+ /// <response code="204">Not a valid jellyfin Archive.</response>
+ /// <response code="404">Not a valid path.</response>
+ /// <response code="403">User does not have permission to retrieve information.</response>
+ /// <returns>The backup manifest.</returns>
+ [HttpGet("Manifest")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status204NoContent)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ [ProducesResponseType(StatusCodes.Status403Forbidden)]
+ public async Task<ActionResult<BackupManifestDto>> GetBackup([BindRequired] string path)
+ {
+ var backupPath = SanitizePath(path);
+
+ if (!System.IO.File.Exists(backupPath))
+ {
+ return NotFound();
+ }
+
+ var manifest = await _backupService.GetBackupManifest(backupPath).ConfigureAwait(false);
+ if (manifest is null)
+ {
+ return NoContent();
+ }
+
+ return Ok(manifest);
+ }
+
+ [NonAction]
+ private string SanitizePath(string path)
+ {
+ // sanitize path
+ var archiveRestorePath = Path.GetFileName(Path.GetFullPath(path));
+ var archivePath = Path.Combine(_applicationPaths.BackupPath, archiveRestorePath);
+ return archivePath;
+ }
+}
diff --git a/Jellyfin.Api/Controllers/BrandingController.cs b/Jellyfin.Api/Controllers/BrandingController.cs
index 3c2c4b4db..1d948ff20 100644
--- a/Jellyfin.Api/Controllers/BrandingController.cs
+++ b/Jellyfin.Api/Controllers/BrandingController.cs
@@ -29,9 +29,18 @@ public class BrandingController : BaseJellyfinApiController
/// <returns>An <see cref="OkResult"/> containing the branding configuration.</returns>
[HttpGet("Configuration")]
[ProducesResponseType(StatusCodes.Status200OK)]
- public ActionResult<BrandingOptions> GetBrandingOptions()
+ public ActionResult<BrandingOptionsDto> GetBrandingOptions()
{
- return _serverConfigurationManager.GetConfiguration<BrandingOptions>("branding");
+ var brandingOptions = _serverConfigurationManager.GetConfiguration<BrandingOptions>("branding");
+
+ var brandingOptionsDto = new BrandingOptionsDto
+ {
+ LoginDisclaimer = brandingOptions.LoginDisclaimer,
+ CustomCss = brandingOptions.CustomCss,
+ SplashscreenEnabled = brandingOptions.SplashscreenEnabled
+ };
+
+ return brandingOptionsDto;
}
/// <summary>
diff --git a/Jellyfin.Api/Controllers/ChannelsController.cs b/Jellyfin.Api/Controllers/ChannelsController.cs
index f83c71b57..880b3a82d 100644
--- a/Jellyfin.Api/Controllers/ChannelsController.cs
+++ b/Jellyfin.Api/Controllers/ChannelsController.cs
@@ -6,6 +6,7 @@ using System.Threading.Tasks;
using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders;
using Jellyfin.Data.Enums;
+using Jellyfin.Database.Implementations.Enums;
using Jellyfin.Extensions;
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Dto;
@@ -121,10 +122,10 @@ public class ChannelsController : BaseJellyfinApiController
[FromQuery] Guid? userId,
[FromQuery] int? startIndex,
[FromQuery] int? limit,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SortOrder[] sortOrder,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemSortBy[] sortBy,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields)
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] SortOrder[] sortOrder,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFilter[] filters,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemSortBy[] sortBy,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields)
{
userId = RequestHelpers.GetUserId(User, userId);
var user = userId.IsNullOrEmpty()
@@ -197,9 +198,9 @@ public class ChannelsController : BaseJellyfinApiController
[FromQuery] Guid? userId,
[FromQuery] int? startIndex,
[FromQuery] int? limit,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] channelIds)
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFilter[] filters,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] channelIds)
{
userId = RequestHelpers.GetUserId(User, userId);
var user = userId.IsNullOrEmpty()
diff --git a/Jellyfin.Api/Controllers/CollectionController.cs b/Jellyfin.Api/Controllers/CollectionController.cs
index 2d9f1ed69..c37f37633 100644
--- a/Jellyfin.Api/Controllers/CollectionController.cs
+++ b/Jellyfin.Api/Controllers/CollectionController.cs
@@ -50,7 +50,7 @@ public class CollectionController : BaseJellyfinApiController
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<CollectionCreationResult>> CreateCollection(
[FromQuery] string? name,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] ids,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] string[] ids,
[FromQuery] Guid? parentId,
[FromQuery] bool isLocked = false)
{
@@ -86,7 +86,7 @@ public class CollectionController : BaseJellyfinApiController
[ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task<ActionResult> AddToCollection(
[FromRoute, Required] Guid collectionId,
- [FromQuery, Required, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] ids)
+ [FromQuery, Required, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] ids)
{
await _collectionManager.AddToCollectionAsync(collectionId, ids).ConfigureAwait(true);
return NoContent();
@@ -103,7 +103,7 @@ public class CollectionController : BaseJellyfinApiController
[ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task<ActionResult> RemoveFromCollection(
[FromRoute, Required] Guid collectionId,
- [FromQuery, Required, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] ids)
+ [FromQuery, Required, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] ids)
{
await _collectionManager.RemoveFromCollectionAsync(collectionId, ids).ConfigureAwait(false);
return NoContent();
diff --git a/Jellyfin.Api/Controllers/ConfigurationController.cs b/Jellyfin.Api/Controllers/ConfigurationController.cs
index abe8bec2d..8dcaebf6d 100644
--- a/Jellyfin.Api/Controllers/ConfigurationController.cs
+++ b/Jellyfin.Api/Controllers/ConfigurationController.cs
@@ -9,6 +9,7 @@ using Jellyfin.Extensions.Json;
using MediaBrowser.Common.Api;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.MediaEncoding;
+using MediaBrowser.Model.Branding;
using MediaBrowser.Model.Configuration;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
@@ -120,6 +121,30 @@ public class ConfigurationController : BaseJellyfinApiController
}
/// <summary>
+ /// Updates branding configuration.
+ /// </summary>
+ /// <param name="configuration">Branding configuration.</param>
+ /// <response code="204">Branding configuration updated.</response>
+ /// <returns>Update status.</returns>
+ [HttpPost("Configuration/Branding")]
+ [Authorize(Policy = Policies.RequiresElevation)]
+ [ProducesResponseType(StatusCodes.Status204NoContent)]
+ public ActionResult UpdateBrandingConfiguration([FromBody, Required] BrandingOptionsDto configuration)
+ {
+ // Get the current branding configuration to preserve SplashscreenLocation
+ var currentBranding = (BrandingOptions)_configurationManager.GetConfiguration("branding");
+
+ // Update only the properties from BrandingOptionsDto
+ currentBranding.LoginDisclaimer = configuration.LoginDisclaimer;
+ currentBranding.CustomCss = configuration.CustomCss;
+ currentBranding.SplashscreenEnabled = configuration.SplashscreenEnabled;
+
+ _configurationManager.SaveConfiguration("branding", currentBranding);
+
+ return NoContent();
+ }
+
+ /// <summary>
/// Updates the path to the media encoder.
/// </summary>
/// <param name="mediaEncoderPath">Media encoder path form body.</param>
diff --git a/Jellyfin.Api/Controllers/DisplayPreferencesController.cs b/Jellyfin.Api/Controllers/DisplayPreferencesController.cs
index 6d94d96f3..13064882c 100644
--- a/Jellyfin.Api/Controllers/DisplayPreferencesController.cs
+++ b/Jellyfin.Api/Controllers/DisplayPreferencesController.cs
@@ -4,8 +4,8 @@ using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using Jellyfin.Api.Helpers;
-using Jellyfin.Data.Entities;
-using Jellyfin.Data.Enums;
+using Jellyfin.Database.Implementations.Entities;
+using Jellyfin.Database.Implementations.Enums;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller;
using MediaBrowser.Model.Dto;
diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs
index a641ec209..4cac8ed67 100644
--- a/Jellyfin.Api/Controllers/DynamicHlsController.cs
+++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs
@@ -166,18 +166,18 @@ public class DynamicHlsController : BaseJellyfinApiController
[ProducesPlaylistFile]
public async Task<ActionResult> GetLiveHlsStream(
[FromRoute, Required] Guid itemId,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? container,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? container,
[FromQuery] bool? @static,
[FromQuery] string? @params,
[FromQuery] string? tag,
[FromQuery, ParameterObsolete] string? deviceProfileId,
[FromQuery] string? playSessionId,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? segmentContainer,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? segmentContainer,
[FromQuery] int? segmentLength,
[FromQuery] int? minSegments,
[FromQuery] string? mediaSourceId,
[FromQuery] string? deviceId,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? audioCodec,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? audioCodec,
[FromQuery] bool? enableAutoStreamCopy,
[FromQuery] bool? allowVideoStreamCopy,
[FromQuery] bool? allowAudioStreamCopy,
@@ -188,7 +188,7 @@ public class DynamicHlsController : BaseJellyfinApiController
[FromQuery] int? audioChannels,
[FromQuery] int? maxAudioChannels,
[FromQuery] string? profile,
- [FromQuery] string? level,
+ [FromQuery] [RegularExpression(EncodingHelper.LevelValidationRegex)] string? level,
[FromQuery] float? framerate,
[FromQuery] float? maxFramerate,
[FromQuery] bool? copyTimestamps,
@@ -207,8 +207,8 @@ public class DynamicHlsController : BaseJellyfinApiController
[FromQuery] int? cpuCoreLimit,
[FromQuery] string? liveStreamId,
[FromQuery] bool? enableMpegtsM2TsMode,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? videoCodec,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? subtitleCodec,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? videoCodec,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? subtitleCodec,
[FromQuery] string? transcodeReasons,
[FromQuery] int? audioStreamIndex,
[FromQuery] int? videoStreamIndex,
@@ -415,12 +415,12 @@ public class DynamicHlsController : BaseJellyfinApiController
[FromQuery] string? tag,
[FromQuery, ParameterObsolete] string? deviceProfileId,
[FromQuery] string? playSessionId,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? segmentContainer,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? segmentContainer,
[FromQuery] int? segmentLength,
[FromQuery] int? minSegments,
[FromQuery, Required] string mediaSourceId,
[FromQuery] string? deviceId,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? audioCodec,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? audioCodec,
[FromQuery] bool? enableAutoStreamCopy,
[FromQuery] bool? allowVideoStreamCopy,
[FromQuery] bool? allowAudioStreamCopy,
@@ -431,7 +431,7 @@ public class DynamicHlsController : BaseJellyfinApiController
[FromQuery] int? audioChannels,
[FromQuery] int? maxAudioChannels,
[FromQuery] string? profile,
- [FromQuery] string? level,
+ [FromQuery] [RegularExpression(EncodingHelper.LevelValidationRegex)] string? level,
[FromQuery] float? framerate,
[FromQuery] float? maxFramerate,
[FromQuery] bool? copyTimestamps,
@@ -452,14 +452,14 @@ public class DynamicHlsController : BaseJellyfinApiController
[FromQuery] int? cpuCoreLimit,
[FromQuery] string? liveStreamId,
[FromQuery] bool? enableMpegtsM2TsMode,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? videoCodec,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? subtitleCodec,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? videoCodec,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? subtitleCodec,
[FromQuery] string? transcodeReasons,
[FromQuery] int? audioStreamIndex,
[FromQuery] int? videoStreamIndex,
[FromQuery] EncodingContext? context,
[FromQuery] Dictionary<string, string> streamOptions,
- [FromQuery] bool enableAdaptiveBitrateStreaming = true,
+ [FromQuery] bool enableAdaptiveBitrateStreaming = false,
[FromQuery] bool enableTrickplay = true,
[FromQuery] bool enableAudioVbrEncoding = true,
[FromQuery] bool alwaysBurnInSubtitleWhenTranscoding = false)
@@ -591,12 +591,12 @@ public class DynamicHlsController : BaseJellyfinApiController
[FromQuery] string? tag,
[FromQuery, ParameterObsolete] string? deviceProfileId,
[FromQuery] string? playSessionId,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? segmentContainer,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? segmentContainer,
[FromQuery] int? segmentLength,
[FromQuery] int? minSegments,
[FromQuery, Required] string mediaSourceId,
[FromQuery] string? deviceId,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? audioCodec,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? audioCodec,
[FromQuery] bool? enableAutoStreamCopy,
[FromQuery] bool? allowVideoStreamCopy,
[FromQuery] bool? allowAudioStreamCopy,
@@ -608,7 +608,7 @@ public class DynamicHlsController : BaseJellyfinApiController
[FromQuery] int? audioChannels,
[FromQuery] int? maxAudioChannels,
[FromQuery] string? profile,
- [FromQuery] string? level,
+ [FromQuery] [RegularExpression(EncodingHelper.LevelValidationRegex)] string? level,
[FromQuery] float? framerate,
[FromQuery] float? maxFramerate,
[FromQuery] bool? copyTimestamps,
@@ -627,14 +627,14 @@ public class DynamicHlsController : BaseJellyfinApiController
[FromQuery] int? cpuCoreLimit,
[FromQuery] string? liveStreamId,
[FromQuery] bool? enableMpegtsM2TsMode,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? videoCodec,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? subtitleCodec,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? videoCodec,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? subtitleCodec,
[FromQuery] string? transcodeReasons,
[FromQuery] int? audioStreamIndex,
[FromQuery] int? videoStreamIndex,
[FromQuery] EncodingContext? context,
[FromQuery] Dictionary<string, string> streamOptions,
- [FromQuery] bool enableAdaptiveBitrateStreaming = true,
+ [FromQuery] bool enableAdaptiveBitrateStreaming = false,
[FromQuery] bool enableAudioVbrEncoding = true)
{
var streamingRequest = new HlsAudioRequestDto
@@ -761,12 +761,12 @@ public class DynamicHlsController : BaseJellyfinApiController
[FromQuery] string? tag,
[FromQuery, ParameterObsolete] string? deviceProfileId,
[FromQuery] string? playSessionId,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? segmentContainer,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? segmentContainer,
[FromQuery] int? segmentLength,
[FromQuery] int? minSegments,
[FromQuery] string? mediaSourceId,
[FromQuery] string? deviceId,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? audioCodec,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? audioCodec,
[FromQuery] bool? enableAutoStreamCopy,
[FromQuery] bool? allowVideoStreamCopy,
[FromQuery] bool? allowAudioStreamCopy,
@@ -777,7 +777,7 @@ public class DynamicHlsController : BaseJellyfinApiController
[FromQuery] int? audioChannels,
[FromQuery] int? maxAudioChannels,
[FromQuery] string? profile,
- [FromQuery] string? level,
+ [FromQuery] [RegularExpression(EncodingHelper.LevelValidationRegex)] string? level,
[FromQuery] float? framerate,
[FromQuery] float? maxFramerate,
[FromQuery] bool? copyTimestamps,
@@ -798,8 +798,8 @@ public class DynamicHlsController : BaseJellyfinApiController
[FromQuery] int? cpuCoreLimit,
[FromQuery] string? liveStreamId,
[FromQuery] bool? enableMpegtsM2TsMode,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? videoCodec,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? subtitleCodec,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? videoCodec,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? subtitleCodec,
[FromQuery] string? transcodeReasons,
[FromQuery] int? audioStreamIndex,
[FromQuery] int? videoStreamIndex,
@@ -933,12 +933,12 @@ public class DynamicHlsController : BaseJellyfinApiController
[FromQuery] string? tag,
[FromQuery, ParameterObsolete] string? deviceProfileId,
[FromQuery] string? playSessionId,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? segmentContainer,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? segmentContainer,
[FromQuery] int? segmentLength,
[FromQuery] int? minSegments,
[FromQuery] string? mediaSourceId,
[FromQuery] string? deviceId,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? audioCodec,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? audioCodec,
[FromQuery] bool? enableAutoStreamCopy,
[FromQuery] bool? allowVideoStreamCopy,
[FromQuery] bool? allowAudioStreamCopy,
@@ -950,7 +950,7 @@ public class DynamicHlsController : BaseJellyfinApiController
[FromQuery] int? audioChannels,
[FromQuery] int? maxAudioChannels,
[FromQuery] string? profile,
- [FromQuery] string? level,
+ [FromQuery] [RegularExpression(EncodingHelper.LevelValidationRegex)] string? level,
[FromQuery] float? framerate,
[FromQuery] float? maxFramerate,
[FromQuery] bool? copyTimestamps,
@@ -969,8 +969,8 @@ public class DynamicHlsController : BaseJellyfinApiController
[FromQuery] int? cpuCoreLimit,
[FromQuery] string? liveStreamId,
[FromQuery] bool? enableMpegtsM2TsMode,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? videoCodec,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? subtitleCodec,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? videoCodec,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? subtitleCodec,
[FromQuery] string? transcodeReasons,
[FromQuery] int? audioStreamIndex,
[FromQuery] int? videoStreamIndex,
@@ -1106,7 +1106,7 @@ public class DynamicHlsController : BaseJellyfinApiController
[FromRoute, Required] Guid itemId,
[FromRoute, Required] string playlistId,
[FromRoute, Required] int segmentId,
- [FromRoute, Required] string container,
+ [FromRoute, Required] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string container,
[FromQuery, Required] long runtimeTicks,
[FromQuery, Required] long actualSegmentLengthTicks,
[FromQuery] bool? @static,
@@ -1114,12 +1114,12 @@ public class DynamicHlsController : BaseJellyfinApiController
[FromQuery] string? tag,
[FromQuery, ParameterObsolete] string? deviceProfileId,
[FromQuery] string? playSessionId,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? segmentContainer,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? segmentContainer,
[FromQuery] int? segmentLength,
[FromQuery] int? minSegments,
[FromQuery] string? mediaSourceId,
[FromQuery] string? deviceId,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? audioCodec,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? audioCodec,
[FromQuery] bool? enableAutoStreamCopy,
[FromQuery] bool? allowVideoStreamCopy,
[FromQuery] bool? allowAudioStreamCopy,
@@ -1130,7 +1130,7 @@ public class DynamicHlsController : BaseJellyfinApiController
[FromQuery] int? audioChannels,
[FromQuery] int? maxAudioChannels,
[FromQuery] string? profile,
- [FromQuery] string? level,
+ [FromQuery] [RegularExpression(EncodingHelper.LevelValidationRegex)] string? level,
[FromQuery] float? framerate,
[FromQuery] float? maxFramerate,
[FromQuery] bool? copyTimestamps,
@@ -1151,8 +1151,8 @@ public class DynamicHlsController : BaseJellyfinApiController
[FromQuery] int? cpuCoreLimit,
[FromQuery] string? liveStreamId,
[FromQuery] bool? enableMpegtsM2TsMode,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? videoCodec,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? subtitleCodec,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? videoCodec,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? subtitleCodec,
[FromQuery] string? transcodeReasons,
[FromQuery] int? audioStreamIndex,
[FromQuery] int? videoStreamIndex,
@@ -1291,7 +1291,7 @@ public class DynamicHlsController : BaseJellyfinApiController
[FromRoute, Required] Guid itemId,
[FromRoute, Required] string playlistId,
[FromRoute, Required] int segmentId,
- [FromRoute, Required] string container,
+ [FromRoute, Required] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string container,
[FromQuery, Required] long runtimeTicks,
[FromQuery, Required] long actualSegmentLengthTicks,
[FromQuery] bool? @static,
@@ -1299,12 +1299,12 @@ public class DynamicHlsController : BaseJellyfinApiController
[FromQuery] string? tag,
[FromQuery, ParameterObsolete] string? deviceProfileId,
[FromQuery] string? playSessionId,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? segmentContainer,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? segmentContainer,
[FromQuery] int? segmentLength,
[FromQuery] int? minSegments,
[FromQuery] string? mediaSourceId,
[FromQuery] string? deviceId,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? audioCodec,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? audioCodec,
[FromQuery] bool? enableAutoStreamCopy,
[FromQuery] bool? allowVideoStreamCopy,
[FromQuery] bool? allowAudioStreamCopy,
@@ -1316,7 +1316,7 @@ public class DynamicHlsController : BaseJellyfinApiController
[FromQuery] int? audioChannels,
[FromQuery] int? maxAudioChannels,
[FromQuery] string? profile,
- [FromQuery] string? level,
+ [FromQuery] [RegularExpression(EncodingHelper.LevelValidationRegex)] string? level,
[FromQuery] float? framerate,
[FromQuery] float? maxFramerate,
[FromQuery] bool? copyTimestamps,
@@ -1335,8 +1335,8 @@ public class DynamicHlsController : BaseJellyfinApiController
[FromQuery] int? cpuCoreLimit,
[FromQuery] string? liveStreamId,
[FromQuery] bool? enableMpegtsM2TsMode,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? videoCodec,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? subtitleCodec,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? videoCodec,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? subtitleCodec,
[FromQuery] string? transcodeReasons,
[FromQuery] int? audioStreamIndex,
[FromQuery] int? videoStreamIndex,
@@ -1419,8 +1419,9 @@ public class DynamicHlsController : BaseJellyfinApiController
TranscodingJobType,
cancellationTokenSource.Token)
.ConfigureAwait(false);
-
+ var mediaSourceId = state.BaseRequest.MediaSourceId;
var request = new CreateMainPlaylistRequest(
+ mediaSourceId is null ? null : Guid.Parse(mediaSourceId),
state.MediaPath,
state.SegmentLength * 1000,
state.RunTimeTicks ?? 0,
@@ -1675,7 +1676,7 @@ public class DynamicHlsController : BaseJellyfinApiController
}
var audioCodec = _encodingHelper.GetAudioEncoder(state);
- var bitStreamArgs = EncodingHelper.GetAudioBitStreamArguments(state, state.Request.SegmentContainer, state.MediaSource.Container);
+ var bitStreamArgs = _encodingHelper.GetAudioBitStreamArguments(state, state.Request.SegmentContainer, state.MediaSource.Container);
// opus, dts, truehd and flac (in FFmpeg 5 and older) are experimental in mp4 muxer
var strictArgs = string.Empty;
@@ -1753,7 +1754,7 @@ public class DynamicHlsController : BaseJellyfinApiController
if (channels.HasValue
&& (channels.Value != 2
- || (state.AudioStream?.Channels != null && !useDownMixAlgorithm)))
+ || (state.AudioStream?.Channels is not null && !useDownMixAlgorithm)))
{
args += " -ac " + channels.Value;
}
@@ -1778,7 +1779,7 @@ public class DynamicHlsController : BaseJellyfinApiController
}
else if (state.AudioStream?.CodecTag is not null && state.AudioStream.CodecTag.Equals("ac-4", StringComparison.Ordinal))
{
- // ac-4 audio tends to hava a super weird sample rate that will fail most encoders
+ // ac-4 audio tends to have a super weird sample rate that will fail most encoders
// force resample it to 48KHz
args += " -ar 48000";
}
@@ -1822,10 +1823,11 @@ public class DynamicHlsController : BaseJellyfinApiController
// Clients reporting Dolby Vision capabilities with fallbacks may only support the fallback layer.
// Only enable Dolby Vision remuxing if the client explicitly declares support for profiles without fallbacks.
var clientSupportsDoVi = requestedRange.Contains(VideoRangeType.DOVI.ToString(), StringComparison.OrdinalIgnoreCase);
- var videoIsDoVi = state.VideoStream.VideoRangeType is VideoRangeType.DOVI or VideoRangeType.DOVIWithHDR10 or VideoRangeType.DOVIWithHLG or VideoRangeType.DOVIWithSDR;
+ var videoIsDoVi = EncodingHelper.IsDovi(state.VideoStream);
if (EncodingHelper.IsCopyCodec(codec)
- && (videoIsDoVi && clientSupportsDoVi))
+ && (videoIsDoVi && clientSupportsDoVi)
+ && !_encodingHelper.IsDoviRemoved(state))
{
if (isActualOutputVideoCodecHevc)
{
@@ -1855,7 +1857,7 @@ public class DynamicHlsController : BaseJellyfinApiController
// If h264_mp4toannexb is ever added, do not use it for live tv.
if (state.VideoStream is not null && !string.Equals(state.VideoStream.NalLengthSize, "0", StringComparison.OrdinalIgnoreCase))
{
- string bitStreamArgs = EncodingHelper.GetBitStreamArgs(state.VideoStream);
+ string bitStreamArgs = _encodingHelper.GetBitStreamArgs(state, MediaStreamType.Video);
if (!string.IsNullOrEmpty(bitStreamArgs))
{
args += " " + bitStreamArgs;
@@ -2056,16 +2058,16 @@ public class DynamicHlsController : BaseJellyfinApiController
}
}
- private Task DeleteLastFile(string playlistPath, string segmentExtension, int retryCount)
+ private async Task DeleteLastFile(string playlistPath, string segmentExtension, int retryCount)
{
var file = GetLastTranscodingFile(playlistPath, segmentExtension, _fileSystem);
if (file is null)
{
- return Task.CompletedTask;
+ return;
}
- return DeleteFile(file.FullName, retryCount);
+ await DeleteFile(file.FullName, retryCount).ConfigureAwait(false);
}
private async Task DeleteFile(string path, int retryCount)
diff --git a/Jellyfin.Api/Controllers/FilterController.cs b/Jellyfin.Api/Controllers/FilterController.cs
index 4abca3271..3f9aa93a6 100644
--- a/Jellyfin.Api/Controllers/FilterController.cs
+++ b/Jellyfin.Api/Controllers/FilterController.cs
@@ -50,8 +50,8 @@ public class FilterController : BaseJellyfinApiController
public ActionResult<QueryFiltersLegacy> GetQueryFiltersLegacy(
[FromQuery] Guid? userId,
[FromQuery] Guid? parentId,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] MediaType[] mediaTypes)
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] includeItemTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] MediaType[] mediaTypes)
{
userId = RequestHelpers.GetUserId(User, userId);
var user = userId.IsNullOrEmpty()
@@ -137,7 +137,7 @@ public class FilterController : BaseJellyfinApiController
public ActionResult<QueryFilters> GetQueryFilters(
[FromQuery] Guid? userId,
[FromQuery] Guid? parentId,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] includeItemTypes,
[FromQuery] bool? isAiring,
[FromQuery] bool? isMovie,
[FromQuery] bool? isSports,
diff --git a/Jellyfin.Api/Controllers/GenresController.cs b/Jellyfin.Api/Controllers/GenresController.cs
index 54d48aec2..dd60d01e0 100644
--- a/Jellyfin.Api/Controllers/GenresController.cs
+++ b/Jellyfin.Api/Controllers/GenresController.cs
@@ -4,8 +4,9 @@ using System.Linq;
using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders;
-using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
+using Jellyfin.Database.Implementations.Entities;
+using Jellyfin.Database.Implementations.Enums;
using Jellyfin.Extensions;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
@@ -76,18 +77,18 @@ public class GenresController : BaseJellyfinApiController
[FromQuery] int? limit,
[FromQuery] string? searchTerm,
[FromQuery] Guid? parentId,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] excludeItemTypes,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] excludeItemTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] includeItemTypes,
[FromQuery] bool? isFavorite,
[FromQuery] int? imageTypeLimit,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes,
[FromQuery] Guid? userId,
[FromQuery] string? nameStartsWithOrGreater,
[FromQuery] string? nameStartsWith,
[FromQuery] string? nameLessThan,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemSortBy[] sortBy,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SortOrder[] sortOrder,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemSortBy[] sortBy,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] SortOrder[] sortOrder,
[FromQuery] bool? enableImages = true,
[FromQuery] bool enableTotalRecordCount = true)
{
diff --git a/Jellyfin.Api/Controllers/ImageController.cs b/Jellyfin.Api/Controllers/ImageController.cs
index b71199026..abda053d3 100644
--- a/Jellyfin.Api/Controllers/ImageController.cs
+++ b/Jellyfin.Api/Controllers/ImageController.cs
@@ -130,7 +130,7 @@ public class ImageController : BaseJellyfinApiController
await _userManager.ClearProfileImageAsync(user).ConfigureAwait(false);
}
- user.ProfileImage = new Data.Entities.ImageInfo(Path.Combine(userDataPath, "profile" + extension));
+ user.ProfileImage = new Database.Implementations.Entities.ImageInfo(Path.Combine(userDataPath, "profile" + extension));
await _providerManager
.SaveImage(stream, mimeType, user.ProfileImage.Path)
@@ -1727,7 +1727,8 @@ public class ImageController : BaseJellyfinApiController
[FromQuery, Range(0, 100)] int quality = 90)
{
var brandingOptions = _serverConfigurationManager.GetConfiguration<BrandingOptions>("branding");
- if (!brandingOptions.SplashscreenEnabled)
+ var isAdmin = User.IsInRole(Constants.UserRoles.Administrator);
+ if (!brandingOptions.SplashscreenEnabled && !isAdmin)
{
return NotFound();
}
diff --git a/Jellyfin.Api/Controllers/InstantMixController.cs b/Jellyfin.Api/Controllers/InstantMixController.cs
index dcbacf1d7..c4b976756 100644
--- a/Jellyfin.Api/Controllers/InstantMixController.cs
+++ b/Jellyfin.Api/Controllers/InstantMixController.cs
@@ -1,10 +1,12 @@
using System;
using System.Collections.Generic;
+using System.Collections.Immutable;
using System.ComponentModel.DataAnnotations;
+using System.Linq;
using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders;
-using Jellyfin.Data.Entities;
+using Jellyfin.Database.Implementations.Entities;
using Jellyfin.Extensions;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
@@ -71,11 +73,11 @@ public class InstantMixController : BaseJellyfinApiController
[FromRoute, Required] Guid itemId,
[FromQuery] Guid? userId,
[FromQuery] int? limit,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields,
[FromQuery] bool? enableImages,
[FromQuery] bool? enableUserData,
[FromQuery] int? imageTypeLimit,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes)
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes)
{
userId = RequestHelpers.GetUserId(User, userId);
var user = userId.IsNullOrEmpty()
@@ -115,11 +117,11 @@ public class InstantMixController : BaseJellyfinApiController
[FromRoute, Required] Guid itemId,
[FromQuery] Guid? userId,
[FromQuery] int? limit,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields,
[FromQuery] bool? enableImages,
[FromQuery] bool? enableUserData,
[FromQuery] int? imageTypeLimit,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes)
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes)
{
userId = RequestHelpers.GetUserId(User, userId);
var user = userId.IsNullOrEmpty()
@@ -159,11 +161,11 @@ public class InstantMixController : BaseJellyfinApiController
[FromRoute, Required] Guid itemId,
[FromQuery] Guid? userId,
[FromQuery] int? limit,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields,
[FromQuery] bool? enableImages,
[FromQuery] bool? enableUserData,
[FromQuery] int? imageTypeLimit,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes)
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes)
{
userId = RequestHelpers.GetUserId(User, userId);
var user = userId.IsNullOrEmpty()
@@ -201,11 +203,11 @@ public class InstantMixController : BaseJellyfinApiController
[FromRoute, Required] string name,
[FromQuery] Guid? userId,
[FromQuery] int? limit,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields,
[FromQuery] bool? enableImages,
[FromQuery] bool? enableUserData,
[FromQuery] int? imageTypeLimit,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes)
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes)
{
userId = RequestHelpers.GetUserId(User, userId);
var user = userId.IsNullOrEmpty()
@@ -239,11 +241,11 @@ public class InstantMixController : BaseJellyfinApiController
[FromRoute, Required] Guid itemId,
[FromQuery] Guid? userId,
[FromQuery] int? limit,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields,
[FromQuery] bool? enableImages,
[FromQuery] bool? enableUserData,
[FromQuery] int? imageTypeLimit,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes)
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes)
{
userId = RequestHelpers.GetUserId(User, userId);
var user = userId.IsNullOrEmpty()
@@ -283,11 +285,11 @@ public class InstantMixController : BaseJellyfinApiController
[FromRoute, Required] Guid itemId,
[FromQuery] Guid? userId,
[FromQuery] int? limit,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields,
[FromQuery] bool? enableImages,
[FromQuery] bool? enableUserData,
[FromQuery] int? imageTypeLimit,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes)
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes)
{
userId = RequestHelpers.GetUserId(User, userId);
var user = userId.IsNullOrEmpty()
@@ -328,11 +330,11 @@ public class InstantMixController : BaseJellyfinApiController
[FromQuery, Required] Guid id,
[FromQuery] Guid? userId,
[FromQuery] int? limit,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields,
[FromQuery] bool? enableImages,
[FromQuery] bool? enableUserData,
[FromQuery] int? imageTypeLimit,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes)
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes)
{
return GetInstantMixFromArtists(
id,
@@ -366,11 +368,11 @@ public class InstantMixController : BaseJellyfinApiController
[FromQuery, Required] Guid id,
[FromQuery] Guid? userId,
[FromQuery] int? limit,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields,
[FromQuery] bool? enableImages,
[FromQuery] bool? enableUserData,
[FromQuery] int? imageTypeLimit,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes)
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes)
{
userId = RequestHelpers.GetUserId(User, userId);
var user = userId.IsNullOrEmpty()
@@ -389,23 +391,19 @@ public class InstantMixController : BaseJellyfinApiController
return GetResult(items, user, limit, dtoOptions);
}
- private QueryResult<BaseItemDto> GetResult(List<BaseItem> items, User? user, int? limit, DtoOptions dtoOptions)
+ private QueryResult<BaseItemDto> GetResult(IReadOnlyList<BaseItem> items, User? user, int? limit, DtoOptions dtoOptions)
{
- var list = items;
+ var totalCount = items.Count;
- var totalCount = list.Count;
-
- if (limit.HasValue && limit < list.Count)
+ if (limit.HasValue && limit < items.Count)
{
- list = list.GetRange(0, limit.Value);
+ items = items.Take(limit.Value).ToArray();
}
- var returnList = _dtoService.GetBaseItemDtos(list, dtoOptions, user);
-
var result = new QueryResult<BaseItemDto>(
0,
totalCount,
- returnList);
+ _dtoService.GetBaseItemDtos(items, dtoOptions, user));
return result;
}
diff --git a/Jellyfin.Api/Controllers/ItemUpdateController.cs b/Jellyfin.Api/Controllers/ItemUpdateController.cs
index d49e0753e..50eeaeac6 100644
--- a/Jellyfin.Api/Controllers/ItemUpdateController.cs
+++ b/Jellyfin.Api/Controllers/ItemUpdateController.cs
@@ -299,7 +299,7 @@ public class ItemUpdateController : BaseJellyfinApiController
if (!season.LockedFields.Contains(MetadataField.Tags))
{
- season.Tags = season.Tags.Concat(addedTags).Except(removedTags).Distinct().ToArray();
+ season.Tags = season.Tags.Concat(addedTags).Except(removedTags).Distinct(StringComparer.OrdinalIgnoreCase).ToArray();
}
season.OnMetadataChanged();
@@ -316,7 +316,7 @@ public class ItemUpdateController : BaseJellyfinApiController
if (!ep.LockedFields.Contains(MetadataField.Tags))
{
- ep.Tags = ep.Tags.Concat(addedTags).Except(removedTags).Distinct().ToArray();
+ ep.Tags = ep.Tags.Concat(addedTags).Except(removedTags).Distinct(StringComparer.OrdinalIgnoreCase).ToArray();
}
ep.OnMetadataChanged();
@@ -337,7 +337,7 @@ public class ItemUpdateController : BaseJellyfinApiController
if (!ep.LockedFields.Contains(MetadataField.Tags))
{
- ep.Tags = ep.Tags.Concat(addedTags).Except(removedTags).Distinct().ToArray();
+ ep.Tags = ep.Tags.Concat(addedTags).Except(removedTags).Distinct(StringComparer.OrdinalIgnoreCase).ToArray();
}
ep.OnMetadataChanged();
@@ -357,7 +357,7 @@ public class ItemUpdateController : BaseJellyfinApiController
if (!track.LockedFields.Contains(MetadataField.Tags))
{
- track.Tags = track.Tags.Concat(addedTags).Except(removedTags).Distinct().ToArray();
+ track.Tags = track.Tags.Concat(addedTags).Except(removedTags).Distinct(StringComparer.OrdinalIgnoreCase).ToArray();
}
track.OnMetadataChanged();
diff --git a/Jellyfin.Api/Controllers/ItemsController.cs b/Jellyfin.Api/Controllers/ItemsController.cs
index 828bd5174..a49128336 100644
--- a/Jellyfin.Api/Controllers/ItemsController.cs
+++ b/Jellyfin.Api/Controllers/ItemsController.cs
@@ -4,7 +4,9 @@ using System.Linq;
using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders;
+using Jellyfin.Data;
using Jellyfin.Data.Enums;
+using Jellyfin.Database.Implementations.Enums;
using Jellyfin.Extensions;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Dto;
@@ -171,8 +173,8 @@ public class ItemsController : BaseJellyfinApiController
[FromQuery] bool? hasParentalRating,
[FromQuery] bool? isHd,
[FromQuery] bool? is4K,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] LocationType[] locationTypes,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] LocationType[] excludeLocationTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] LocationType[] locationTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] LocationType[] excludeLocationTypes,
[FromQuery] bool? isMissing,
[FromQuery] bool? isUnaired,
[FromQuery] double? minCommunityRating,
@@ -190,42 +192,42 @@ public class ItemsController : BaseJellyfinApiController
[FromQuery] bool? isNews,
[FromQuery] bool? isKids,
[FromQuery] bool? isSports,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] excludeItemIds,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] excludeItemIds,
[FromQuery] int? startIndex,
[FromQuery] int? limit,
[FromQuery] bool? recursive,
[FromQuery] string? searchTerm,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SortOrder[] sortOrder,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] SortOrder[] sortOrder,
[FromQuery] Guid? parentId,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] excludeItemTypes,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] excludeItemTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] includeItemTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFilter[] filters,
[FromQuery] bool? isFavorite,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] MediaType[] mediaTypes,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] imageTypes,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemSortBy[] sortBy,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] MediaType[] mediaTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] imageTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemSortBy[] sortBy,
[FromQuery] bool? isPlayed,
- [FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] genres,
- [FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] officialRatings,
- [FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] tags,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] int[] years,
+ [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] genres,
+ [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] officialRatings,
+ [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] tags,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] int[] years,
[FromQuery] bool? enableUserData,
[FromQuery] int? imageTypeLimit,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes,
[FromQuery] string? person,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] personIds,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] personTypes,
- [FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] studios,
- [FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] artists,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] excludeArtistIds,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] artistIds,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] albumArtistIds,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] contributingArtistIds,
- [FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] albums,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] albumIds,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] ids,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] VideoType[] videoTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] personIds,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] string[] personTypes,
+ [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] studios,
+ [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] artists,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] excludeArtistIds,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] artistIds,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] albumArtistIds,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] contributingArtistIds,
+ [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] albums,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] albumIds,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] ids,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] VideoType[] videoTypes,
[FromQuery] string? minOfficialRating,
[FromQuery] bool? isLocked,
[FromQuery] bool? isPlaceHolder,
@@ -236,12 +238,12 @@ public class ItemsController : BaseJellyfinApiController
[FromQuery] int? maxWidth,
[FromQuery] int? maxHeight,
[FromQuery] bool? is3D,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SeriesStatus[] seriesStatus,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] SeriesStatus[] seriesStatus,
[FromQuery] string? nameStartsWithOrGreater,
[FromQuery] string? nameStartsWith,
[FromQuery] string? nameLessThan,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] studioIds,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] genreIds,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] studioIds,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] genreIds,
[FromQuery] bool enableTotalRecordCount = true,
[FromQuery] bool? enableImages = true)
{
@@ -446,13 +448,13 @@ public class ItemsController : BaseJellyfinApiController
// Min official rating
if (!string.IsNullOrWhiteSpace(minOfficialRating))
{
- query.MinParentalRating = _localization.GetRatingLevel(minOfficialRating);
+ query.MinParentalRating = _localization.GetRatingScore(minOfficialRating);
}
// Max official rating
if (!string.IsNullOrWhiteSpace(maxOfficialRating))
{
- query.MaxParentalRating = _localization.GetRatingLevel(maxOfficialRating);
+ query.MaxParentalRating = _localization.GetRatingScore(maxOfficialRating);
}
// Artists
@@ -638,8 +640,8 @@ public class ItemsController : BaseJellyfinApiController
[FromQuery] bool? hasParentalRating,
[FromQuery] bool? isHd,
[FromQuery] bool? is4K,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] LocationType[] locationTypes,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] LocationType[] excludeLocationTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] LocationType[] locationTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] LocationType[] excludeLocationTypes,
[FromQuery] bool? isMissing,
[FromQuery] bool? isUnaired,
[FromQuery] double? minCommunityRating,
@@ -657,42 +659,42 @@ public class ItemsController : BaseJellyfinApiController
[FromQuery] bool? isNews,
[FromQuery] bool? isKids,
[FromQuery] bool? isSports,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] excludeItemIds,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] excludeItemIds,
[FromQuery] int? startIndex,
[FromQuery] int? limit,
[FromQuery] bool? recursive,
[FromQuery] string? searchTerm,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SortOrder[] sortOrder,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] SortOrder[] sortOrder,
[FromQuery] Guid? parentId,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] excludeItemTypes,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] excludeItemTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] includeItemTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFilter[] filters,
[FromQuery] bool? isFavorite,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] MediaType[] mediaTypes,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] imageTypes,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemSortBy[] sortBy,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] MediaType[] mediaTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] imageTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemSortBy[] sortBy,
[FromQuery] bool? isPlayed,
- [FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] genres,
- [FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] officialRatings,
- [FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] tags,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] int[] years,
+ [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] genres,
+ [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] officialRatings,
+ [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] tags,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] int[] years,
[FromQuery] bool? enableUserData,
[FromQuery] int? imageTypeLimit,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes,
[FromQuery] string? person,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] personIds,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] personTypes,
- [FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] studios,
- [FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] artists,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] excludeArtistIds,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] artistIds,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] albumArtistIds,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] contributingArtistIds,
- [FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] albums,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] albumIds,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] ids,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] VideoType[] videoTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] personIds,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] string[] personTypes,
+ [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] studios,
+ [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] artists,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] excludeArtistIds,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] artistIds,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] albumArtistIds,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] contributingArtistIds,
+ [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] albums,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] albumIds,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] ids,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] VideoType[] videoTypes,
[FromQuery] string? minOfficialRating,
[FromQuery] bool? isLocked,
[FromQuery] bool? isPlaceHolder,
@@ -703,12 +705,12 @@ public class ItemsController : BaseJellyfinApiController
[FromQuery] int? maxWidth,
[FromQuery] int? maxHeight,
[FromQuery] bool? is3D,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SeriesStatus[] seriesStatus,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] SeriesStatus[] seriesStatus,
[FromQuery] string? nameStartsWithOrGreater,
[FromQuery] string? nameStartsWith,
[FromQuery] string? nameLessThan,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] studioIds,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] genreIds,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] studioIds,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] genreIds,
[FromQuery] bool enableTotalRecordCount = true,
[FromQuery] bool? enableImages = true)
=> GetItems(
@@ -827,13 +829,13 @@ public class ItemsController : BaseJellyfinApiController
[FromQuery] int? limit,
[FromQuery] string? searchTerm,
[FromQuery] Guid? parentId,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] MediaType[] mediaTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] MediaType[] mediaTypes,
[FromQuery] bool? enableUserData,
[FromQuery] int? imageTypeLimit,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] excludeItemTypes,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] excludeItemTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] includeItemTypes,
[FromQuery] bool enableTotalRecordCount = true,
[FromQuery] bool? enableImages = true,
[FromQuery] bool excludeActiveSessions = false)
@@ -929,13 +931,13 @@ public class ItemsController : BaseJellyfinApiController
[FromQuery] int? limit,
[FromQuery] string? searchTerm,
[FromQuery] Guid? parentId,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] MediaType[] mediaTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] MediaType[] mediaTypes,
[FromQuery] bool? enableUserData,
[FromQuery] int? imageTypeLimit,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] excludeItemTypes,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] excludeItemTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] includeItemTypes,
[FromQuery] bool enableTotalRecordCount = true,
[FromQuery] bool? enableImages = true,
[FromQuery] bool excludeActiveSessions = false)
@@ -967,7 +969,7 @@ public class ItemsController : BaseJellyfinApiController
[HttpGet("UserItems/{itemId}/UserData")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
- public ActionResult<UserItemDataDto> GetItemUserData(
+ public ActionResult<UserItemDataDto?> GetItemUserData(
[FromQuery] Guid? userId,
[FromRoute, Required] Guid itemId)
{
@@ -1005,7 +1007,7 @@ public class ItemsController : BaseJellyfinApiController
[ProducesResponseType(StatusCodes.Status404NotFound)]
[Obsolete("Kept for backwards compatibility")]
[ApiExplorerSettings(IgnoreApi = true)]
- public ActionResult<UserItemDataDto> GetItemUserDataLegacy(
+ public ActionResult<UserItemDataDto?> GetItemUserDataLegacy(
[FromRoute, Required] Guid userId,
[FromRoute, Required] Guid itemId)
=> GetItemUserData(userId, itemId);
@@ -1022,7 +1024,7 @@ public class ItemsController : BaseJellyfinApiController
[HttpPost("UserItems/{itemId}/UserData")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
- public ActionResult<UserItemDataDto> UpdateItemUserData(
+ public ActionResult<UserItemDataDto?> UpdateItemUserData(
[FromQuery] Guid? userId,
[FromRoute, Required] Guid itemId,
[FromBody, Required] UpdateUserItemDataDto userDataDto)
@@ -1064,7 +1066,7 @@ public class ItemsController : BaseJellyfinApiController
[ProducesResponseType(StatusCodes.Status404NotFound)]
[Obsolete("Kept for backwards compatibility")]
[ApiExplorerSettings(IgnoreApi = true)]
- public ActionResult<UserItemDataDto> UpdateItemUserDataLegacy(
+ public ActionResult<UserItemDataDto?> UpdateItemUserDataLegacy(
[FromRoute, Required] Guid userId,
[FromRoute, Required] Guid itemId,
[FromBody, Required] UpdateUserItemDataDto userDataDto)
diff --git a/Jellyfin.Api/Controllers/LibraryController.cs b/Jellyfin.Api/Controllers/LibraryController.cs
index 1b23683fb..bde1758e9 100644
--- a/Jellyfin.Api/Controllers/LibraryController.cs
+++ b/Jellyfin.Api/Controllers/LibraryController.cs
@@ -11,8 +11,9 @@ using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders;
using Jellyfin.Api.Models.LibraryDtos;
-using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
+using Jellyfin.Database.Implementations.Entities;
+using Jellyfin.Database.Implementations.Enums;
using Jellyfin.Extensions;
using MediaBrowser.Common.Api;
using MediaBrowser.Common.Extensions;
@@ -144,8 +145,8 @@ public class LibraryController : BaseJellyfinApiController
[FromRoute, Required] Guid itemId,
[FromQuery] Guid? userId,
[FromQuery] bool inheritFromParent = false,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemSortBy[]? sortBy = null,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SortOrder[]? sortOrder = null)
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemSortBy[]? sortBy = null,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] SortOrder[]? sortOrder = null)
{
userId = RequestHelpers.GetUserId(User, userId);
var user = userId.IsNullOrEmpty()
@@ -218,8 +219,8 @@ public class LibraryController : BaseJellyfinApiController
[FromRoute, Required] Guid itemId,
[FromQuery] Guid? userId,
[FromQuery] bool inheritFromParent = false,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemSortBy[]? sortBy = null,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SortOrder[]? sortOrder = null)
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemSortBy[]? sortBy = null,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] SortOrder[]? sortOrder = null)
{
userId = RequestHelpers.GetUserId(User, userId);
var user = userId.IsNullOrEmpty()
@@ -290,8 +291,8 @@ public class LibraryController : BaseJellyfinApiController
[FromRoute, Required] Guid itemId,
[FromQuery] Guid? userId,
[FromQuery] bool inheritFromParent = false,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemSortBy[]? sortBy = null,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SortOrder[]? sortOrder = null)
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemSortBy[]? sortBy = null,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] SortOrder[]? sortOrder = null)
{
var themeSongs = GetThemeSongs(
itemId,
@@ -400,7 +401,7 @@ public class LibraryController : BaseJellyfinApiController
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
- public ActionResult DeleteItems([FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] ids)
+ public ActionResult DeleteItems([FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] ids)
{
var isApiKey = User.GetIsApiKey();
var userId = User.GetUserId();
@@ -722,10 +723,10 @@ public class LibraryController : BaseJellyfinApiController
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QueryResult<BaseItemDto>> GetSimilarItems(
[FromRoute, Required] Guid itemId,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] excludeArtistIds,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] excludeArtistIds,
[FromQuery] Guid? userId,
[FromQuery] int? limit,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields)
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields)
{
userId = RequestHelpers.GetUserId(User, userId);
var user = userId.IsNullOrEmpty()
@@ -780,11 +781,9 @@ public class LibraryController : BaseJellyfinApiController
Genres = item.Genres,
Limit = limit,
IncludeItemTypes = includeItemTypes.ToArray(),
- SimilarTo = item,
DtoOptions = dtoOptions,
EnableTotalRecordCount = !isMovie ?? true,
EnableGroupByMetadataKey = isMovie ?? false,
- MinSimilarityScore = 2 // A remnant from album/artist scoring
};
// ExcludeArtistIds
@@ -793,7 +792,7 @@ public class LibraryController : BaseJellyfinApiController
query.ExcludeArtistIds = excludeArtistIds;
}
- List<BaseItem> itemsResult = _libraryManager.GetItemList(query);
+ var itemsResult = _libraryManager.GetItemList(query);
var returnList = _dtoService.GetBaseItemDtos(itemsResult, dtoOptions, user);
diff --git a/Jellyfin.Api/Controllers/LibraryStructureController.cs b/Jellyfin.Api/Controllers/LibraryStructureController.cs
index 93c2393f3..2a885662b 100644
--- a/Jellyfin.Api/Controllers/LibraryStructureController.cs
+++ b/Jellyfin.Api/Controllers/LibraryStructureController.cs
@@ -77,7 +77,7 @@ public class LibraryStructureController : BaseJellyfinApiController
public async Task<ActionResult> AddVirtualFolder(
[FromQuery] string name,
[FromQuery] CollectionTypeOptions? collectionType,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] paths,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] string[] paths,
[FromBody] AddVirtualFolderDto? libraryOptionsDto,
[FromQuery] bool refreshLibrary = false)
{
@@ -99,6 +99,7 @@ public class LibraryStructureController : BaseJellyfinApiController
/// <param name="name">The name of the folder.</param>
/// <param name="refreshLibrary">Whether to refresh the library.</param>
/// <response code="204">Folder removed.</response>
+ /// <response code="404">Folder not found.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpDelete]
[ProducesResponseType(StatusCodes.Status204NoContent)]
@@ -106,7 +107,9 @@ public class LibraryStructureController : BaseJellyfinApiController
[FromQuery] string name,
[FromQuery] bool refreshLibrary = false)
{
+ // TODO: refactor! this relies on an FileNotFound exception to return NotFound when attempting to remove a library that does not exist.
await _libraryManager.RemoveVirtualFolder(name, refreshLibrary).ConfigureAwait(false);
+
return NoContent();
}
diff --git a/Jellyfin.Api/Controllers/LiveTvController.cs b/Jellyfin.Api/Controllers/LiveTvController.cs
index 421f23fa1..10f1789ad 100644
--- a/Jellyfin.Api/Controllers/LiveTvController.cs
+++ b/Jellyfin.Api/Controllers/LiveTvController.cs
@@ -15,6 +15,7 @@ using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders;
using Jellyfin.Api.Models.LiveTvDtos;
using Jellyfin.Data.Enums;
+using Jellyfin.Database.Implementations.Enums;
using Jellyfin.Extensions;
using MediaBrowser.Common.Api;
using MediaBrowser.Common.Configuration;
@@ -159,10 +160,10 @@ public class LiveTvController : BaseJellyfinApiController
[FromQuery] bool? isDisliked,
[FromQuery] bool? enableImages,
[FromQuery] int? imageTypeLimit,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields,
[FromQuery] bool? enableUserData,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemSortBy[] sortBy,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemSortBy[] sortBy,
[FromQuery] SortOrder? sortOrder,
[FromQuery] bool enableFavoriteSorting = false,
[FromQuery] bool addCurrentProgram = true)
@@ -283,8 +284,8 @@ public class LiveTvController : BaseJellyfinApiController
[FromQuery] string? seriesTimerId,
[FromQuery] bool? enableImages,
[FromQuery] int? imageTypeLimit,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields,
[FromQuery] bool? enableUserData,
[FromQuery] bool? isMovie,
[FromQuery] bool? isSeries,
@@ -371,8 +372,8 @@ public class LiveTvController : BaseJellyfinApiController
[FromQuery] string? seriesTimerId,
[FromQuery] bool? enableImages,
[FromQuery] int? imageTypeLimit,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields,
[FromQuery] bool? enableUserData,
[FromQuery] bool enableTotalRecordCount = true)
{
@@ -566,7 +567,7 @@ public class LiveTvController : BaseJellyfinApiController
[ProducesResponseType(StatusCodes.Status200OK)]
[Authorize(Policy = Policies.LiveTvAccess)]
public async Task<ActionResult<QueryResult<BaseItemDto>>> GetLiveTvPrograms(
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] channelIds,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] channelIds,
[FromQuery] Guid? userId,
[FromQuery] DateTime? minStartDate,
[FromQuery] bool? hasAired,
@@ -581,17 +582,17 @@ public class LiveTvController : BaseJellyfinApiController
[FromQuery] bool? isSports,
[FromQuery] int? startIndex,
[FromQuery] int? limit,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemSortBy[] sortBy,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SortOrder[] sortOrder,
- [FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] genres,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] genreIds,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemSortBy[] sortBy,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] SortOrder[] sortOrder,
+ [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] genres,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] genreIds,
[FromQuery] bool? enableImages,
[FromQuery] int? imageTypeLimit,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes,
[FromQuery] bool? enableUserData,
[FromQuery] string? seriesTimerId,
[FromQuery] Guid? librarySeriesId,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields,
[FromQuery] bool enableTotalRecordCount = true)
{
userId = RequestHelpers.GetUserId(User, userId);
@@ -698,6 +699,7 @@ public class LiveTvController : BaseJellyfinApiController
/// Gets recommended live tv epgs.
/// </summary>
/// <param name="userId">Optional. filter by user id.</param>
+ /// <param name="startIndex">Optional. The record index to start at. All items with a lower index will be dropped from the results.</param>
/// <param name="limit">Optional. The maximum number of records to return.</param>
/// <param name="isAiring">Optional. Filter by programs that are currently airing, or not.</param>
/// <param name="hasAired">Optional. Filter by programs that have completed airing, or not.</param>
@@ -720,6 +722,7 @@ public class LiveTvController : BaseJellyfinApiController
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<QueryResult<BaseItemDto>>> GetRecommendedPrograms(
[FromQuery] Guid? userId,
+ [FromQuery] int? startIndex,
[FromQuery] int? limit,
[FromQuery] bool? isAiring,
[FromQuery] bool? hasAired,
@@ -730,9 +733,9 @@ public class LiveTvController : BaseJellyfinApiController
[FromQuery] bool? isSports,
[FromQuery] bool? enableImages,
[FromQuery] int? imageTypeLimit,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] genreIds,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] genreIds,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields,
[FromQuery] bool? enableUserData,
[FromQuery] bool enableTotalRecordCount = true)
{
@@ -744,6 +747,7 @@ public class LiveTvController : BaseJellyfinApiController
var query = new InternalItemsQuery(user)
{
IsAiring = isAiring,
+ StartIndex = startIndex,
Limit = limit,
HasAired = hasAired,
IsSeries = isSeries,
@@ -1186,7 +1190,9 @@ public class LiveTvController : BaseJellyfinApiController
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesVideoFile]
- public ActionResult GetLiveStreamFile([FromRoute, Required] string streamId, [FromRoute, Required] string container)
+ public ActionResult GetLiveStreamFile(
+ [FromRoute, Required] string streamId,
+ [FromRoute, Required] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string container)
{
var liveStreamInfo = _mediaSourceManager.GetLiveStreamInfoByUniqueId(streamId);
if (liveStreamInfo is null)
diff --git a/Jellyfin.Api/Controllers/LocalizationController.cs b/Jellyfin.Api/Controllers/LocalizationController.cs
index f65d95c41..bbce5a9e1 100644
--- a/Jellyfin.Api/Controllers/LocalizationController.cs
+++ b/Jellyfin.Api/Controllers/LocalizationController.cs
@@ -1,5 +1,4 @@
using System.Collections.Generic;
-using Jellyfin.Api.Constants;
using MediaBrowser.Common.Api;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Globalization;
@@ -45,7 +44,7 @@ public class LocalizationController : BaseJellyfinApiController
/// <returns>An <see cref="OkResult"/> containing the list of countries.</returns>
[HttpGet("Countries")]
[ProducesResponseType(StatusCodes.Status200OK)]
- public ActionResult<IEnumerable<CountryInfo>> GetCountries()
+ public ActionResult<IReadOnlyList<CountryInfo>> GetCountries()
{
return Ok(_localization.GetCountries());
}
@@ -57,7 +56,7 @@ public class LocalizationController : BaseJellyfinApiController
/// <returns>An <see cref="OkResult"/> containing the list of parental ratings.</returns>
[HttpGet("ParentalRatings")]
[ProducesResponseType(StatusCodes.Status200OK)]
- public ActionResult<IEnumerable<ParentalRating>> GetParentalRatings()
+ public ActionResult<IReadOnlyList<ParentalRating>> GetParentalRatings()
{
return Ok(_localization.GetParentalRatings());
}
diff --git a/Jellyfin.Api/Controllers/MediaSegmentsController.cs b/Jellyfin.Api/Controllers/MediaSegmentsController.cs
index 2d1d4e2c8..b8836d7cf 100644
--- a/Jellyfin.Api/Controllers/MediaSegmentsController.cs
+++ b/Jellyfin.Api/Controllers/MediaSegmentsController.cs
@@ -4,10 +4,10 @@ using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
using Jellyfin.Api.Extensions;
-using Jellyfin.Data.Enums;
-using MediaBrowser.Controller;
+using Jellyfin.Database.Implementations.Enums;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.MediaSegments;
using MediaBrowser.Model.MediaSegments;
using MediaBrowser.Model.Querying;
using Microsoft.AspNetCore.Authorization;
@@ -55,7 +55,8 @@ public class MediaSegmentsController : BaseJellyfinApiController
return NotFound();
}
- var items = await _mediaSegmentManager.GetSegmentsAsync(item, includeSegmentTypes).ConfigureAwait(false);
+ var libraryOptions = _libraryManager.GetLibraryOptions(item);
+ var items = await _mediaSegmentManager.GetSegmentsAsync(item, includeSegmentTypes, libraryOptions).ConfigureAwait(false);
return Ok(new QueryResult<MediaSegmentDto>(items.ToArray()));
}
}
diff --git a/Jellyfin.Api/Controllers/MoviesController.cs b/Jellyfin.Api/Controllers/MoviesController.cs
index 471bcd096..363acf815 100644
--- a/Jellyfin.Api/Controllers/MoviesController.cs
+++ b/Jellyfin.Api/Controllers/MoviesController.cs
@@ -5,8 +5,9 @@ using System.Linq;
using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders;
-using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
+using Jellyfin.Database.Implementations.Entities;
+using Jellyfin.Database.Implementations.Enums;
using Jellyfin.Extensions;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Configuration;
@@ -65,7 +66,7 @@ public class MoviesController : BaseJellyfinApiController
public ActionResult<IEnumerable<RecommendationDto>> GetMovieRecommendations(
[FromQuery] Guid? userId,
[FromQuery] Guid? parentId,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields,
[FromQuery] int categoryLimit = 5,
[FromQuery] int itemLimit = 8)
{
@@ -120,7 +121,7 @@ public class MoviesController : BaseJellyfinApiController
DtoOptions = dtoOptions
});
- var mostRecentMovies = recentlyPlayedMovies.GetRange(0, Math.Min(recentlyPlayedMovies.Count, 6));
+ var mostRecentMovies = recentlyPlayedMovies.Take(Math.Min(recentlyPlayedMovies.Count, 6)).ToList();
// Get recently played directors
var recentDirectors = GetDirectors(mostRecentMovies)
.ToList();
@@ -276,7 +277,6 @@ public class MoviesController : BaseJellyfinApiController
Limit = itemLimit,
IncludeItemTypes = itemTypes.ToArray(),
IsMovie = true,
- SimilarTo = item,
EnableGroupByMetadataKey = true,
DtoOptions = dtoOptions
});
diff --git a/Jellyfin.Api/Controllers/MusicGenresController.cs b/Jellyfin.Api/Controllers/MusicGenresController.cs
index 5411baa3e..1e45e53ca 100644
--- a/Jellyfin.Api/Controllers/MusicGenresController.cs
+++ b/Jellyfin.Api/Controllers/MusicGenresController.cs
@@ -4,8 +4,9 @@ using System.Linq;
using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders;
-using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
+using Jellyfin.Database.Implementations.Entities;
+using Jellyfin.Database.Implementations.Enums;
using Jellyfin.Extensions;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
@@ -76,18 +77,18 @@ public class MusicGenresController : BaseJellyfinApiController
[FromQuery] int? limit,
[FromQuery] string? searchTerm,
[FromQuery] Guid? parentId,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] excludeItemTypes,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] excludeItemTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] includeItemTypes,
[FromQuery] bool? isFavorite,
[FromQuery] int? imageTypeLimit,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes,
[FromQuery] Guid? userId,
[FromQuery] string? nameStartsWithOrGreater,
[FromQuery] string? nameStartsWith,
[FromQuery] string? nameLessThan,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemSortBy[] sortBy,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SortOrder[] sortOrder,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemSortBy[] sortBy,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] SortOrder[] sortOrder,
[FromQuery] bool? enableImages = true,
[FromQuery] bool enableTotalRecordCount = true)
{
diff --git a/Jellyfin.Api/Controllers/PersonsController.cs b/Jellyfin.Api/Controllers/PersonsController.cs
index 6ca308601..4d12dc18f 100644
--- a/Jellyfin.Api/Controllers/PersonsController.cs
+++ b/Jellyfin.Api/Controllers/PersonsController.cs
@@ -4,7 +4,7 @@ using System.Linq;
using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders;
-using Jellyfin.Data.Entities;
+using Jellyfin.Database.Implementations.Entities;
using Jellyfin.Extensions;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
@@ -67,14 +67,14 @@ public class PersonsController : BaseJellyfinApiController
public ActionResult<QueryResult<BaseItemDto>> GetPersons(
[FromQuery] int? limit,
[FromQuery] string? searchTerm,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFilter[] filters,
[FromQuery] bool? isFavorite,
[FromQuery] bool? enableUserData,
[FromQuery] int? imageTypeLimit,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] excludePersonTypes,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] personTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] string[] excludePersonTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] string[] personTypes,
[FromQuery] Guid? appearsInItemId,
[FromQuery] Guid? userId,
[FromQuery] bool? enableImages = true)
diff --git a/Jellyfin.Api/Controllers/PlaylistsController.cs b/Jellyfin.Api/Controllers/PlaylistsController.cs
index 1ab36ccc6..79c71d23a 100644
--- a/Jellyfin.Api/Controllers/PlaylistsController.cs
+++ b/Jellyfin.Api/Controllers/PlaylistsController.cs
@@ -76,7 +76,7 @@ public class PlaylistsController : BaseJellyfinApiController
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<PlaylistCreationResult>> CreatePlaylist(
[FromQuery, ParameterObsolete] string? name,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder)), ParameterObsolete] IReadOnlyList<Guid> ids,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder)), ParameterObsolete] IReadOnlyList<Guid> ids,
[FromQuery, ParameterObsolete] Guid? userId,
[FromQuery, ParameterObsolete] MediaType? mediaType,
[FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Allow)] CreatePlaylistDto? createPlaylistRequest)
@@ -370,7 +370,7 @@ public class PlaylistsController : BaseJellyfinApiController
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> AddItemToPlaylist(
[FromRoute, Required] Guid playlistId,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] ids,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] ids,
[FromQuery] Guid? userId)
{
userId = RequestHelpers.GetUserId(User, userId);
@@ -446,26 +446,45 @@ public class PlaylistsController : BaseJellyfinApiController
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> RemoveItemFromPlaylist(
[FromRoute, Required] string playlistId,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] entryIds)
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] string[] entryIds)
{
var callingUserId = User.GetUserId();
- var playlist = _playlistManager.GetPlaylistForUser(Guid.Parse(playlistId), callingUserId);
- if (playlist is null)
+ if (!callingUserId.IsEmpty())
{
- return NotFound("Playlist not found");
+ var playlist = _playlistManager.GetPlaylistForUser(Guid.Parse(playlistId), callingUserId);
+ if (playlist is null)
+ {
+ return NotFound("Playlist not found");
+ }
+
+ var isPermitted = playlist.OwnerUserId.Equals(callingUserId)
+ || playlist.Shares.Any(s => s.CanEdit && s.UserId.Equals(callingUserId));
+
+ if (!isPermitted)
+ {
+ return Forbid();
+ }
}
+ else
+ {
+ var isApiKey = User.GetIsApiKey();
- var isPermitted = playlist.OwnerUserId.Equals(callingUserId)
- || playlist.Shares.Any(s => s.CanEdit && s.UserId.Equals(callingUserId));
+ if (!isApiKey)
+ {
+ return Forbid();
+ }
+ }
- if (!isPermitted)
+ try
{
- return Forbid();
+ await _playlistManager.RemoveItemFromPlaylistAsync(playlistId, entryIds).ConfigureAwait(false);
+ return NoContent();
+ }
+ catch (ArgumentException)
+ {
+ return NotFound();
}
-
- await _playlistManager.RemoveItemFromPlaylistAsync(playlistId, entryIds).ConfigureAwait(false);
- return NoContent();
}
/// <summary>
@@ -493,11 +512,11 @@ public class PlaylistsController : BaseJellyfinApiController
[FromQuery] Guid? userId,
[FromQuery] int? startIndex,
[FromQuery] int? limit,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields,
[FromQuery] bool? enableImages,
[FromQuery] bool? enableUserData,
[FromQuery] int? imageTypeLimit,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes)
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes)
{
var callingUserId = userId ?? User.GetUserId();
var playlist = _playlistManager.GetPlaylistForUser(playlistId, callingUserId);
diff --git a/Jellyfin.Api/Controllers/PlaystateController.cs b/Jellyfin.Api/Controllers/PlaystateController.cs
index 88aa0178f..ade0906b3 100644
--- a/Jellyfin.Api/Controllers/PlaystateController.cs
+++ b/Jellyfin.Api/Controllers/PlaystateController.cs
@@ -5,7 +5,7 @@ using System.Threading.Tasks;
using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders;
-using Jellyfin.Data.Entities;
+using Jellyfin.Database.Implementations.Entities;
using Jellyfin.Extensions;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
@@ -72,7 +72,7 @@ public class PlaystateController : BaseJellyfinApiController
[HttpPost("UserPlayedItems/{itemId}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
- public async Task<ActionResult<UserItemDataDto>> MarkPlayedItem(
+ public async Task<ActionResult<UserItemDataDto?>> MarkPlayedItem(
[FromQuery] Guid? userId,
[FromRoute, Required] Guid itemId,
[FromQuery, ModelBinder(typeof(LegacyDateTimeModelBinder))] DateTime? datePlayed)
@@ -121,7 +121,7 @@ public class PlaystateController : BaseJellyfinApiController
[ProducesResponseType(StatusCodes.Status404NotFound)]
[Obsolete("Kept for backwards compatibility")]
[ApiExplorerSettings(IgnoreApi = true)]
- public Task<ActionResult<UserItemDataDto>> MarkPlayedItemLegacy(
+ public Task<ActionResult<UserItemDataDto?>> MarkPlayedItemLegacy(
[FromRoute, Required] Guid userId,
[FromRoute, Required] Guid itemId,
[FromQuery, ModelBinder(typeof(LegacyDateTimeModelBinder))] DateTime? datePlayed)
@@ -138,7 +138,7 @@ public class PlaystateController : BaseJellyfinApiController
[HttpDelete("UserPlayedItems/{itemId}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
- public async Task<ActionResult<UserItemDataDto>> MarkUnplayedItem(
+ public async Task<ActionResult<UserItemDataDto?>> MarkUnplayedItem(
[FromQuery] Guid? userId,
[FromRoute, Required] Guid itemId)
{
@@ -185,7 +185,7 @@ public class PlaystateController : BaseJellyfinApiController
[ProducesResponseType(StatusCodes.Status404NotFound)]
[Obsolete("Kept for backwards compatibility")]
[ApiExplorerSettings(IgnoreApi = true)]
- public Task<ActionResult<UserItemDataDto>> MarkUnplayedItemLegacy(
+ public Task<ActionResult<UserItemDataDto?>> MarkUnplayedItemLegacy(
[FromRoute, Required] Guid userId,
[FromRoute, Required] Guid itemId)
=> MarkUnplayedItem(userId, itemId);
@@ -272,6 +272,7 @@ public class PlaystateController : BaseJellyfinApiController
/// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpPost("PlayingItems/{itemId}")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
+ [Obsolete("This endpoint is obsolete. Use ReportPlaybackStart instead")]
public async Task<ActionResult> OnPlaybackStart(
[FromRoute, Required] Guid itemId,
[FromQuery] string? mediaSourceId,
@@ -350,6 +351,7 @@ public class PlaystateController : BaseJellyfinApiController
/// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpPost("PlayingItems/{itemId}/Progress")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
+ [Obsolete("This endpoint is obsolete. Use ReportPlaybackProgress instead")]
public async Task<ActionResult> OnPlaybackProgress(
[FromRoute, Required] Guid itemId,
[FromQuery] string? mediaSourceId,
@@ -438,6 +440,7 @@ public class PlaystateController : BaseJellyfinApiController
/// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpDelete("PlayingItems/{itemId}")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
+ [Obsolete("This endpoint is obsolete. Use ReportPlaybackStop instead")]
public async Task<ActionResult> OnPlaybackStopped(
[FromRoute, Required] Guid itemId,
[FromQuery] string? mediaSourceId,
@@ -502,7 +505,7 @@ public class PlaystateController : BaseJellyfinApiController
/// <param name="wasPlayed">if set to <c>true</c> [was played].</param>
/// <param name="datePlayed">The date played.</param>
/// <returns>Task.</returns>
- private UserItemDataDto UpdatePlayedStatus(User user, BaseItem item, bool wasPlayed, DateTime? datePlayed)
+ private UserItemDataDto? UpdatePlayedStatus(User user, BaseItem item, bool wasPlayed, DateTime? datePlayed)
{
if (wasPlayed)
{
diff --git a/Jellyfin.Api/Controllers/SearchController.cs b/Jellyfin.Api/Controllers/SearchController.cs
index 8bae6fb9b..ecf2335ba 100644
--- a/Jellyfin.Api/Controllers/SearchController.cs
+++ b/Jellyfin.Api/Controllers/SearchController.cs
@@ -84,9 +84,9 @@ public class SearchController : BaseJellyfinApiController
[FromQuery] int? limit,
[FromQuery] Guid? userId,
[FromQuery, Required] string searchTerm,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] excludeItemTypes,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] MediaType[] mediaTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] includeItemTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] excludeItemTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] MediaType[] mediaTypes,
[FromQuery] Guid? parentId,
[FromQuery] bool? isMovie,
[FromQuery] bool? isSeries,
diff --git a/Jellyfin.Api/Controllers/SessionController.cs b/Jellyfin.Api/Controllers/SessionController.cs
index 2f9e9f091..9886d03de 100644
--- a/Jellyfin.Api/Controllers/SessionController.cs
+++ b/Jellyfin.Api/Controllers/SessionController.cs
@@ -122,7 +122,7 @@ public class SessionController : BaseJellyfinApiController
public async Task<ActionResult> Play(
[FromRoute, Required] string sessionId,
[FromQuery, Required] PlayCommand playCommand,
- [FromQuery, Required, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] itemIds,
+ [FromQuery, Required, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] itemIds,
[FromQuery] long? startPositionTicks,
[FromQuery] string? mediaSourceId,
[FromQuery] int? audioStreamIndex,
@@ -347,8 +347,8 @@ public class SessionController : BaseJellyfinApiController
[ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task<ActionResult> PostCapabilities(
[FromQuery] string? id,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] MediaType[] playableMediaTypes,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] GeneralCommandType[] supportedCommands,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] MediaType[] playableMediaTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] GeneralCommandType[] supportedCommands,
[FromQuery] bool supportsMediaControl = false,
[FromQuery] bool supportsPersistentIdentifier = true)
{
diff --git a/Jellyfin.Api/Controllers/StartupController.cs b/Jellyfin.Api/Controllers/StartupController.cs
index 41b0858d1..09f20558f 100644
--- a/Jellyfin.Api/Controllers/StartupController.cs
+++ b/Jellyfin.Api/Controllers/StartupController.cs
@@ -58,6 +58,7 @@ public class StartupController : BaseJellyfinApiController
{
return new StartupConfigurationDto
{
+ ServerName = _config.Configuration.ServerName,
UICulture = _config.Configuration.UICulture,
MetadataCountryCode = _config.Configuration.MetadataCountryCode,
PreferredMetadataLanguage = _config.Configuration.PreferredMetadataLanguage
@@ -74,6 +75,7 @@ public class StartupController : BaseJellyfinApiController
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult UpdateInitialConfiguration([FromBody, Required] StartupConfigurationDto startupConfiguration)
{
+ _config.Configuration.ServerName = startupConfiguration.ServerName ?? string.Empty;
_config.Configuration.UICulture = startupConfiguration.UICulture ?? string.Empty;
_config.Configuration.MetadataCountryCode = startupConfiguration.MetadataCountryCode ?? string.Empty;
_config.Configuration.PreferredMetadataLanguage = startupConfiguration.PreferredMetadataLanguage ?? string.Empty;
@@ -93,7 +95,6 @@ public class StartupController : BaseJellyfinApiController
{
NetworkConfiguration settings = _config.GetNetworkConfiguration();
settings.EnableRemoteAccess = startupRemoteAccessDto.EnableRemoteAccess;
- settings.EnableUPnP = startupRemoteAccessDto.EnableAutomaticPortMapping;
_config.SaveConfiguration(NetworkConfigurationStore.StoreKey, settings);
return NoContent();
}
@@ -113,8 +114,7 @@ public class StartupController : BaseJellyfinApiController
var user = _userManager.Users.First();
return new StartupUserDto
{
- Name = user.Username,
- Password = user.Password
+ Name = user.Username
};
}
diff --git a/Jellyfin.Api/Controllers/StudiosController.cs b/Jellyfin.Api/Controllers/StudiosController.cs
index 708fc7436..52cb87e72 100644
--- a/Jellyfin.Api/Controllers/StudiosController.cs
+++ b/Jellyfin.Api/Controllers/StudiosController.cs
@@ -3,8 +3,8 @@ using System.ComponentModel.DataAnnotations;
using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders;
-using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
+using Jellyfin.Database.Implementations.Entities;
using Jellyfin.Extensions;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
@@ -73,13 +73,13 @@ public class StudiosController : BaseJellyfinApiController
[FromQuery] int? limit,
[FromQuery] string? searchTerm,
[FromQuery] Guid? parentId,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] excludeItemTypes,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] excludeItemTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] includeItemTypes,
[FromQuery] bool? isFavorite,
[FromQuery] bool? enableUserData,
[FromQuery] int? imageTypeLimit,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes,
[FromQuery] Guid? userId,
[FromQuery] string? nameStartsWithOrGreater,
[FromQuery] string? nameStartsWith,
diff --git a/Jellyfin.Api/Controllers/SubtitleController.cs b/Jellyfin.Api/Controllers/SubtitleController.cs
index 9da1dce93..e5df873f5 100644
--- a/Jellyfin.Api/Controllers/SubtitleController.cs
+++ b/Jellyfin.Api/Controllers/SubtitleController.cs
@@ -395,7 +395,7 @@ public class SubtitleController : BaseJellyfinApiController
var url = string.Format(
CultureInfo.InvariantCulture,
- "stream.vtt?CopyTimestamps=true&AddVttTimeMap=true&StartPositionTicks={0}&EndPositionTicks={1}&api_key={2}",
+ "stream.vtt?CopyTimestamps=true&AddVttTimeMap=true&StartPositionTicks={0}&EndPositionTicks={1}&ApiKey={2}",
positionTicks.ToString(CultureInfo.InvariantCulture),
endPositionTicks.ToString(CultureInfo.InvariantCulture),
accessToken);
diff --git a/Jellyfin.Api/Controllers/SuggestionsController.cs b/Jellyfin.Api/Controllers/SuggestionsController.cs
index ad625cc6e..52982c362 100644
--- a/Jellyfin.Api/Controllers/SuggestionsController.cs
+++ b/Jellyfin.Api/Controllers/SuggestionsController.cs
@@ -3,8 +3,9 @@ using System.ComponentModel.DataAnnotations;
using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders;
-using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
+using Jellyfin.Database.Implementations.Entities;
+using Jellyfin.Database.Implementations.Enums;
using Jellyfin.Extensions;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
@@ -59,8 +60,8 @@ public class SuggestionsController : BaseJellyfinApiController
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QueryResult<BaseItemDto>> GetSuggestions(
[FromQuery] Guid? userId,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] MediaType[] mediaType,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] type,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] MediaType[] mediaType,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] type,
[FromQuery] int? startIndex,
[FromQuery] int? limit,
[FromQuery] bool enableTotalRecordCount = false)
@@ -115,8 +116,8 @@ public class SuggestionsController : BaseJellyfinApiController
[ApiExplorerSettings(IgnoreApi = true)]
public ActionResult<QueryResult<BaseItemDto>> GetSuggestionsLegacy(
[FromRoute, Required] Guid userId,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] MediaType[] mediaType,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] type,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] MediaType[] mediaType,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] type,
[FromQuery] int? startIndex,
[FromQuery] int? limit,
[FromQuery] bool enableTotalRecordCount = false)
diff --git a/Jellyfin.Api/Controllers/SyncPlayController.cs b/Jellyfin.Api/Controllers/SyncPlayController.cs
index 383978197..3d6874079 100644
--- a/Jellyfin.Api/Controllers/SyncPlayController.cs
+++ b/Jellyfin.Api/Controllers/SyncPlayController.cs
@@ -1,9 +1,9 @@
+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.Helpers;
using Jellyfin.Api.Models.SyncPlayDtos;
using MediaBrowser.Common.Api;
@@ -50,17 +50,16 @@ public class SyncPlayController : BaseJellyfinApiController
/// </summary>
/// <param name="requestData">The settings of the new group.</param>
/// <response code="204">New group created.</response>
- /// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
+ /// <returns>An <see cref="GroupInfoDto"/> for the created group.</returns>
[HttpPost("New")]
- [ProducesResponseType(StatusCodes.Status204NoContent)]
+ [ProducesResponseType(StatusCodes.Status200OK)]
[Authorize(Policy = Policies.SyncPlayCreateGroup)]
- public async Task<ActionResult> SyncPlayCreateGroup(
+ public async Task<ActionResult<GroupInfoDto>> SyncPlayCreateGroup(
[FromBody, Required] NewGroupRequestDto requestData)
{
var currentSession = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false);
var syncPlayRequest = new NewGroupRequest(requestData.GroupName);
- _syncPlayManager.NewGroup(currentSession, syncPlayRequest, CancellationToken.None);
- return NoContent();
+ return Ok(_syncPlayManager.NewGroup(currentSession, syncPlayRequest, CancellationToken.None));
}
/// <summary>
@@ -113,6 +112,23 @@ public class SyncPlayController : BaseJellyfinApiController
}
/// <summary>
+ /// Gets a SyncPlay group by id.
+ /// </summary>
+ /// <param name="id">The id of the group.</param>
+ /// <response code="200">Group returned.</response>
+ /// <returns>An <see cref="GroupInfoDto"/> for the requested group.</returns>
+ [HttpGet("{id:guid}")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ [Authorize(Policy = Policies.SyncPlayJoinGroup)]
+ public async Task<ActionResult<GroupInfoDto>> SyncPlayGetGroup([FromRoute] Guid id)
+ {
+ var currentSession = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false);
+ var group = _syncPlayManager.GetGroup(currentSession, id);
+ return group is null ? NotFound() : Ok(group);
+ }
+
+ /// <summary>
/// Request to set new playlist in SyncPlay group.
/// </summary>
/// <param name="requestData">The new playlist to play in the group.</param>
diff --git a/Jellyfin.Api/Controllers/SystemController.cs b/Jellyfin.Api/Controllers/SystemController.cs
index 6c5ce4715..450225c37 100644
--- a/Jellyfin.Api/Controllers/SystemController.cs
+++ b/Jellyfin.Api/Controllers/SystemController.cs
@@ -5,7 +5,7 @@ using System.IO;
using System.Linq;
using System.Net.Mime;
using Jellyfin.Api.Attributes;
-using Jellyfin.Api.Constants;
+using Jellyfin.Api.Models.SystemInfoDtos;
using MediaBrowser.Common.Api;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
@@ -72,6 +72,19 @@ public class SystemController : BaseJellyfinApiController
=> _systemManager.GetSystemInfo(Request);
/// <summary>
+ /// Gets information about the server.
+ /// </summary>
+ /// <response code="200">Information retrieved.</response>
+ /// <response code="403">User does not have permission to retrieve information.</response>
+ /// <returns>A <see cref="SystemInfo"/> with info about the system.</returns>
+ [HttpGet("Info/Storage")]
+ [Authorize(Policy = Policies.RequiresElevation)]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status403Forbidden)]
+ public ActionResult<SystemStorageDto> GetSystemStorage()
+ => Ok(SystemStorageDto.FromSystemStorageInfo(_systemManager.GetSystemStorageInfo()));
+
+ /// <summary>
/// Gets public information about the server.
/// </summary>
/// <response code="200">Information retrieved.</response>
@@ -212,20 +225,4 @@ public class SystemController : BaseJellyfinApiController
FileStream stream = new FileStream(file.FullName, FileMode.Open, FileAccess.Read, fileShare, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous);
return File(stream, "text/plain; charset=utf-8");
}
-
- /// <summary>
- /// Gets wake on lan information.
- /// </summary>
- /// <response code="200">Information retrieved.</response>
- /// <returns>An <see cref="IEnumerable{WakeOnLanInfo}"/> with the WakeOnLan infos.</returns>
- [HttpGet("WakeOnLanInfo")]
- [Authorize]
- [Obsolete("This endpoint is obsolete.")]
- [ProducesResponseType(StatusCodes.Status200OK)]
- public ActionResult<IEnumerable<WakeOnLanInfo>> GetWakeOnLanInfo()
- {
- var result = _networkManager.GetMacAddresses()
- .Select(i => new WakeOnLanInfo(i));
- return Ok(result);
- }
}
diff --git a/Jellyfin.Api/Controllers/TrailersController.cs b/Jellyfin.Api/Controllers/TrailersController.cs
index d7d0cc454..3e4bac89a 100644
--- a/Jellyfin.Api/Controllers/TrailersController.cs
+++ b/Jellyfin.Api/Controllers/TrailersController.cs
@@ -1,6 +1,7 @@
using System;
using Jellyfin.Api.ModelBinders;
using Jellyfin.Data.Enums;
+using Jellyfin.Database.Implementations.Enums;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Querying;
@@ -130,8 +131,8 @@ public class TrailersController : BaseJellyfinApiController
[FromQuery] bool? hasParentalRating,
[FromQuery] bool? isHd,
[FromQuery] bool? is4K,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] LocationType[] locationTypes,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] LocationType[] excludeLocationTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] LocationType[] locationTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] LocationType[] excludeLocationTypes,
[FromQuery] bool? isMissing,
[FromQuery] bool? isUnaired,
[FromQuery] double? minCommunityRating,
@@ -149,41 +150,41 @@ public class TrailersController : BaseJellyfinApiController
[FromQuery] bool? isNews,
[FromQuery] bool? isKids,
[FromQuery] bool? isSports,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] excludeItemIds,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] excludeItemIds,
[FromQuery] int? startIndex,
[FromQuery] int? limit,
[FromQuery] bool? recursive,
[FromQuery] string? searchTerm,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SortOrder[] sortOrder,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] SortOrder[] sortOrder,
[FromQuery] Guid? parentId,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] excludeItemTypes,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] excludeItemTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFilter[] filters,
[FromQuery] bool? isFavorite,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] MediaType[] mediaTypes,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] imageTypes,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemSortBy[] sortBy,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] MediaType[] mediaTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] imageTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemSortBy[] sortBy,
[FromQuery] bool? isPlayed,
- [FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] genres,
- [FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] officialRatings,
- [FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] tags,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] int[] years,
+ [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] genres,
+ [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] officialRatings,
+ [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] tags,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] int[] years,
[FromQuery] bool? enableUserData,
[FromQuery] int? imageTypeLimit,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes,
[FromQuery] string? person,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] personIds,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] personTypes,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] studios,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] artists,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] excludeArtistIds,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] artistIds,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] albumArtistIds,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] contributingArtistIds,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] albums,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] albumIds,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] ids,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] VideoType[] videoTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] personIds,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] string[] personTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] string[] studios,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] string[] artists,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] excludeArtistIds,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] artistIds,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] albumArtistIds,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] contributingArtistIds,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] string[] albums,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] albumIds,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] ids,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] VideoType[] videoTypes,
[FromQuery] string? minOfficialRating,
[FromQuery] bool? isLocked,
[FromQuery] bool? isPlaceHolder,
@@ -194,12 +195,12 @@ public class TrailersController : BaseJellyfinApiController
[FromQuery] int? maxWidth,
[FromQuery] int? maxHeight,
[FromQuery] bool? is3D,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SeriesStatus[] seriesStatus,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] SeriesStatus[] seriesStatus,
[FromQuery] string? nameStartsWithOrGreater,
[FromQuery] string? nameStartsWith,
[FromQuery] string? nameLessThan,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] studioIds,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] genreIds,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] studioIds,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] genreIds,
[FromQuery] bool enableTotalRecordCount = true,
[FromQuery] bool? enableImages = true)
{
diff --git a/Jellyfin.Api/Controllers/TvShowsController.cs b/Jellyfin.Api/Controllers/TvShowsController.cs
index 914ccd7f9..0f08854d2 100644
--- a/Jellyfin.Api/Controllers/TvShowsController.cs
+++ b/Jellyfin.Api/Controllers/TvShowsController.cs
@@ -2,10 +2,12 @@ using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
+using Jellyfin.Api.Attributes;
using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders;
using Jellyfin.Data.Enums;
+using Jellyfin.Database.Implementations.Enums;
using Jellyfin.Extensions;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
@@ -77,16 +79,16 @@ public class TvShowsController : BaseJellyfinApiController
[FromQuery] Guid? userId,
[FromQuery] int? startIndex,
[FromQuery] int? limit,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields,
[FromQuery] Guid? seriesId,
[FromQuery] Guid? parentId,
[FromQuery] bool? enableImages,
[FromQuery] int? imageTypeLimit,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes,
[FromQuery] bool? enableUserData,
[FromQuery] DateTime? nextUpDateCutoff,
[FromQuery] bool enableTotalRecordCount = true,
- [FromQuery] bool disableFirstEpisode = false,
+ [FromQuery][ParameterObsolete] bool disableFirstEpisode = false,
[FromQuery] bool enableResumable = true,
[FromQuery] bool enableRewatching = false)
{
@@ -109,7 +111,6 @@ public class TvShowsController : BaseJellyfinApiController
StartIndex = startIndex,
User = user,
EnableTotalRecordCount = enableTotalRecordCount,
- DisableFirstEpisode = disableFirstEpisode,
NextUpDateCutoff = nextUpDateCutoff ?? DateTime.MinValue,
EnableResumable = enableResumable,
EnableRewatching = enableRewatching
@@ -143,11 +144,11 @@ public class TvShowsController : BaseJellyfinApiController
[FromQuery] Guid? userId,
[FromQuery] int? startIndex,
[FromQuery] int? limit,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields,
[FromQuery] Guid? parentId,
[FromQuery] bool? enableImages,
[FromQuery] int? imageTypeLimit,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes,
[FromQuery] bool? enableUserData)
{
userId = RequestHelpers.GetUserId(User, userId);
@@ -208,7 +209,7 @@ public class TvShowsController : BaseJellyfinApiController
public ActionResult<QueryResult<BaseItemDto>> GetEpisodes(
[FromRoute, Required] Guid seriesId,
[FromQuery] Guid? userId,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields,
[FromQuery] int? season,
[FromQuery] Guid? seasonId,
[FromQuery] bool? isMissing,
@@ -218,7 +219,7 @@ public class TvShowsController : BaseJellyfinApiController
[FromQuery] int? limit,
[FromQuery] bool? enableImages,
[FromQuery] int? imageTypeLimit,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes,
[FromQuery] bool? enableUserData,
[FromQuery] ItemSortBy? sortBy)
{
@@ -332,13 +333,13 @@ public class TvShowsController : BaseJellyfinApiController
public ActionResult<QueryResult<BaseItemDto>> GetSeasons(
[FromRoute, Required] Guid seriesId,
[FromQuery] Guid? userId,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields,
[FromQuery] bool? isSpecialSeason,
[FromQuery] bool? isMissing,
[FromQuery] Guid? adjacentTo,
[FromQuery] bool? enableImages,
[FromQuery] int? imageTypeLimit,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes,
[FromQuery] bool? enableUserData)
{
userId = RequestHelpers.GetUserId(User, userId);
diff --git a/Jellyfin.Api/Controllers/UniversalAudioController.cs b/Jellyfin.Api/Controllers/UniversalAudioController.cs
index 41c4886d4..fd6334703 100644
--- a/Jellyfin.Api/Controllers/UniversalAudioController.cs
+++ b/Jellyfin.Api/Controllers/UniversalAudioController.cs
@@ -98,17 +98,17 @@ public class UniversalAudioController : BaseJellyfinApiController
[ProducesAudioFile]
public async Task<ActionResult> GetUniversalAudioStream(
[FromRoute, Required] Guid itemId,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] container,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] string[] container,
[FromQuery] string? mediaSourceId,
[FromQuery] string? deviceId,
[FromQuery] Guid? userId,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? audioCodec,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? audioCodec,
[FromQuery] int? maxAudioChannels,
[FromQuery] int? transcodingAudioChannels,
[FromQuery] int? maxStreamingBitrate,
[FromQuery] int? audioBitRate,
[FromQuery] long? startTimeTicks,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? transcodingContainer,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? transcodingContainer,
[FromQuery] MediaStreamProtocol? transcodingProtocol,
[FromQuery] int? maxAudioSampleRate,
[FromQuery] int? maxAudioBitDepth,
@@ -222,7 +222,7 @@ public class UniversalAudioController : BaseJellyfinApiController
TranscodeReasons = mediaSource.TranscodeReasons == 0 ? null : mediaSource.TranscodeReasons.ToString(),
Context = EncodingContext.Static,
StreamOptions = new Dictionary<string, string>(),
- EnableAdaptiveBitrateStreaming = true,
+ EnableAdaptiveBitrateStreaming = false,
EnableAudioVbrEncoding = enableAudioVbrEncoding
};
diff --git a/Jellyfin.Api/Controllers/UserController.cs b/Jellyfin.Api/Controllers/UserController.cs
index d7886d247..d0ced277a 100644
--- a/Jellyfin.Api/Controllers/UserController.cs
+++ b/Jellyfin.Api/Controllers/UserController.cs
@@ -7,7 +7,8 @@ using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers;
using Jellyfin.Api.Models.UserDtos;
-using Jellyfin.Data.Enums;
+using Jellyfin.Data;
+using Jellyfin.Database.Implementations.Enums;
using Jellyfin.Extensions;
using MediaBrowser.Common.Api;
using MediaBrowser.Common.Extensions;
diff --git a/Jellyfin.Api/Controllers/UserLibraryController.cs b/Jellyfin.Api/Controllers/UserLibraryController.cs
index e7bf71727..0e04beb14 100644
--- a/Jellyfin.Api/Controllers/UserLibraryController.cs
+++ b/Jellyfin.Api/Controllers/UserLibraryController.cs
@@ -7,8 +7,8 @@ using System.Threading.Tasks;
using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders;
-using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
+using Jellyfin.Database.Implementations.Entities;
using Jellyfin.Extensions;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
@@ -305,7 +305,7 @@ public class UserLibraryController : BaseJellyfinApiController
/// <returns>An <see cref="OkResult"/> containing the <see cref="UserItemDataDto"/>.</returns>
[HttpDelete("UserItems/{itemId}/Rating")]
[ProducesResponseType(StatusCodes.Status200OK)]
- public ActionResult<UserItemDataDto> DeleteUserItemRating(
+ public ActionResult<UserItemDataDto?> DeleteUserItemRating(
[FromQuery] Guid? userId,
[FromRoute, Required] Guid itemId)
{
@@ -338,7 +338,7 @@ public class UserLibraryController : BaseJellyfinApiController
[ProducesResponseType(StatusCodes.Status200OK)]
[Obsolete("Kept for backwards compatibility")]
[ApiExplorerSettings(IgnoreApi = true)]
- public ActionResult<UserItemDataDto> DeleteUserItemRatingLegacy(
+ public ActionResult<UserItemDataDto?> DeleteUserItemRatingLegacy(
[FromRoute, Required] Guid userId,
[FromRoute, Required] Guid itemId)
=> DeleteUserItemRating(userId, itemId);
@@ -353,7 +353,7 @@ public class UserLibraryController : BaseJellyfinApiController
/// <returns>An <see cref="OkResult"/> containing the <see cref="UserItemDataDto"/>.</returns>
[HttpPost("UserItems/{itemId}/Rating")]
[ProducesResponseType(StatusCodes.Status200OK)]
- public ActionResult<UserItemDataDto> UpdateUserItemRating(
+ public ActionResult<UserItemDataDto?> UpdateUserItemRating(
[FromQuery] Guid? userId,
[FromRoute, Required] Guid itemId,
[FromQuery] bool? likes)
@@ -388,7 +388,7 @@ public class UserLibraryController : BaseJellyfinApiController
[ProducesResponseType(StatusCodes.Status200OK)]
[Obsolete("Kept for backwards compatibility")]
[ApiExplorerSettings(IgnoreApi = true)]
- public ActionResult<UserItemDataDto> UpdateUserItemRatingLegacy(
+ public ActionResult<UserItemDataDto?> UpdateUserItemRatingLegacy(
[FromRoute, Required] Guid userId,
[FromRoute, Required] Guid itemId,
[FromQuery] bool? likes)
@@ -523,12 +523,12 @@ public class UserLibraryController : BaseJellyfinApiController
public ActionResult<IEnumerable<BaseItemDto>> GetLatestMedia(
[FromQuery] Guid? userId,
[FromQuery] Guid? parentId,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] includeItemTypes,
[FromQuery] bool? isPlayed,
[FromQuery] bool? enableImages,
[FromQuery] int? imageTypeLimit,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes,
[FromQuery] bool? enableUserData,
[FromQuery] int limit = 20,
[FromQuery] bool groupItems = true)
@@ -608,12 +608,12 @@ public class UserLibraryController : BaseJellyfinApiController
public ActionResult<IEnumerable<BaseItemDto>> GetLatestMediaLegacy(
[FromRoute, Required] Guid userId,
[FromQuery] Guid? parentId,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] includeItemTypes,
[FromQuery] bool? isPlayed,
[FromQuery] bool? enableImages,
[FromQuery] int? imageTypeLimit,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes,
[FromQuery] bool? enableUserData,
[FromQuery] int limit = 20,
[FromQuery] bool groupItems = true)
@@ -634,10 +634,10 @@ public class UserLibraryController : BaseJellyfinApiController
{
if (item is Person)
{
- var hasMetdata = !string.IsNullOrWhiteSpace(item.Overview) && item.HasImage(ImageType.Primary);
- var performFullRefresh = !hasMetdata && (DateTime.UtcNow - item.DateLastRefreshed).TotalDays >= 3;
+ var hasMetadata = !string.IsNullOrWhiteSpace(item.Overview) && item.HasImage(ImageType.Primary);
+ var performFullRefresh = !hasMetadata && (DateTime.UtcNow - item.DateLastRefreshed).TotalDays >= 3;
- if (!hasMetdata)
+ if (!hasMetadata)
{
var options = new MetadataRefreshOptions(new DirectoryService(_fileSystem))
{
@@ -662,12 +662,15 @@ public class UserLibraryController : BaseJellyfinApiController
// Get the user data for this item
var data = _userDataRepository.GetUserData(user, item);
- // Set favorite status
- data.IsFavorite = isFavorite;
+ if (data is not null)
+ {
+ // Set favorite status
+ data.IsFavorite = isFavorite;
- _userDataRepository.SaveUserData(user, item, data, UserDataSaveReason.UpdateUserRating, CancellationToken.None);
+ _userDataRepository.SaveUserData(user, item, data, UserDataSaveReason.UpdateUserRating, CancellationToken.None);
+ }
- return _userDataRepository.GetUserDataDto(item, user);
+ return _userDataRepository.GetUserDataDto(item, user)!;
}
/// <summary>
@@ -676,14 +679,17 @@ public class UserLibraryController : BaseJellyfinApiController
/// <param name="user">The user.</param>
/// <param name="item">The item.</param>
/// <param name="likes">if set to <c>true</c> [likes].</param>
- private UserItemDataDto UpdateUserItemRatingInternal(User user, BaseItem item, bool? likes)
+ private UserItemDataDto? UpdateUserItemRatingInternal(User user, BaseItem item, bool? likes)
{
// Get the user data for this item
var data = _userDataRepository.GetUserData(user, item);
- data.Likes = likes;
+ if (data is not null)
+ {
+ data.Likes = likes;
- _userDataRepository.SaveUserData(user, item, data, UserDataSaveReason.UpdateUserRating, CancellationToken.None);
+ _userDataRepository.SaveUserData(user, item, data, UserDataSaveReason.UpdateUserRating, CancellationToken.None);
+ }
return _userDataRepository.GetUserDataDto(item, user);
}
diff --git a/Jellyfin.Api/Controllers/UserViewsController.cs b/Jellyfin.Api/Controllers/UserViewsController.cs
index e24f78a88..64b2dffb3 100644
--- a/Jellyfin.Api/Controllers/UserViewsController.cs
+++ b/Jellyfin.Api/Controllers/UserViewsController.cs
@@ -66,7 +66,7 @@ public class UserViewsController : BaseJellyfinApiController
public QueryResult<BaseItemDto> GetUserViews(
[FromQuery] Guid? userId,
[FromQuery] bool? includeExternalContent,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] CollectionType?[] presetViews,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] CollectionType?[] presetViews,
[FromQuery] bool includeHidden = false)
{
userId = RequestHelpers.GetUserId(User, userId);
@@ -110,7 +110,7 @@ public class UserViewsController : BaseJellyfinApiController
public QueryResult<BaseItemDto> GetUserViewsLegacy(
[FromRoute, Required] Guid userId,
[FromQuery] bool? includeExternalContent,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] CollectionType?[] presetViews,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] CollectionType?[] presetViews,
[FromQuery] bool includeHidden = false)
=> GetUserViews(userId, includeExternalContent, presetViews, includeHidden);
diff --git a/Jellyfin.Api/Controllers/VideosController.cs b/Jellyfin.Api/Controllers/VideosController.cs
index 8348fd937..97f3239bb 100644
--- a/Jellyfin.Api/Controllers/VideosController.cs
+++ b/Jellyfin.Api/Controllers/VideosController.cs
@@ -184,7 +184,7 @@ public class VideosController : BaseJellyfinApiController
[Authorize(Policy = Policies.RequiresElevation)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
- public async Task<ActionResult> MergeVersions([FromQuery, Required, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] ids)
+ public async Task<ActionResult> MergeVersions([FromQuery, Required, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] ids)
{
var userId = User.GetUserId();
var items = ids
@@ -315,18 +315,18 @@ public class VideosController : BaseJellyfinApiController
[ProducesVideoFile]
public async Task<ActionResult> GetVideoStream(
[FromRoute, Required] Guid itemId,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? container,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? container,
[FromQuery] bool? @static,
[FromQuery] string? @params,
[FromQuery] string? tag,
[FromQuery, ParameterObsolete] string? deviceProfileId,
[FromQuery] string? playSessionId,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? segmentContainer,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? segmentContainer,
[FromQuery] int? segmentLength,
[FromQuery] int? minSegments,
[FromQuery] string? mediaSourceId,
[FromQuery] string? deviceId,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? audioCodec,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? audioCodec,
[FromQuery] bool? enableAutoStreamCopy,
[FromQuery] bool? allowVideoStreamCopy,
[FromQuery] bool? allowAudioStreamCopy,
@@ -337,7 +337,7 @@ public class VideosController : BaseJellyfinApiController
[FromQuery] int? audioChannels,
[FromQuery] int? maxAudioChannels,
[FromQuery] string? profile,
- [FromQuery] string? level,
+ [FromQuery] [RegularExpression(EncodingHelper.LevelValidationRegex)] string? level,
[FromQuery] float? framerate,
[FromQuery] float? maxFramerate,
[FromQuery] bool? copyTimestamps,
@@ -358,8 +358,8 @@ public class VideosController : BaseJellyfinApiController
[FromQuery] int? cpuCoreLimit,
[FromQuery] string? liveStreamId,
[FromQuery] bool? enableMpegtsM2TsMode,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? videoCodec,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? subtitleCodec,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? videoCodec,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? subtitleCodec,
[FromQuery] string? transcodeReasons,
[FromQuery] int? audioStreamIndex,
[FromQuery] int? videoStreamIndex,
@@ -556,18 +556,18 @@ public class VideosController : BaseJellyfinApiController
[ProducesVideoFile]
public Task<ActionResult> GetVideoStreamByContainer(
[FromRoute, Required] Guid itemId,
- [FromRoute, Required] string container,
+ [FromRoute, Required] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string container,
[FromQuery] bool? @static,
[FromQuery] string? @params,
[FromQuery] string? tag,
[FromQuery] string? deviceProfileId,
[FromQuery] string? playSessionId,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? segmentContainer,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? segmentContainer,
[FromQuery] int? segmentLength,
[FromQuery] int? minSegments,
[FromQuery] string? mediaSourceId,
[FromQuery] string? deviceId,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? audioCodec,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? audioCodec,
[FromQuery] bool? enableAutoStreamCopy,
[FromQuery] bool? allowVideoStreamCopy,
[FromQuery] bool? allowAudioStreamCopy,
@@ -578,7 +578,7 @@ public class VideosController : BaseJellyfinApiController
[FromQuery] int? audioChannels,
[FromQuery] int? maxAudioChannels,
[FromQuery] string? profile,
- [FromQuery] string? level,
+ [FromQuery] [RegularExpression(EncodingHelper.LevelValidationRegex)] string? level,
[FromQuery] float? framerate,
[FromQuery] float? maxFramerate,
[FromQuery] bool? copyTimestamps,
@@ -599,8 +599,8 @@ public class VideosController : BaseJellyfinApiController
[FromQuery] int? cpuCoreLimit,
[FromQuery] string? liveStreamId,
[FromQuery] bool? enableMpegtsM2TsMode,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? videoCodec,
- [FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? subtitleCodec,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? videoCodec,
+ [FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? subtitleCodec,
[FromQuery] string? transcodeReasons,
[FromQuery] int? audioStreamIndex,
[FromQuery] int? videoStreamIndex,
diff --git a/Jellyfin.Api/Controllers/YearsController.cs b/Jellyfin.Api/Controllers/YearsController.cs
index e4aa0ea42..b60258586 100644
--- a/Jellyfin.Api/Controllers/YearsController.cs
+++ b/Jellyfin.Api/Controllers/YearsController.cs
@@ -1,12 +1,14 @@
using System;
using System.Collections.Generic;
+using System.Collections.Immutable;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders;
-using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
+using Jellyfin.Database.Implementations.Entities;
+using Jellyfin.Database.Implementations.Enums;
using Jellyfin.Extensions;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
@@ -71,16 +73,16 @@ public class YearsController : BaseJellyfinApiController
public ActionResult<QueryResult<BaseItemDto>> GetYears(
[FromQuery] int? startIndex,
[FromQuery] int? limit,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SortOrder[] sortOrder,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] SortOrder[] sortOrder,
[FromQuery] Guid? parentId,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] excludeItemTypes,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] MediaType[] mediaTypes,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemSortBy[] sortBy,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] excludeItemTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] includeItemTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] MediaType[] mediaTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemSortBy[] sortBy,
[FromQuery] bool? enableUserData,
[FromQuery] int? imageTypeLimit,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes,
[FromQuery] Guid? userId,
[FromQuery] bool recursive = true,
[FromQuery] bool? enableImages = true)
@@ -105,18 +107,18 @@ public class YearsController : BaseJellyfinApiController
bool Filter(BaseItem i) => FilterItem(i, excludeItemTypes, includeItemTypes, mediaTypes);
- IList<BaseItem> items;
+ IReadOnlyList<BaseItem> items;
if (parentItem.IsFolder)
{
var folder = (Folder)parentItem;
if (userId.IsNullOrEmpty())
{
- items = recursive ? folder.GetRecursiveChildren(Filter) : folder.Children.Where(Filter).ToList();
+ items = recursive ? folder.GetRecursiveChildren(Filter) : folder.Children.Where(Filter).ToArray();
}
else
{
- items = recursive ? folder.GetRecursiveChildren(user, query).ToList() : folder.GetChildren(user, true).Where(Filter).ToList();
+ items = recursive ? folder.GetRecursiveChildren(user, query) : folder.GetChildren(user, true).Where(Filter).ToArray();
}
}
else
@@ -221,6 +223,6 @@ public class YearsController : BaseJellyfinApiController
.Select(i => i.ProductionYear ?? 0)
.Where(i => i > 0)
.Distinct()
- .Select(year => _libraryManager.GetYear(year));
+ .Select(_libraryManager.GetYear);
}
}