aboutsummaryrefslogtreecommitdiff
path: root/Jellyfin.Api/Controllers
diff options
context:
space:
mode:
Diffstat (limited to 'Jellyfin.Api/Controllers')
-rw-r--r--Jellyfin.Api/Controllers/AlbumsController.cs135
-rw-r--r--Jellyfin.Api/Controllers/ArtistsController.cs18
-rw-r--r--Jellyfin.Api/Controllers/ChannelsController.cs14
-rw-r--r--Jellyfin.Api/Controllers/DisplayPreferencesController.cs3
-rw-r--r--Jellyfin.Api/Controllers/DlnaServerController.cs26
-rw-r--r--Jellyfin.Api/Controllers/DynamicHlsController.cs7
-rw-r--r--Jellyfin.Api/Controllers/EnvironmentController.cs6
-rw-r--r--Jellyfin.Api/Controllers/FilterController.cs6
-rw-r--r--Jellyfin.Api/Controllers/GenresController.cs151
-rw-r--r--Jellyfin.Api/Controllers/HlsSegmentController.cs4
-rw-r--r--Jellyfin.Api/Controllers/ImageByNameController.cs2
-rw-r--r--Jellyfin.Api/Controllers/ImageController.cs3
-rw-r--r--Jellyfin.Api/Controllers/InstantMixController.cs64
-rw-r--r--Jellyfin.Api/Controllers/ItemLookupController.cs12
-rw-r--r--Jellyfin.Api/Controllers/ItemsController.cs16
-rw-r--r--Jellyfin.Api/Controllers/LibraryController.cs166
-rw-r--r--Jellyfin.Api/Controllers/LiveTvController.cs50
-rw-r--r--Jellyfin.Api/Controllers/MoviesController.cs6
-rw-r--r--Jellyfin.Api/Controllers/MusicGenresController.cs150
-rw-r--r--Jellyfin.Api/Controllers/PackageController.cs8
-rw-r--r--Jellyfin.Api/Controllers/PersonsController.cs196
-rw-r--r--Jellyfin.Api/Controllers/PlaylistsController.cs10
-rw-r--r--Jellyfin.Api/Controllers/RemoteImageController.cs20
-rw-r--r--Jellyfin.Api/Controllers/SearchController.cs2
-rw-r--r--Jellyfin.Api/Controllers/StudiosController.cs142
-rw-r--r--Jellyfin.Api/Controllers/SubtitleController.cs125
-rw-r--r--Jellyfin.Api/Controllers/TrailersController.cs6
-rw-r--r--Jellyfin.Api/Controllers/TvShowsController.cs33
-rw-r--r--Jellyfin.Api/Controllers/UserLibraryController.cs10
-rw-r--r--Jellyfin.Api/Controllers/VideoAttachmentsController.cs2
-rw-r--r--Jellyfin.Api/Controllers/VideoHlsController.cs4
-rw-r--r--Jellyfin.Api/Controllers/YearsController.cs10
32 files changed, 452 insertions, 955 deletions
diff --git a/Jellyfin.Api/Controllers/AlbumsController.cs b/Jellyfin.Api/Controllers/AlbumsController.cs
deleted file mode 100644
index 357f646a2..000000000
--- a/Jellyfin.Api/Controllers/AlbumsController.cs
+++ /dev/null
@@ -1,135 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.ComponentModel.DataAnnotations;
-using System.Linq;
-using Jellyfin.Api.Extensions;
-using Jellyfin.Api.Helpers;
-using MediaBrowser.Controller.Dto;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Entities.Audio;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Model.Dto;
-using MediaBrowser.Model.Querying;
-using Microsoft.AspNetCore.Http;
-using Microsoft.AspNetCore.Mvc;
-
-namespace Jellyfin.Api.Controllers
-{
- /// <summary>
- /// The albums controller.
- /// </summary>
- [Route("")]
- public class AlbumsController : BaseJellyfinApiController
- {
- private readonly IUserManager _userManager;
- private readonly ILibraryManager _libraryManager;
- private readonly IDtoService _dtoService;
-
- /// <summary>
- /// Initializes a new instance of the <see cref="AlbumsController"/> class.
- /// </summary>
- /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
- /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
- /// <param name="dtoService">Instance of the <see cref="IDtoService"/> interface.</param>
- public AlbumsController(
- IUserManager userManager,
- ILibraryManager libraryManager,
- IDtoService dtoService)
- {
- _userManager = userManager;
- _libraryManager = libraryManager;
- _dtoService = dtoService;
- }
-
- /// <summary>
- /// Finds albums similar to a given album.
- /// </summary>
- /// <param name="albumId">The album id.</param>
- /// <param name="userId">Optional. Filter by user id, and attach user data.</param>
- /// <param name="excludeArtistIds">Optional. Ids of artists to exclude.</param>
- /// <param name="limit">Optional. The maximum number of records to return.</param>
- /// <response code="200">Similar albums returned.</response>
- /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with similar albums.</returns>
- [HttpGet("Albums/{albumId}/Similar")]
- [ProducesResponseType(StatusCodes.Status200OK)]
- public ActionResult<QueryResult<BaseItemDto>> GetSimilarAlbums(
- [FromRoute, Required] string albumId,
- [FromQuery] Guid? userId,
- [FromQuery] string? excludeArtistIds,
- [FromQuery] int? limit)
- {
- var dtoOptions = new DtoOptions().AddClientFields(Request);
-
- return SimilarItemsHelper.GetSimilarItemsResult(
- dtoOptions,
- _userManager,
- _libraryManager,
- _dtoService,
- userId,
- albumId,
- excludeArtistIds,
- limit,
- new[] { typeof(MusicAlbum) },
- GetAlbumSimilarityScore);
- }
-
- /// <summary>
- /// Finds artists similar to a given artist.
- /// </summary>
- /// <param name="artistId">The artist id.</param>
- /// <param name="userId">Optional. Filter by user id, and attach user data.</param>
- /// <param name="excludeArtistIds">Optional. Ids of artists to exclude.</param>
- /// <param name="limit">Optional. The maximum number of records to return.</param>
- /// <response code="200">Similar artists returned.</response>
- /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with similar artists.</returns>
- [HttpGet("Artists/{artistId}/Similar")]
- [ProducesResponseType(StatusCodes.Status200OK)]
- public ActionResult<QueryResult<BaseItemDto>> GetSimilarArtists(
- [FromRoute, Required] string artistId,
- [FromQuery] Guid? userId,
- [FromQuery] string? excludeArtistIds,
- [FromQuery] int? limit)
- {
- var dtoOptions = new DtoOptions().AddClientFields(Request);
-
- return SimilarItemsHelper.GetSimilarItemsResult(
- dtoOptions,
- _userManager,
- _libraryManager,
- _dtoService,
- userId,
- artistId,
- excludeArtistIds,
- limit,
- new[] { typeof(MusicArtist) },
- SimilarItemsHelper.GetSimiliarityScore);
- }
-
- /// <summary>
- /// Gets a similairty score of two albums.
- /// </summary>
- /// <param name="item1">The first item.</param>
- /// <param name="item1People">The item1 people.</param>
- /// <param name="allPeople">All people.</param>
- /// <param name="item2">The second item.</param>
- /// <returns>System.Int32.</returns>
- private int GetAlbumSimilarityScore(BaseItem item1, List<PersonInfo> item1People, List<PersonInfo> allPeople, BaseItem item2)
- {
- var points = SimilarItemsHelper.GetSimiliarityScore(item1, item1People, allPeople, item2);
-
- var album1 = (MusicAlbum)item1;
- var album2 = (MusicAlbum)item2;
-
- var artists1 = album1
- .GetAllArtists()
- .DistinctNames()
- .ToList();
-
- var artists2 = new HashSet<string>(
- album2.GetAllArtists().DistinctNames(),
- StringComparer.OrdinalIgnoreCase);
-
- return points + artists1.Where(artists2.Contains).Sum(i => 5);
- }
- }
-}
diff --git a/Jellyfin.Api/Controllers/ArtistsController.cs b/Jellyfin.Api/Controllers/ArtistsController.cs
index 826fce6b0..9bad206e0 100644
--- a/Jellyfin.Api/Controllers/ArtistsController.cs
+++ b/Jellyfin.Api/Controllers/ArtistsController.cs
@@ -53,7 +53,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="limit">Optional. The maximum number of records to return.</param>
/// <param name="searchTerm">Optional. Search term.</param>
/// <param name="parentId">Specify this to localize the search to a specific item or folder. Omit to use the root.</param>
- /// <param name="fields">Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines.</param>
+ /// <param name="fields">Optional. Specify additional fields of information to return in the output.</param>
/// <param name="excludeItemTypes">Optional. If specified, results will be filtered out based on item type. This allows multiple, comma delimited.</param>
/// <param name="includeItemTypes">Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimited.</param>
/// <param name="filters">Optional. Specify additional filters to apply.</param>
@@ -88,7 +88,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? limit,
[FromQuery] string? searchTerm,
[FromQuery] string? parentId,
- [FromQuery] string? fields,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
[FromQuery] string? excludeItemTypes,
[FromQuery] string? includeItemTypes,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters,
@@ -101,7 +101,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] string? years,
[FromQuery] bool? enableUserData,
[FromQuery] int? imageTypeLimit,
- [FromQuery] ImageType[] enableImageTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes,
[FromQuery] string? person,
[FromQuery] string? personIds,
[FromQuery] string? personTypes,
@@ -114,8 +114,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] bool? enableImages = true,
[FromQuery] bool enableTotalRecordCount = true)
{
- var dtoOptions = new DtoOptions()
- .AddItemFields(fields)
+ var dtoOptions = new DtoOptions { Fields = fields }
.AddClientFields(Request)
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
@@ -262,7 +261,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="limit">Optional. The maximum number of records to return.</param>
/// <param name="searchTerm">Optional. Search term.</param>
/// <param name="parentId">Specify this to localize the search to a specific item or folder. Omit to use the root.</param>
- /// <param name="fields">Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines.</param>
+ /// <param name="fields">Optional. Specify additional fields of information to return in the output.</param>
/// <param name="excludeItemTypes">Optional. If specified, results will be filtered out based on item type. This allows multiple, comma delimited.</param>
/// <param name="includeItemTypes">Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimited.</param>
/// <param name="filters">Optional. Specify additional filters to apply.</param>
@@ -297,7 +296,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? limit,
[FromQuery] string? searchTerm,
[FromQuery] string? parentId,
- [FromQuery] string? fields,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
[FromQuery] string? excludeItemTypes,
[FromQuery] string? includeItemTypes,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters,
@@ -310,7 +309,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] string? years,
[FromQuery] bool? enableUserData,
[FromQuery] int? imageTypeLimit,
- [FromQuery] ImageType[] enableImageTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes,
[FromQuery] string? person,
[FromQuery] string? personIds,
[FromQuery] string? personTypes,
@@ -323,8 +322,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] bool? enableImages = true,
[FromQuery] bool enableTotalRecordCount = true)
{
- var dtoOptions = new DtoOptions()
- .AddItemFields(fields)
+ var dtoOptions = new DtoOptions { Fields = fields }
.AddClientFields(Request)
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
diff --git a/Jellyfin.Api/Controllers/ChannelsController.cs b/Jellyfin.Api/Controllers/ChannelsController.cs
index 20fc96ba8..ec9d7cdce 100644
--- a/Jellyfin.Api/Controllers/ChannelsController.cs
+++ b/Jellyfin.Api/Controllers/ChannelsController.cs
@@ -108,7 +108,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="sortOrder">Optional. Sort Order - Ascending,Descending.</param>
/// <param name="filters">Optional. Specify additional filters to apply.</param>
/// <param name="sortBy">Optional. Specify one or more sort orders, comma delimited. Options: Album, AlbumArtist, Artist, Budget, CommunityRating, CriticRating, DateCreated, DatePlayed, PlayCount, PremiereDate, ProductionYear, SortName, Random, Revenue, Runtime.</param>
- /// <param name="fields">Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines.</param>
+ /// <param name="fields">Optional. Specify additional fields of information to return in the output.</param>
/// <response code="200">Channel items returned.</response>
/// <returns>
/// A <see cref="Task"/> representing the request to get the channel items.
@@ -124,7 +124,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] string? sortOrder,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters,
[FromQuery] string? sortBy,
- [FromQuery] string? fields)
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields)
{
var user = userId.HasValue && !userId.Equals(Guid.Empty)
? _userManager.GetUserById(userId.Value)
@@ -137,8 +137,7 @@ namespace Jellyfin.Api.Controllers
ChannelIds = new[] { channelId },
ParentId = folderId ?? Guid.Empty,
OrderBy = RequestHelpers.GetOrderBy(sortBy, sortOrder),
- DtoOptions = new DtoOptions()
- .AddItemFields(fields)
+ DtoOptions = new DtoOptions { Fields = fields }
};
foreach (var filter in filters)
@@ -185,7 +184,7 @@ namespace Jellyfin.Api.Controllers
/// <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="filters">Optional. Specify additional filters to apply.</param>
- /// <param name="fields">Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines.</param>
+ /// <param name="fields">Optional. Specify additional fields of information to return in the output.</param>
/// <param name="channelIds">Optional. Specify one or more channel id's, comma delimited.</param>
/// <response code="200">Latest channel items returned.</response>
/// <returns>
@@ -198,7 +197,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? startIndex,
[FromQuery] int? limit,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters,
- [FromQuery] string? fields,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
[FromQuery] string? channelIds)
{
var user = userId.HasValue && !userId.Equals(Guid.Empty)
@@ -214,8 +213,7 @@ namespace Jellyfin.Api.Controllers
.Where(i => !string.IsNullOrWhiteSpace(i))
.Select(i => new Guid(i))
.ToArray(),
- DtoOptions = new DtoOptions()
- .AddItemFields(fields)
+ DtoOptions = new DtoOptions { Fields = fields }
};
foreach (var filter in filters)
diff --git a/Jellyfin.Api/Controllers/DisplayPreferencesController.cs b/Jellyfin.Api/Controllers/DisplayPreferencesController.cs
index 874467c75..76f5717e3 100644
--- a/Jellyfin.Api/Controllers/DisplayPreferencesController.cs
+++ b/Jellyfin.Api/Controllers/DisplayPreferencesController.cs
@@ -81,6 +81,9 @@ namespace Jellyfin.Api.Controllers
dto.CustomPrefs["enableNextVideoInfoOverlay"] = displayPreferences.EnableNextVideoInfoOverlay.ToString(CultureInfo.InvariantCulture);
dto.CustomPrefs["tvhome"] = displayPreferences.TvHome;
+ // This will essentially be a noop if no changes have been made, but new prefs must be saved at least.
+ _displayPreferencesManager.SaveChanges();
+
return dto;
}
diff --git a/Jellyfin.Api/Controllers/DlnaServerController.cs b/Jellyfin.Api/Controllers/DlnaServerController.cs
index 271ae293b..4e6455eaa 100644
--- a/Jellyfin.Api/Controllers/DlnaServerController.cs
+++ b/Jellyfin.Api/Controllers/DlnaServerController.cs
@@ -77,6 +77,7 @@ namespace Jellyfin.Api.Controllers
/// Gets Dlna media receiver registrar xml.
/// </summary>
/// <param name="serverId">Server UUID.</param>
+ /// <response code="200">Dlna media receiver registrar xml returned.</response>
/// <returns>Dlna media receiver registrar xml.</returns>
[HttpGet("{serverId}/MediaReceiverRegistrar")]
[HttpGet("{serverId}/MediaReceiverRegistrar/MediaReceiverRegistrar", Name = "GetMediaReceiverRegistrar_2")]
@@ -94,6 +95,7 @@ namespace Jellyfin.Api.Controllers
/// Gets Dlna media receiver registrar xml.
/// </summary>
/// <param name="serverId">Server UUID.</param>
+ /// <response code="200">Dlna media receiver registrar xml returned.</response>
/// <returns>Dlna media receiver registrar xml.</returns>
[HttpGet("{serverId}/ConnectionManager")]
[HttpGet("{serverId}/ConnectionManager/ConnectionManager", Name = "GetConnectionManager_2")]
@@ -111,8 +113,12 @@ namespace Jellyfin.Api.Controllers
/// Process a content directory control request.
/// </summary>
/// <param name="serverId">Server UUID.</param>
+ /// <response code="200">Request processed.</response>
/// <returns>Control response.</returns>
[HttpPost("{serverId}/ContentDirectory/Control")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [Produces(MediaTypeNames.Text.Xml)]
+ [ProducesFile(MediaTypeNames.Text.Xml)]
public async Task<ActionResult<ControlResponse>> ProcessContentDirectoryControlRequest([FromRoute, Required] string serverId)
{
return await ProcessControlRequestInternalAsync(serverId, Request.Body, _contentDirectory).ConfigureAwait(false);
@@ -122,8 +128,12 @@ namespace Jellyfin.Api.Controllers
/// Process a connection manager control request.
/// </summary>
/// <param name="serverId">Server UUID.</param>
+ /// <response code="200">Request processed.</response>
/// <returns>Control response.</returns>
[HttpPost("{serverId}/ConnectionManager/Control")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [Produces(MediaTypeNames.Text.Xml)]
+ [ProducesFile(MediaTypeNames.Text.Xml)]
public async Task<ActionResult<ControlResponse>> ProcessConnectionManagerControlRequest([FromRoute, Required] string serverId)
{
return await ProcessControlRequestInternalAsync(serverId, Request.Body, _connectionManager).ConfigureAwait(false);
@@ -133,8 +143,12 @@ namespace Jellyfin.Api.Controllers
/// Process a media receiver registrar control request.
/// </summary>
/// <param name="serverId">Server UUID.</param>
+ /// <response code="200">Request processed.</response>
/// <returns>Control response.</returns>
[HttpPost("{serverId}/MediaReceiverRegistrar/Control")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [Produces(MediaTypeNames.Text.Xml)]
+ [ProducesFile(MediaTypeNames.Text.Xml)]
public async Task<ActionResult<ControlResponse>> ProcessMediaReceiverRegistrarControlRequest([FromRoute, Required] string serverId)
{
return await ProcessControlRequestInternalAsync(serverId, Request.Body, _mediaReceiverRegistrar).ConfigureAwait(false);
@@ -144,11 +158,15 @@ namespace Jellyfin.Api.Controllers
/// Processes an event subscription request.
/// </summary>
/// <param name="serverId">Server UUID.</param>
+ /// <response code="200">Request processed.</response>
/// <returns>Event subscription response.</returns>
[HttpSubscribe("{serverId}/MediaReceiverRegistrar/Events")]
[HttpUnsubscribe("{serverId}/MediaReceiverRegistrar/Events")]
[ApiExplorerSettings(IgnoreApi = true)] // Ignore in openapi docs
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [Produces(MediaTypeNames.Text.Xml)]
+ [ProducesFile(MediaTypeNames.Text.Xml)]
public ActionResult<EventSubscriptionResponse> ProcessMediaReceiverRegistrarEventRequest(string serverId)
{
return ProcessEventRequest(_mediaReceiverRegistrar);
@@ -158,11 +176,15 @@ namespace Jellyfin.Api.Controllers
/// Processes an event subscription request.
/// </summary>
/// <param name="serverId">Server UUID.</param>
+ /// <response code="200">Request processed.</response>
/// <returns>Event subscription response.</returns>
[HttpSubscribe("{serverId}/ContentDirectory/Events")]
[HttpUnsubscribe("{serverId}/ContentDirectory/Events")]
[ApiExplorerSettings(IgnoreApi = true)] // Ignore in openapi docs
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [Produces(MediaTypeNames.Text.Xml)]
+ [ProducesFile(MediaTypeNames.Text.Xml)]
public ActionResult<EventSubscriptionResponse> ProcessContentDirectoryEventRequest(string serverId)
{
return ProcessEventRequest(_contentDirectory);
@@ -172,11 +194,15 @@ namespace Jellyfin.Api.Controllers
/// Processes an event subscription request.
/// </summary>
/// <param name="serverId">Server UUID.</param>
+ /// <response code="200">Request processed.</response>
/// <returns>Event subscription response.</returns>
[HttpSubscribe("{serverId}/ConnectionManager/Events")]
[HttpUnsubscribe("{serverId}/ConnectionManager/Events")]
[ApiExplorerSettings(IgnoreApi = true)] // Ignore in openapi docs
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [Produces(MediaTypeNames.Text.Xml)]
+ [ProducesFile(MediaTypeNames.Text.Xml)]
public ActionResult<EventSubscriptionResponse> ProcessConnectionManagerEventRequest(string serverId)
{
return ProcessEventRequest(_connectionManager);
diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs
index fc13fb873..28357091b 100644
--- a/Jellyfin.Api/Controllers/DynamicHlsController.cs
+++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs
@@ -15,6 +15,7 @@ using Jellyfin.Api.Helpers;
using Jellyfin.Api.Models.PlaybackDtos;
using Jellyfin.Api.Models.StreamingDtos;
using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Dlna;
@@ -1373,8 +1374,9 @@ namespace Jellyfin.Api.Controllers
var inputModifier = _encodingHelper.GetInputModifier(state, _encodingOptions);
var mapArgs = state.IsOutputVideo ? _encodingHelper.GetMapArgs(state) : string.Empty;
+ var directory = Path.GetDirectoryName(outputPath) ?? throw new ArgumentException($"Provided path ({outputPath}) is not valid.", nameof(outputPath));
var outputFileNameWithoutExtension = Path.GetFileNameWithoutExtension(outputPath);
- var outputPrefix = Path.Combine(Path.GetDirectoryName(outputPath), outputFileNameWithoutExtension);
+ var outputPrefix = Path.Combine(directory, outputFileNameWithoutExtension);
var outputExtension = GetSegmentFileExtension(state.Request.SegmentContainer);
var outputTsArg = outputPrefix + "%d" + outputExtension;
@@ -1684,8 +1686,7 @@ namespace Jellyfin.Api.Controllers
private string GetSegmentPath(StreamState state, string playlist, int index)
{
- var folder = Path.GetDirectoryName(playlist);
-
+ var folder = Path.GetDirectoryName(playlist) ?? throw new ArgumentException($"Provided path ({playlist}) is not valid.", nameof(playlist));
var filename = Path.GetFileNameWithoutExtension(playlist);
return Path.Combine(folder, filename + index.ToString(CultureInfo.InvariantCulture) + GetSegmentFileExtension(state.Request.SegmentContainer));
diff --git a/Jellyfin.Api/Controllers/EnvironmentController.cs b/Jellyfin.Api/Controllers/EnvironmentController.cs
index ce88b0b99..6dd536254 100644
--- a/Jellyfin.Api/Controllers/EnvironmentController.cs
+++ b/Jellyfin.Api/Controllers/EnvironmentController.cs
@@ -5,6 +5,7 @@ using System.IO;
using System.Linq;
using Jellyfin.Api.Constants;
using Jellyfin.Api.Models.EnvironmentDtos;
+using MediaBrowser.Common.Extensions;
using MediaBrowser.Model.IO;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
@@ -103,6 +104,11 @@ namespace Jellyfin.Api.Controllers
if (validatePathDto.ValidateWritable)
{
+ if (validatePathDto.Path == null)
+ {
+ throw new ResourceNotFoundException(nameof(validatePathDto.Path));
+ }
+
var file = Path.Combine(validatePathDto.Path, Guid.NewGuid().ToString());
try
{
diff --git a/Jellyfin.Api/Controllers/FilterController.cs b/Jellyfin.Api/Controllers/FilterController.cs
index 2a567c846..008bb58d1 100644
--- a/Jellyfin.Api/Controllers/FilterController.cs
+++ b/Jellyfin.Api/Controllers/FilterController.cs
@@ -78,8 +78,8 @@ namespace Jellyfin.Api.Controllers
var query = new InternalItemsQuery
{
User = user,
- MediaTypes = (mediaTypes ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries),
- IncludeItemTypes = (includeItemTypes ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries),
+ MediaTypes = (mediaTypes ?? string.Empty).Split(',', StringSplitOptions.RemoveEmptyEntries),
+ IncludeItemTypes = (includeItemTypes ?? string.Empty).Split(',', StringSplitOptions.RemoveEmptyEntries),
Recursive = true,
EnableTotalRecordCount = false,
DtoOptions = new DtoOptions
@@ -168,7 +168,7 @@ namespace Jellyfin.Api.Controllers
var genreQuery = new InternalItemsQuery(user)
{
IncludeItemTypes =
- (includeItemTypes ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries),
+ (includeItemTypes ?? string.Empty).Split(',', StringSplitOptions.RemoveEmptyEntries),
DtoOptions = new DtoOptions
{
Fields = Array.Empty<ItemFields>(),
diff --git a/Jellyfin.Api/Controllers/GenresController.cs b/Jellyfin.Api/Controllers/GenresController.cs
index aa7d02de0..9c222135d 100644
--- a/Jellyfin.Api/Controllers/GenresController.cs
+++ b/Jellyfin.Api/Controllers/GenresController.cs
@@ -1,6 +1,5 @@
using System;
using System.ComponentModel.DataAnnotations;
-using System.Globalization;
using System.Linq;
using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions;
@@ -49,30 +48,16 @@ namespace Jellyfin.Api.Controllers
/// <summary>
/// Gets all genres from a given item, folder, or the entire library.
/// </summary>
- /// <param name="minCommunityRating">Optional filter by minimum community rating.</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="searchTerm">The search term.</param>
/// <param name="parentId">Specify this to localize the search to a specific item or folder. Omit to use the root.</param>
- /// <param name="fields">Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines.</param>
+ /// <param name="fields">Optional. Specify additional fields of information to return in the output.</param>
/// <param name="excludeItemTypes">Optional. If specified, results will be filtered out based on item type. This allows multiple, comma delimited.</param>
/// <param name="includeItemTypes">Optional. If specified, results will be filtered in based on item type. This allows multiple, comma delimited.</param>
- /// <param name="filters">Optional. Specify additional filters to apply.</param>
/// <param name="isFavorite">Optional filter by items that are marked as favorite, or not.</param>
- /// <param name="mediaTypes">Optional filter by MediaType. Allows multiple, comma delimited.</param>
- /// <param name="genres">Optional. If specified, results will be filtered based on genre. This allows multiple, pipe delimited.</param>
- /// <param name="genreIds">Optional. If specified, results will be filtered based on genre id. This allows multiple, pipe delimited.</param>
- /// <param name="officialRatings">Optional. If specified, results will be filtered based on OfficialRating. This allows multiple, pipe delimited.</param>
- /// <param name="tags">Optional. If specified, results will be filtered based on tag. This allows multiple, pipe delimited.</param>
- /// <param name="years">Optional. If specified, results will be filtered based on production year. This allows multiple, comma delimited.</param>
- /// <param name="enableUserData">Optional, include user data.</param>
/// <param name="imageTypeLimit">Optional, the max number of images to return, per image type.</param>
/// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
- /// <param name="person">Optional. If specified, results will be filtered to include only those containing the specified person.</param>
- /// <param name="personIds">Optional. If specified, results will be filtered to include only those containing the specified person id.</param>
- /// <param name="personTypes">Optional. If specified, along with Person, results will be filtered to include only those containing the specified person and PersonType. Allows multiple, comma-delimited.</param>
- /// <param name="studios">Optional. If specified, results will be filtered based on studio. This allows multiple, pipe delimited.</param>
- /// <param name="studioIds">Optional. If specified, results will be filtered based on studio id. This allows multiple, pipe delimited.</param>
/// <param name="userId">User id.</param>
/// <param name="nameStartsWithOrGreater">Optional filter by items whose name is sorted equally or greater than a given input string.</param>
/// <param name="nameStartsWith">Optional filter by items whose name is sorted equally than a given input string.</param>
@@ -84,30 +69,16 @@ namespace Jellyfin.Api.Controllers
[HttpGet]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QueryResult<BaseItemDto>> GetGenres(
- [FromQuery] double? minCommunityRating,
[FromQuery] int? startIndex,
[FromQuery] int? limit,
[FromQuery] string? searchTerm,
[FromQuery] string? parentId,
- [FromQuery] string? fields,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
[FromQuery] string? excludeItemTypes,
[FromQuery] string? includeItemTypes,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters,
[FromQuery] bool? isFavorite,
- [FromQuery] string? mediaTypes,
- [FromQuery] string? genres,
- [FromQuery] string? genreIds,
- [FromQuery] string? officialRatings,
- [FromQuery] string? tags,
- [FromQuery] string? years,
- [FromQuery] bool? enableUserData,
[FromQuery] int? imageTypeLimit,
- [FromQuery] ImageType[] enableImageTypes,
- [FromQuery] string? person,
- [FromQuery] string? personIds,
- [FromQuery] string? personTypes,
- [FromQuery] string? studios,
- [FromQuery] string? studioIds,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes,
[FromQuery] Guid? userId,
[FromQuery] string? nameStartsWithOrGreater,
[FromQuery] string? nameStartsWith,
@@ -115,45 +86,24 @@ namespace Jellyfin.Api.Controllers
[FromQuery] bool? enableImages = true,
[FromQuery] bool enableTotalRecordCount = true)
{
- var dtoOptions = new DtoOptions()
- .AddItemFields(fields)
+ var dtoOptions = new DtoOptions { Fields = fields }
.AddClientFields(Request)
- .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
+ .AddAdditionalDtoOptions(enableImages, false, imageTypeLimit, enableImageTypes);
- User? user = null;
- BaseItem parentItem;
+ User? user = userId.HasValue && userId != Guid.Empty ? _userManager.GetUserById(userId.Value) : null;
- if (userId.HasValue && !userId.Equals(Guid.Empty))
- {
- user = _userManager.GetUserById(userId.Value);
- parentItem = string.IsNullOrEmpty(parentId) ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(parentId);
- }
- else
- {
- parentItem = string.IsNullOrEmpty(parentId) ? _libraryManager.RootFolder : _libraryManager.GetItemById(parentId);
- }
+ var parentItem = _libraryManager.GetParentItem(parentId, userId);
var query = new InternalItemsQuery(user)
{
ExcludeItemTypes = RequestHelpers.Split(excludeItemTypes, ',', true),
IncludeItemTypes = RequestHelpers.Split(includeItemTypes, ',', true),
- MediaTypes = RequestHelpers.Split(mediaTypes, ',', true),
StartIndex = startIndex,
Limit = limit,
IsFavorite = isFavorite,
NameLessThan = nameLessThan,
NameStartsWith = nameStartsWith,
NameStartsWithOrGreater = nameStartsWithOrGreater,
- Tags = RequestHelpers.Split(tags, '|', true),
- OfficialRatings = RequestHelpers.Split(officialRatings, '|', true),
- Genres = RequestHelpers.Split(genres, '|', true),
- GenreIds = RequestHelpers.GetGuids(genreIds),
- StudioIds = RequestHelpers.GetGuids(studioIds),
- Person = person,
- PersonIds = RequestHelpers.GetGuids(personIds),
- PersonTypes = RequestHelpers.Split(personTypes, ',', true),
- Years = RequestHelpers.Split(years, ',', true).Select(y => Convert.ToInt32(y, CultureInfo.InvariantCulture)).ToArray(),
- MinCommunityRating = minCommunityRating,
DtoOptions = dtoOptions,
SearchTerm = searchTerm,
EnableTotalRecordCount = enableTotalRecordCount
@@ -171,87 +121,20 @@ namespace Jellyfin.Api.Controllers
}
}
- // Studios
- if (!string.IsNullOrEmpty(studios))
+ QueryResult<(BaseItem, ItemCounts)> result;
+ if (parentItem is ICollectionFolder parentCollectionFolder
+ && (string.Equals(parentCollectionFolder.CollectionType, CollectionType.Music, StringComparison.Ordinal)
+ || string.Equals(parentCollectionFolder.CollectionType, CollectionType.MusicVideos, StringComparison.Ordinal)))
{
- query.StudioIds = studios.Split('|')
- .Select(i =>
- {
- try
- {
- return _libraryManager.GetStudio(i);
- }
- catch
- {
- return null;
- }
- }).Where(i => i != null)
- .Select(i => i!.Id)
- .ToArray();
+ result = _libraryManager.GetMusicGenres(query);
}
-
- foreach (var filter in filters)
+ else
{
- switch (filter)
- {
- case ItemFilter.Dislikes:
- query.IsLiked = false;
- break;
- case ItemFilter.IsFavorite:
- query.IsFavorite = true;
- break;
- case ItemFilter.IsFavoriteOrLikes:
- query.IsFavoriteOrLiked = true;
- break;
- case ItemFilter.IsFolder:
- query.IsFolder = true;
- break;
- case ItemFilter.IsNotFolder:
- query.IsFolder = false;
- break;
- case ItemFilter.IsPlayed:
- query.IsPlayed = true;
- break;
- case ItemFilter.IsResumable:
- query.IsResumable = true;
- break;
- case ItemFilter.IsUnplayed:
- query.IsPlayed = false;
- break;
- case ItemFilter.Likes:
- query.IsLiked = true;
- break;
- }
+ result = _libraryManager.GetGenres(query);
}
- var result = new QueryResult<(BaseItem, ItemCounts)>();
-
- var dtos = result.Items.Select(i =>
- {
- var (baseItem, counts) = i;
- var dto = _dtoService.GetItemByNameDto(baseItem, dtoOptions, null, user);
-
- if (!string.IsNullOrWhiteSpace(includeItemTypes))
- {
- dto.ChildCount = counts.ItemCount;
- dto.ProgramCount = counts.ProgramCount;
- dto.SeriesCount = counts.SeriesCount;
- dto.EpisodeCount = counts.EpisodeCount;
- dto.MovieCount = counts.MovieCount;
- dto.TrailerCount = counts.TrailerCount;
- dto.AlbumCount = counts.AlbumCount;
- dto.SongCount = counts.SongCount;
- dto.ArtistCount = counts.ArtistCount;
- }
-
- return dto;
- });
-
- return new QueryResult<BaseItemDto>
- {
- Items = dtos.ToArray(),
- TotalRecordCount = result.TotalRecordCount
- };
+ var shouldIncludeItemTypes = !string.IsNullOrEmpty(includeItemTypes);
+ return RequestHelpers.CreateQueryResult(result, dtoOptions, _dtoService, shouldIncludeItemTypes, user);
}
/// <summary>
@@ -293,7 +176,7 @@ namespace Jellyfin.Api.Controllers
return _dtoService.GetBaseItemDto(item, dtoOptions);
}
- private T GetItemFromSlugName<T>(ILibraryManager libraryManager, string name, DtoOptions dtoOptions)
+ private T? GetItemFromSlugName<T>(ILibraryManager libraryManager, string name, DtoOptions dtoOptions)
where T : BaseItem, new()
{
var result = libraryManager.GetItemList(new InternalItemsQuery
diff --git a/Jellyfin.Api/Controllers/HlsSegmentController.cs b/Jellyfin.Api/Controllers/HlsSegmentController.cs
index 054e586ce..3b75e8d43 100644
--- a/Jellyfin.Api/Controllers/HlsSegmentController.cs
+++ b/Jellyfin.Api/Controllers/HlsSegmentController.cs
@@ -8,6 +8,7 @@ using Jellyfin.Api.Attributes;
using Jellyfin.Api.Constants;
using Jellyfin.Api.Helpers;
using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.IO;
@@ -134,7 +135,8 @@ namespace Jellyfin.Api.Controllers
var playlistPath = _fileSystem.GetFilePaths(transcodeFolderPath)
.FirstOrDefault(i =>
string.Equals(Path.GetExtension(i), ".m3u8", StringComparison.OrdinalIgnoreCase)
- && i.IndexOf(normalizedPlaylistId, StringComparison.OrdinalIgnoreCase) != -1);
+ && i.IndexOf(normalizedPlaylistId, StringComparison.OrdinalIgnoreCase) != -1)
+ ?? throw new ResourceNotFoundException($"Provided path ({transcodeFolderPath}) is not valid.");
return GetFileResult(file, playlistPath);
}
diff --git a/Jellyfin.Api/Controllers/ImageByNameController.cs b/Jellyfin.Api/Controllers/ImageByNameController.cs
index 980c3273d..198dbc51f 100644
--- a/Jellyfin.Api/Controllers/ImageByNameController.cs
+++ b/Jellyfin.Api/Controllers/ImageByNameController.cs
@@ -161,7 +161,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="theme">Theme to search.</param>
/// <param name="name">File name to search for.</param>
/// <returns>A <see cref="FileStreamResult"/> containing the image contents on success, or a <see cref="NotFoundResult"/> if the image could not be found.</returns>
- private ActionResult GetImageFile(string basePath, string? theme, string? name)
+ private ActionResult GetImageFile(string basePath, string theme, string? name)
{
var themeFolder = Path.Combine(basePath, theme);
if (Directory.Exists(themeFolder))
diff --git a/Jellyfin.Api/Controllers/ImageController.cs b/Jellyfin.Api/Controllers/ImageController.cs
index 4a67c1aed..76e53b9a5 100644
--- a/Jellyfin.Api/Controllers/ImageController.cs
+++ b/Jellyfin.Api/Controllers/ImageController.cs
@@ -5,6 +5,7 @@ using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Linq;
+using System.Net.Mime;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Api.Attributes;
@@ -1268,7 +1269,7 @@ namespace Jellyfin.Api.Controllers
Response.Headers.Add(key, value);
}
- Response.ContentType = imageContentType;
+ Response.ContentType = imageContentType ?? MediaTypeNames.Text.Plain;
Response.Headers.Add(HeaderNames.Age, Convert.ToInt64((DateTime.UtcNow - dateImageModified).TotalSeconds).ToString(CultureInfo.InvariantCulture));
Response.Headers.Add(HeaderNames.Vary, HeaderNames.Accept);
diff --git a/Jellyfin.Api/Controllers/InstantMixController.cs b/Jellyfin.Api/Controllers/InstantMixController.cs
index 7f8a2be12..d17a26db4 100644
--- a/Jellyfin.Api/Controllers/InstantMixController.cs
+++ b/Jellyfin.Api/Controllers/InstantMixController.cs
@@ -4,6 +4,7 @@ using System.ComponentModel.DataAnnotations;
using System.Linq;
using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions;
+using Jellyfin.Api.ModelBinders;
using Jellyfin.Data.Entities;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
@@ -55,7 +56,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="id">The item id.</param>
/// <param name="userId">Optional. Filter by user id, and attach user data.</param>
/// <param name="limit">Optional. The maximum number of records to return.</param>
- /// <param name="fields">Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls.</param>
+ /// <param name="fields">Optional. Specify additional fields of information to return in the output.</param>
/// <param name="enableImages">Optional. Include image information in output.</param>
/// <param name="enableUserData">Optional. Include user data.</param>
/// <param name="imageTypeLimit">Optional. The max number of images to return, per image type.</param>
@@ -68,18 +69,17 @@ namespace Jellyfin.Api.Controllers
[FromRoute, Required] Guid id,
[FromQuery] Guid? userId,
[FromQuery] int? limit,
- [FromQuery] string? fields,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
[FromQuery] bool? enableImages,
[FromQuery] bool? enableUserData,
[FromQuery] int? imageTypeLimit,
- [FromQuery] ImageType[] enableImageTypes)
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes)
{
var item = _libraryManager.GetItemById(id);
var user = userId.HasValue && !userId.Equals(Guid.Empty)
? _userManager.GetUserById(userId.Value)
: null;
- var dtoOptions = new DtoOptions()
- .AddItemFields(fields)
+ var dtoOptions = new DtoOptions { Fields = fields }
.AddClientFields(Request)
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes!);
var items = _musicManager.GetInstantMixFromItem(item, user, dtoOptions);
@@ -92,7 +92,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="id">The item id.</param>
/// <param name="userId">Optional. Filter by user id, and attach user data.</param>
/// <param name="limit">Optional. The maximum number of records to return.</param>
- /// <param name="fields">Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls.</param>
+ /// <param name="fields">Optional. Specify additional fields of information to return in the output.</param>
/// <param name="enableImages">Optional. Include image information in output.</param>
/// <param name="enableUserData">Optional. Include user data.</param>
/// <param name="imageTypeLimit">Optional. The max number of images to return, per image type.</param>
@@ -105,18 +105,17 @@ namespace Jellyfin.Api.Controllers
[FromRoute, Required] Guid id,
[FromQuery] Guid? userId,
[FromQuery] int? limit,
- [FromQuery] string? fields,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
[FromQuery] bool? enableImages,
[FromQuery] bool? enableUserData,
[FromQuery] int? imageTypeLimit,
- [FromQuery] ImageType[] enableImageTypes)
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes)
{
var album = _libraryManager.GetItemById(id);
var user = userId.HasValue && !userId.Equals(Guid.Empty)
? _userManager.GetUserById(userId.Value)
: null;
- var dtoOptions = new DtoOptions()
- .AddItemFields(fields)
+ var dtoOptions = new DtoOptions { Fields = fields }
.AddClientFields(Request)
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes!);
var items = _musicManager.GetInstantMixFromItem(album, user, dtoOptions);
@@ -129,7 +128,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="id">The item id.</param>
/// <param name="userId">Optional. Filter by user id, and attach user data.</param>
/// <param name="limit">Optional. The maximum number of records to return.</param>
- /// <param name="fields">Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls.</param>
+ /// <param name="fields">Optional. Specify additional fields of information to return in the output.</param>
/// <param name="enableImages">Optional. Include image information in output.</param>
/// <param name="enableUserData">Optional. Include user data.</param>
/// <param name="imageTypeLimit">Optional. The max number of images to return, per image type.</param>
@@ -142,18 +141,17 @@ namespace Jellyfin.Api.Controllers
[FromRoute, Required] Guid id,
[FromQuery] Guid? userId,
[FromQuery] int? limit,
- [FromQuery] string? fields,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
[FromQuery] bool? enableImages,
[FromQuery] bool? enableUserData,
[FromQuery] int? imageTypeLimit,
- [FromQuery] ImageType[] enableImageTypes)
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes)
{
var playlist = (Playlist)_libraryManager.GetItemById(id);
var user = userId.HasValue && !userId.Equals(Guid.Empty)
? _userManager.GetUserById(userId.Value)
: null;
- var dtoOptions = new DtoOptions()
- .AddItemFields(fields)
+ var dtoOptions = new DtoOptions { Fields = fields }
.AddClientFields(Request)
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes!);
var items = _musicManager.GetInstantMixFromItem(playlist, user, dtoOptions);
@@ -166,7 +164,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="name">The genre name.</param>
/// <param name="userId">Optional. Filter by user id, and attach user data.</param>
/// <param name="limit">Optional. The maximum number of records to return.</param>
- /// <param name="fields">Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls.</param>
+ /// <param name="fields">Optional. Specify additional fields of information to return in the output.</param>
/// <param name="enableImages">Optional. Include image information in output.</param>
/// <param name="enableUserData">Optional. Include user data.</param>
/// <param name="imageTypeLimit">Optional. The max number of images to return, per image type.</param>
@@ -179,17 +177,16 @@ namespace Jellyfin.Api.Controllers
[FromRoute, Required] string name,
[FromQuery] Guid? userId,
[FromQuery] int? limit,
- [FromQuery] string? fields,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
[FromQuery] bool? enableImages,
[FromQuery] bool? enableUserData,
[FromQuery] int? imageTypeLimit,
- [FromQuery] ImageType[] enableImageTypes)
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes)
{
var user = userId.HasValue && !userId.Equals(Guid.Empty)
? _userManager.GetUserById(userId.Value)
: null;
- var dtoOptions = new DtoOptions()
- .AddItemFields(fields)
+ var dtoOptions = new DtoOptions { Fields = fields }
.AddClientFields(Request)
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes!);
var items = _musicManager.GetInstantMixFromGenres(new[] { name }, user, dtoOptions);
@@ -202,7 +199,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="id">The item id.</param>
/// <param name="userId">Optional. Filter by user id, and attach user data.</param>
/// <param name="limit">Optional. The maximum number of records to return.</param>
- /// <param name="fields">Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls.</param>
+ /// <param name="fields">Optional. Specify additional fields of information to return in the output.</param>
/// <param name="enableImages">Optional. Include image information in output.</param>
/// <param name="enableUserData">Optional. Include user data.</param>
/// <param name="imageTypeLimit">Optional. The max number of images to return, per image type.</param>
@@ -215,18 +212,17 @@ namespace Jellyfin.Api.Controllers
[FromRoute, Required] Guid id,
[FromQuery] Guid? userId,
[FromQuery] int? limit,
- [FromQuery] string? fields,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
[FromQuery] bool? enableImages,
[FromQuery] bool? enableUserData,
[FromQuery] int? imageTypeLimit,
- [FromQuery] ImageType[] enableImageTypes)
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes)
{
var item = _libraryManager.GetItemById(id);
var user = userId.HasValue && !userId.Equals(Guid.Empty)
? _userManager.GetUserById(userId.Value)
: null;
- var dtoOptions = new DtoOptions()
- .AddItemFields(fields)
+ var dtoOptions = new DtoOptions { Fields = fields }
.AddClientFields(Request)
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes!);
var items = _musicManager.GetInstantMixFromItem(item, user, dtoOptions);
@@ -239,7 +235,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="id">The item id.</param>
/// <param name="userId">Optional. Filter by user id, and attach user data.</param>
/// <param name="limit">Optional. The maximum number of records to return.</param>
- /// <param name="fields">Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls.</param>
+ /// <param name="fields">Optional. Specify additional fields of information to return in the output.</param>
/// <param name="enableImages">Optional. Include image information in output.</param>
/// <param name="enableUserData">Optional. Include user data.</param>
/// <param name="imageTypeLimit">Optional. The max number of images to return, per image type.</param>
@@ -252,18 +248,17 @@ namespace Jellyfin.Api.Controllers
[FromRoute, Required] Guid id,
[FromQuery] Guid? userId,
[FromQuery] int? limit,
- [FromQuery] string? fields,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
[FromQuery] bool? enableImages,
[FromQuery] bool? enableUserData,
[FromQuery] int? imageTypeLimit,
- [FromQuery] ImageType[] enableImageTypes)
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes)
{
var item = _libraryManager.GetItemById(id);
var user = userId.HasValue && !userId.Equals(Guid.Empty)
? _userManager.GetUserById(userId.Value)
: null;
- var dtoOptions = new DtoOptions()
- .AddItemFields(fields)
+ var dtoOptions = new DtoOptions { Fields = fields }
.AddClientFields(Request)
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes!);
var items = _musicManager.GetInstantMixFromItem(item, user, dtoOptions);
@@ -276,7 +271,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="id">The item id.</param>
/// <param name="userId">Optional. Filter by user id, and attach user data.</param>
/// <param name="limit">Optional. The maximum number of records to return.</param>
- /// <param name="fields">Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls.</param>
+ /// <param name="fields">Optional. Specify additional fields of information to return in the output.</param>
/// <param name="enableImages">Optional. Include image information in output.</param>
/// <param name="enableUserData">Optional. Include user data.</param>
/// <param name="imageTypeLimit">Optional. The max number of images to return, per image type.</param>
@@ -289,18 +284,17 @@ namespace Jellyfin.Api.Controllers
[FromRoute, Required] Guid id,
[FromQuery] Guid? userId,
[FromQuery] int? limit,
- [FromQuery] string? fields,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
[FromQuery] bool? enableImages,
[FromQuery] bool? enableUserData,
[FromQuery] int? imageTypeLimit,
- [FromQuery] ImageType[] enableImageTypes)
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes)
{
var item = _libraryManager.GetItemById(id);
var user = userId.HasValue && !userId.Equals(Guid.Empty)
? _userManager.GetUserById(userId.Value)
: null;
- var dtoOptions = new DtoOptions()
- .AddItemFields(fields)
+ var dtoOptions = new DtoOptions { Fields = fields }
.AddClientFields(Request)
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes!);
var items = _musicManager.GetInstantMixFromItem(item, user, dtoOptions);
diff --git a/Jellyfin.Api/Controllers/ItemLookupController.cs b/Jellyfin.Api/Controllers/ItemLookupController.cs
index ab73aa428..a7c1a6388 100644
--- a/Jellyfin.Api/Controllers/ItemLookupController.cs
+++ b/Jellyfin.Api/Controllers/ItemLookupController.cs
@@ -334,10 +334,16 @@ namespace Jellyfin.Api.Controllers
private async Task DownloadImage(string providerName, string url, Guid urlHash, string pointerCachePath)
{
using var result = await _providerManager.GetSearchImage(providerName, url, CancellationToken.None).ConfigureAwait(false);
+ if (result.Content.Headers.ContentType?.MediaType == null)
+ {
+ throw new ResourceNotFoundException(nameof(result.Content.Headers.ContentType));
+ }
+
var ext = result.Content.Headers.ContentType.MediaType.Split('/')[^1];
var fullCachePath = GetFullCachePath(urlHash + "." + ext);
- Directory.CreateDirectory(Path.GetDirectoryName(fullCachePath));
+ var directory = Path.GetDirectoryName(fullCachePath) ?? throw new ResourceNotFoundException($"Provided path ({fullCachePath}) is not valid.");
+ Directory.CreateDirectory(directory);
using (var stream = result.Content)
{
await using var fileStream = new FileStream(
@@ -351,7 +357,9 @@ namespace Jellyfin.Api.Controllers
await stream.CopyToAsync(fileStream).ConfigureAwait(false);
}
- Directory.CreateDirectory(Path.GetDirectoryName(pointerCachePath));
+ var pointerCacheDirectory = Path.GetDirectoryName(pointerCachePath) ?? throw new ArgumentException($"Provided path ({pointerCachePath}) is not valid.", nameof(pointerCachePath));
+
+ Directory.CreateDirectory(pointerCacheDirectory);
await System.IO.File.WriteAllTextAsync(pointerCachePath, fullCachePath).ConfigureAwait(false);
}
diff --git a/Jellyfin.Api/Controllers/ItemsController.cs b/Jellyfin.Api/Controllers/ItemsController.cs
index 7ca577543..d8d371ebc 100644
--- a/Jellyfin.Api/Controllers/ItemsController.cs
+++ b/Jellyfin.Api/Controllers/ItemsController.cs
@@ -180,13 +180,13 @@ namespace Jellyfin.Api.Controllers
[FromQuery] string? searchTerm,
[FromQuery] string? sortOrder,
[FromQuery] string? parentId,
- [FromQuery] string? fields,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
[FromQuery] string? excludeItemTypes,
[FromQuery] string? includeItemTypes,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters,
[FromQuery] bool? isFavorite,
[FromQuery] string? mediaTypes,
- [FromQuery] ImageType[] imageTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] imageTypes,
[FromQuery] string? sortBy,
[FromQuery] bool? isPlayed,
[FromQuery] string? genres,
@@ -195,7 +195,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] string? years,
[FromQuery] bool? enableUserData,
[FromQuery] int? imageTypeLimit,
- [FromQuery] ImageType[] enableImageTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes,
[FromQuery] string? person,
[FromQuery] string? personIds,
[FromQuery] string? personTypes,
@@ -234,8 +234,7 @@ namespace Jellyfin.Api.Controllers
var user = userId.HasValue && !userId.Equals(Guid.Empty)
? _userManager.GetUserById(userId.Value)
: null;
- var dtoOptions = new DtoOptions()
- .AddItemFields(fields)
+ var dtoOptions = new DtoOptions { Fields = fields }
.AddClientFields(Request)
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
@@ -533,11 +532,11 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? limit,
[FromQuery] string? searchTerm,
[FromQuery] string? parentId,
- [FromQuery] string? fields,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
[FromQuery] string? mediaTypes,
[FromQuery] bool? enableUserData,
[FromQuery] int? imageTypeLimit,
- [FromQuery] ImageType[] enableImageTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes,
[FromQuery] string? excludeItemTypes,
[FromQuery] string? includeItemTypes,
[FromQuery] bool enableTotalRecordCount = true,
@@ -545,8 +544,7 @@ namespace Jellyfin.Api.Controllers
{
var user = _userManager.GetUserById(userId);
var parentIdGuid = string.IsNullOrWhiteSpace(parentId) ? Guid.Empty : new Guid(parentId);
- var dtoOptions = new DtoOptions()
- .AddItemFields(fields)
+ var dtoOptions = new DtoOptions { Fields = fields }
.AddClientFields(Request)
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
diff --git a/Jellyfin.Api/Controllers/LibraryController.cs b/Jellyfin.Api/Controllers/LibraryController.cs
index 8a872ae13..1b115d800 100644
--- a/Jellyfin.Api/Controllers/LibraryController.cs
+++ b/Jellyfin.Api/Controllers/LibraryController.cs
@@ -12,6 +12,7 @@ using Jellyfin.Api.Attributes;
using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers;
+using Jellyfin.Api.ModelBinders;
using Jellyfin.Api.Models.LibraryDtos;
using Jellyfin.Data.Entities;
using MediaBrowser.Common.Progress;
@@ -455,7 +456,7 @@ namespace Jellyfin.Api.Controllers
: null;
var dtoOptions = new DtoOptions().AddClientFields(Request);
- BaseItem parent = item.GetParent();
+ BaseItem? parent = item.GetParent();
while (parent != null)
{
@@ -466,7 +467,7 @@ namespace Jellyfin.Api.Controllers
baseItemDtos.Add(_dtoService.GetBaseItemDto(parent, dtoOptions, user));
- parent = parent.GetParent();
+ parent = parent?.GetParent();
}
return baseItemDtos;
@@ -680,12 +681,12 @@ namespace Jellyfin.Api.Controllers
/// <param name="fields">Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls.</param>
/// <response code="200">Similar items returned.</response>
/// <returns>A <see cref="QueryResult{BaseItemDto}"/> containing the similar items.</returns>
- [HttpGet("Artists/{itemId}/Similar", Name = "GetSimilarArtists2")]
+ [HttpGet("Artists/{itemId}/Similar", Name = "GetSimilarArtists")]
[HttpGet("Items/{itemId}/Similar")]
- [HttpGet("Albums/{itemId}/Similar", Name = "GetSimilarAlbums2")]
- [HttpGet("Shows/{itemId}/Similar", Name = "GetSimilarShows2")]
- [HttpGet("Movies/{itemId}/Similar", Name = "GetSimilarMovies2")]
- [HttpGet("Trailers/{itemId}/Similar", Name = "GetSimilarTrailers2")]
+ [HttpGet("Albums/{itemId}/Similar", Name = "GetSimilarAlbums")]
+ [HttpGet("Shows/{itemId}/Similar", Name = "GetSimilarShows")]
+ [HttpGet("Movies/{itemId}/Similar", Name = "GetSimilarMovies")]
+ [HttpGet("Trailers/{itemId}/Similar", Name = "GetSimilarTrailers")]
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QueryResult<BaseItemDto>> GetSimilarItems(
@@ -693,7 +694,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] string? excludeArtistIds,
[FromQuery] Guid? userId,
[FromQuery] int? limit,
- [FromQuery] string? fields)
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields)
{
var item = itemId.Equals(Guid.Empty)
? (!userId.Equals(Guid.Empty)
@@ -701,33 +702,71 @@ namespace Jellyfin.Api.Controllers
: _libraryManager.RootFolder)
: _libraryManager.GetItemById(itemId);
+ if (item is Episode || (item is IItemByName && !(item is MusicArtist)))
+ {
+ return new QueryResult<BaseItemDto>();
+ }
+
+ var user = userId.HasValue && !userId.Equals(Guid.Empty)
+ ? _userManager.GetUserById(userId.Value)
+ : null;
+ var dtoOptions = new DtoOptions { Fields = fields }
+ .AddClientFields(Request);
+
var program = item as IHasProgramAttributes;
- var isMovie = item is MediaBrowser.Controller.Entities.Movies.Movie || (program != null && program.IsMovie) || item is Trailer;
- if (program != null && program.IsSeries)
+ bool? isMovie = item is Movie || (program != null && program.IsMovie) || item is Trailer;
+ bool? isSeries = item is Series || (program != null && program.IsSeries);
+
+ var includeItemTypes = new List<string>();
+ if (isMovie.Value)
{
- return GetSimilarItemsResult(
- item,
- excludeArtistIds,
- userId,
- limit,
- fields,
- new[] { nameof(Series) },
- false);
+ includeItemTypes.Add(nameof(Movie));
+ if (_serverConfigurationManager.Configuration.EnableExternalContentInSuggestions)
+ {
+ includeItemTypes.Add(nameof(Trailer));
+ includeItemTypes.Add(nameof(LiveTvProgram));
+ }
+ }
+ else if (isSeries.Value)
+ {
+ includeItemTypes.Add(nameof(Series));
+ }
+ else
+ {
+ // For non series and movie types these columns are typically null
+ isSeries = null;
+ isMovie = null;
+ includeItemTypes.Add(item.GetType().Name);
}
- if (item is MediaBrowser.Controller.Entities.TV.Episode || (item is IItemByName && !(item is MusicArtist)))
+ var query = new InternalItemsQuery(user)
{
- return new QueryResult<BaseItemDto>();
+ Limit = limit,
+ IncludeItemTypes = includeItemTypes.ToArray(),
+ IsMovie = isMovie,
+ IsSeries = isSeries,
+ SimilarTo = item,
+ DtoOptions = dtoOptions,
+ EnableTotalRecordCount = !isMovie ?? true,
+ EnableGroupByMetadataKey = isMovie ?? false,
+ MinSimilarityScore = 2 // A remnant from album/artist scoring
+ };
+
+ // ExcludeArtistIds
+ if (!string.IsNullOrEmpty(excludeArtistIds))
+ {
+ query.ExcludeArtistIds = RequestHelpers.GetGuids(excludeArtistIds);
}
- return GetSimilarItemsResult(
- item,
- excludeArtistIds,
- userId,
- limit,
- fields,
- new[] { item.GetType().Name },
- isMovie);
+ List<BaseItem> itemsResult = _libraryManager.GetItemList(query);
+
+ var returnList = _dtoService.GetBaseItemDtos(itemsResult, dtoOptions, user);
+
+ return new QueryResult<BaseItemDto>
+ {
+ Items = returnList,
+ TotalRecordCount = itemsResult.Count
+ };
}
/// <summary>
@@ -854,7 +893,7 @@ namespace Jellyfin.Api.Controllers
return _libraryManager.GetItemsResult(query).TotalRecordCount;
}
- private BaseItem TranslateParentItem(BaseItem item, User user)
+ private BaseItem? TranslateParentItem(BaseItem item, User user)
{
return item.GetParent() is AggregateFolder
? _libraryManager.GetUserRootFolder().GetChildren(user, true)
@@ -880,75 +919,6 @@ namespace Jellyfin.Api.Controllers
}
}
- private QueryResult<BaseItemDto> GetSimilarItemsResult(
- BaseItem item,
- string? excludeArtistIds,
- Guid? userId,
- int? limit,
- string? fields,
- string[] includeItemTypes,
- bool isMovie)
- {
- var user = userId.HasValue && !userId.Equals(Guid.Empty)
- ? _userManager.GetUserById(userId.Value)
- : null;
- var dtoOptions = new DtoOptions()
- .AddItemFields(fields)
- .AddClientFields(Request);
-
- var query = new InternalItemsQuery(user)
- {
- Limit = limit,
- IncludeItemTypes = includeItemTypes,
- IsMovie = isMovie,
- SimilarTo = item,
- DtoOptions = dtoOptions,
- EnableTotalRecordCount = !isMovie,
- EnableGroupByMetadataKey = isMovie
- };
-
- // ExcludeArtistIds
- if (!string.IsNullOrEmpty(excludeArtistIds))
- {
- query.ExcludeArtistIds = RequestHelpers.GetGuids(excludeArtistIds);
- }
-
- List<BaseItem> itemsResult;
-
- if (isMovie)
- {
- var itemTypes = new List<string> { nameof(MediaBrowser.Controller.Entities.Movies.Movie) };
- if (_serverConfigurationManager.Configuration.EnableExternalContentInSuggestions)
- {
- itemTypes.Add(nameof(Trailer));
- itemTypes.Add(nameof(LiveTvProgram));
- }
-
- query.IncludeItemTypes = itemTypes.ToArray();
- itemsResult = _libraryManager.GetArtists(query).Items.Select(i => i.Item1).ToList();
- }
- else if (item is MusicArtist)
- {
- query.IncludeItemTypes = Array.Empty<string>();
-
- itemsResult = _libraryManager.GetArtists(query).Items.Select(i => i.Item1).ToList();
- }
- else
- {
- itemsResult = _libraryManager.GetItemList(query);
- }
-
- var returnList = _dtoService.GetBaseItemDtos(itemsResult, dtoOptions, user);
-
- var result = new QueryResult<BaseItemDto>
- {
- Items = returnList,
- TotalRecordCount = itemsResult.Count
- };
-
- return result;
- }
-
private static string[] GetRepresentativeItemTypes(string? contentType)
{
return contentType switch
diff --git a/Jellyfin.Api/Controllers/LiveTvController.cs b/Jellyfin.Api/Controllers/LiveTvController.cs
index 88a7542ce..29c0f1df4 100644
--- a/Jellyfin.Api/Controllers/LiveTvController.cs
+++ b/Jellyfin.Api/Controllers/LiveTvController.cs
@@ -14,6 +14,7 @@ using Jellyfin.Api.Attributes;
using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers;
+using Jellyfin.Api.ModelBinders;
using Jellyfin.Api.Models.LiveTvDtos;
using Jellyfin.Data.Enums;
using MediaBrowser.Common;
@@ -118,7 +119,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="enableImages">Optional. Include image information in output.</param>
/// <param name="imageTypeLimit">Optional. The max number of images to return, per image type.</param>
/// <param name="enableImageTypes">"Optional. The image types to include in the output.</param>
- /// <param name="fields">Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines.</param>
+ /// <param name="fields">Optional. Specify additional fields of information to return in the output.</param>
/// <param name="enableUserData">Optional. Include user data.</param>
/// <param name="sortBy">Optional. Key to sort by.</param>
/// <param name="sortOrder">Optional. Sort order.</param>
@@ -146,16 +147,15 @@ namespace Jellyfin.Api.Controllers
[FromQuery] bool? isDisliked,
[FromQuery] bool? enableImages,
[FromQuery] int? imageTypeLimit,
- [FromQuery] ImageType[] enableImageTypes,
- [FromQuery] string? fields,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
[FromQuery] bool? enableUserData,
[FromQuery] string? sortBy,
[FromQuery] SortOrder? sortOrder,
[FromQuery] bool enableFavoriteSorting = false,
[FromQuery] bool addCurrentProgram = true)
{
- var dtoOptions = new DtoOptions()
- .AddItemFields(fields)
+ var dtoOptions = new DtoOptions { Fields = fields }
.AddClientFields(Request)
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
@@ -239,7 +239,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="enableImages">Optional. Include image information in output.</param>
/// <param name="imageTypeLimit">Optional. The max number of images to return, per image type.</param>
/// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
- /// <param name="fields">Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines.</param>
+ /// <param name="fields">Optional. Specify additional fields of information to return in the output.</param>
/// <param name="enableUserData">Optional. Include user data.</param>
/// <param name="isMovie">Optional. Filter for movies.</param>
/// <param name="isSeries">Optional. Filter for series.</param>
@@ -263,8 +263,8 @@ namespace Jellyfin.Api.Controllers
[FromQuery] string? seriesTimerId,
[FromQuery] bool? enableImages,
[FromQuery] int? imageTypeLimit,
- [FromQuery] ImageType[] enableImageTypes,
- [FromQuery] string? fields,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
[FromQuery] bool? enableUserData,
[FromQuery] bool? isMovie,
[FromQuery] bool? isSeries,
@@ -274,8 +274,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] bool? isLibraryItem,
[FromQuery] bool enableTotalRecordCount = true)
{
- var dtoOptions = new DtoOptions()
- .AddItemFields(fields)
+ var dtoOptions = new DtoOptions { Fields = fields }
.AddClientFields(Request)
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
@@ -296,7 +295,7 @@ namespace Jellyfin.Api.Controllers
IsKids = isKids,
IsSports = isSports,
IsLibraryItem = isLibraryItem,
- Fields = RequestHelpers.GetItemFields(fields),
+ Fields = fields,
ImageTypeLimit = imageTypeLimit,
EnableImages = enableImages
}, dtoOptions);
@@ -316,7 +315,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="enableImages">Optional. Include image information in output.</param>
/// <param name="imageTypeLimit">Optional. The max number of images to return, per image type.</param>
/// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
- /// <param name="fields">Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines.</param>
+ /// <param name="fields">Optional. Specify additional fields of information to return in the output.</param>
/// <param name="enableUserData">Optional. Include user data.</param>
/// <param name="enableTotalRecordCount">Optional. Return total record count.</param>
/// <response code="200">Live tv recordings returned.</response>
@@ -350,8 +349,8 @@ namespace Jellyfin.Api.Controllers
[FromQuery] string? seriesTimerId,
[FromQuery] bool? enableImages,
[FromQuery] int? imageTypeLimit,
- [FromQuery] ImageType[] enableImageTypes,
- [FromQuery] string? fields,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
[FromQuery] bool? enableUserData,
[FromQuery] bool enableTotalRecordCount = true)
{
@@ -530,7 +529,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="enableUserData">Optional. Include user data.</param>
/// <param name="seriesTimerId">Optional. Filter by series timer id.</param>
/// <param name="librarySeriesId">Optional. Filter by library series id.</param>
- /// <param name="fields">Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines.</param>
+ /// <param name="fields">Optional. Specify additional fields of information to return in the output.</param>
/// <param name="enableTotalRecordCount">Retrieve total record count.</param>
/// <response code="200">Live tv epgs returned.</response>
/// <returns>
@@ -561,11 +560,11 @@ namespace Jellyfin.Api.Controllers
[FromQuery] string? genreIds,
[FromQuery] bool? enableImages,
[FromQuery] int? imageTypeLimit,
- [FromQuery] ImageType[] enableImageTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes,
[FromQuery] bool? enableUserData,
[FromQuery] string? seriesTimerId,
[FromQuery] Guid? librarySeriesId,
- [FromQuery] string? fields,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
[FromQuery] bool enableTotalRecordCount = true)
{
var user = userId.HasValue && !userId.Equals(Guid.Empty)
@@ -606,8 +605,7 @@ namespace Jellyfin.Api.Controllers
}
}
- var dtoOptions = new DtoOptions()
- .AddItemFields(fields)
+ var dtoOptions = new DtoOptions { Fields = fields }
.AddClientFields(Request)
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
return await _liveTvManager.GetPrograms(query, dtoOptions, CancellationToken.None).ConfigureAwait(false);
@@ -662,8 +660,7 @@ namespace Jellyfin.Api.Controllers
}
}
- var dtoOptions = new DtoOptions()
- .AddItemFields(body.Fields)
+ var dtoOptions = new DtoOptions { Fields = body.Fields }
.AddClientFields(Request)
.AddAdditionalDtoOptions(body.EnableImages, body.EnableUserData, body.ImageTypeLimit, body.EnableImageTypes);
return await _liveTvManager.GetPrograms(query, dtoOptions, CancellationToken.None).ConfigureAwait(false);
@@ -685,7 +682,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="imageTypeLimit">Optional. The max number of images to return, per image type.</param>
/// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
/// <param name="genreIds">The genres to return guide information for.</param>
- /// <param name="fields">Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines.</param>
+ /// <param name="fields">Optional. Specify additional fields of information to return in the output.</param>
/// <param name="enableUserData">Optional. include user data.</param>
/// <param name="enableTotalRecordCount">Retrieve total record count.</param>
/// <response code="200">Recommended epgs returned.</response>
@@ -705,9 +702,9 @@ namespace Jellyfin.Api.Controllers
[FromQuery] bool? isSports,
[FromQuery] bool? enableImages,
[FromQuery] int? imageTypeLimit,
- [FromQuery] ImageType[] enableImageTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes,
[FromQuery] string? genreIds,
- [FromQuery] string? fields,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
[FromQuery] bool? enableUserData,
[FromQuery] bool enableTotalRecordCount = true)
{
@@ -729,8 +726,7 @@ namespace Jellyfin.Api.Controllers
GenreIds = RequestHelpers.GetGuids(genreIds)
};
- var dtoOptions = new DtoOptions()
- .AddItemFields(fields)
+ var dtoOptions = new DtoOptions { Fields = fields }
.AddClientFields(Request)
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
return _liveTvManager.GetRecommendedPrograms(query, dtoOptions, CancellationToken.None);
@@ -1077,7 +1073,7 @@ namespace Jellyfin.Api.Controllers
var client = _httpClientFactory.CreateClient(NamedClient.Default);
// https://json.schedulesdirect.org/20141201/available/countries
// Can't dispose the response as it's required up the call chain.
- var response = await client.GetAsync("https://json.schedulesdirect.org/20141201/available/countries")
+ var response = await client.GetAsync(new Uri("https://json.schedulesdirect.org/20141201/available/countries"))
.ConfigureAwait(false);
return File(await response.Content.ReadAsStreamAsync().ConfigureAwait(false), MediaTypeNames.Application.Json);
diff --git a/Jellyfin.Api/Controllers/MoviesController.cs b/Jellyfin.Api/Controllers/MoviesController.cs
index afb536e74..ebc148fe5 100644
--- a/Jellyfin.Api/Controllers/MoviesController.cs
+++ b/Jellyfin.Api/Controllers/MoviesController.cs
@@ -4,6 +4,7 @@ using System.Globalization;
using System.Linq;
using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions;
+using Jellyfin.Api.ModelBinders;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
using MediaBrowser.Common.Extensions;
@@ -65,15 +66,14 @@ namespace Jellyfin.Api.Controllers
public ActionResult<IEnumerable<RecommendationDto>> GetMovieRecommendations(
[FromQuery] Guid? userId,
[FromQuery] string? parentId,
- [FromQuery] string? fields,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
[FromQuery] int categoryLimit = 5,
[FromQuery] int itemLimit = 8)
{
var user = userId.HasValue && !userId.Equals(Guid.Empty)
? _userManager.GetUserById(userId.Value)
: null;
- var dtoOptions = new DtoOptions()
- .AddItemFields(fields)
+ var dtoOptions = new DtoOptions { Fields = fields }
.AddClientFields(Request);
var categories = new List<RecommendationDto>();
diff --git a/Jellyfin.Api/Controllers/MusicGenresController.cs b/Jellyfin.Api/Controllers/MusicGenresController.cs
index 4de7ef8c8..229d9ff02 100644
--- a/Jellyfin.Api/Controllers/MusicGenresController.cs
+++ b/Jellyfin.Api/Controllers/MusicGenresController.cs
@@ -1,6 +1,5 @@
using System;
using System.ComponentModel.DataAnnotations;
-using System.Globalization;
using System.Linq;
using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions;
@@ -49,30 +48,16 @@ namespace Jellyfin.Api.Controllers
/// <summary>
/// Gets all music genres from a given item, folder, or the entire library.
/// </summary>
- /// <param name="minCommunityRating">Optional filter by minimum community rating.</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="searchTerm">The search term.</param>
/// <param name="parentId">Specify this to localize the search to a specific item or folder. Omit to use the root.</param>
- /// <param name="fields">Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines.</param>
+ /// <param name="fields">Optional. Specify additional fields of information to return in the output.</param>
/// <param name="excludeItemTypes">Optional. If specified, results will be filtered out based on item type. This allows multiple, comma delimited.</param>
/// <param name="includeItemTypes">Optional. If specified, results will be filtered in based on item type. This allows multiple, comma delimited.</param>
- /// <param name="filters">Optional. Specify additional filters to apply.</param>
/// <param name="isFavorite">Optional filter by items that are marked as favorite, or not.</param>
- /// <param name="mediaTypes">Optional filter by MediaType. Allows multiple, comma delimited.</param>
- /// <param name="genres">Optional. If specified, results will be filtered based on genre. This allows multiple, pipe delimited.</param>
- /// <param name="genreIds">Optional. If specified, results will be filtered based on genre id. This allows multiple, pipe delimited.</param>
- /// <param name="officialRatings">Optional. If specified, results will be filtered based on OfficialRating. This allows multiple, pipe delimited.</param>
- /// <param name="tags">Optional. If specified, results will be filtered based on tag. This allows multiple, pipe delimited.</param>
- /// <param name="years">Optional. If specified, results will be filtered based on production year. This allows multiple, comma delimited.</param>
- /// <param name="enableUserData">Optional, include user data.</param>
/// <param name="imageTypeLimit">Optional, the max number of images to return, per image type.</param>
/// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
- /// <param name="person">Optional. If specified, results will be filtered to include only those containing the specified person.</param>
- /// <param name="personIds">Optional. If specified, results will be filtered to include only those containing the specified person id.</param>
- /// <param name="personTypes">Optional. If specified, along with Person, results will be filtered to include only those containing the specified person and PersonType. Allows multiple, comma-delimited.</param>
- /// <param name="studios">Optional. If specified, results will be filtered based on studio. This allows multiple, pipe delimited.</param>
- /// <param name="studioIds">Optional. If specified, results will be filtered based on studio id. This allows multiple, pipe delimited.</param>
/// <param name="userId">User id.</param>
/// <param name="nameStartsWithOrGreater">Optional filter by items whose name is sorted equally or greater than a given input string.</param>
/// <param name="nameStartsWith">Optional filter by items whose name is sorted equally than a given input string.</param>
@@ -82,31 +67,18 @@ namespace Jellyfin.Api.Controllers
/// <response code="200">Music genres returned.</response>
/// <returns>An <see cref="OkResult"/> containing the queryresult of music genres.</returns>
[HttpGet]
+ [Obsolete("Use GetGenres instead")]
public ActionResult<QueryResult<BaseItemDto>> GetMusicGenres(
- [FromQuery] double? minCommunityRating,
[FromQuery] int? startIndex,
[FromQuery] int? limit,
[FromQuery] string? searchTerm,
[FromQuery] string? parentId,
- [FromQuery] string? fields,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
[FromQuery] string? excludeItemTypes,
[FromQuery] string? includeItemTypes,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters,
[FromQuery] bool? isFavorite,
- [FromQuery] string? mediaTypes,
- [FromQuery] string? genres,
- [FromQuery] string? genreIds,
- [FromQuery] string? officialRatings,
- [FromQuery] string? tags,
- [FromQuery] string? years,
- [FromQuery] bool? enableUserData,
[FromQuery] int? imageTypeLimit,
- [FromQuery] ImageType[] enableImageTypes,
- [FromQuery] string? person,
- [FromQuery] string? personIds,
- [FromQuery] string? personTypes,
- [FromQuery] string? studios,
- [FromQuery] string? studioIds,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes,
[FromQuery] Guid? userId,
[FromQuery] string? nameStartsWithOrGreater,
[FromQuery] string? nameStartsWith,
@@ -114,45 +86,24 @@ namespace Jellyfin.Api.Controllers
[FromQuery] bool? enableImages = true,
[FromQuery] bool enableTotalRecordCount = true)
{
- var dtoOptions = new DtoOptions()
- .AddItemFields(fields)
+ var dtoOptions = new DtoOptions { Fields = fields }
.AddClientFields(Request)
- .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
+ .AddAdditionalDtoOptions(enableImages, false, imageTypeLimit, enableImageTypes);
- User? user = null;
- BaseItem parentItem;
+ User? user = userId.HasValue && userId != Guid.Empty ? _userManager.GetUserById(userId.Value) : null;
- if (userId.HasValue && !userId.Equals(Guid.Empty))
- {
- user = _userManager.GetUserById(userId.Value);
- parentItem = string.IsNullOrEmpty(parentId) ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(parentId);
- }
- else
- {
- parentItem = string.IsNullOrEmpty(parentId) ? _libraryManager.RootFolder : _libraryManager.GetItemById(parentId);
- }
+ var parentItem = _libraryManager.GetParentItem(parentId, userId);
var query = new InternalItemsQuery(user)
{
ExcludeItemTypes = RequestHelpers.Split(excludeItemTypes, ',', true),
IncludeItemTypes = RequestHelpers.Split(includeItemTypes, ',', true),
- MediaTypes = RequestHelpers.Split(mediaTypes, ',', true),
StartIndex = startIndex,
Limit = limit,
IsFavorite = isFavorite,
NameLessThan = nameLessThan,
NameStartsWith = nameStartsWith,
NameStartsWithOrGreater = nameStartsWithOrGreater,
- Tags = RequestHelpers.Split(tags, '|', true),
- OfficialRatings = RequestHelpers.Split(officialRatings, '|', true),
- Genres = RequestHelpers.Split(genres, '|', true),
- GenreIds = RequestHelpers.GetGuids(genreIds),
- StudioIds = RequestHelpers.GetGuids(studioIds),
- Person = person,
- PersonIds = RequestHelpers.GetGuids(personIds),
- PersonTypes = RequestHelpers.Split(personTypes, ',', true),
- Years = RequestHelpers.Split(years, ',', true).Select(y => Convert.ToInt32(y, CultureInfo.InvariantCulture)).ToArray(),
- MinCommunityRating = minCommunityRating,
DtoOptions = dtoOptions,
SearchTerm = searchTerm,
EnableTotalRecordCount = enableTotalRecordCount
@@ -170,87 +121,10 @@ namespace Jellyfin.Api.Controllers
}
}
- // Studios
- if (!string.IsNullOrEmpty(studios))
- {
- query.StudioIds = studios.Split('|')
- .Select(i =>
- {
- try
- {
- return _libraryManager.GetStudio(i);
- }
- catch
- {
- return null;
- }
- }).Where(i => i != null)
- .Select(i => i!.Id)
- .ToArray();
- }
-
- foreach (var filter in filters)
- {
- switch (filter)
- {
- case ItemFilter.Dislikes:
- query.IsLiked = false;
- break;
- case ItemFilter.IsFavorite:
- query.IsFavorite = true;
- break;
- case ItemFilter.IsFavoriteOrLikes:
- query.IsFavoriteOrLiked = true;
- break;
- case ItemFilter.IsFolder:
- query.IsFolder = true;
- break;
- case ItemFilter.IsNotFolder:
- query.IsFolder = false;
- break;
- case ItemFilter.IsPlayed:
- query.IsPlayed = true;
- break;
- case ItemFilter.IsResumable:
- query.IsResumable = true;
- break;
- case ItemFilter.IsUnplayed:
- query.IsPlayed = false;
- break;
- case ItemFilter.Likes:
- query.IsLiked = true;
- break;
- }
- }
-
var result = _libraryManager.GetMusicGenres(query);
- var dtos = result.Items.Select(i =>
- {
- var (baseItem, counts) = i;
- var dto = _dtoService.GetItemByNameDto(baseItem, dtoOptions, null, user);
-
- if (!string.IsNullOrWhiteSpace(includeItemTypes))
- {
- dto.ChildCount = counts.ItemCount;
- dto.ProgramCount = counts.ProgramCount;
- dto.SeriesCount = counts.SeriesCount;
- dto.EpisodeCount = counts.EpisodeCount;
- dto.MovieCount = counts.MovieCount;
- dto.TrailerCount = counts.TrailerCount;
- dto.AlbumCount = counts.AlbumCount;
- dto.SongCount = counts.SongCount;
- dto.ArtistCount = counts.ArtistCount;
- }
-
- return dto;
- });
-
- return new QueryResult<BaseItemDto>
- {
- Items = dtos.ToArray(),
- TotalRecordCount = result.TotalRecordCount
- };
+ var shouldIncludeItemTypes = !string.IsNullOrWhiteSpace(includeItemTypes);
+ return RequestHelpers.CreateQueryResult(result, dtoOptions, _dtoService, shouldIncludeItemTypes, user);
}
/// <summary>
@@ -265,7 +139,7 @@ namespace Jellyfin.Api.Controllers
{
var dtoOptions = new DtoOptions().AddClientFields(Request);
- MusicGenre item;
+ MusicGenre? item;
if (genreName.IndexOf(BaseItem.SlugChar, StringComparison.OrdinalIgnoreCase) != -1)
{
@@ -286,7 +160,7 @@ namespace Jellyfin.Api.Controllers
return _dtoService.GetBaseItemDto(item, dtoOptions);
}
- private T GetItemFromSlugName<T>(ILibraryManager libraryManager, string name, DtoOptions dtoOptions)
+ private T? GetItemFromSlugName<T>(ILibraryManager libraryManager, string name, DtoOptions dtoOptions)
where T : BaseItem, new()
{
var result = libraryManager.GetItemList(new InternalItemsQuery
diff --git a/Jellyfin.Api/Controllers/PackageController.cs b/Jellyfin.Api/Controllers/PackageController.cs
index 1d9de14d2..1f797d6bc 100644
--- a/Jellyfin.Api/Controllers/PackageController.cs
+++ b/Jellyfin.Api/Controllers/PackageController.cs
@@ -54,6 +54,11 @@ namespace Jellyfin.Api.Controllers
string.IsNullOrEmpty(assemblyGuid) ? default : Guid.Parse(assemblyGuid))
.FirstOrDefault();
+ if (result == null)
+ {
+ return NotFound();
+ }
+
return result;
}
@@ -149,12 +154,13 @@ namespace Jellyfin.Api.Controllers
/// <param name="repositoryInfos">The list of package repositories.</param>
/// <response code="204">Package repositories saved.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns>
- [HttpOptions("Repositories")]
+ [HttpPost("Repositories")]
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult SetRepositories([FromBody] List<RepositoryInfo> repositoryInfos)
{
_serverConfigurationManager.Configuration.PluginRepositories = repositoryInfos;
+ _serverConfigurationManager.SaveConfiguration();
return NoContent();
}
}
diff --git a/Jellyfin.Api/Controllers/PersonsController.cs b/Jellyfin.Api/Controllers/PersonsController.cs
index f173f75ba..6ac3e6417 100644
--- a/Jellyfin.Api/Controllers/PersonsController.cs
+++ b/Jellyfin.Api/Controllers/PersonsController.cs
@@ -1,6 +1,5 @@
using System;
using System.ComponentModel.DataAnnotations;
-using System.Globalization;
using System.Linq;
using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions;
@@ -28,6 +27,7 @@ namespace Jellyfin.Api.Controllers
private readonly ILibraryManager _libraryManager;
private readonly IDtoService _dtoService;
private readonly IUserManager _userManager;
+ private readonly IUserDataManager _userDataManager;
/// <summary>
/// Initializes a new instance of the <see cref="PersonsController"/> class.
@@ -35,221 +35,81 @@ namespace Jellyfin.Api.Controllers
/// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
/// <param name="dtoService">Instance of the <see cref="IDtoService"/> interface.</param>
/// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
+ /// <param name="userDataManager">Instance of the <see cref="IUserDataManager"/> interface.</param>
public PersonsController(
ILibraryManager libraryManager,
IDtoService dtoService,
- IUserManager userManager)
+ IUserManager userManager,
+ IUserDataManager userDataManager)
{
_libraryManager = libraryManager;
_dtoService = dtoService;
_userManager = userManager;
+ _userDataManager = userDataManager;
}
/// <summary>
- /// Gets all persons from a given item, folder, or the entire library.
+ /// Gets all persons.
/// </summary>
- /// <param name="minCommunityRating">Optional filter by minimum community rating.</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="searchTerm">The search term.</param>
- /// <param name="parentId">Specify this to localize the search to a specific item or folder. Omit to use the root.</param>
- /// <param name="fields">Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines.</param>
- /// <param name="excludeItemTypes">Optional. If specified, results will be filtered out based on item type. This allows multiple, comma delimited.</param>
- /// <param name="includeItemTypes">Optional. If specified, results will be filtered in based on item type. This allows multiple, comma delimited.</param>
- /// <param name="filters">Optional. Specify additional filters to apply. This allows multiple, comma delimited. Options: IsFolder, IsNotFolder, IsUnplayed, IsPlayed, IsFavorite, IsResumable, Likes, Dislikes.</param>
- /// <param name="isFavorite">Optional filter by items that are marked as favorite, or not.</param>
- /// <param name="mediaTypes">Optional filter by MediaType. Allows multiple, comma delimited.</param>
- /// <param name="genres">Optional. If specified, results will be filtered based on genre. This allows multiple, pipe delimited.</param>
- /// <param name="genreIds">Optional. If specified, results will be filtered based on genre id. This allows multiple, pipe delimited.</param>
- /// <param name="officialRatings">Optional. If specified, results will be filtered based on OfficialRating. This allows multiple, pipe delimited.</param>
- /// <param name="tags">Optional. If specified, results will be filtered based on tag. This allows multiple, pipe delimited.</param>
- /// <param name="years">Optional. If specified, results will be filtered based on production year. This allows multiple, comma delimited.</param>
+ /// <param name="fields">Optional. Specify additional fields of information to return in the output.</param>
+ /// <param name="filters">Optional. Specify additional filters to apply.</param>
+ /// <param name="isFavorite">Optional filter by items that are marked as favorite, or not. userId is required.</param>
/// <param name="enableUserData">Optional, include user data.</param>
/// <param name="imageTypeLimit">Optional, the max number of images to return, per image type.</param>
/// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
- /// <param name="person">Optional. If specified, results will be filtered to include only those containing the specified person.</param>
- /// <param name="personIds">Optional. If specified, results will be filtered to include only those containing the specified person id.</param>
- /// <param name="personTypes">Optional. If specified, along with Person, results will be filtered to include only those containing the specified person and PersonType. Allows multiple, comma-delimited.</param>
- /// <param name="studios">Optional. If specified, results will be filtered based on studio. This allows multiple, pipe delimited.</param>
- /// <param name="studioIds">Optional. If specified, results will be filtered based on studio id. This allows multiple, pipe delimited.</param>
+ /// <param name="excludePersonTypes">Optional. If specified results will be filtered to exclude those containing the specified PersonType. Allows multiple, comma-delimited.</param>
+ /// <param name="personTypes">Optional. If specified results will be filtered to include only those containing the specified PersonType. Allows multiple, comma-delimited.</param>
+ /// <param name="appearsInItemId">Optional. If specified, person results will be filtered on items related to said persons.</param>
/// <param name="userId">User id.</param>
- /// <param name="nameStartsWithOrGreater">Optional filter by items whose name is sorted equally or greater than a given input string.</param>
- /// <param name="nameStartsWith">Optional filter by items whose name is sorted equally than a given input string.</param>
- /// <param name="nameLessThan">Optional filter by items whose name is equally or lesser than a given input string.</param>
/// <param name="enableImages">Optional, include image information in output.</param>
- /// <param name="enableTotalRecordCount">Optional. Include total record count.</param>
/// <response code="200">Persons returned.</response>
/// <returns>An <see cref="OkResult"/> containing the queryresult of persons.</returns>
[HttpGet]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QueryResult<BaseItemDto>> GetPersons(
- [FromQuery] double? minCommunityRating,
- [FromQuery] int? startIndex,
[FromQuery] int? limit,
[FromQuery] string? searchTerm,
- [FromQuery] string? parentId,
- [FromQuery] string? fields,
- [FromQuery] string? excludeItemTypes,
- [FromQuery] string? includeItemTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters,
[FromQuery] bool? isFavorite,
- [FromQuery] string? mediaTypes,
- [FromQuery] string? genres,
- [FromQuery] string? genreIds,
- [FromQuery] string? officialRatings,
- [FromQuery] string? tags,
- [FromQuery] string? years,
[FromQuery] bool? enableUserData,
[FromQuery] int? imageTypeLimit,
- [FromQuery] ImageType[] enableImageTypes,
- [FromQuery] string? person,
- [FromQuery] string? personIds,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes,
+ [FromQuery] string? excludePersonTypes,
[FromQuery] string? personTypes,
- [FromQuery] string? studios,
- [FromQuery] string? studioIds,
+ [FromQuery] string? appearsInItemId,
[FromQuery] Guid? userId,
- [FromQuery] string? nameStartsWithOrGreater,
- [FromQuery] string? nameStartsWith,
- [FromQuery] string? nameLessThan,
- [FromQuery] bool? enableImages = true,
- [FromQuery] bool enableTotalRecordCount = true)
+ [FromQuery] bool? enableImages = true)
{
- var dtoOptions = new DtoOptions()
- .AddItemFields(fields)
+ var dtoOptions = new DtoOptions { Fields = fields }
.AddClientFields(Request)
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
User? user = null;
- BaseItem parentItem;
if (userId.HasValue && !userId.Equals(Guid.Empty))
{
user = _userManager.GetUserById(userId.Value);
- parentItem = string.IsNullOrEmpty(parentId) ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(parentId);
- }
- else
- {
- parentItem = string.IsNullOrEmpty(parentId) ? _libraryManager.RootFolder : _libraryManager.GetItemById(parentId);
}
- var query = new InternalItemsQuery(user)
+ var isFavoriteInFilters = filters.Any(f => f == ItemFilter.IsFavorite);
+ var peopleItems = _libraryManager.GetPeopleItems(new InternalPeopleQuery
{
- ExcludeItemTypes = RequestHelpers.Split(excludeItemTypes, ',', true),
- IncludeItemTypes = RequestHelpers.Split(includeItemTypes, ',', true),
- MediaTypes = RequestHelpers.Split(mediaTypes, ',', true),
- StartIndex = startIndex,
- Limit = limit,
- IsFavorite = isFavorite,
- NameLessThan = nameLessThan,
- NameStartsWith = nameStartsWith,
- NameStartsWithOrGreater = nameStartsWithOrGreater,
- Tags = RequestHelpers.Split(tags, '|', true),
- OfficialRatings = RequestHelpers.Split(officialRatings, '|', true),
- Genres = RequestHelpers.Split(genres, '|', true),
- GenreIds = RequestHelpers.GetGuids(genreIds),
- StudioIds = RequestHelpers.GetGuids(studioIds),
- Person = person,
- PersonIds = RequestHelpers.GetGuids(personIds),
PersonTypes = RequestHelpers.Split(personTypes, ',', true),
- Years = RequestHelpers.Split(years, ',', true).Select(y => Convert.ToInt32(y, CultureInfo.InvariantCulture)).ToArray(),
- MinCommunityRating = minCommunityRating,
- DtoOptions = dtoOptions,
- SearchTerm = searchTerm,
- EnableTotalRecordCount = enableTotalRecordCount
- };
-
- if (!string.IsNullOrWhiteSpace(parentId))
- {
- if (parentItem is Folder)
- {
- query.AncestorIds = new[] { new Guid(parentId) };
- }
- else
- {
- query.ItemIds = new[] { new Guid(parentId) };
- }
- }
-
- // Studios
- if (!string.IsNullOrEmpty(studios))
- {
- query.StudioIds = studios.Split('|')
- .Select(i =>
- {
- try
- {
- return _libraryManager.GetStudio(i);
- }
- catch
- {
- return null;
- }
- }).Where(i => i != null)
- .Select(i => i!.Id)
- .ToArray();
- }
-
- foreach (var filter in filters)
- {
- switch (filter)
- {
- case ItemFilter.Dislikes:
- query.IsLiked = false;
- break;
- case ItemFilter.IsFavorite:
- query.IsFavorite = true;
- break;
- case ItemFilter.IsFavoriteOrLikes:
- query.IsFavoriteOrLiked = true;
- break;
- case ItemFilter.IsFolder:
- query.IsFolder = true;
- break;
- case ItemFilter.IsNotFolder:
- query.IsFolder = false;
- break;
- case ItemFilter.IsPlayed:
- query.IsPlayed = true;
- break;
- case ItemFilter.IsResumable:
- query.IsResumable = true;
- break;
- case ItemFilter.IsUnplayed:
- query.IsPlayed = false;
- break;
- case ItemFilter.Likes:
- query.IsLiked = true;
- break;
- }
- }
-
- var result = new QueryResult<(BaseItem, ItemCounts)>();
-
- var dtos = result.Items.Select(i =>
- {
- var (baseItem, counts) = i;
- var dto = _dtoService.GetItemByNameDto(baseItem, dtoOptions, null, user);
-
- if (!string.IsNullOrWhiteSpace(includeItemTypes))
- {
- dto.ChildCount = counts.ItemCount;
- dto.ProgramCount = counts.ProgramCount;
- dto.SeriesCount = counts.SeriesCount;
- dto.EpisodeCount = counts.EpisodeCount;
- dto.MovieCount = counts.MovieCount;
- dto.TrailerCount = counts.TrailerCount;
- dto.AlbumCount = counts.AlbumCount;
- dto.SongCount = counts.SongCount;
- dto.ArtistCount = counts.ArtistCount;
- }
-
- return dto;
+ ExcludePersonTypes = RequestHelpers.Split(excludePersonTypes, ',', true),
+ NameContains = searchTerm,
+ User = user,
+ IsFavorite = !isFavorite.HasValue && isFavoriteInFilters ? true : isFavorite,
+ AppearsInItemId = string.IsNullOrEmpty(appearsInItemId) ? Guid.Empty : Guid.Parse(appearsInItemId),
+ Limit = limit ?? 0
});
return new QueryResult<BaseItemDto>
{
- Items = dtos.ToArray(),
- TotalRecordCount = result.TotalRecordCount
+ Items = peopleItems.Select(person => _dtoService.GetItemByNameDto(person, dtoOptions, null, user)).ToArray(),
+ TotalRecordCount = peopleItems.Count
};
}
diff --git a/Jellyfin.Api/Controllers/PlaylistsController.cs b/Jellyfin.Api/Controllers/PlaylistsController.cs
index 0419b2436..4b3d8d3d3 100644
--- a/Jellyfin.Api/Controllers/PlaylistsController.cs
+++ b/Jellyfin.Api/Controllers/PlaylistsController.cs
@@ -5,6 +5,7 @@ using System.Threading.Tasks;
using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers;
+using Jellyfin.Api.ModelBinders;
using Jellyfin.Api.Models.PlaylistDtos;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Library;
@@ -134,7 +135,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="userId">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="fields">Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines.</param>
+ /// <param name="fields">Optional. Specify additional fields of information to return in the output.</param>
/// <param name="enableImages">Optional. Include image information in output.</param>
/// <param name="enableUserData">Optional. Include user data.</param>
/// <param name="imageTypeLimit">Optional. The max number of images to return, per image type.</param>
@@ -148,11 +149,11 @@ namespace Jellyfin.Api.Controllers
[FromQuery, Required] Guid userId,
[FromQuery] int? startIndex,
[FromQuery] int? limit,
- [FromQuery] string? fields,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
[FromQuery] bool? enableImages,
[FromQuery] bool? enableUserData,
[FromQuery] int? imageTypeLimit,
- [FromQuery] ImageType[] enableImageTypes)
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes)
{
var playlist = (Playlist)_libraryManager.GetItemById(playlistId);
if (playlist == null)
@@ -176,8 +177,7 @@ namespace Jellyfin.Api.Controllers
items = items.Take(limit.Value).ToArray();
}
- var dtoOptions = new DtoOptions()
- .AddItemFields(fields)
+ var dtoOptions = new DtoOptions { Fields = fields }
.AddClientFields(Request)
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
diff --git a/Jellyfin.Api/Controllers/RemoteImageController.cs b/Jellyfin.Api/Controllers/RemoteImageController.cs
index 5f095443b..5284888d8 100644
--- a/Jellyfin.Api/Controllers/RemoteImageController.cs
+++ b/Jellyfin.Api/Controllers/RemoteImageController.cs
@@ -157,9 +157,9 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesImageFile]
- public async Task<ActionResult> GetRemoteImage([FromQuery, Required] string imageUrl)
+ public async Task<ActionResult> GetRemoteImage([FromQuery, Required] Uri imageUrl)
{
- var urlHash = imageUrl.GetMD5();
+ var urlHash = imageUrl.ToString().GetMD5();
var pointerCachePath = GetFullCachePath(urlHash.ToString());
string? contentPath = null;
@@ -245,17 +245,25 @@ namespace Jellyfin.Api.Controllers
/// <param name="urlHash">The URL hash.</param>
/// <param name="pointerCachePath">The pointer cache path.</param>
/// <returns>Task.</returns>
- private async Task DownloadImage(string url, Guid urlHash, string pointerCachePath)
+ private async Task DownloadImage(Uri url, Guid urlHash, string pointerCachePath)
{
var httpClient = _httpClientFactory.CreateClient(NamedClient.Default);
using var response = await httpClient.GetAsync(url).ConfigureAwait(false);
- var ext = response.Content.Headers.ContentType.MediaType.Split('/').Last();
+ if (response.Content.Headers.ContentType?.MediaType == null)
+ {
+ throw new ResourceNotFoundException(nameof(response.Content.Headers.ContentType));
+ }
+
+ var ext = response.Content.Headers.ContentType.MediaType.Split('/')[^1];
var fullCachePath = GetFullCachePath(urlHash + "." + ext);
- Directory.CreateDirectory(Path.GetDirectoryName(fullCachePath));
+ var fullCacheDirectory = Path.GetDirectoryName(fullCachePath) ?? throw new ResourceNotFoundException($"Provided path ({fullCachePath}) is not valid.");
+ Directory.CreateDirectory(fullCacheDirectory);
await using var fileStream = new FileStream(fullCachePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, true);
await response.Content.CopyToAsync(fileStream).ConfigureAwait(false);
- Directory.CreateDirectory(Path.GetDirectoryName(pointerCachePath));
+
+ var pointerCacheDirectory = Path.GetDirectoryName(pointerCachePath) ?? throw new ArgumentException($"Provided path ({pointerCachePath}) is not valid.", nameof(pointerCachePath));
+ Directory.CreateDirectory(pointerCacheDirectory);
await System.IO.File.WriteAllTextAsync(pointerCachePath, fullCachePath, CancellationToken.None)
.ConfigureAwait(false);
}
diff --git a/Jellyfin.Api/Controllers/SearchController.cs b/Jellyfin.Api/Controllers/SearchController.cs
index 62c870cb1..e75f0d06b 100644
--- a/Jellyfin.Api/Controllers/SearchController.cs
+++ b/Jellyfin.Api/Controllers/SearchController.cs
@@ -260,7 +260,7 @@ namespace Jellyfin.Api.Controllers
}
}
- private T GetParentWithImage<T>(BaseItem item, ImageType type)
+ private T? GetParentWithImage<T>(BaseItem item, ImageType type)
where T : BaseItem
{
return item.GetParents().OfType<T>().FirstOrDefault(i => i.HasImage(type));
diff --git a/Jellyfin.Api/Controllers/StudiosController.cs b/Jellyfin.Api/Controllers/StudiosController.cs
index 94eb3f7fa..27dcd51bc 100644
--- a/Jellyfin.Api/Controllers/StudiosController.cs
+++ b/Jellyfin.Api/Controllers/StudiosController.cs
@@ -1,6 +1,5 @@
using System;
using System.ComponentModel.DataAnnotations;
-using System.Linq;
using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers;
@@ -47,30 +46,17 @@ namespace Jellyfin.Api.Controllers
/// <summary>
/// Gets all studios from a given item, folder, or the entire library.
/// </summary>
- /// <param name="minCommunityRating">Optional filter by minimum community rating.</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="searchTerm">Optional. Search term.</param>
/// <param name="parentId">Specify this to localize the search to a specific item or folder. Omit to use the root.</param>
- /// <param name="fields">Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines.</param>
+ /// <param name="fields">Optional. Specify additional fields of information to return in the output.</param>
/// <param name="excludeItemTypes">Optional. If specified, results will be filtered out based on item type. This allows multiple, comma delimited.</param>
/// <param name="includeItemTypes">Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimited.</param>
- /// <param name="filters">Optional. Specify additional filters to apply.</param>
/// <param name="isFavorite">Optional filter by items that are marked as favorite, or not.</param>
- /// <param name="mediaTypes">Optional filter by MediaType. Allows multiple, comma delimited.</param>
- /// <param name="genres">Optional. If specified, results will be filtered based on genre. This allows multiple, pipe delimited.</param>
- /// <param name="genreIds">Optional. If specified, results will be filtered based on genre id. This allows multiple, pipe delimited.</param>
- /// <param name="officialRatings">Optional. If specified, results will be filtered based on OfficialRating. This allows multiple, pipe delimited.</param>
- /// <param name="tags">Optional. If specified, results will be filtered based on tag. This allows multiple, pipe delimited.</param>
- /// <param name="years">Optional. If specified, results will be filtered based on production year. This allows multiple, comma delimited.</param>
/// <param name="enableUserData">Optional, include user data.</param>
/// <param name="imageTypeLimit">Optional, the max number of images to return, per image type.</param>
/// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
- /// <param name="person">Optional. If specified, results will be filtered to include only those containing the specified person.</param>
- /// <param name="personIds">Optional. If specified, results will be filtered to include only those containing the specified person ids.</param>
- /// <param name="personTypes">Optional. If specified, along with Person, results will be filtered to include only those containing the specified person and PersonType. Allows multiple, comma-delimited.</param>
- /// <param name="studios">Optional. If specified, results will be filtered based on studio. This allows multiple, pipe delimited.</param>
- /// <param name="studioIds">Optional. If specified, results will be filtered based on studio id. This allows multiple, pipe delimited.</param>
/// <param name="userId">User id.</param>
/// <param name="nameStartsWithOrGreater">Optional filter by items whose name is sorted equally or greater than a given input string.</param>
/// <param name="nameStartsWith">Optional filter by items whose name is sorted equally than a given input string.</param>
@@ -82,30 +68,17 @@ namespace Jellyfin.Api.Controllers
[HttpGet]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QueryResult<BaseItemDto>> GetStudios(
- [FromQuery] double? minCommunityRating,
[FromQuery] int? startIndex,
[FromQuery] int? limit,
[FromQuery] string? searchTerm,
[FromQuery] string? parentId,
- [FromQuery] string? fields,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
[FromQuery] string? excludeItemTypes,
[FromQuery] string? includeItemTypes,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters,
[FromQuery] bool? isFavorite,
- [FromQuery] string? mediaTypes,
- [FromQuery] string? genres,
- [FromQuery] string? genreIds,
- [FromQuery] string? officialRatings,
- [FromQuery] string? tags,
- [FromQuery] string? years,
[FromQuery] bool? enableUserData,
[FromQuery] int? imageTypeLimit,
- [FromQuery] ImageType[] enableImageTypes,
- [FromQuery] string? person,
- [FromQuery] string? personIds,
- [FromQuery] string? personTypes,
- [FromQuery] string? studios,
- [FromQuery] string? studioIds,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes,
[FromQuery] Guid? userId,
[FromQuery] string? nameStartsWithOrGreater,
[FromQuery] string? nameStartsWith,
@@ -113,49 +86,27 @@ namespace Jellyfin.Api.Controllers
[FromQuery] bool? enableImages = true,
[FromQuery] bool enableTotalRecordCount = true)
{
- var dtoOptions = new DtoOptions()
- .AddItemFields(fields)
+ var dtoOptions = new DtoOptions { Fields = fields }
.AddClientFields(Request)
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
- User? user = null;
- BaseItem parentItem;
+ User? user = userId.HasValue && userId != Guid.Empty ? _userManager.GetUserById(userId.Value) : null;
- if (userId.HasValue && !userId.Equals(Guid.Empty))
- {
- user = _userManager.GetUserById(userId.Value);
- parentItem = string.IsNullOrEmpty(parentId) ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(parentId);
- }
- else
- {
- parentItem = string.IsNullOrEmpty(parentId) ? _libraryManager.RootFolder : _libraryManager.GetItemById(parentId);
- }
+ var parentItem = _libraryManager.GetParentItem(parentId, userId);
var excludeItemTypesArr = RequestHelpers.Split(excludeItemTypes, ',', true);
var includeItemTypesArr = RequestHelpers.Split(includeItemTypes, ',', true);
- var mediaTypesArr = RequestHelpers.Split(mediaTypes, ',', true);
var query = new InternalItemsQuery(user)
{
ExcludeItemTypes = excludeItemTypesArr,
IncludeItemTypes = includeItemTypesArr,
- MediaTypes = mediaTypesArr,
StartIndex = startIndex,
Limit = limit,
IsFavorite = isFavorite,
NameLessThan = nameLessThan,
NameStartsWith = nameStartsWith,
NameStartsWithOrGreater = nameStartsWithOrGreater,
- Tags = RequestHelpers.Split(tags, '|', true),
- OfficialRatings = RequestHelpers.Split(officialRatings, '|', true),
- Genres = RequestHelpers.Split(genres, '|', true),
- GenreIds = RequestHelpers.GetGuids(genreIds),
- StudioIds = RequestHelpers.GetGuids(studioIds),
- Person = person,
- PersonIds = RequestHelpers.GetGuids(personIds),
- PersonTypes = RequestHelpers.Split(personTypes, ',', true),
- Years = RequestHelpers.Split(years, ',', true).Select(int.Parse).ToArray(),
- MinCommunityRating = minCommunityRating,
DtoOptions = dtoOptions,
SearchTerm = searchTerm,
EnableTotalRecordCount = enableTotalRecordCount
@@ -173,84 +124,9 @@ namespace Jellyfin.Api.Controllers
}
}
- // Studios
- if (!string.IsNullOrEmpty(studios))
- {
- query.StudioIds = studios.Split('|').Select(i =>
- {
- try
- {
- return _libraryManager.GetStudio(i);
- }
- catch
- {
- return null;
- }
- }).Where(i => i != null).Select(i => i!.Id)
- .ToArray();
- }
-
- foreach (var filter in filters)
- {
- switch (filter)
- {
- case ItemFilter.Dislikes:
- query.IsLiked = false;
- break;
- case ItemFilter.IsFavorite:
- query.IsFavorite = true;
- break;
- case ItemFilter.IsFavoriteOrLikes:
- query.IsFavoriteOrLiked = true;
- break;
- case ItemFilter.IsFolder:
- query.IsFolder = true;
- break;
- case ItemFilter.IsNotFolder:
- query.IsFolder = false;
- break;
- case ItemFilter.IsPlayed:
- query.IsPlayed = true;
- break;
- case ItemFilter.IsResumable:
- query.IsResumable = true;
- break;
- case ItemFilter.IsUnplayed:
- query.IsPlayed = false;
- break;
- case ItemFilter.Likes:
- query.IsLiked = true;
- break;
- }
- }
-
- var result = new QueryResult<(BaseItem, ItemCounts)>();
- var dtos = result.Items.Select(i =>
- {
- var (baseItem, itemCounts) = i;
- var dto = _dtoService.GetItemByNameDto(baseItem, dtoOptions, null, user);
-
- if (!string.IsNullOrWhiteSpace(includeItemTypes))
- {
- dto.ChildCount = itemCounts.ItemCount;
- dto.ProgramCount = itemCounts.ProgramCount;
- dto.SeriesCount = itemCounts.SeriesCount;
- dto.EpisodeCount = itemCounts.EpisodeCount;
- dto.MovieCount = itemCounts.MovieCount;
- dto.TrailerCount = itemCounts.TrailerCount;
- dto.AlbumCount = itemCounts.AlbumCount;
- dto.SongCount = itemCounts.SongCount;
- dto.ArtistCount = itemCounts.ArtistCount;
- }
-
- return dto;
- });
-
- return new QueryResult<BaseItemDto>
- {
- Items = dtos.ToArray(),
- TotalRecordCount = result.TotalRecordCount
- };
+ var result = _libraryManager.GetStudios(query);
+ var shouldIncludeItemTypes = !string.IsNullOrEmpty(includeItemTypes);
+ return RequestHelpers.CreateQueryResult(result, dtoOptions, _dtoService, shouldIncludeItemTypes, user);
}
/// <summary>
diff --git a/Jellyfin.Api/Controllers/SubtitleController.cs b/Jellyfin.Api/Controllers/SubtitleController.cs
index cc682ed54..a01ae31a0 100644
--- a/Jellyfin.Api/Controllers/SubtitleController.cs
+++ b/Jellyfin.Api/Controllers/SubtitleController.cs
@@ -11,6 +11,9 @@ using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Api.Attributes;
using Jellyfin.Api.Constants;
+using Jellyfin.Api.Models.SubtitleDtos;
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding;
@@ -21,6 +24,7 @@ using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Net;
using MediaBrowser.Model.Providers;
+using MediaBrowser.Model.Subtitles;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
@@ -34,6 +38,7 @@ namespace Jellyfin.Api.Controllers
[Route("")]
public class SubtitleController : BaseJellyfinApiController
{
+ private readonly IServerConfigurationManager _serverConfigurationManager;
private readonly ILibraryManager _libraryManager;
private readonly ISubtitleManager _subtitleManager;
private readonly ISubtitleEncoder _subtitleEncoder;
@@ -46,6 +51,7 @@ namespace Jellyfin.Api.Controllers
/// <summary>
/// Initializes a new instance of the <see cref="SubtitleController"/> class.
/// </summary>
+ /// <param name="serverConfigurationManager">Instance of <see cref="IServerConfigurationManager"/> interface.</param>
/// <param name="libraryManager">Instance of <see cref="ILibraryManager"/> interface.</param>
/// <param name="subtitleManager">Instance of <see cref="ISubtitleManager"/> interface.</param>
/// <param name="subtitleEncoder">Instance of <see cref="ISubtitleEncoder"/> interface.</param>
@@ -55,6 +61,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="authContext">Instance of <see cref="IAuthorizationContext"/> interface.</param>
/// <param name="logger">Instance of <see cref="ILogger{SubtitleController}"/> interface.</param>
public SubtitleController(
+ IServerConfigurationManager serverConfigurationManager,
ILibraryManager libraryManager,
ISubtitleManager subtitleManager,
ISubtitleEncoder subtitleEncoder,
@@ -64,6 +71,7 @@ namespace Jellyfin.Api.Controllers
IAuthorizationContext authContext,
ILogger<SubtitleController> logger)
{
+ _serverConfigurationManager = serverConfigurationManager;
_libraryManager = libraryManager;
_subtitleManager = subtitleManager;
_subtitleEncoder = subtitleEncoder;
@@ -320,6 +328,33 @@ namespace Jellyfin.Api.Controllers
}
/// <summary>
+ /// Upload an external subtitle file.
+ /// </summary>
+ /// <param name="itemId">The item the subtitle belongs to.</param>
+ /// <param name="body">The request body.</param>
+ /// <response code="204">Subtitle uploaded.</response>
+ /// <returns>A <see cref="NoContentResult"/>.</returns>
+ [HttpPost("Videos/{itemId}/Subtitles")]
+ public async Task<ActionResult> UploadSubtitle(
+ [FromRoute, Required] Guid itemId,
+ [FromBody, Required] UploadSubtitleDto body)
+ {
+ var video = (Video)_libraryManager.GetItemById(itemId);
+ var data = Convert.FromBase64String(body.Data);
+ await using var memoryStream = new MemoryStream(data);
+ await _subtitleManager.UploadSubtitle(
+ video,
+ new SubtitleResponse
+ {
+ Format = body.Format,
+ Language = body.Language,
+ IsForced = body.IsForced,
+ Stream = memoryStream
+ }).ConfigureAwait(false);
+ return NoContent();
+ }
+
+ /// <summary>
/// Encodes a subtitle in the specified format.
/// </summary>
/// <param name="id">The media id.</param>
@@ -351,5 +386,95 @@ namespace Jellyfin.Api.Controllers
copyTimestamps,
CancellationToken.None);
}
+
+ /// <summary>
+ /// Gets a list of available fallback font files.
+ /// </summary>
+ /// <response code="200">Information retrieved.</response>
+ /// <returns>An array of <see cref="FontFile"/> with the available font files.</returns>
+ [HttpGet("FallbackFont/Fonts")]
+ [Authorize(Policy = Policies.DefaultAuthorization)]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ public IEnumerable<FontFile> GetFallbackFontList()
+ {
+ var encodingOptions = _serverConfigurationManager.GetEncodingOptions();
+ var fallbackFontPath = encodingOptions.FallbackFontPath;
+
+ if (!string.IsNullOrEmpty(fallbackFontPath))
+ {
+ var files = _fileSystem.GetFiles(fallbackFontPath, new[] { ".woff", ".woff2", ".ttf", ".otf" }, false, false);
+ var fontFiles = files
+ .Select(i => new FontFile
+ {
+ Name = i.Name,
+ Size = i.Length,
+ DateCreated = _fileSystem.GetCreationTimeUtc(i),
+ DateModified = _fileSystem.GetLastWriteTimeUtc(i)
+ })
+ .OrderBy(i => i.Size)
+ .ThenBy(i => i.Name)
+ .ThenByDescending(i => i.DateModified)
+ .ThenByDescending(i => i.DateCreated);
+ // max total size 20M
+ const int MaxSize = 20971520;
+ var sizeCounter = 0L;
+ foreach (var fontFile in fontFiles)
+ {
+ sizeCounter += fontFile.Size;
+ if (sizeCounter >= MaxSize)
+ {
+ _logger.LogWarning("Some fonts will not be sent due to size limitations");
+ yield break;
+ }
+
+ yield return fontFile;
+ }
+ }
+ else
+ {
+ _logger.LogWarning("The path of fallback font folder has not been set");
+ encodingOptions.EnableFallbackFont = false;
+ }
+ }
+
+ /// <summary>
+ /// Gets a fallback font file.
+ /// </summary>
+ /// <param name="name">The name of the fallback font file to get.</param>
+ /// <response code="200">Fallback font file retrieved.</response>
+ /// <returns>The fallback font file.</returns>
+ [HttpGet("FallbackFont/Fonts/{name}")]
+ [Authorize(Policy = Policies.DefaultAuthorization)]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ public ActionResult GetFallbackFont([FromRoute, Required] string name)
+ {
+ var encodingOptions = _serverConfigurationManager.GetEncodingOptions();
+ var fallbackFontPath = encodingOptions.FallbackFontPath;
+
+ if (!string.IsNullOrEmpty(fallbackFontPath))
+ {
+ var fontFile = _fileSystem.GetFiles(fallbackFontPath)
+ .First(i => string.Equals(i.Name, name, StringComparison.OrdinalIgnoreCase));
+ var fileSize = fontFile?.Length;
+
+ if (fontFile != null && fileSize != null && fileSize > 0)
+ {
+ _logger.LogDebug("Fallback font size is {fileSize} Bytes", fileSize);
+ return PhysicalFile(fontFile.FullName, MimeTypes.GetMimeType(fontFile.FullName));
+ }
+ else
+ {
+ _logger.LogWarning("The selected font is null or empty");
+ }
+ }
+ else
+ {
+ _logger.LogWarning("The path of fallback font folder has not been set");
+ encodingOptions.EnableFallbackFont = false;
+ }
+
+ // returning HTTP 204 will break the SubtitlesOctopus
+ return Ok();
+ }
}
}
diff --git a/Jellyfin.Api/Controllers/TrailersController.cs b/Jellyfin.Api/Controllers/TrailersController.cs
index 281c0016e..d78adcbcd 100644
--- a/Jellyfin.Api/Controllers/TrailersController.cs
+++ b/Jellyfin.Api/Controllers/TrailersController.cs
@@ -146,12 +146,12 @@ namespace Jellyfin.Api.Controllers
[FromQuery] string? searchTerm,
[FromQuery] string? sortOrder,
[FromQuery] string? parentId,
- [FromQuery] string? fields,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
[FromQuery] string? excludeItemTypes,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters,
[FromQuery] bool? isFavorite,
[FromQuery] string? mediaTypes,
- [FromQuery] ImageType[] imageTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] imageTypes,
[FromQuery] string? sortBy,
[FromQuery] bool? isPlayed,
[FromQuery] string? genres,
@@ -160,7 +160,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] string? years,
[FromQuery] bool? enableUserData,
[FromQuery] int? imageTypeLimit,
- [FromQuery] ImageType[] enableImageTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes,
[FromQuery] string? person,
[FromQuery] string? personIds,
[FromQuery] string? personTypes,
diff --git a/Jellyfin.Api/Controllers/TvShowsController.cs b/Jellyfin.Api/Controllers/TvShowsController.cs
index 49a6c386f..6fd154836 100644
--- a/Jellyfin.Api/Controllers/TvShowsController.cs
+++ b/Jellyfin.Api/Controllers/TvShowsController.cs
@@ -5,6 +5,7 @@ using System.Globalization;
using System.Linq;
using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions;
+using Jellyfin.Api.ModelBinders;
using Jellyfin.Data.Enums;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Dto;
@@ -58,7 +59,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="userId">The user id of the user to get the next up episodes for.</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="fields">Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls.</param>
+ /// <param name="fields">Optional. Specify additional fields of information to return in the output.</param>
/// <param name="seriesId">Optional. Filter by series id.</param>
/// <param name="parentId">Optional. Specify this to localize the search to a specific item or folder. Omit to use the root.</param>
/// <param name="enableImges">Optional. Include image information in output.</param>
@@ -73,17 +74,16 @@ namespace Jellyfin.Api.Controllers
[FromQuery] Guid? userId,
[FromQuery] int? startIndex,
[FromQuery] int? limit,
- [FromQuery] string? fields,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
[FromQuery] string? seriesId,
[FromQuery] string? parentId,
[FromQuery] bool? enableImges,
[FromQuery] int? imageTypeLimit,
- [FromQuery] ImageType[] enableImageTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes,
[FromQuery] bool? enableUserData,
[FromQuery] bool enableTotalRecordCount = true)
{
- var options = new DtoOptions()
- .AddItemFields(fields!)
+ var options = new DtoOptions { Fields = fields }
.AddClientFields(Request)
.AddAdditionalDtoOptions(enableImges, enableUserData, imageTypeLimit, enableImageTypes!);
@@ -118,7 +118,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="userId">The user id of the user to get the upcoming episodes for.</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="fields">Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls.</param>
+ /// <param name="fields">Optional. Specify additional fields of information to return in the output.</param>
/// <param name="parentId">Optional. Specify this to localize the search to a specific item or folder. Omit to use the root.</param>
/// <param name="enableImges">Optional. Include image information in output.</param>
/// <param name="imageTypeLimit">Optional. The max number of images to return, per image type.</param>
@@ -131,11 +131,11 @@ namespace Jellyfin.Api.Controllers
[FromQuery] Guid? userId,
[FromQuery] int? startIndex,
[FromQuery] int? limit,
- [FromQuery] string? fields,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
[FromQuery] string? parentId,
[FromQuery] bool? enableImges,
[FromQuery] int? imageTypeLimit,
- [FromQuery] ImageType[] enableImageTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes,
[FromQuery] bool? enableUserData)
{
var user = userId.HasValue && !userId.Equals(Guid.Empty)
@@ -146,8 +146,7 @@ namespace Jellyfin.Api.Controllers
var parentIdGuid = string.IsNullOrWhiteSpace(parentId) ? Guid.Empty : new Guid(parentId);
- var options = new DtoOptions()
- .AddItemFields(fields!)
+ var options = new DtoOptions { Fields = fields }
.AddClientFields(Request)
.AddAdditionalDtoOptions(enableImges, enableUserData, imageTypeLimit, enableImageTypes!);
@@ -197,7 +196,7 @@ namespace Jellyfin.Api.Controllers
public ActionResult<QueryResult<BaseItemDto>> GetEpisodes(
[FromRoute, Required] string seriesId,
[FromQuery] Guid? userId,
- [FromQuery] string? fields,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
[FromQuery] int? season,
[FromQuery] string? seasonId,
[FromQuery] bool? isMissing,
@@ -207,7 +206,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? limit,
[FromQuery] bool? enableImages,
[FromQuery] int? imageTypeLimit,
- [FromQuery] ImageType[] enableImageTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes,
[FromQuery] bool? enableUserData,
[FromQuery] string? sortBy)
{
@@ -217,8 +216,7 @@ namespace Jellyfin.Api.Controllers
List<BaseItem> episodes;
- var dtoOptions = new DtoOptions()
- .AddItemFields(fields!)
+ var dtoOptions = new DtoOptions { Fields = fields }
.AddClientFields(Request)
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes!);
@@ -320,13 +318,13 @@ namespace Jellyfin.Api.Controllers
public ActionResult<QueryResult<BaseItemDto>> GetSeasons(
[FromRoute, Required] string seriesId,
[FromQuery] Guid? userId,
- [FromQuery] string? fields,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
[FromQuery] bool? isSpecialSeason,
[FromQuery] bool? isMissing,
[FromQuery] string? adjacentTo,
[FromQuery] bool? enableImages,
[FromQuery] int? imageTypeLimit,
- [FromQuery] ImageType[] enableImageTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes,
[FromQuery] bool? enableUserData)
{
var user = userId.HasValue && !userId.Equals(Guid.Empty)
@@ -345,8 +343,7 @@ namespace Jellyfin.Api.Controllers
AdjacentTo = adjacentTo
});
- var dtoOptions = new DtoOptions()
- .AddItemFields(fields)
+ var dtoOptions = new DtoOptions { Fields = fields }
.AddClientFields(Request)
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes!);
diff --git a/Jellyfin.Api/Controllers/UserLibraryController.cs b/Jellyfin.Api/Controllers/UserLibraryController.cs
index a52af1781..cfd851129 100644
--- a/Jellyfin.Api/Controllers/UserLibraryController.cs
+++ b/Jellyfin.Api/Controllers/UserLibraryController.cs
@@ -7,6 +7,7 @@ using System.Threading.Tasks;
using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers;
+using Jellyfin.Api.ModelBinders;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
@@ -251,7 +252,7 @@ namespace Jellyfin.Api.Controllers
/// </summary>
/// <param name="userId">User id.</param>
/// <param name="parentId">Specify this to localize the search to a specific item or folder. Omit to use the root.</param>
- /// <param name="fields">Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, SortName, Studios, Taglines.</param>
+ /// <param name="fields">Optional. Specify additional fields of information to return in the output.</param>
/// <param name="includeItemTypes">Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimeted.</param>
/// <param name="isPlayed">Filter by items that are played, or not.</param>
/// <param name="enableImages">Optional. include image information in output.</param>
@@ -267,12 +268,12 @@ namespace Jellyfin.Api.Controllers
public ActionResult<IEnumerable<BaseItemDto>> GetLatestMedia(
[FromRoute, Required] Guid userId,
[FromQuery] Guid? parentId,
- [FromQuery] string? fields,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
[FromQuery] string? includeItemTypes,
[FromQuery] bool? isPlayed,
[FromQuery] bool? enableImages,
[FromQuery] int? imageTypeLimit,
- [FromQuery] ImageType[] enableImageTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes,
[FromQuery] bool? enableUserData,
[FromQuery] int limit = 20,
[FromQuery] bool groupItems = true)
@@ -287,8 +288,7 @@ namespace Jellyfin.Api.Controllers
}
}
- var dtoOptions = new DtoOptions()
- .AddItemFields(fields)
+ var dtoOptions = new DtoOptions { Fields = fields }
.AddClientFields(Request)
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
diff --git a/Jellyfin.Api/Controllers/VideoAttachmentsController.cs b/Jellyfin.Api/Controllers/VideoAttachmentsController.cs
index 09a1c93e6..418c0c123 100644
--- a/Jellyfin.Api/Controllers/VideoAttachmentsController.cs
+++ b/Jellyfin.Api/Controllers/VideoAttachmentsController.cs
@@ -46,7 +46,7 @@ namespace Jellyfin.Api.Controllers
[Produces(MediaTypeNames.Application.Octet)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
- public async Task<ActionResult<FileStreamResult>> GetAttachment(
+ public async Task<ActionResult> GetAttachment(
[FromRoute, Required] Guid videoId,
[FromRoute, Required] string mediaSourceId,
[FromRoute, Required] int index)
diff --git a/Jellyfin.Api/Controllers/VideoHlsController.cs b/Jellyfin.Api/Controllers/VideoHlsController.cs
index aebfeab67..b03402c1b 100644
--- a/Jellyfin.Api/Controllers/VideoHlsController.cs
+++ b/Jellyfin.Api/Controllers/VideoHlsController.cs
@@ -12,6 +12,7 @@ using Jellyfin.Api.Helpers;
using Jellyfin.Api.Models.PlaybackDtos;
using Jellyfin.Api.Models.StreamingDtos;
using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Dlna;
@@ -363,8 +364,9 @@ namespace Jellyfin.Api.Controllers
var inputModifier = _encodingHelper.GetInputModifier(state, _encodingOptions);
var mapArgs = state.IsOutputVideo ? _encodingHelper.GetMapArgs(state) : string.Empty;
+ var directory = Path.GetDirectoryName(outputPath) ?? throw new ArgumentException($"Provided path ({outputPath}) is not valid.", nameof(outputPath));
var outputFileNameWithoutExtension = Path.GetFileNameWithoutExtension(outputPath);
- var outputPrefix = Path.Combine(Path.GetDirectoryName(outputPath), outputFileNameWithoutExtension);
+ var outputPrefix = Path.Combine(directory, outputFileNameWithoutExtension);
var outputExtension = HlsHelpers.GetSegmentFileExtension(state.Request.SegmentContainer);
var outputTsArg = outputPrefix + "%d" + outputExtension;
diff --git a/Jellyfin.Api/Controllers/YearsController.cs b/Jellyfin.Api/Controllers/YearsController.cs
index 2c685309a..1b38e399d 100644
--- a/Jellyfin.Api/Controllers/YearsController.cs
+++ b/Jellyfin.Api/Controllers/YearsController.cs
@@ -5,6 +5,7 @@ using System.Linq;
using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers;
+using Jellyfin.Api.ModelBinders;
using Jellyfin.Data.Entities;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
@@ -51,7 +52,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="limit">Optional. The maximum number of records to return.</param>
/// <param name="sortOrder">Sort Order - Ascending,Descending.</param>
/// <param name="parentId">Specify this to localize the search to a specific item or folder. Omit to use the root.</param>
- /// <param name="fields">Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines.</param>
+ /// <param name="fields">Optional. Specify additional fields of information to return in the output.</param>
/// <param name="excludeItemTypes">Optional. If specified, results will be excluded based on item type. This allows multiple, comma delimited.</param>
/// <param name="includeItemTypes">Optional. If specified, results will be included based on item type. This allows multiple, comma delimited.</param>
/// <param name="mediaTypes">Optional. Filter by MediaType. Allows multiple, comma delimited.</param>
@@ -71,20 +72,19 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? limit,
[FromQuery] string? sortOrder,
[FromQuery] string? parentId,
- [FromQuery] string? fields,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
[FromQuery] string? excludeItemTypes,
[FromQuery] string? includeItemTypes,
[FromQuery] string? mediaTypes,
[FromQuery] string? sortBy,
[FromQuery] bool? enableUserData,
[FromQuery] int? imageTypeLimit,
- [FromQuery] ImageType[] enableImageTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes,
[FromQuery] Guid? userId,
[FromQuery] bool recursive = true,
[FromQuery] bool? enableImages = true)
{
- var dtoOptions = new DtoOptions()
- .AddItemFields(fields)
+ var dtoOptions = new DtoOptions { Fields = fields }
.AddClientFields(Request)
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);