aboutsummaryrefslogtreecommitdiff
path: root/Jellyfin.Api/Controllers
diff options
context:
space:
mode:
Diffstat (limited to 'Jellyfin.Api/Controllers')
-rw-r--r--Jellyfin.Api/Controllers/ArtistsController.cs14
-rw-r--r--Jellyfin.Api/Controllers/ConfigurationController.cs2
-rw-r--r--Jellyfin.Api/Controllers/DynamicHlsController.cs59
-rw-r--r--Jellyfin.Api/Controllers/GenresController.cs7
-rw-r--r--Jellyfin.Api/Controllers/InstantMixController.cs57
-rw-r--r--Jellyfin.Api/Controllers/ItemsController.cs6
-rw-r--r--Jellyfin.Api/Controllers/MediaInfoController.cs22
-rw-r--r--Jellyfin.Api/Controllers/MoviesController.cs11
-rw-r--r--Jellyfin.Api/Controllers/MusicGenresController.cs7
-rw-r--r--Jellyfin.Api/Controllers/PersonsController.cs6
-rw-r--r--Jellyfin.Api/Controllers/PluginsController.cs2
-rw-r--r--Jellyfin.Api/Controllers/QuickConnectController.cs79
-rw-r--r--Jellyfin.Api/Controllers/SubtitleController.cs2
-rw-r--r--Jellyfin.Api/Controllers/TrailersController.cs2
-rw-r--r--Jellyfin.Api/Controllers/TvShowsController.cs7
-rw-r--r--Jellyfin.Api/Controllers/UserLibraryController.cs2
-rw-r--r--Jellyfin.Api/Controllers/VideoHlsController.cs6
-rw-r--r--Jellyfin.Api/Controllers/VideosController.cs13
18 files changed, 127 insertions, 177 deletions
diff --git a/Jellyfin.Api/Controllers/ArtistsController.cs b/Jellyfin.Api/Controllers/ArtistsController.cs
index 4b2e5e7ea6..154a567020 100644
--- a/Jellyfin.Api/Controllers/ArtistsController.cs
+++ b/Jellyfin.Api/Controllers/ArtistsController.cs
@@ -77,6 +77,8 @@ namespace Jellyfin.Api.Controllers
/// <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="sortBy">Optional. Specify one or more sort orders, comma delimited.</param>
+ /// <param name="sortOrder">Sort Order - Ascending,Descending.</param>
/// <param name="enableImages">Optional, include image information in output.</param>
/// <param name="enableTotalRecordCount">Total record count.</param>
/// <response code="200">Artists returned.</response>
@@ -112,6 +114,8 @@ namespace Jellyfin.Api.Controllers
[FromQuery] string? nameStartsWithOrGreater,
[FromQuery] string? nameStartsWith,
[FromQuery] string? nameLessThan,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] sortBy,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SortOrder[] sortOrder,
[FromQuery] bool? enableImages = true,
[FromQuery] bool enableTotalRecordCount = true)
{
@@ -150,7 +154,8 @@ namespace Jellyfin.Api.Controllers
MinCommunityRating = minCommunityRating,
DtoOptions = dtoOptions,
SearchTerm = searchTerm,
- EnableTotalRecordCount = enableTotalRecordCount
+ EnableTotalRecordCount = enableTotalRecordCount,
+ OrderBy = RequestHelpers.GetOrderBy(sortBy, sortOrder)
};
if (parentId.HasValue)
@@ -276,6 +281,8 @@ namespace Jellyfin.Api.Controllers
/// <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="sortBy">Optional. Specify one or more sort orders, comma delimited.</param>
+ /// <param name="sortOrder">Sort Order - Ascending,Descending.</param>
/// <param name="enableImages">Optional, include image information in output.</param>
/// <param name="enableTotalRecordCount">Total record count.</param>
/// <response code="200">Album artists returned.</response>
@@ -311,6 +318,8 @@ namespace Jellyfin.Api.Controllers
[FromQuery] string? nameStartsWithOrGreater,
[FromQuery] string? nameStartsWith,
[FromQuery] string? nameLessThan,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] sortBy,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SortOrder[] sortOrder,
[FromQuery] bool? enableImages = true,
[FromQuery] bool enableTotalRecordCount = true)
{
@@ -349,7 +358,8 @@ namespace Jellyfin.Api.Controllers
MinCommunityRating = minCommunityRating,
DtoOptions = dtoOptions,
SearchTerm = searchTerm,
- EnableTotalRecordCount = enableTotalRecordCount
+ EnableTotalRecordCount = enableTotalRecordCount,
+ OrderBy = RequestHelpers.GetOrderBy(sortBy, sortOrder)
};
if (parentId.HasValue)
diff --git a/Jellyfin.Api/Controllers/ConfigurationController.cs b/Jellyfin.Api/Controllers/ConfigurationController.cs
index b6309baab0..60529e9904 100644
--- a/Jellyfin.Api/Controllers/ConfigurationController.cs
+++ b/Jellyfin.Api/Controllers/ConfigurationController.cs
@@ -6,7 +6,7 @@ using System.Threading.Tasks;
using Jellyfin.Api.Attributes;
using Jellyfin.Api.Constants;
using Jellyfin.Api.Models.ConfigurationDtos;
-using MediaBrowser.Common.Json;
+using Jellyfin.Extensions.Json;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.Configuration;
diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs
index b4154b361e..35435b0071 100644
--- a/Jellyfin.Api/Controllers/DynamicHlsController.cs
+++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs
@@ -28,7 +28,6 @@ using MediaBrowser.Model.Net;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
-using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Microsoft.Net.Http.Headers;
@@ -545,7 +544,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] EncodingContext? context,
[FromQuery] Dictionary<string, string> streamOptions)
{
- var cancellationTokenSource = new CancellationTokenSource();
+ using var cancellationTokenSource = new CancellationTokenSource();
var streamingRequest = new VideoRequestDto
{
Id = itemId,
@@ -710,7 +709,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] EncodingContext? context,
[FromQuery] Dictionary<string, string> streamOptions)
{
- var cancellationTokenSource = new CancellationTokenSource();
+ using var cancellationTokenSource = new CancellationTokenSource();
var streamingRequest = new StreamingRequestDto
{
Id = itemId,
@@ -1138,7 +1137,7 @@ namespace Jellyfin.Api.Controllers
var isHlsInFmp4 = string.Equals(segmentContainer, "mp4", StringComparison.OrdinalIgnoreCase);
var hlsVersion = isHlsInFmp4 ? "7" : "3";
- var builder = new StringBuilder();
+ var builder = new StringBuilder(128);
builder.AppendLine("#EXTM3U")
.AppendLine("#EXT-X-PLAYLIST-TYPE:VOD")
@@ -1191,6 +1190,7 @@ namespace Jellyfin.Api.Controllers
throw new ArgumentException("StartTimeTicks is not allowed.");
}
+ // CTS lifecycle is managed internally.
var cancellationTokenSource = new CancellationTokenSource();
var cancellationToken = cancellationTokenSource.Token;
@@ -1208,7 +1208,7 @@ namespace Jellyfin.Api.Controllers
_deviceManager,
_transcodingJobHelper,
TranscodingJobType,
- cancellationTokenSource.Token)
+ cancellationToken)
.ConfigureAwait(false);
var playlistPath = Path.ChangeExtension(state.OutputFilePath, ".m3u8");
@@ -1227,7 +1227,7 @@ namespace Jellyfin.Api.Controllers
}
var transcodingLock = _transcodingJobHelper.GetTranscodingLock(playlistPath);
- await transcodingLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false);
+ await transcodingLock.WaitAsync(cancellationToken).ConfigureAwait(false);
var released = false;
var startTranscoding = false;
@@ -1323,24 +1323,28 @@ namespace Jellyfin.Api.Controllers
return await GetSegmentResult(state, playlistPath, segmentPath, segmentExtension, segmentId, job, cancellationToken).ConfigureAwait(false);
}
- private double[] GetSegmentLengths(StreamState state)
- {
- var result = new List<double>();
+ private static double[] GetSegmentLengths(StreamState state)
+ => GetSegmentLengthsInternal(state.RunTimeTicks ?? 0, state.SegmentLength);
- var ticks = state.RunTimeTicks ?? 0;
-
- var segmentLengthTicks = TimeSpan.FromSeconds(state.SegmentLength).Ticks;
+ internal static double[] GetSegmentLengthsInternal(long runtimeTicks, int segmentlength)
+ {
+ var segmentLengthTicks = TimeSpan.FromSeconds(segmentlength).Ticks;
+ var wholeSegments = runtimeTicks / segmentLengthTicks;
+ var remainingTicks = runtimeTicks % segmentLengthTicks;
- while (ticks > 0)
+ var segmentsLen = wholeSegments + (remainingTicks == 0 ? 0 : 1);
+ var segments = new double[segmentsLen];
+ for (int i = 0; i < wholeSegments; i++)
{
- var length = ticks >= segmentLengthTicks ? segmentLengthTicks : ticks;
-
- result.Add(TimeSpan.FromTicks(length).TotalSeconds);
+ segments[i] = segmentlength;
+ }
- ticks -= length;
+ if (remainingTicks != 0)
+ {
+ segments[^1] = TimeSpan.FromTicks(remainingTicks).TotalSeconds;
}
- return result.ToArray();
+ return segments;
}
private string GetCommandLineArguments(string outputPath, StreamState state, bool isEncoding, int startNumber)
@@ -1376,18 +1380,13 @@ namespace Jellyfin.Api.Controllers
}
else if (string.Equals(segmentFormat, "mp4", StringComparison.OrdinalIgnoreCase))
{
- var outputFmp4HeaderArg = string.Empty;
- var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
- if (isWindows)
+ var outputFmp4HeaderArg = OperatingSystem.IsWindows() switch
{
// on Windows, the path of fmp4 header file needs to be configured
- outputFmp4HeaderArg = " -hls_fmp4_init_filename \"" + outputPrefix + "-1" + outputExtension + "\"";
- }
- else
- {
+ true => " -hls_fmp4_init_filename \"" + outputPrefix + "-1" + outputExtension + "\"",
// on Linux/Unix, ffmpeg generate fmp4 header file to m3u8 output folder
- outputFmp4HeaderArg = " -hls_fmp4_init_filename \"" + outputFileNameWithoutExtension + "-1" + outputExtension + "\"";
- }
+ false => " -hls_fmp4_init_filename \"" + outputFileNameWithoutExtension + "-1" + outputExtension + "\""
+ };
segmentFormat = "fmp4" + outputFmp4HeaderArg;
}
@@ -1497,7 +1496,7 @@ namespace Jellyfin.Api.Controllers
args += " -ar " + state.OutputAudioSampleRate.Value.ToString(CultureInfo.InvariantCulture);
}
- args += _encodingHelper.GetAudioFilterParam(state, _encodingOptions, true);
+ args += _encodingHelper.GetAudioFilterParam(state, _encodingOptions);
return args;
}
@@ -1762,9 +1761,9 @@ namespace Jellyfin.Api.Controllers
private static FileSystemMetadata? GetLastTranscodingFile(string playlist, string segmentExtension, IFileSystem fileSystem)
{
- var folder = Path.GetDirectoryName(playlist);
+ var folder = Path.GetDirectoryName(playlist) ?? throw new ArgumentException("Path can't be a root directory.", nameof(playlist));
- var filePrefix = Path.GetFileNameWithoutExtension(playlist) ?? string.Empty;
+ var filePrefix = Path.GetFileNameWithoutExtension(playlist);
try
{
diff --git a/Jellyfin.Api/Controllers/GenresController.cs b/Jellyfin.Api/Controllers/GenresController.cs
index 7bcf4674c6..5aa457153c 100644
--- a/Jellyfin.Api/Controllers/GenresController.cs
+++ b/Jellyfin.Api/Controllers/GenresController.cs
@@ -63,6 +63,8 @@ namespace Jellyfin.Api.Controllers
/// <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="sortBy">Optional. Specify one or more sort orders, comma delimited.</param>
+ /// <param name="sortOrder">Sort Order - Ascending,Descending.</param>
/// <param name="enableImages">Optional, include image information in output.</param>
/// <param name="enableTotalRecordCount">Optional. Include total record count.</param>
/// <response code="200">Genres returned.</response>
@@ -84,6 +86,8 @@ namespace Jellyfin.Api.Controllers
[FromQuery] string? nameStartsWithOrGreater,
[FromQuery] string? nameStartsWith,
[FromQuery] string? nameLessThan,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] sortBy,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SortOrder[] sortOrder,
[FromQuery] bool? enableImages = true,
[FromQuery] bool enableTotalRecordCount = true)
{
@@ -107,7 +111,8 @@ namespace Jellyfin.Api.Controllers
NameStartsWithOrGreater = nameStartsWithOrGreater,
DtoOptions = dtoOptions,
SearchTerm = searchTerm,
- EnableTotalRecordCount = enableTotalRecordCount
+ EnableTotalRecordCount = enableTotalRecordCount,
+ OrderBy = RequestHelpers.GetOrderBy(sortBy, sortOrder)
};
if (parentId.HasValue)
diff --git a/Jellyfin.Api/Controllers/InstantMixController.cs b/Jellyfin.Api/Controllers/InstantMixController.cs
index f232dffaa5..4774ed4efa 100644
--- a/Jellyfin.Api/Controllers/InstantMixController.cs
+++ b/Jellyfin.Api/Controllers/InstantMixController.cs
@@ -229,42 +229,6 @@ namespace Jellyfin.Api.Controllers
}
/// <summary>
- /// Creates an instant playlist based on a given genre.
- /// </summary>
- /// <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.</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>
- /// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
- /// <response code="200">Instant playlist returned.</response>
- /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the playlist items.</returns>
- [HttpGet("MusicGenres/{id}/InstantMix")]
- [ProducesResponseType(StatusCodes.Status200OK)]
- public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromMusicGenreById(
- [FromRoute, Required] Guid id,
- [FromQuery] Guid? userId,
- [FromQuery] int? limit,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
- [FromQuery] bool? enableImages,
- [FromQuery] bool? enableUserData,
- [FromQuery] int? imageTypeLimit,
- [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 { Fields = fields }
- .AddClientFields(Request)
- .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes!);
- var items = _musicManager.GetInstantMixFromItem(item, user, dtoOptions);
- return GetResult(items, user, limit, dtoOptions);
- }
-
- /// <summary>
/// Creates an instant playlist based on a given item.
/// </summary>
/// <param name="id">The item id.</param>
@@ -352,8 +316,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the playlist items.</returns>
[HttpGet("MusicGenres/InstantMix")]
[ProducesResponseType(StatusCodes.Status200OK)]
- [Obsolete("Use GetInstantMixFromMusicGenres instead")]
- public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromMusicGenreById2(
+ public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromMusicGenreById(
[FromQuery, Required] Guid id,
[FromQuery] Guid? userId,
[FromQuery] int? limit,
@@ -363,15 +326,15 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? imageTypeLimit,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes)
{
- return GetInstantMixFromMusicGenreById(
- id,
- userId,
- limit,
- fields,
- enableImages,
- enableUserData,
- imageTypeLimit,
- enableImageTypes);
+ var item = _libraryManager.GetItemById(id);
+ var user = userId.HasValue && !userId.Equals(Guid.Empty)
+ ? _userManager.GetUserById(userId.Value)
+ : null;
+ var dtoOptions = new DtoOptions { Fields = fields }
+ .AddClientFields(Request)
+ .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes!);
+ var items = _musicManager.GetInstantMixFromItem(item, user, dtoOptions);
+ return GetResult(items, user, limit, dtoOptions);
}
private QueryResult<BaseItemDto> GetResult(List<BaseItem> items, User? user, int? limit, DtoOptions dtoOptions)
diff --git a/Jellyfin.Api/Controllers/ItemsController.cs b/Jellyfin.Api/Controllers/ItemsController.cs
index 74cf3b1624..35c27dd0e9 100644
--- a/Jellyfin.Api/Controllers/ItemsController.cs
+++ b/Jellyfin.Api/Controllers/ItemsController.cs
@@ -143,7 +143,7 @@ namespace Jellyfin.Api.Controllers
[HttpGet("Items")]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QueryResult<BaseItemDto>> GetItems(
- [FromQuery] Guid? userId,
+ [FromQuery] Guid userId,
[FromQuery] string? maxOfficialRating,
[FromQuery] bool? hasThemeSong,
[FromQuery] bool? hasThemeVideo,
@@ -224,8 +224,8 @@ namespace Jellyfin.Api.Controllers
[FromQuery] bool enableTotalRecordCount = true,
[FromQuery] bool? enableImages = true)
{
- var user = userId.HasValue && !userId.Equals(Guid.Empty)
- ? _userManager.GetUserById(userId.Value)
+ var user = !userId.Equals(Guid.Empty)
+ ? _userManager.GetUserById(userId)
: null;
var dtoOptions = new DtoOptions { Fields = fields }
.AddClientFields(Request)
diff --git a/Jellyfin.Api/Controllers/MediaInfoController.cs b/Jellyfin.Api/Controllers/MediaInfoController.cs
index e330f02b61..8903e0ce89 100644
--- a/Jellyfin.Api/Controllers/MediaInfoController.cs
+++ b/Jellyfin.Api/Controllers/MediaInfoController.cs
@@ -161,6 +161,11 @@ namespace Jellyfin.Api.Controllers
liveStreamId)
.ConfigureAwait(false);
+ if (info.ErrorCode != null)
+ {
+ return info;
+ }
+
if (profile != null)
{
// set device specific data
@@ -302,27 +307,12 @@ namespace Jellyfin.Api.Controllers
/// </summary>
/// <param name="size">The bitrate. Defaults to 102400.</param>
/// <response code="200">Test buffer returned.</response>
- /// <response code="400">Size has to be a numer between 0 and 10,000,000.</response>
/// <returns>A <see cref="FileResult"/> with specified bitrate.</returns>
[HttpGet("Playback/BitrateTest")]
[ProducesResponseType(StatusCodes.Status200OK)]
- [ProducesResponseType(StatusCodes.Status400BadRequest)]
- [Produces(MediaTypeNames.Application.Octet)]
[ProducesFile(MediaTypeNames.Application.Octet)]
- public ActionResult GetBitrateTestBytes([FromQuery] int size = 102400)
+ public ActionResult GetBitrateTestBytes([FromQuery][Range(1, 100_000_000, ErrorMessage = "The requested size must be greater than or equal to {1} and less than or equal to {2}")] int size = 102400)
{
- const int MaxSize = 10_000_000;
-
- if (size <= 0)
- {
- return BadRequest($"The requested size ({size}) is equal to or smaller than 0.");
- }
-
- if (size > MaxSize)
- {
- return BadRequest($"The requested size ({size}) is larger than the max allowed value ({MaxSize}).");
- }
-
byte[] buffer = ArrayPool<byte>.Shared.Rent(size);
try
{
diff --git a/Jellyfin.Api/Controllers/MoviesController.cs b/Jellyfin.Api/Controllers/MoviesController.cs
index d0a2358ae2..010a3b19a7 100644
--- a/Jellyfin.Api/Controllers/MoviesController.cs
+++ b/Jellyfin.Api/Controllers/MoviesController.cs
@@ -18,6 +18,7 @@ using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Querying;
using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace Jellyfin.Api.Controllers
@@ -300,9 +301,8 @@ namespace Jellyfin.Api.Controllers
private IEnumerable<string> GetActors(IEnumerable<BaseItem> items)
{
- var people = _libraryManager.GetPeople(new InternalPeopleQuery
+ var people = _libraryManager.GetPeople(new InternalPeopleQuery(Array.Empty<string>(), new[] { PersonType.Director })
{
- ExcludePersonTypes = new[] { PersonType.Director },
MaxListOrder = 3
});
@@ -316,10 +316,9 @@ namespace Jellyfin.Api.Controllers
private IEnumerable<string> GetDirectors(IEnumerable<BaseItem> items)
{
- var people = _libraryManager.GetPeople(new InternalPeopleQuery
- {
- PersonTypes = new[] { PersonType.Director }
- });
+ var people = _libraryManager.GetPeople(new InternalPeopleQuery(
+ new[] { PersonType.Director },
+ Array.Empty<string>()));
var itemIds = items.Select(i => i.Id).ToList();
diff --git a/Jellyfin.Api/Controllers/MusicGenresController.cs b/Jellyfin.Api/Controllers/MusicGenresController.cs
index 7f7058b5ef..27eec2b9a8 100644
--- a/Jellyfin.Api/Controllers/MusicGenresController.cs
+++ b/Jellyfin.Api/Controllers/MusicGenresController.cs
@@ -63,6 +63,8 @@ namespace Jellyfin.Api.Controllers
/// <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="sortBy">Optional. Specify one or more sort orders, comma delimited.</param>
+ /// <param name="sortOrder">Sort Order - Ascending,Descending.</param>
/// <param name="enableImages">Optional, include image information in output.</param>
/// <param name="enableTotalRecordCount">Optional. Include total record count.</param>
/// <response code="200">Music genres returned.</response>
@@ -84,6 +86,8 @@ namespace Jellyfin.Api.Controllers
[FromQuery] string? nameStartsWithOrGreater,
[FromQuery] string? nameStartsWith,
[FromQuery] string? nameLessThan,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] sortBy,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SortOrder[] sortOrder,
[FromQuery] bool? enableImages = true,
[FromQuery] bool enableTotalRecordCount = true)
{
@@ -107,7 +111,8 @@ namespace Jellyfin.Api.Controllers
NameStartsWithOrGreater = nameStartsWithOrGreater,
DtoOptions = dtoOptions,
SearchTerm = searchTerm,
- EnableTotalRecordCount = enableTotalRecordCount
+ EnableTotalRecordCount = enableTotalRecordCount,
+ OrderBy = RequestHelpers.GetOrderBy(sortBy, sortOrder)
};
if (parentId.HasValue)
diff --git a/Jellyfin.Api/Controllers/PersonsController.cs b/Jellyfin.Api/Controllers/PersonsController.cs
index 70a94e27cb..b98307f879 100644
--- a/Jellyfin.Api/Controllers/PersonsController.cs
+++ b/Jellyfin.Api/Controllers/PersonsController.cs
@@ -94,10 +94,10 @@ namespace Jellyfin.Api.Controllers
}
var isFavoriteInFilters = filters.Any(f => f == ItemFilter.IsFavorite);
- var peopleItems = _libraryManager.GetPeopleItems(new InternalPeopleQuery
+ var peopleItems = _libraryManager.GetPeopleItems(new InternalPeopleQuery(
+ personTypes,
+ excludePersonTypes)
{
- PersonTypes = personTypes,
- ExcludePersonTypes = excludePersonTypes,
NameContains = searchTerm,
User = user,
IsFavorite = !isFavorite.HasValue && isFavoriteInFilters ? true : isFavorite,
diff --git a/Jellyfin.Api/Controllers/PluginsController.cs b/Jellyfin.Api/Controllers/PluginsController.cs
index 7a61307195..0ae6109bcc 100644
--- a/Jellyfin.Api/Controllers/PluginsController.cs
+++ b/Jellyfin.Api/Controllers/PluginsController.cs
@@ -8,8 +8,8 @@ using System.Threading.Tasks;
using Jellyfin.Api.Attributes;
using Jellyfin.Api.Constants;
using Jellyfin.Api.Models.PluginDtos;
+using Jellyfin.Extensions.Json;
using MediaBrowser.Common.Configuration;
-using MediaBrowser.Common.Json;
using MediaBrowser.Common.Plugins;
using MediaBrowser.Common.Updates;
using MediaBrowser.Model.Net;
diff --git a/Jellyfin.Api/Controllers/QuickConnectController.cs b/Jellyfin.Api/Controllers/QuickConnectController.cs
index 4ac8491815..3cd1bc6d4c 100644
--- a/Jellyfin.Api/Controllers/QuickConnectController.cs
+++ b/Jellyfin.Api/Controllers/QuickConnectController.cs
@@ -2,6 +2,7 @@ using System.ComponentModel.DataAnnotations;
using Jellyfin.Api.Constants;
using Jellyfin.Api.Helpers;
using MediaBrowser.Common.Extensions;
+using MediaBrowser.Controller.Authentication;
using MediaBrowser.Controller.QuickConnect;
using MediaBrowser.Model.QuickConnect;
using Microsoft.AspNetCore.Authorization;
@@ -30,13 +31,12 @@ namespace Jellyfin.Api.Controllers
/// Gets the current quick connect state.
/// </summary>
/// <response code="200">Quick connect state returned.</response>
- /// <returns>The current <see cref="QuickConnectState"/>.</returns>
- [HttpGet("Status")]
+ /// <returns>Whether Quick Connect is enabled on the server or not.</returns>
+ [HttpGet("Enabled")]
[ProducesResponseType(StatusCodes.Status200OK)]
- public ActionResult<QuickConnectState> GetStatus()
+ public ActionResult<bool> GetEnabled()
{
- _quickConnect.ExpireRequests();
- return _quickConnect.State;
+ return _quickConnect.IsEnabled;
}
/// <summary>
@@ -49,7 +49,14 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QuickConnectResult> Initiate()
{
- return _quickConnect.TryConnect();
+ try
+ {
+ return _quickConnect.TryConnect();
+ }
+ catch (AuthenticationException)
+ {
+ return Unauthorized("Quick connect is disabled");
+ }
}
/// <summary>
@@ -72,42 +79,10 @@ namespace Jellyfin.Api.Controllers
{
return NotFound("Unknown secret");
}
- }
-
- /// <summary>
- /// Temporarily activates quick connect for five minutes.
- /// </summary>
- /// <response code="204">Quick connect has been temporarily activated.</response>
- /// <response code="403">Quick connect is unavailable on this server.</response>
- /// <returns>An <see cref="NoContentResult"/> on success.</returns>
- [HttpPost("Activate")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
- [ProducesResponseType(StatusCodes.Status204NoContent)]
- [ProducesResponseType(StatusCodes.Status403Forbidden)]
- public ActionResult Activate()
- {
- if (_quickConnect.State == QuickConnectState.Unavailable)
+ catch (AuthenticationException)
{
- return StatusCode(StatusCodes.Status403Forbidden, "Quick connect is unavailable");
+ return Unauthorized("Quick connect is disabled");
}
-
- _quickConnect.Activate();
- return NoContent();
- }
-
- /// <summary>
- /// Enables or disables quick connect.
- /// </summary>
- /// <param name="status">New <see cref="QuickConnectState"/>.</param>
- /// <response code="204">Quick connect state set successfully.</response>
- /// <returns>An <see cref="NoContentResult"/> on success.</returns>
- [HttpPost("Available")]
- [Authorize(Policy = Policies.RequiresElevation)]
- [ProducesResponseType(StatusCodes.Status204NoContent)]
- public ActionResult Available([FromQuery] QuickConnectState status = QuickConnectState.Available)
- {
- _quickConnect.SetState(status);
- return NoContent();
}
/// <summary>
@@ -129,26 +104,14 @@ namespace Jellyfin.Api.Controllers
return StatusCode(StatusCodes.Status403Forbidden, "Unknown user id");
}
- return _quickConnect.AuthorizeRequest(userId.Value, code);
- }
-
- /// <summary>
- /// Deauthorize all quick connect devices for the current user.
- /// </summary>
- /// <response code="200">All quick connect devices were deleted.</response>
- /// <returns>The number of devices that were deleted.</returns>
- [HttpPost("Deauthorize")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
- [ProducesResponseType(StatusCodes.Status200OK)]
- public ActionResult<int> Deauthorize()
- {
- var userId = ClaimHelpers.GetUserId(Request.HttpContext.User);
- if (!userId.HasValue)
+ try
{
- return 0;
+ return _quickConnect.AuthorizeRequest(userId.Value, code);
+ }
+ catch (AuthenticationException)
+ {
+ return Unauthorized("Quick connect is disabled");
}
-
- return _quickConnect.DeleteAllDevices(userId.Value);
}
}
}
diff --git a/Jellyfin.Api/Controllers/SubtitleController.cs b/Jellyfin.Api/Controllers/SubtitleController.cs
index 1669a659dc..b473574e0a 100644
--- a/Jellyfin.Api/Controllers/SubtitleController.cs
+++ b/Jellyfin.Api/Controllers/SubtitleController.cs
@@ -196,7 +196,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="startPositionTicks">The start position of the subtitle in ticks.</param>
/// <response code="200">File returned.</response>
/// <returns>A <see cref="FileContentResult"/> with the subtitle file.</returns>
- [HttpGet("Videos/{routeItemId}/routeMediaSourceId/Subtitles/{routeIndex}/Stream.{routeFormat}")]
+ [HttpGet("Videos/{routeItemId}/{routeMediaSourceId}/Subtitles/{routeIndex}/Stream.{routeFormat}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesFile("text/*")]
public async Task<ActionResult> GetSubtitle(
diff --git a/Jellyfin.Api/Controllers/TrailersController.cs b/Jellyfin.Api/Controllers/TrailersController.cs
index dd3836551b..5cb7468b24 100644
--- a/Jellyfin.Api/Controllers/TrailersController.cs
+++ b/Jellyfin.Api/Controllers/TrailersController.cs
@@ -114,7 +114,7 @@ namespace Jellyfin.Api.Controllers
[HttpGet]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QueryResult<BaseItemDto>> GetTrailers(
- [FromQuery] Guid? userId,
+ [FromQuery] Guid userId,
[FromQuery] string? maxOfficialRating,
[FromQuery] bool? hasThemeSong,
[FromQuery] bool? hasThemeVideo,
diff --git a/Jellyfin.Api/Controllers/TvShowsController.cs b/Jellyfin.Api/Controllers/TvShowsController.cs
index 59400db2a0..51d40994e1 100644
--- a/Jellyfin.Api/Controllers/TvShowsController.cs
+++ b/Jellyfin.Api/Controllers/TvShowsController.cs
@@ -6,7 +6,7 @@ using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions;
using Jellyfin.Api.ModelBinders;
using Jellyfin.Data.Enums;
-using MediaBrowser.Common.Extensions;
+using Jellyfin.Extensions;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV;
@@ -65,6 +65,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="enableUserData">Optional. Include user data.</param>
+ /// <param name="nextUpDateCutoff">Optional. Starting date of shows to show in Next Up section.</param>
/// <param name="enableTotalRecordCount">Whether to enable the total records count. Defaults to true.</param>
/// <param name="disableFirstEpisode">Whether to disable sending the first episode in a series as next up.</param>
/// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the next up episodes.</returns>
@@ -81,6 +82,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? imageTypeLimit,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes,
[FromQuery] bool? enableUserData,
+ [FromQuery] DateTime? nextUpDateCutoff,
[FromQuery] bool enableTotalRecordCount = true,
[FromQuery] bool disableFirstEpisode = false)
{
@@ -97,7 +99,8 @@ namespace Jellyfin.Api.Controllers
StartIndex = startIndex,
UserId = userId ?? Guid.Empty,
EnableTotalRecordCount = enableTotalRecordCount,
- DisableFirstEpisode = disableFirstEpisode
+ DisableFirstEpisode = disableFirstEpisode,
+ NextUpDateCutoff = nextUpDateCutoff ?? DateTime.MinValue
},
options);
diff --git a/Jellyfin.Api/Controllers/UserLibraryController.cs b/Jellyfin.Api/Controllers/UserLibraryController.cs
index 1d70406acc..a33a0826c9 100644
--- a/Jellyfin.Api/Controllers/UserLibraryController.cs
+++ b/Jellyfin.Api/Controllers/UserLibraryController.cs
@@ -9,7 +9,7 @@ using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders;
using Jellyfin.Data.Enums;
-using MediaBrowser.Common.Extensions;
+using Jellyfin.Extensions;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
diff --git a/Jellyfin.Api/Controllers/VideoHlsController.cs b/Jellyfin.Api/Controllers/VideoHlsController.cs
index 308334b237..5c941b2767 100644
--- a/Jellyfin.Api/Controllers/VideoHlsController.cs
+++ b/Jellyfin.Api/Controllers/VideoHlsController.cs
@@ -265,6 +265,7 @@ namespace Jellyfin.Api.Controllers
EnableSubtitlesInManifest = enableSubtitlesInManifest ?? true
};
+ // CTS lifecycle is managed internally.
var cancellationTokenSource = new CancellationTokenSource();
using var state = await StreamingHelpers.GetStreamingState(
streamingRequest,
@@ -365,8 +366,7 @@ namespace Jellyfin.Api.Controllers
else if (string.Equals(segmentFormat, "mp4", StringComparison.OrdinalIgnoreCase))
{
var outputFmp4HeaderArg = string.Empty;
- var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
- if (isWindows)
+ if (OperatingSystem.IsWindows())
{
// on Windows, the path of fmp4 header file needs to be configured
outputFmp4HeaderArg = " -hls_fmp4_init_filename \"" + outputPrefix + "-1" + outputExtension + "\"";
@@ -484,7 +484,7 @@ namespace Jellyfin.Api.Controllers
args += " -ar " + state.OutputAudioSampleRate.Value.ToString(CultureInfo.InvariantCulture);
}
- args += _encodingHelper.GetAudioFilterParam(state, _encodingOptions, true);
+ args += _encodingHelper.GetAudioFilterParam(state, _encodingOptions);
return args;
}
diff --git a/Jellyfin.Api/Controllers/VideosController.cs b/Jellyfin.Api/Controllers/VideosController.cs
index e544d001ed..29a25fa6aa 100644
--- a/Jellyfin.Api/Controllers/VideosController.cs
+++ b/Jellyfin.Api/Controllers/VideosController.cs
@@ -296,6 +296,8 @@ namespace Jellyfin.Api.Controllers
/// <param name="startTimeTicks">Optional. Specify a starting offset, in ticks. 1 tick = 10000 ms.</param>
/// <param name="width">Optional. The fixed horizontal resolution of the encoded video.</param>
/// <param name="height">Optional. The fixed vertical resolution of the encoded video.</param>
+ /// <param name="maxWidth">Optional. The maximum horizontal resolution of the encoded video.</param>
+ /// <param name="maxHeight">Optional. The maximum vertical resolution of the encoded video.</param>
/// <param name="videoBitRate">Optional. Specify a video bitrate to encode to, e.g. 500000. If omitted this will be left to encoder defaults.</param>
/// <param name="subtitleStreamIndex">Optional. The index of the subtitle stream to use. If omitted no subtitles will be used.</param>
/// <param name="subtitleMethod">Optional. Specify the subtitle delivery method.</param>
@@ -352,6 +354,8 @@ namespace Jellyfin.Api.Controllers
[FromQuery] long? startTimeTicks,
[FromQuery] int? width,
[FromQuery] int? height,
+ [FromQuery] int? maxWidth,
+ [FromQuery] int? maxHeight,
[FromQuery] int? videoBitRate,
[FromQuery] int? subtitleStreamIndex,
[FromQuery] SubtitleDeliveryMethod? subtitleMethod,
@@ -373,6 +377,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] Dictionary<string, string> streamOptions)
{
var isHeadRequest = Request.Method == System.Net.WebRequestMethods.Http.Head;
+ // CTS lifecycle is managed internally.
var cancellationTokenSource = new CancellationTokenSource();
var streamingRequest = new VideoRequestDto
{
@@ -406,6 +411,8 @@ namespace Jellyfin.Api.Controllers
StartTimeTicks = startTimeTicks,
Width = width,
Height = height,
+ MaxWidth = maxWidth,
+ MaxHeight = maxHeight,
VideoBitRate = videoBitRate,
SubtitleStreamIndex = subtitleStreamIndex,
SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode,
@@ -549,6 +556,8 @@ namespace Jellyfin.Api.Controllers
/// <param name="startTimeTicks">Optional. Specify a starting offset, in ticks. 1 tick = 10000 ms.</param>
/// <param name="width">Optional. The fixed horizontal resolution of the encoded video.</param>
/// <param name="height">Optional. The fixed vertical resolution of the encoded video.</param>
+ /// <param name="maxWidth">Optional. The maximum horizontal resolution of the encoded video.</param>
+ /// <param name="maxHeight">Optional. The maximum vertical resolution of the encoded video.</param>
/// <param name="videoBitRate">Optional. Specify a video bitrate to encode to, e.g. 500000. If omitted this will be left to encoder defaults.</param>
/// <param name="subtitleStreamIndex">Optional. The index of the subtitle stream to use. If omitted no subtitles will be used.</param>
/// <param name="subtitleMethod">Optional. Specify the subtitle delivery method.</param>
@@ -605,6 +614,8 @@ namespace Jellyfin.Api.Controllers
[FromQuery] long? startTimeTicks,
[FromQuery] int? width,
[FromQuery] int? height,
+ [FromQuery] int? maxWidth,
+ [FromQuery] int? maxHeight,
[FromQuery] int? videoBitRate,
[FromQuery] int? subtitleStreamIndex,
[FromQuery] SubtitleDeliveryMethod? subtitleMethod,
@@ -656,6 +667,8 @@ namespace Jellyfin.Api.Controllers
startTimeTicks,
width,
height,
+ maxWidth,
+ maxHeight,
videoBitRate,
subtitleStreamIndex,
subtitleMethod,