diff options
Diffstat (limited to 'Jellyfin.Api')
18 files changed, 178 insertions, 72 deletions
diff --git a/Jellyfin.Api/Controllers/AudioController.cs b/Jellyfin.Api/Controllers/AudioController.cs index 8954c8ef5..a47c60473 100644 --- a/Jellyfin.Api/Controllers/AudioController.cs +++ b/Jellyfin.Api/Controllers/AudioController.cs @@ -46,7 +46,7 @@ public class AudioController : BaseJellyfinApiController /// <param name="minSegments">The minimum number of segments.</param> /// <param name="mediaSourceId">The media version id, if playing an alternate version.</param> /// <param name="deviceId">The device id of the client requesting. Used to stop encoding processes when needed.</param> - /// <param name="audioCodec">Optional. Specify a audio codec to encode to, e.g. mp3. If omitted the server will auto-select using the url's extension. Options: aac, mp3, vorbis, wma.</param> + /// <param name="audioCodec">Optional. Specify an audio codec to encode to, e.g. mp3. If omitted the server will auto-select using the url's extension.</param> /// <param name="enableAutoStreamCopy">Whether or not to allow automatic stream copy if requested values match the original source. Defaults to true.</param> /// <param name="allowVideoStreamCopy">Whether or not to allow copying of the video stream url.</param> /// <param name="allowAudioStreamCopy">Whether or not to allow copying of the audio stream url.</param> @@ -76,7 +76,7 @@ public class AudioController : BaseJellyfinApiController /// <param name="cpuCoreLimit">Optional. The limit of how many cpu cores to use.</param> /// <param name="liveStreamId">The live stream id.</param> /// <param name="enableMpegtsM2TsMode">Optional. Whether to enable the MpegtsM2Ts mode.</param> - /// <param name="videoCodec">Optional. Specify a video codec to encode to, e.g. h264. If omitted the server will auto-select using the url's extension. Options: h265, h264, mpeg4, theora, vp8, vp9, vpx (deprecated), wmv.</param> + /// <param name="videoCodec">Optional. Specify a video codec to encode to, e.g. h264. If omitted the server will auto-select using the url's extension.</param> /// <param name="subtitleCodec">Optional. Specify a subtitle codec to encode to.</param> /// <param name="transcodeReasons">Optional. The transcoding reason.</param> /// <param name="audioStreamIndex">Optional. The index of the audio stream to use. If omitted the first audio stream will be used.</param> @@ -213,7 +213,7 @@ public class AudioController : BaseJellyfinApiController /// <param name="minSegments">The minimum number of segments.</param> /// <param name="mediaSourceId">The media version id, if playing an alternate version.</param> /// <param name="deviceId">The device id of the client requesting. Used to stop encoding processes when needed.</param> - /// <param name="audioCodec">Optional. Specify a audio codec to encode to, e.g. mp3. If omitted the server will auto-select using the url's extension. Options: aac, mp3, vorbis, wma.</param> + /// <param name="audioCodec">Optional. Specify an audio codec to encode to, e.g. mp3. If omitted the server will auto-select using the url's extension.</param> /// <param name="enableAutoStreamCopy">Whether or not to allow automatic stream copy if requested values match the original source. Defaults to true.</param> /// <param name="allowVideoStreamCopy">Whether or not to allow copying of the video stream url.</param> /// <param name="allowAudioStreamCopy">Whether or not to allow copying of the audio stream url.</param> @@ -243,7 +243,7 @@ public class AudioController : BaseJellyfinApiController /// <param name="cpuCoreLimit">Optional. The limit of how many cpu cores to use.</param> /// <param name="liveStreamId">The live stream id.</param> /// <param name="enableMpegtsM2TsMode">Optional. Whether to enable the MpegtsM2Ts mode.</param> - /// <param name="videoCodec">Optional. Specify a video codec to encode to, e.g. h264. If omitted the server will auto-select using the url's extension. Options: h265, h264, mpeg4, theora, vp8, vp9, vpx (deprecated), wmv.</param> + /// <param name="videoCodec">Optional. Specify a video codec to encode to, e.g. h264. If omitted the server will auto-select using the url's extension.</param> /// <param name="subtitleCodec">Optional. Specify a subtitle codec to encode to.</param> /// <param name="transcodeReasons">Optional. The transcoding reason.</param> /// <param name="audioStreamIndex">Optional. The index of the audio stream to use. If omitted the first audio stream will be used.</param> diff --git a/Jellyfin.Api/Controllers/DevicesController.cs b/Jellyfin.Api/Controllers/DevicesController.cs index 6d9ec343e..2a2ab4ad1 100644 --- a/Jellyfin.Api/Controllers/DevicesController.cs +++ b/Jellyfin.Api/Controllers/DevicesController.cs @@ -47,10 +47,10 @@ public class DevicesController : BaseJellyfinApiController /// <returns>An <see cref="OkResult"/> containing the list of devices.</returns> [HttpGet] [ProducesResponseType(StatusCodes.Status200OK)] - public async Task<ActionResult<QueryResult<DeviceInfo>>> GetDevices([FromQuery] Guid? userId) + public ActionResult<QueryResult<DeviceInfo>> GetDevices([FromQuery] Guid? userId) { userId = RequestHelpers.GetUserId(User, userId); - return await _deviceManager.GetDevicesForUser(userId).ConfigureAwait(false); + return _deviceManager.GetDevicesForUser(userId); } /// <summary> @@ -63,9 +63,9 @@ public class DevicesController : BaseJellyfinApiController [HttpGet("Info")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task<ActionResult<DeviceInfo>> GetDeviceInfo([FromQuery, Required] string id) + public ActionResult<DeviceInfo> GetDeviceInfo([FromQuery, Required] string id) { - var deviceInfo = await _deviceManager.GetDevice(id).ConfigureAwait(false); + var deviceInfo = _deviceManager.GetDevice(id); if (deviceInfo is null) { return NotFound(); @@ -84,9 +84,9 @@ public class DevicesController : BaseJellyfinApiController [HttpGet("Options")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task<ActionResult<DeviceOptions>> GetDeviceOptions([FromQuery, Required] string id) + public ActionResult<DeviceOptions> GetDeviceOptions([FromQuery, Required] string id) { - var deviceInfo = await _deviceManager.GetDeviceOptions(id).ConfigureAwait(false); + var deviceInfo = _deviceManager.GetDeviceOptions(id); if (deviceInfo is null) { return NotFound(); @@ -124,13 +124,13 @@ public class DevicesController : BaseJellyfinApiController [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task<ActionResult> DeleteDevice([FromQuery, Required] string id) { - var existingDevice = await _deviceManager.GetDevice(id).ConfigureAwait(false); + var existingDevice = _deviceManager.GetDevice(id); if (existingDevice is null) { return NotFound(); } - var sessions = await _deviceManager.GetDevices(new DeviceQuery { DeviceId = id }).ConfigureAwait(false); + var sessions = _deviceManager.GetDevices(new DeviceQuery { DeviceId = id }); foreach (var session in sessions.Items) { diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs index 130c1192f..662e2acbc 100644 --- a/Jellyfin.Api/Controllers/DynamicHlsController.cs +++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs @@ -45,6 +45,7 @@ public class DynamicHlsController : BaseJellyfinApiController private const TranscodingJobType TranscodingJobType = MediaBrowser.Controller.MediaEncoding.TranscodingJobType.Hls; private readonly Version _minFFmpegFlacInMp4 = new Version(6, 0); + private readonly Version _minFFmpegX265BframeInFmp4 = new Version(7, 0, 1); private readonly ILibraryManager _libraryManager; private readonly IUserManager _userManager; @@ -116,7 +117,7 @@ public class DynamicHlsController : BaseJellyfinApiController /// <param name="minSegments">The minimum number of segments.</param> /// <param name="mediaSourceId">The media version id, if playing an alternate version.</param> /// <param name="deviceId">The device id of the client requesting. Used to stop encoding processes when needed.</param> - /// <param name="audioCodec">Optional. Specify a audio codec to encode to, e.g. mp3. If omitted the server will auto-select using the url's extension. Options: aac, mp3, vorbis, wma.</param> + /// <param name="audioCodec">Optional. Specify an audio codec to encode to, e.g. mp3.</param> /// <param name="enableAutoStreamCopy">Whether or not to allow automatic stream copy if requested values match the original source. Defaults to true.</param> /// <param name="allowVideoStreamCopy">Whether or not to allow copying of the video stream url.</param> /// <param name="allowAudioStreamCopy">Whether or not to allow copying of the audio stream url.</param> @@ -146,7 +147,7 @@ public class DynamicHlsController : BaseJellyfinApiController /// <param name="cpuCoreLimit">Optional. The limit of how many cpu cores to use.</param> /// <param name="liveStreamId">The live stream id.</param> /// <param name="enableMpegtsM2TsMode">Optional. Whether to enable the MpegtsM2Ts mode.</param> - /// <param name="videoCodec">Optional. Specify a video codec to encode to, e.g. h264. If omitted the server will auto-select using the url's extension. Options: h265, h264, mpeg4, theora, vp8, vp9, vpx (deprecated), wmv.</param> + /// <param name="videoCodec">Optional. Specify a video codec to encode to, e.g. h264.</param> /// <param name="subtitleCodec">Optional. Specify a subtitle codec to encode to.</param> /// <param name="transcodeReasons">Optional. The transcoding reason.</param> /// <param name="audioStreamIndex">Optional. The index of the audio stream to use. If omitted the first audio stream will be used.</param> @@ -355,7 +356,7 @@ public class DynamicHlsController : BaseJellyfinApiController /// <param name="minSegments">The minimum number of segments.</param> /// <param name="mediaSourceId">The media version id, if playing an alternate version.</param> /// <param name="deviceId">The device id of the client requesting. Used to stop encoding processes when needed.</param> - /// <param name="audioCodec">Optional. Specify a audio codec to encode to, e.g. mp3. If omitted the server will auto-select using the url's extension. Options: aac, mp3, vorbis, wma.</param> + /// <param name="audioCodec">Optional. Specify an audio codec to encode to, e.g. mp3.</param> /// <param name="enableAutoStreamCopy">Whether or not to allow automatic stream copy if requested values match the original source. Defaults to true.</param> /// <param name="allowVideoStreamCopy">Whether or not to allow copying of the video stream url.</param> /// <param name="allowAudioStreamCopy">Whether or not to allow copying of the audio stream url.</param> @@ -387,7 +388,7 @@ public class DynamicHlsController : BaseJellyfinApiController /// <param name="cpuCoreLimit">Optional. The limit of how many cpu cores to use.</param> /// <param name="liveStreamId">The live stream id.</param> /// <param name="enableMpegtsM2TsMode">Optional. Whether to enable the MpegtsM2Ts mode.</param> - /// <param name="videoCodec">Optional. Specify a video codec to encode to, e.g. h264. If omitted the server will auto-select using the url's extension. Options: h265, h264, mpeg4, theora, vp8, vp9, vpx (deprecated), wmv.</param> + /// <param name="videoCodec">Optional. Specify a video codec to encode to, e.g. h264.</param> /// <param name="subtitleCodec">Optional. Specify a subtitle codec to encode to.</param> /// <param name="transcodeReasons">Optional. The transcoding reason.</param> /// <param name="audioStreamIndex">Optional. The index of the audio stream to use. If omitted the first audio stream will be used.</param> @@ -531,7 +532,7 @@ public class DynamicHlsController : BaseJellyfinApiController /// <param name="minSegments">The minimum number of segments.</param> /// <param name="mediaSourceId">The media version id, if playing an alternate version.</param> /// <param name="deviceId">The device id of the client requesting. Used to stop encoding processes when needed.</param> - /// <param name="audioCodec">Optional. Specify a audio codec to encode to, e.g. mp3. If omitted the server will auto-select using the url's extension. Options: aac, mp3, vorbis, wma.</param> + /// <param name="audioCodec">Optional. Specify an audio codec to encode to, e.g. mp3.</param> /// <param name="enableAutoStreamCopy">Whether or not to allow automatic stream copy if requested values match the original source. Defaults to true.</param> /// <param name="allowVideoStreamCopy">Whether or not to allow copying of the video stream url.</param> /// <param name="allowAudioStreamCopy">Whether or not to allow copying of the audio stream url.</param> @@ -562,7 +563,7 @@ public class DynamicHlsController : BaseJellyfinApiController /// <param name="cpuCoreLimit">Optional. The limit of how many cpu cores to use.</param> /// <param name="liveStreamId">The live stream id.</param> /// <param name="enableMpegtsM2TsMode">Optional. Whether to enable the MpegtsM2Ts mode.</param> - /// <param name="videoCodec">Optional. Specify a video codec to encode to, e.g. h264. If omitted the server will auto-select using the url's extension. Options: h265, h264, mpeg4, theora, vp8, vp9, vpx (deprecated), wmv.</param> + /// <param name="videoCodec">Optional. Specify a video codec to encode to, e.g. h264.</param> /// <param name="subtitleCodec">Optional. Specify a subtitle codec to encode to.</param> /// <param name="transcodeReasons">Optional. The transcoding reason.</param> /// <param name="audioStreamIndex">Optional. The index of the audio stream to use. If omitted the first audio stream will be used.</param> @@ -700,7 +701,7 @@ public class DynamicHlsController : BaseJellyfinApiController /// <param name="minSegments">The minimum number of segments.</param> /// <param name="mediaSourceId">The media version id, if playing an alternate version.</param> /// <param name="deviceId">The device id of the client requesting. Used to stop encoding processes when needed.</param> - /// <param name="audioCodec">Optional. Specify a audio codec to encode to, e.g. mp3. If omitted the server will auto-select using the url's extension. Options: aac, mp3, vorbis, wma.</param> + /// <param name="audioCodec">Optional. Specify an audio codec to encode to, e.g. mp3.</param> /// <param name="enableAutoStreamCopy">Whether or not to allow automatic stream copy if requested values match the original source. Defaults to true.</param> /// <param name="allowVideoStreamCopy">Whether or not to allow copying of the video stream url.</param> /// <param name="allowAudioStreamCopy">Whether or not to allow copying of the audio stream url.</param> @@ -732,7 +733,7 @@ public class DynamicHlsController : BaseJellyfinApiController /// <param name="cpuCoreLimit">Optional. The limit of how many cpu cores to use.</param> /// <param name="liveStreamId">The live stream id.</param> /// <param name="enableMpegtsM2TsMode">Optional. Whether to enable the MpegtsM2Ts mode.</param> - /// <param name="videoCodec">Optional. Specify a video codec to encode to, e.g. h264. If omitted the server will auto-select using the url's extension. Options: h265, h264, mpeg4, theora, vp8, vp9, vpx (deprecated), wmv.</param> + /// <param name="videoCodec">Optional. Specify a video codec to encode to, e.g. h264.</param> /// <param name="subtitleCodec">Optional. Specify a subtitle codec to encode to.</param> /// <param name="transcodeReasons">Optional. The transcoding reason.</param> /// <param name="audioStreamIndex">Optional. The index of the audio stream to use. If omitted the first audio stream will be used.</param> @@ -871,7 +872,7 @@ public class DynamicHlsController : BaseJellyfinApiController /// <param name="minSegments">The minimum number of segments.</param> /// <param name="mediaSourceId">The media version id, if playing an alternate version.</param> /// <param name="deviceId">The device id of the client requesting. Used to stop encoding processes when needed.</param> - /// <param name="audioCodec">Optional. Specify a audio codec to encode to, e.g. mp3. If omitted the server will auto-select using the url's extension. Options: aac, mp3, vorbis, wma.</param> + /// <param name="audioCodec">Optional. Specify an audio codec to encode to, e.g. mp3.</param> /// <param name="enableAutoStreamCopy">Whether or not to allow automatic stream copy if requested values match the original source. Defaults to true.</param> /// <param name="allowVideoStreamCopy">Whether or not to allow copying of the video stream url.</param> /// <param name="allowAudioStreamCopy">Whether or not to allow copying of the audio stream url.</param> @@ -902,7 +903,7 @@ public class DynamicHlsController : BaseJellyfinApiController /// <param name="cpuCoreLimit">Optional. The limit of how many cpu cores to use.</param> /// <param name="liveStreamId">The live stream id.</param> /// <param name="enableMpegtsM2TsMode">Optional. Whether to enable the MpegtsM2Ts mode.</param> - /// <param name="videoCodec">Optional. Specify a video codec to encode to, e.g. h264. If omitted the server will auto-select using the url's extension. Options: h265, h264, mpeg4, theora, vpx, wmv.</param> + /// <param name="videoCodec">Optional. Specify a video codec to encode to, e.g. h264.</param> /// <param name="subtitleCodec">Optional. Specify a subtitle codec to encode to.</param> /// <param name="transcodeReasons">Optional. The transcoding reason.</param> /// <param name="audioStreamIndex">Optional. The index of the audio stream to use. If omitted the first audio stream will be used.</param> @@ -1043,7 +1044,7 @@ public class DynamicHlsController : BaseJellyfinApiController /// <param name="minSegments">The minimum number of segments.</param> /// <param name="mediaSourceId">The media version id, if playing an alternate version.</param> /// <param name="deviceId">The device id of the client requesting. Used to stop encoding processes when needed.</param> - /// <param name="audioCodec">Optional. Specify a audio codec to encode to, e.g. mp3. If omitted the server will auto-select using the url's extension. Options: aac, mp3, vorbis, wma.</param> + /// <param name="audioCodec">Optional. Specify an audio codec to encode to, e.g. mp3.</param> /// <param name="enableAutoStreamCopy">Whether or not to allow automatic stream copy if requested values match the original source. Defaults to true.</param> /// <param name="allowVideoStreamCopy">Whether or not to allow copying of the video stream url.</param> /// <param name="allowAudioStreamCopy">Whether or not to allow copying of the audio stream url.</param> @@ -1075,7 +1076,7 @@ public class DynamicHlsController : BaseJellyfinApiController /// <param name="cpuCoreLimit">Optional. The limit of how many cpu cores to use.</param> /// <param name="liveStreamId">The live stream id.</param> /// <param name="enableMpegtsM2TsMode">Optional. Whether to enable the MpegtsM2Ts mode.</param> - /// <param name="videoCodec">Optional. Specify a video codec to encode to, e.g. h264. If omitted the server will auto-select using the url's extension. Options: h265, h264, mpeg4, theora, vp8, vp9, vpx (deprecated), wmv.</param> + /// <param name="videoCodec">Optional. Specify a video codec to encode to, e.g. h264.</param> /// <param name="subtitleCodec">Optional. Specify a subtitle codec to encode to.</param> /// <param name="transcodeReasons">Optional. The transcoding reason.</param> /// <param name="audioStreamIndex">Optional. The index of the audio stream to use. If omitted the first audio stream will be used.</param> @@ -1227,7 +1228,7 @@ public class DynamicHlsController : BaseJellyfinApiController /// <param name="minSegments">The minimum number of segments.</param> /// <param name="mediaSourceId">The media version id, if playing an alternate version.</param> /// <param name="deviceId">The device id of the client requesting. Used to stop encoding processes when needed.</param> - /// <param name="audioCodec">Optional. Specify a audio codec to encode to, e.g. mp3. If omitted the server will auto-select using the url's extension. Options: aac, mp3, vorbis, wma.</param> + /// <param name="audioCodec">Optional. Specify an audio codec to encode to, e.g. mp3.</param> /// <param name="enableAutoStreamCopy">Whether or not to allow automatic stream copy if requested values match the original source. Defaults to true.</param> /// <param name="allowVideoStreamCopy">Whether or not to allow copying of the video stream url.</param> /// <param name="allowAudioStreamCopy">Whether or not to allow copying of the audio stream url.</param> @@ -1258,7 +1259,7 @@ public class DynamicHlsController : BaseJellyfinApiController /// <param name="cpuCoreLimit">Optional. The limit of how many cpu cores to use.</param> /// <param name="liveStreamId">The live stream id.</param> /// <param name="enableMpegtsM2TsMode">Optional. Whether to enable the MpegtsM2Ts mode.</param> - /// <param name="videoCodec">Optional. Specify a video codec to encode to, e.g. h264. If omitted the server will auto-select using the url's extension. Options: h265, h264, mpeg4, theora, vpx, wmv.</param> + /// <param name="videoCodec">Optional. Specify a video codec to encode to, e.g. h264.</param> /// <param name="subtitleCodec">Optional. Specify a subtitle codec to encode to.</param> /// <param name="transcodeReasons">Optional. The transcoding reason.</param> /// <param name="audioStreamIndex">Optional. The index of the audio stream to use. If omitted the first audio stream will be used.</param> @@ -1851,13 +1852,12 @@ public class DynamicHlsController : BaseJellyfinApiController args += _encodingHelper.GetHlsVideoKeyFrameArguments(state, codec, state.SegmentLength, isEventPlaylist, startNumber); // Currently b-frames in libx265 breaks the FMP4-HLS playback on iOS, disable it for now. - if (string.Equals(codec, "libx265", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(codec, "libx265", StringComparison.OrdinalIgnoreCase) + && _mediaEncoder.EncoderVersion < _minFFmpegX265BframeInFmp4) { args += " -bf 0"; } - // args += " -mixed-refs 0 -refs 3 -x264opts b_pyramid=0:weightb=0:weightp=0"; - // video processing filters. var videoProcessParam = _encodingHelper.GetVideoProcessingFilterParam(state, _encodingOptions, codec); diff --git a/Jellyfin.Api/Controllers/ImageController.cs b/Jellyfin.Api/Controllers/ImageController.cs index 8e8accab3..b71199026 100644 --- a/Jellyfin.Api/Controllers/ImageController.cs +++ b/Jellyfin.Api/Controllers/ImageController.cs @@ -109,7 +109,7 @@ public class ImageController : BaseJellyfinApiController return NotFound(); } - if (!RequestHelpers.AssertCanUpdateUser(_userManager, HttpContext.User, requestUserId, true)) + if (!RequestHelpers.AssertCanUpdateUser(HttpContext.User, user, true)) { return StatusCode(StatusCodes.Status403Forbidden, "User is not allowed to update the image."); } @@ -203,13 +203,18 @@ public class ImageController : BaseJellyfinApiController [FromQuery] Guid? userId) { var requestUserId = RequestHelpers.GetUserId(User, userId); - if (!RequestHelpers.AssertCanUpdateUser(_userManager, HttpContext.User, requestUserId, true)) + var user = _userManager.GetUserById(requestUserId); + if (user is null) + { + return NotFound(); + } + + if (!RequestHelpers.AssertCanUpdateUser(HttpContext.User, user, true)) { return StatusCode(StatusCodes.Status403Forbidden, "User is not allowed to delete the image."); } - var user = _userManager.GetUserById(requestUserId); - if (user?.ProfileImage is null) + if (user.ProfileImage is null) { return NoContent(); } @@ -2089,6 +2094,8 @@ public class ImageController : BaseJellyfinApiController Response.Headers.Append(HeaderNames.Age, Convert.ToInt64((DateTime.UtcNow - dateImageModified).TotalSeconds).ToString(CultureInfo.InvariantCulture)); Response.Headers.Append(HeaderNames.Vary, HeaderNames.Accept); + Response.Headers.ContentDisposition = "attachment"; + if (disableCaching) { Response.Headers.Append(HeaderNames.CacheControl, "no-cache, no-store, must-revalidate"); diff --git a/Jellyfin.Api/Controllers/ItemsController.cs b/Jellyfin.Api/Controllers/ItemsController.cs index d33634412..828bd5174 100644 --- a/Jellyfin.Api/Controllers/ItemsController.cs +++ b/Jellyfin.Api/Controllers/ItemsController.cs @@ -972,12 +972,17 @@ public class ItemsController : BaseJellyfinApiController [FromRoute, Required] Guid itemId) { var requestUserId = RequestHelpers.GetUserId(User, userId); - if (!RequestHelpers.AssertCanUpdateUser(_userManager, User, requestUserId, true)) + var user = _userManager.GetUserById(requestUserId); + if (user is null) + { + return NotFound(); + } + + if (!RequestHelpers.AssertCanUpdateUser(User, user, true)) { return StatusCode(StatusCodes.Status403Forbidden, "User is not allowed to view this item user data."); } - var user = _userManager.GetUserById(requestUserId) ?? throw new ResourceNotFoundException(); var item = _libraryManager.GetItemById<BaseItem>(itemId, user); if (item is null) { @@ -1023,12 +1028,17 @@ public class ItemsController : BaseJellyfinApiController [FromBody, Required] UpdateUserItemDataDto userDataDto) { var requestUserId = RequestHelpers.GetUserId(User, userId); - if (!RequestHelpers.AssertCanUpdateUser(_userManager, User, requestUserId, true)) + var user = _userManager.GetUserById(requestUserId); + if (user is null) + { + return NotFound(); + } + + if (!RequestHelpers.AssertCanUpdateUser(User, user, true)) { return StatusCode(StatusCodes.Status403Forbidden, "User is not allowed to update this item user data."); } - var user = _userManager.GetUserById(requestUserId) ?? throw new ResourceNotFoundException(); var item = _libraryManager.GetItemById<BaseItem>(itemId, user); if (item is null) { diff --git a/Jellyfin.Api/Controllers/LibraryController.cs b/Jellyfin.Api/Controllers/LibraryController.cs index 62cb59335..afc93c3a8 100644 --- a/Jellyfin.Api/Controllers/LibraryController.cs +++ b/Jellyfin.Api/Controllers/LibraryController.cs @@ -857,6 +857,16 @@ public class LibraryController : BaseJellyfinApiController .DistinctBy(i => i.Name, StringComparer.OrdinalIgnoreCase) .ToArray(); + result.LyricFetchers = plugins + .SelectMany(i => i.Plugins.Where(p => p.Type == MetadataPluginType.LyricFetcher)) + .Select(i => new LibraryOptionInfoDto + { + Name = i.Name, + DefaultEnabled = true + }) + .DistinctBy(i => i.Name, StringComparer.OrdinalIgnoreCase) + .ToArray(); + var typeOptions = new List<LibraryTypeOptionsDto>(); foreach (var type in types) diff --git a/Jellyfin.Api/Controllers/MediaSegmentsController.cs b/Jellyfin.Api/Controllers/MediaSegmentsController.cs new file mode 100644 index 000000000..e97704d48 --- /dev/null +++ b/Jellyfin.Api/Controllers/MediaSegmentsController.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Threading.Tasks; +using Jellyfin.Api.Extensions; +using Jellyfin.Data.Enums; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Model.MediaSegments; +using MediaBrowser.Model.Querying; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace Jellyfin.Api.Controllers; + +/// <summary> +/// Media Segments api. +/// </summary> +[Authorize] +public class MediaSegmentsController : BaseJellyfinApiController +{ + private readonly IMediaSegmentManager _mediaSegmentManager; + private readonly ILibraryManager _libraryManager; + + /// <summary> + /// Initializes a new instance of the <see cref="MediaSegmentsController"/> class. + /// </summary> + /// <param name="mediaSegmentManager">MediaSegments Manager.</param> + /// <param name="libraryManager">The Library manager.</param> + public MediaSegmentsController(IMediaSegmentManager mediaSegmentManager, ILibraryManager libraryManager) + { + _mediaSegmentManager = mediaSegmentManager; + _libraryManager = libraryManager; + } + + /// <summary> + /// Gets all media segments based on an itemId. + /// </summary> + /// <param name="itemId">The ItemId.</param> + /// <param name="includeSegmentTypes">Optional filter of requested segment types.</param> + /// <returns>A list of media segment objects related to the requested itemId.</returns> + [HttpGet("{itemId}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task<ActionResult<QueryResult<MediaSegmentDto>>> GetSegmentsAsync( + [FromRoute, Required] Guid itemId, + [FromQuery] IEnumerable<MediaSegmentType>? includeSegmentTypes = null) + { + var item = _libraryManager.GetItemById<BaseItem>(itemId, User.GetUserId()); + if (item is null) + { + return NotFound(); + } + + var items = await _mediaSegmentManager.GetSegmentsAsync(item.Id, includeSegmentTypes).ConfigureAwait(false); + return Ok(new QueryResult<MediaSegmentDto>(items.ToArray())); + } +} diff --git a/Jellyfin.Api/Controllers/PluginsController.cs b/Jellyfin.Api/Controllers/PluginsController.cs index 6abd7a23e..53b7349e7 100644 --- a/Jellyfin.Api/Controllers/PluginsController.cs +++ b/Jellyfin.Api/Controllers/PluginsController.cs @@ -233,6 +233,8 @@ public class PluginsController : BaseJellyfinApiController return NotFound(); } + Response.Headers.ContentDisposition = "attachment"; + imagePath = Path.Combine(plugin.Path, plugin.Manifest.ImagePath); return PhysicalFile(imagePath, MimeTypes.GetMimeType(imagePath)); } diff --git a/Jellyfin.Api/Controllers/TrickplayController.cs b/Jellyfin.Api/Controllers/TrickplayController.cs index 0afe053da..60d49af9e 100644 --- a/Jellyfin.Api/Controllers/TrickplayController.cs +++ b/Jellyfin.Api/Controllers/TrickplayController.cs @@ -95,6 +95,7 @@ public class TrickplayController : BaseJellyfinApiController var path = _trickplayManager.GetTrickplayTilePath(item, width, index); if (System.IO.File.Exists(path)) { + Response.Headers.ContentDisposition = "attachment"; return PhysicalFile(path, MediaTypeNames.Image.Jpeg); } diff --git a/Jellyfin.Api/Controllers/TvShowsController.cs b/Jellyfin.Api/Controllers/TvShowsController.cs index 426402667..914ccd7f9 100644 --- a/Jellyfin.Api/Controllers/TvShowsController.cs +++ b/Jellyfin.Api/Controllers/TvShowsController.cs @@ -90,7 +90,12 @@ public class TvShowsController : BaseJellyfinApiController [FromQuery] bool enableResumable = true, [FromQuery] bool enableRewatching = false) { - userId = RequestHelpers.GetUserId(User, userId); + var user = _userManager.GetUserById(RequestHelpers.GetUserId(User, userId)); + if (user is null) + { + return NotFound(); + } + var options = new DtoOptions { Fields = fields } .AddClientFields(User) .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); @@ -102,7 +107,7 @@ public class TvShowsController : BaseJellyfinApiController ParentId = parentId, SeriesId = seriesId, StartIndex = startIndex, - UserId = userId.Value, + User = user, EnableTotalRecordCount = enableTotalRecordCount, DisableFirstEpisode = disableFirstEpisode, NextUpDateCutoff = nextUpDateCutoff ?? DateTime.MinValue, @@ -111,10 +116,6 @@ public class TvShowsController : BaseJellyfinApiController }, options); - var user = userId.IsNullOrEmpty() - ? null - : _userManager.GetUserById(userId.Value); - var returnItems = _dtoService.GetBaseItemDtos(result.Items, options, user); return new QueryResult<BaseItemDto>( diff --git a/Jellyfin.Api/Controllers/UserController.cs b/Jellyfin.Api/Controllers/UserController.cs index c3923a2ad..2df79c80c 100644 --- a/Jellyfin.Api/Controllers/UserController.cs +++ b/Jellyfin.Api/Controllers/UserController.cs @@ -274,16 +274,15 @@ public class UserController : BaseJellyfinApiController [FromBody, Required] UpdateUserPassword request) { var requestUserId = userId ?? User.GetUserId(); - if (!RequestHelpers.AssertCanUpdateUser(_userManager, User, requestUserId, true)) + var user = _userManager.GetUserById(requestUserId); + if (user is null) { - return StatusCode(StatusCodes.Status403Forbidden, "User is not allowed to update the password."); + return NotFound(); } - var user = _userManager.GetUserById(requestUserId); - - if (user is null) + if (!RequestHelpers.AssertCanUpdateUser(User, user, true)) { - return NotFound("User not found"); + return StatusCode(StatusCodes.Status403Forbidden, "User is not allowed to update the password."); } if (request.ResetPassword) @@ -386,7 +385,7 @@ public class UserController : BaseJellyfinApiController return NotFound(); } - if (!RequestHelpers.AssertCanUpdateUser(_userManager, User, requestUserId, true)) + if (!RequestHelpers.AssertCanUpdateUser(User, user, true)) { return StatusCode(StatusCodes.Status403Forbidden, "User update not allowed."); } @@ -396,7 +395,7 @@ public class UserController : BaseJellyfinApiController await _userManager.RenameUser(user, updateUser.Name).ConfigureAwait(false); } - await _userManager.UpdateConfigurationAsync(user.Id, updateUser.Configuration).ConfigureAwait(false); + await _userManager.UpdateConfigurationAsync(requestUserId, updateUser.Configuration).ConfigureAwait(false); return NoContent(); } @@ -495,7 +494,13 @@ public class UserController : BaseJellyfinApiController [FromBody, Required] UserConfiguration userConfig) { var requestUserId = userId ?? User.GetUserId(); - if (!RequestHelpers.AssertCanUpdateUser(_userManager, User, requestUserId, true)) + var user = _userManager.GetUserById(requestUserId); + if (user is null) + { + return NotFound(); + } + + if (!RequestHelpers.AssertCanUpdateUser(User, user, true)) { return StatusCode(StatusCodes.Status403Forbidden, "User configuration update not allowed"); } diff --git a/Jellyfin.Api/Controllers/UserLibraryController.cs b/Jellyfin.Api/Controllers/UserLibraryController.cs index 421f1bfb5..e7bf71727 100644 --- a/Jellyfin.Api/Controllers/UserLibraryController.cs +++ b/Jellyfin.Api/Controllers/UserLibraryController.cs @@ -560,7 +560,7 @@ public class UserLibraryController : BaseJellyfinApiController IsPlayed = isPlayed, Limit = limit, ParentId = parentId ?? Guid.Empty, - UserId = requestUserId, + User = user, }, dtoOptions); diff --git a/Jellyfin.Api/Controllers/UserViewsController.cs b/Jellyfin.Api/Controllers/UserViewsController.cs index 01da50d02..e24f78a88 100644 --- a/Jellyfin.Api/Controllers/UserViewsController.cs +++ b/Jellyfin.Api/Controllers/UserViewsController.cs @@ -8,6 +8,7 @@ using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; using Jellyfin.Api.Models.UserViewDtos; using Jellyfin.Data.Enums; +using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; @@ -69,8 +70,9 @@ public class UserViewsController : BaseJellyfinApiController [FromQuery] bool includeHidden = false) { userId = RequestHelpers.GetUserId(User, userId); + var user = _userManager.GetUserById(userId.Value) ?? throw new ResourceNotFoundException(); - var query = new UserViewQuery { UserId = userId.Value, IncludeHidden = includeHidden }; + var query = new UserViewQuery { User = user, IncludeHidden = includeHidden }; if (includeExternalContent.HasValue) { @@ -87,8 +89,6 @@ public class UserViewsController : BaseJellyfinApiController var dtoOptions = new DtoOptions().AddClientFields(User); dtoOptions.Fields = [..dtoOptions.Fields, ItemFields.PrimaryImageAspectRatio, ItemFields.DisplayPreferencesId]; - var user = _userManager.GetUserById(userId.Value); - var dtos = Array.ConvertAll(folders, i => _dtoService.GetBaseItemDto(i, dtoOptions, user)); return new QueryResult<BaseItemDto>(dtos); diff --git a/Jellyfin.Api/Controllers/VideosController.cs b/Jellyfin.Api/Controllers/VideosController.cs index 7f9608378..effe7b021 100644 --- a/Jellyfin.Api/Controllers/VideosController.cs +++ b/Jellyfin.Api/Controllers/VideosController.cs @@ -267,7 +267,7 @@ public class VideosController : BaseJellyfinApiController /// <param name="minSegments">The minimum number of segments.</param> /// <param name="mediaSourceId">The media version id, if playing an alternate version.</param> /// <param name="deviceId">The device id of the client requesting. Used to stop encoding processes when needed.</param> - /// <param name="audioCodec">Optional. Specify a audio codec to encode to, e.g. mp3. If omitted the server will auto-select using the url's extension. Options: aac, mp3, vorbis, wma.</param> + /// <param name="audioCodec">Optional. Specify an audio codec to encode to, e.g. mp3. If omitted the server will auto-select using the url's extension.</param> /// <param name="enableAutoStreamCopy">Whether or not to allow automatic stream copy if requested values match the original source. Defaults to true.</param> /// <param name="allowVideoStreamCopy">Whether or not to allow copying of the video stream url.</param> /// <param name="allowAudioStreamCopy">Whether or not to allow copying of the audio stream url.</param> @@ -299,7 +299,7 @@ public class VideosController : BaseJellyfinApiController /// <param name="cpuCoreLimit">Optional. The limit of how many cpu cores to use.</param> /// <param name="liveStreamId">The live stream id.</param> /// <param name="enableMpegtsM2TsMode">Optional. Whether to enable the MpegtsM2Ts mode.</param> - /// <param name="videoCodec">Optional. Specify a video codec to encode to, e.g. h264. If omitted the server will auto-select using the url's extension. Options: h265, h264, mpeg4, theora, vp8, vp9, vpx (deprecated), wmv.</param> + /// <param name="videoCodec">Optional. Specify a video codec to encode to, e.g. h264. If omitted the server will auto-select using the url's extension.</param> /// <param name="subtitleCodec">Optional. Specify a subtitle codec to encode to.</param> /// <param name="transcodeReasons">Optional. The transcoding reason.</param> /// <param name="audioStreamIndex">Optional. The index of the audio stream to use. If omitted the first audio stream will be used.</param> @@ -508,7 +508,7 @@ public class VideosController : BaseJellyfinApiController /// <param name="minSegments">The minimum number of segments.</param> /// <param name="mediaSourceId">The media version id, if playing an alternate version.</param> /// <param name="deviceId">The device id of the client requesting. Used to stop encoding processes when needed.</param> - /// <param name="audioCodec">Optional. Specify a audio codec to encode to, e.g. mp3. If omitted the server will auto-select using the url's extension. Options: aac, mp3, vorbis, wma.</param> + /// <param name="audioCodec">Optional. Specify an audio codec to encode to, e.g. mp3. If omitted the server will auto-select using the url's extension.</param> /// <param name="enableAutoStreamCopy">Whether or not to allow automatic stream copy if requested values match the original source. Defaults to true.</param> /// <param name="allowVideoStreamCopy">Whether or not to allow copying of the video stream url.</param> /// <param name="allowAudioStreamCopy">Whether or not to allow copying of the audio stream url.</param> @@ -540,7 +540,7 @@ public class VideosController : BaseJellyfinApiController /// <param name="cpuCoreLimit">Optional. The limit of how many cpu cores to use.</param> /// <param name="liveStreamId">The live stream id.</param> /// <param name="enableMpegtsM2TsMode">Optional. Whether to enable the MpegtsM2Ts mode.</param> - /// <param name="videoCodec">Optional. Specify a video codec to encode to, e.g. h264. If omitted the server will auto-select using the url's extension. Options: h265, h264, mpeg4, theora, vp8, vp9, vpx (deprecated), wmv.</param> + /// <param name="videoCodec">Optional. Specify a video codec to encode to, e.g. h264. If omitted the server will auto-select using the url's extension.</param> /// <param name="subtitleCodec">Optional. Specify a subtitle codec to encode to.</param> /// <param name="transcodeReasons">Optional. The transcoding reason.</param> /// <param name="audioStreamIndex">Optional. The index of the audio stream to use. If omitted the first audio stream will be used.</param> diff --git a/Jellyfin.Api/Helpers/DynamicHlsHelper.cs b/Jellyfin.Api/Helpers/DynamicHlsHelper.cs index 6f040cfae..ba92d811c 100644 --- a/Jellyfin.Api/Helpers/DynamicHlsHelper.cs +++ b/Jellyfin.Api/Helpers/DynamicHlsHelper.cs @@ -198,6 +198,17 @@ public class DynamicHlsHelper AddSubtitles(state, subtitleStreams, builder, _httpContextAccessor.HttpContext.User); } + // Video rotation metadata is only supported in fMP4 remuxing + if (state.VideoStream is not null + && state.VideoRequest is not null + && (state.VideoStream?.Rotation ?? 0) != 0 + && EncodingHelper.IsCopyCodec(state.OutputVideoCodec) + && !string.IsNullOrWhiteSpace(state.Request.SegmentContainer) + && !string.Equals(state.Request.SegmentContainer, "mp4", StringComparison.OrdinalIgnoreCase)) + { + playlistUrl += "&AllowVideoStreamCopy=false"; + } + var basicPlaylist = AppendPlaylist(builder, state, playlistUrl, totalBitrate, subtitleGroup); if (state.VideoStream is not null && state.VideoRequest is not null) diff --git a/Jellyfin.Api/Helpers/HlsCodecStringHelpers.cs b/Jellyfin.Api/Helpers/HlsCodecStringHelpers.cs index d0bfa1fbe..fa0db0541 100644 --- a/Jellyfin.Api/Helpers/HlsCodecStringHelpers.cs +++ b/Jellyfin.Api/Helpers/HlsCodecStringHelpers.cs @@ -19,12 +19,12 @@ public static class HlsCodecStringHelpers /// <summary> /// Codec name for AC-3. /// </summary> - public const string AC3 = "mp4a.a5"; + public const string AC3 = "ac-3"; /// <summary> /// Codec name for E-AC-3. /// </summary> - public const string EAC3 = "mp4a.a6"; + public const string EAC3 = "ec-3"; /// <summary> /// Codec name for FLAC. diff --git a/Jellyfin.Api/Helpers/RequestHelpers.cs b/Jellyfin.Api/Helpers/RequestHelpers.cs index a3d7f471e..1d9c189a0 100644 --- a/Jellyfin.Api/Helpers/RequestHelpers.cs +++ b/Jellyfin.Api/Helpers/RequestHelpers.cs @@ -86,18 +86,17 @@ public static class RequestHelpers /// <summary> /// Checks if the user can update an entry. /// </summary> - /// <param name="userManager">An instance of the <see cref="IUserManager"/> interface.</param> /// <param name="claimsPrincipal">The <see cref="ClaimsPrincipal"/> for the current request.</param> - /// <param name="userId">The user id.</param> + /// <param name="user">The user id.</param> /// <param name="restrictUserPreferences">Whether to restrict the user preferences.</param> /// <returns>A <see cref="bool"/> whether the user can update the entry.</returns> - internal static bool AssertCanUpdateUser(IUserManager userManager, ClaimsPrincipal claimsPrincipal, Guid userId, bool restrictUserPreferences) + internal static bool AssertCanUpdateUser(ClaimsPrincipal claimsPrincipal, User user, bool restrictUserPreferences) { var authenticatedUserId = claimsPrincipal.GetUserId(); var isAdministrator = claimsPrincipal.IsInRole(UserRoles.Administrator); // If they're going to update the record of another user, they must be an administrator - if (!userId.Equals(authenticatedUserId) && !isAdministrator) + if (!user.Id.Equals(authenticatedUserId) && !isAdministrator) { return false; } @@ -108,12 +107,6 @@ public static class RequestHelpers return true; } - var user = userManager.GetUserById(userId); - if (user is null) - { - throw new ResourceNotFoundException(); - } - return user.EnableUserPreferenceAccess; } diff --git a/Jellyfin.Api/Models/LibraryDtos/LibraryOptionsResultDto.cs b/Jellyfin.Api/Models/LibraryDtos/LibraryOptionsResultDto.cs index 78efacd94..53b5e3b7c 100644 --- a/Jellyfin.Api/Models/LibraryDtos/LibraryOptionsResultDto.cs +++ b/Jellyfin.Api/Models/LibraryDtos/LibraryOptionsResultDto.cs @@ -24,6 +24,11 @@ public class LibraryOptionsResultDto public IReadOnlyList<LibraryOptionInfoDto> SubtitleFetchers { get; set; } = Array.Empty<LibraryOptionInfoDto>(); /// <summary> + /// Gets or sets the list of lyric fetchers. + /// </summary> + public IReadOnlyList<LibraryOptionInfoDto> LyricFetchers { get; set; } = Array.Empty<LibraryOptionInfoDto>(); + + /// <summary> /// Gets or sets the type options. /// </summary> public IReadOnlyList<LibraryTypeOptionsDto> TypeOptions { get; set; } = Array.Empty<LibraryTypeOptionsDto>(); |
