aboutsummaryrefslogtreecommitdiff
path: root/Jellyfin.Api/Controllers
diff options
context:
space:
mode:
Diffstat (limited to 'Jellyfin.Api/Controllers')
-rw-r--r--Jellyfin.Api/Controllers/DlnaServerController.cs16
-rw-r--r--Jellyfin.Api/Controllers/DynamicHlsController.cs54
-rw-r--r--Jellyfin.Api/Controllers/HlsSegmentController.cs9
-rw-r--r--Jellyfin.Api/Controllers/ImageController.cs43
-rw-r--r--Jellyfin.Api/Controllers/LibraryController.cs6
-rw-r--r--Jellyfin.Api/Controllers/LiveTvController.cs7
-rw-r--r--Jellyfin.Api/Controllers/MediaInfoController.cs2
-rw-r--r--Jellyfin.Api/Controllers/SubtitleController.cs13
-rw-r--r--Jellyfin.Api/Controllers/SystemController.cs56
-rw-r--r--Jellyfin.Api/Controllers/TvShowsController.cs5
-rw-r--r--Jellyfin.Api/Controllers/UniversalAudioController.cs2
-rw-r--r--Jellyfin.Api/Controllers/UserController.cs20
12 files changed, 113 insertions, 120 deletions
diff --git a/Jellyfin.Api/Controllers/DlnaServerController.cs b/Jellyfin.Api/Controllers/DlnaServerController.cs
index 95b296fae..42576934b 100644
--- a/Jellyfin.Api/Controllers/DlnaServerController.cs
+++ b/Jellyfin.Api/Controllers/DlnaServerController.cs
@@ -5,7 +5,6 @@ using System.IO;
using System.Net.Mime;
using System.Threading.Tasks;
using Emby.Dlna;
-using Emby.Dlna.Main;
using Jellyfin.Api.Attributes;
using Jellyfin.Api.Constants;
using MediaBrowser.Controller.Dlna;
@@ -33,12 +32,19 @@ public class DlnaServerController : BaseJellyfinApiController
/// Initializes a new instance of the <see cref="DlnaServerController"/> class.
/// </summary>
/// <param name="dlnaManager">Instance of the <see cref="IDlnaManager"/> interface.</param>
- public DlnaServerController(IDlnaManager dlnaManager)
+ /// <param name="contentDirectory">Instance of the <see cref="IContentDirectory"/> interface.</param>
+ /// <param name="connectionManager">Instance of the <see cref="IConnectionManager"/> interface.</param>
+ /// <param name="mediaReceiverRegistrar">Instance of the <see cref="IMediaReceiverRegistrar"/> interface.</param>
+ public DlnaServerController(
+ IDlnaManager dlnaManager,
+ IContentDirectory contentDirectory,
+ IConnectionManager connectionManager,
+ IMediaReceiverRegistrar mediaReceiverRegistrar)
{
_dlnaManager = dlnaManager;
- _contentDirectory = DlnaEntryPoint.Current.ContentDirectory;
- _connectionManager = DlnaEntryPoint.Current.ConnectionManager;
- _mediaReceiverRegistrar = DlnaEntryPoint.Current.MediaReceiverRegistrar;
+ _contentDirectory = contentDirectory;
+ _connectionManager = connectionManager;
+ _mediaReceiverRegistrar = mediaReceiverRegistrar;
}
/// <summary>
diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs
index 4b89738a1..38953dc21 100644
--- a/Jellyfin.Api/Controllers/DynamicHlsController.cs
+++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs
@@ -45,6 +45,8 @@ public class DynamicHlsController : BaseJellyfinApiController
private const string DefaultEventEncoderPreset = "superfast";
private const TranscodingJobType TranscodingJobType = MediaBrowser.Controller.MediaEncoding.TranscodingJobType.Hls;
+ private readonly Version _minFFmpegFlacInMp4 = new Version(6, 0);
+
private readonly ILibraryManager _libraryManager;
private readonly IUserManager _userManager;
private readonly IDlnaManager _dlnaManager;
@@ -1654,7 +1656,7 @@ public class DynamicHlsController : BaseJellyfinApiController
_encodingHelper.GetInputArgument(state, _encodingOptions, segmentContainer),
threads,
mapArgs,
- GetVideoArguments(state, startNumber, isEventPlaylist),
+ GetVideoArguments(state, startNumber, isEventPlaylist, segmentContainer),
GetAudioArguments(state),
maxMuxingQueueSize,
state.SegmentLength.ToString(CultureInfo.InvariantCulture),
@@ -1706,19 +1708,33 @@ public class DynamicHlsController : BaseJellyfinApiController
}
var audioCodec = _encodingHelper.GetAudioEncoder(state);
+ var bitStreamArgs = EncodingHelper.GetAudioBitStreamArguments(state, state.Request.SegmentContainer, state.MediaSource.Container);
+
+ // opus, dts, truehd and flac (in FFmpeg 5 and older) are experimental in mp4 muxer
+ var strictArgs = string.Empty;
+ var actualOutputAudioCodec = state.ActualOutputAudioCodec;
+ if (string.Equals(actualOutputAudioCodec, "opus", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(actualOutputAudioCodec, "dts", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(actualOutputAudioCodec, "truehd", StringComparison.OrdinalIgnoreCase)
+ || (string.Equals(actualOutputAudioCodec, "flac", StringComparison.OrdinalIgnoreCase)
+ && _mediaEncoder.EncoderVersion < _minFFmpegFlacInMp4))
+ {
+ strictArgs = " -strict -2";
+ }
if (!state.IsOutputVideo)
{
+ var audioTranscodeParams = string.Empty;
+
+ // -vn to drop any video streams
+ audioTranscodeParams += "-vn";
+
if (EncodingHelper.IsCopyCodec(audioCodec))
{
- var bitStreamArgs = EncodingHelper.GetAudioBitStreamArguments(state, state.Request.SegmentContainer, state.MediaSource.Container);
-
- return "-acodec copy -strict -2" + bitStreamArgs;
+ return audioTranscodeParams + " -acodec copy" + bitStreamArgs + strictArgs;
}
- var audioTranscodeParams = string.Empty;
-
- audioTranscodeParams += "-acodec " + audioCodec;
+ audioTranscodeParams += " -acodec " + audioCodec + bitStreamArgs + strictArgs;
var audioBitrate = state.OutputAudioBitrate;
var audioChannels = state.OutputAudioChannels;
@@ -1746,25 +1762,12 @@ public class DynamicHlsController : BaseJellyfinApiController
audioTranscodeParams += " -ar " + state.OutputAudioSampleRate.Value.ToString(CultureInfo.InvariantCulture);
}
- audioTranscodeParams += " -vn";
return audioTranscodeParams;
}
- // dts, flac, opus and truehd are experimental in mp4 muxer
- var strictArgs = string.Empty;
- var actualOutputAudioCodec = state.ActualOutputAudioCodec;
- if (string.Equals(actualOutputAudioCodec, "flac", StringComparison.OrdinalIgnoreCase)
- || string.Equals(actualOutputAudioCodec, "opus", StringComparison.OrdinalIgnoreCase)
- || string.Equals(actualOutputAudioCodec, "dts", StringComparison.OrdinalIgnoreCase)
- || string.Equals(actualOutputAudioCodec, "truehd", StringComparison.OrdinalIgnoreCase))
- {
- strictArgs = " -strict -2";
- }
-
if (EncodingHelper.IsCopyCodec(audioCodec))
{
var videoCodec = _encodingHelper.GetVideoEncoder(state, _encodingOptions);
- var bitStreamArgs = EncodingHelper.GetAudioBitStreamArguments(state, state.Request.SegmentContainer, state.MediaSource.Container);
var copyArgs = "-codec:a:0 copy" + bitStreamArgs + strictArgs;
if (EncodingHelper.IsCopyCodec(videoCodec) && state.EnableBreakOnNonKeyFrames(videoCodec))
@@ -1775,7 +1778,7 @@ public class DynamicHlsController : BaseJellyfinApiController
return copyArgs;
}
- var args = "-codec:a:0 " + audioCodec + strictArgs;
+ var args = "-codec:a:0 " + audioCodec + bitStreamArgs + strictArgs;
var channels = state.OutputAudioChannels;
@@ -1819,8 +1822,9 @@ public class DynamicHlsController : BaseJellyfinApiController
/// <param name="state">The <see cref="StreamState"/>.</param>
/// <param name="startNumber">The first number in the hls sequence.</param>
/// <param name="isEventPlaylist">Whether the playlist is EVENT or VOD.</param>
+ /// <param name="segmentContainer">The segment container.</param>
/// <returns>The command line arguments for video transcoding.</returns>
- private string GetVideoArguments(StreamState state, int startNumber, bool isEventPlaylist)
+ private string GetVideoArguments(StreamState state, int startNumber, bool isEventPlaylist, string segmentContainer)
{
if (state.VideoStream is null)
{
@@ -1912,7 +1916,7 @@ public class DynamicHlsController : BaseJellyfinApiController
}
// TODO why was this not enabled for VOD?
- if (isEventPlaylist)
+ if (isEventPlaylist && string.Equals(segmentContainer, "ts", StringComparison.OrdinalIgnoreCase))
{
args += " -flags -global_header";
}
@@ -2045,9 +2049,9 @@ public class DynamicHlsController : BaseJellyfinApiController
return null;
}
- var playlistFilename = Path.GetFileNameWithoutExtension(playlist);
+ var playlistFilename = Path.GetFileNameWithoutExtension(playlist.AsSpan());
- var indexString = Path.GetFileNameWithoutExtension(file.Name).Substring(playlistFilename.Length);
+ var indexString = Path.GetFileNameWithoutExtension(file.Name.AsSpan()).Slice(playlistFilename.Length);
return int.Parse(indexString, NumberStyles.Integer, CultureInfo.InvariantCulture);
}
diff --git a/Jellyfin.Api/Controllers/HlsSegmentController.cs b/Jellyfin.Api/Controllers/HlsSegmentController.cs
index d7cec865e..6eedfd8c7 100644
--- a/Jellyfin.Api/Controllers/HlsSegmentController.cs
+++ b/Jellyfin.Api/Controllers/HlsSegmentController.cs
@@ -59,7 +59,7 @@ public class HlsSegmentController : BaseJellyfinApiController
public ActionResult GetHlsAudioSegmentLegacy([FromRoute, Required] string itemId, [FromRoute, Required] string segmentId)
{
// TODO: Deprecate with new iOS app
- var file = segmentId + Path.GetExtension(Request.Path);
+ var file = string.Concat(segmentId, Path.GetExtension(Request.Path.Value.AsSpan()));
var transcodePath = _serverConfigurationManager.GetTranscodePath();
file = Path.GetFullPath(Path.Combine(transcodePath, file));
var fileDir = Path.GetDirectoryName(file);
@@ -85,11 +85,12 @@ public class HlsSegmentController : BaseJellyfinApiController
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "itemId", Justification = "Required for ServiceStack")]
public ActionResult GetHlsPlaylistLegacy([FromRoute, Required] string itemId, [FromRoute, Required] string playlistId)
{
- var file = playlistId + Path.GetExtension(Request.Path);
+ var file = string.Concat(playlistId, Path.GetExtension(Request.Path.Value.AsSpan()));
var transcodePath = _serverConfigurationManager.GetTranscodePath();
file = Path.GetFullPath(Path.Combine(transcodePath, file));
var fileDir = Path.GetDirectoryName(file);
- if (string.IsNullOrEmpty(fileDir) || !fileDir.StartsWith(transcodePath, StringComparison.InvariantCulture) || Path.GetExtension(file) != ".m3u8")
+ if (string.IsNullOrEmpty(fileDir) || !fileDir.StartsWith(transcodePath, StringComparison.InvariantCulture)
+ || Path.GetExtension(file.AsSpan()).Equals(".m3u8", StringComparison.OrdinalIgnoreCase))
{
return BadRequest("Invalid segment.");
}
@@ -138,7 +139,7 @@ public class HlsSegmentController : BaseJellyfinApiController
[FromRoute, Required] string segmentId,
[FromRoute, Required] string segmentContainer)
{
- var file = segmentId + Path.GetExtension(Request.Path);
+ var file = string.Concat(segmentId, Path.GetExtension(Request.Path.Value.AsSpan()));
var transcodeFolderPath = _serverConfigurationManager.GetTranscodePath();
file = Path.GetFullPath(Path.Combine(transcodeFolderPath, file));
diff --git a/Jellyfin.Api/Controllers/ImageController.cs b/Jellyfin.Api/Controllers/ImageController.cs
index 3c5f18af5..7b10ea170 100644
--- a/Jellyfin.Api/Controllers/ImageController.cs
+++ b/Jellyfin.Api/Controllers/ImageController.cs
@@ -7,6 +7,7 @@ using System.Globalization;
using System.IO;
using System.Linq;
using System.Net.Mime;
+using System.Security.Cryptography;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Api.Attributes;
@@ -78,6 +79,9 @@ public class ImageController : BaseJellyfinApiController
_appPaths = appPaths;
}
+ private static Stream GetFromBase64Stream(Stream inputStream)
+ => new CryptoStream(inputStream, new FromBase64Transform(), CryptoStreamMode.Read);
+
/// <summary>
/// Sets the user image.
/// </summary>
@@ -116,8 +120,8 @@ public class ImageController : BaseJellyfinApiController
return BadRequest("Incorrect ContentType.");
}
- var memoryStream = await GetMemoryStream(Request.Body).ConfigureAwait(false);
- await using (memoryStream.ConfigureAwait(false))
+ var stream = GetFromBase64Stream(Request.Body);
+ await using (stream.ConfigureAwait(false))
{
// Handle image/png; charset=utf-8
var mimeType = Request.ContentType?.Split(';').FirstOrDefault();
@@ -130,7 +134,7 @@ public class ImageController : BaseJellyfinApiController
user.ProfileImage = new Data.Entities.ImageInfo(Path.Combine(userDataPath, "profile" + extension));
await _providerManager
- .SaveImage(memoryStream, mimeType, user.ProfileImage.Path)
+ .SaveImage(stream, mimeType, user.ProfileImage.Path)
.ConfigureAwait(false);
await _userManager.UpdateUserAsync(user).ConfigureAwait(false);
@@ -176,8 +180,8 @@ public class ImageController : BaseJellyfinApiController
return BadRequest("Incorrect ContentType.");
}
- var memoryStream = await GetMemoryStream(Request.Body).ConfigureAwait(false);
- await using (memoryStream.ConfigureAwait(false))
+ var stream = GetFromBase64Stream(Request.Body);
+ await using (stream.ConfigureAwait(false))
{
// Handle image/png; charset=utf-8
var mimeType = Request.ContentType?.Split(';').FirstOrDefault();
@@ -190,7 +194,7 @@ public class ImageController : BaseJellyfinApiController
user.ProfileImage = new Data.Entities.ImageInfo(Path.Combine(userDataPath, "profile" + extension));
await _providerManager
- .SaveImage(memoryStream, mimeType, user.ProfileImage.Path)
+ .SaveImage(stream, mimeType, user.ProfileImage.Path)
.ConfigureAwait(false);
await _userManager.UpdateUserAsync(user).ConfigureAwait(false);
@@ -372,12 +376,12 @@ public class ImageController : BaseJellyfinApiController
return BadRequest("Incorrect ContentType.");
}
- var memoryStream = await GetMemoryStream(Request.Body).ConfigureAwait(false);
- await using (memoryStream.ConfigureAwait(false))
+ var stream = GetFromBase64Stream(Request.Body);
+ await using (stream.ConfigureAwait(false))
{
// Handle image/png; charset=utf-8
var mimeType = Request.ContentType?.Split(';').FirstOrDefault();
- await _providerManager.SaveImage(item, memoryStream, mimeType, imageType, null, CancellationToken.None).ConfigureAwait(false);
+ await _providerManager.SaveImage(item, stream, mimeType, imageType, null, CancellationToken.None).ConfigureAwait(false);
await item.UpdateToRepositoryAsync(ItemUpdateType.ImageUpdate, CancellationToken.None).ConfigureAwait(false);
return NoContent();
@@ -416,12 +420,12 @@ public class ImageController : BaseJellyfinApiController
return BadRequest("Incorrect ContentType.");
}
- var memoryStream = await GetMemoryStream(Request.Body).ConfigureAwait(false);
- await using (memoryStream.ConfigureAwait(false))
+ var stream = GetFromBase64Stream(Request.Body);
+ await using (stream.ConfigureAwait(false))
{
// Handle image/png; charset=utf-8
var mimeType = Request.ContentType?.Split(';').FirstOrDefault();
- await _providerManager.SaveImage(item, memoryStream, mimeType, imageType, null, CancellationToken.None).ConfigureAwait(false);
+ await _providerManager.SaveImage(item, stream, mimeType, imageType, null, CancellationToken.None).ConfigureAwait(false);
await item.UpdateToRepositoryAsync(ItemUpdateType.ImageUpdate, CancellationToken.None).ConfigureAwait(false);
return NoContent();
@@ -1792,8 +1796,8 @@ public class ImageController : BaseJellyfinApiController
return BadRequest("Incorrect ContentType.");
}
- var memoryStream = await GetMemoryStream(Request.Body).ConfigureAwait(false);
- await using (memoryStream.ConfigureAwait(false))
+ var stream = GetFromBase64Stream(Request.Body);
+ await using (stream.ConfigureAwait(false))
{
var filePath = Path.Combine(_appPaths.DataPath, "splashscreen-upload" + extension);
var brandingOptions = _serverConfigurationManager.GetConfiguration<BrandingOptions>("branding");
@@ -1803,7 +1807,7 @@ public class ImageController : BaseJellyfinApiController
var fs = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous);
await using (fs.ConfigureAwait(false))
{
- await memoryStream.CopyToAsync(fs, CancellationToken.None).ConfigureAwait(false);
+ await stream.CopyToAsync(fs, CancellationToken.None).ConfigureAwait(false);
}
return NoContent();
@@ -1833,15 +1837,6 @@ public class ImageController : BaseJellyfinApiController
return NoContent();
}
- private static async Task<MemoryStream> GetMemoryStream(Stream inputStream)
- {
- using var reader = new StreamReader(inputStream);
- var text = await reader.ReadToEndAsync().ConfigureAwait(false);
-
- var bytes = Convert.FromBase64String(text);
- return new MemoryStream(bytes, 0, bytes.Length, false, true);
- }
-
private ImageInfo? GetImageInfo(BaseItem item, ItemImageInfo info, int? imageIndex)
{
int? width = null;
diff --git a/Jellyfin.Api/Controllers/LibraryController.cs b/Jellyfin.Api/Controllers/LibraryController.cs
index 46c0a8d52..21941ff94 100644
--- a/Jellyfin.Api/Controllers/LibraryController.cs
+++ b/Jellyfin.Api/Controllers/LibraryController.cs
@@ -294,8 +294,8 @@ public class LibraryController : BaseJellyfinApiController
return new AllThemeMediaResult
{
- ThemeSongsResult = themeSongs?.Value,
- ThemeVideosResult = themeVideos?.Value,
+ ThemeSongsResult = themeSongs.Value,
+ ThemeVideosResult = themeVideos.Value,
SoundtrackSongsResult = new ThemeMediaResult()
};
}
@@ -490,7 +490,7 @@ public class LibraryController : BaseJellyfinApiController
baseItemDtos.Add(_dtoService.GetBaseItemDto(parent, dtoOptions, user));
- parent = parent?.GetParent();
+ parent = parent.GetParent();
}
return baseItemDtos;
diff --git a/Jellyfin.Api/Controllers/LiveTvController.cs b/Jellyfin.Api/Controllers/LiveTvController.cs
index 267ba4afb..649397d68 100644
--- a/Jellyfin.Api/Controllers/LiveTvController.cs
+++ b/Jellyfin.Api/Controllers/LiveTvController.cs
@@ -23,7 +23,6 @@ using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv;
-using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.LiveTv;
@@ -48,7 +47,6 @@ public class LiveTvController : BaseJellyfinApiController
private readonly IMediaSourceManager _mediaSourceManager;
private readonly IConfigurationManager _configurationManager;
private readonly TranscodingJobHelper _transcodingJobHelper;
- private readonly ISessionManager _sessionManager;
/// <summary>
/// Initializes a new instance of the <see cref="LiveTvController"/> class.
@@ -61,7 +59,6 @@ public class LiveTvController : BaseJellyfinApiController
/// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> interface.</param>
/// <param name="configurationManager">Instance of the <see cref="IConfigurationManager"/> interface.</param>
/// <param name="transcodingJobHelper">Instance of the <see cref="TranscodingJobHelper"/> class.</param>
- /// <param name="sessionManager">Instance of the <see cref="ISessionManager"/> interface.</param>
public LiveTvController(
ILiveTvManager liveTvManager,
IUserManager userManager,
@@ -70,8 +67,7 @@ public class LiveTvController : BaseJellyfinApiController
IDtoService dtoService,
IMediaSourceManager mediaSourceManager,
IConfigurationManager configurationManager,
- TranscodingJobHelper transcodingJobHelper,
- ISessionManager sessionManager)
+ TranscodingJobHelper transcodingJobHelper)
{
_liveTvManager = liveTvManager;
_userManager = userManager;
@@ -81,7 +77,6 @@ public class LiveTvController : BaseJellyfinApiController
_mediaSourceManager = mediaSourceManager;
_configurationManager = configurationManager;
_transcodingJobHelper = transcodingJobHelper;
- _sessionManager = sessionManager;
}
/// <summary>
diff --git a/Jellyfin.Api/Controllers/MediaInfoController.cs b/Jellyfin.Api/Controllers/MediaInfoController.cs
index da24616ff..bea545cfd 100644
--- a/Jellyfin.Api/Controllers/MediaInfoController.cs
+++ b/Jellyfin.Api/Controllers/MediaInfoController.cs
@@ -184,7 +184,7 @@ public class MediaInfoController : BaseJellyfinApiController
enableTranscoding.Value,
allowVideoStreamCopy.Value,
allowAudioStreamCopy.Value,
- Request.HttpContext.GetNormalizedRemoteIp());
+ Request.HttpContext.GetNormalizedRemoteIP());
}
_mediaInfoHelper.SortMediaSources(info, maxStreamingBitrate);
diff --git a/Jellyfin.Api/Controllers/SubtitleController.cs b/Jellyfin.Api/Controllers/SubtitleController.cs
index b3e9d6297..fb89e9610 100644
--- a/Jellyfin.Api/Controllers/SubtitleController.cs
+++ b/Jellyfin.Api/Controllers/SubtitleController.cs
@@ -6,6 +6,7 @@ using System.Globalization;
using System.IO;
using System.Linq;
using System.Net.Mime;
+using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
@@ -90,7 +91,7 @@ public class SubtitleController : BaseJellyfinApiController
[Authorize(Policy = Policies.RequiresElevation)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
- public ActionResult<Task> DeleteSubtitle(
+ public async Task<ActionResult> DeleteSubtitle(
[FromRoute, Required] Guid itemId,
[FromRoute, Required] int index)
{
@@ -101,7 +102,7 @@ public class SubtitleController : BaseJellyfinApiController
return NotFound();
}
- _subtitleManager.DeleteSubtitles(item, index);
+ await _subtitleManager.DeleteSubtitles(item, index).ConfigureAwait(false);
return NoContent();
}
@@ -405,9 +406,8 @@ public class SubtitleController : BaseJellyfinApiController
[FromBody, Required] UploadSubtitleDto body)
{
var video = (Video)_libraryManager.GetItemById(itemId);
- var data = Convert.FromBase64String(body.Data);
- var memoryStream = new MemoryStream(data, 0, data.Length, false, true);
- await using (memoryStream.ConfigureAwait(false))
+ var stream = new CryptoStream(Request.Body, new FromBase64Transform(), CryptoStreamMode.Read);
+ await using (stream.ConfigureAwait(false))
{
await _subtitleManager.UploadSubtitle(
video,
@@ -416,7 +416,8 @@ public class SubtitleController : BaseJellyfinApiController
Format = body.Format,
Language = body.Language,
IsForced = body.IsForced,
- Stream = memoryStream
+ IsHearingImpaired = body.IsHearingImpaired,
+ Stream = stream
}).ConfigureAwait(false);
_providerManager.QueueRefresh(video.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), RefreshPriority.High);
diff --git a/Jellyfin.Api/Controllers/SystemController.cs b/Jellyfin.Api/Controllers/SystemController.cs
index 9ed69f420..11095a97f 100644
--- a/Jellyfin.Api/Controllers/SystemController.cs
+++ b/Jellyfin.Api/Controllers/SystemController.cs
@@ -4,14 +4,12 @@ using System.ComponentModel.DataAnnotations;
using System.IO;
using System.Linq;
using System.Net.Mime;
-using System.Threading.Tasks;
using Jellyfin.Api.Attributes;
using Jellyfin.Api.Constants;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller;
-using MediaBrowser.Controller.Configuration;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Net;
using MediaBrowser.Model.System;
@@ -27,32 +25,36 @@ namespace Jellyfin.Api.Controllers;
/// </summary>
public class SystemController : BaseJellyfinApiController
{
+ private readonly ILogger<SystemController> _logger;
private readonly IServerApplicationHost _appHost;
private readonly IApplicationPaths _appPaths;
private readonly IFileSystem _fileSystem;
- private readonly INetworkManager _network;
- private readonly ILogger<SystemController> _logger;
+ private readonly INetworkManager _networkManager;
+ private readonly ISystemManager _systemManager;
/// <summary>
/// Initializes a new instance of the <see cref="SystemController"/> class.
/// </summary>
- /// <param name="serverConfigurationManager">Instance of <see cref="IServerConfigurationManager"/> interface.</param>
+ /// <param name="logger">Instance of <see cref="ILogger{SystemController}"/> interface.</param>
+ /// <param name="appPaths">Instance of <see cref="IServerApplicationPaths"/> interface.</param>
/// <param name="appHost">Instance of <see cref="IServerApplicationHost"/> interface.</param>
/// <param name="fileSystem">Instance of <see cref="IFileSystem"/> interface.</param>
- /// <param name="network">Instance of <see cref="INetworkManager"/> interface.</param>
- /// <param name="logger">Instance of <see cref="ILogger{SystemController}"/> interface.</param>
+ /// <param name="networkManager">Instance of <see cref="INetworkManager"/> interface.</param>
+ /// <param name="systemManager">Instance of <see cref="ISystemManager"/> interface.</param>
public SystemController(
- IServerConfigurationManager serverConfigurationManager,
+ ILogger<SystemController> logger,
IServerApplicationHost appHost,
+ IServerApplicationPaths appPaths,
IFileSystem fileSystem,
- INetworkManager network,
- ILogger<SystemController> logger)
+ INetworkManager networkManager,
+ ISystemManager systemManager)
{
- _appPaths = serverConfigurationManager.ApplicationPaths;
+ _logger = logger;
_appHost = appHost;
+ _appPaths = appPaths;
_fileSystem = fileSystem;
- _network = network;
- _logger = logger;
+ _networkManager = networkManager;
+ _systemManager = systemManager;
}
/// <summary>
@@ -66,9 +68,7 @@ public class SystemController : BaseJellyfinApiController
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
public ActionResult<SystemInfo> GetSystemInfo()
- {
- return _appHost.GetSystemInfo(Request);
- }
+ => _systemManager.GetSystemInfo(Request);
/// <summary>
/// Gets public information about the server.
@@ -78,9 +78,7 @@ public class SystemController : BaseJellyfinApiController
[HttpGet("Info/Public")]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<PublicSystemInfo> GetPublicSystemInfo()
- {
- return _appHost.GetPublicSystemInfo(Request);
- }
+ => _systemManager.GetPublicSystemInfo(Request);
/// <summary>
/// Pings the system.
@@ -91,9 +89,7 @@ public class SystemController : BaseJellyfinApiController
[HttpPost("Ping", Name = "PostPingSystem")]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<string> PingSystem()
- {
- return _appHost.Name;
- }
+ => _appHost.Name;
/// <summary>
/// Restarts the application.
@@ -107,11 +103,7 @@ public class SystemController : BaseJellyfinApiController
[ProducesResponseType(StatusCodes.Status403Forbidden)]
public ActionResult RestartApplication()
{
- Task.Run(async () =>
- {
- await Task.Delay(100).ConfigureAwait(false);
- _appHost.Restart();
- });
+ _systemManager.Restart();
return NoContent();
}
@@ -127,11 +119,7 @@ public class SystemController : BaseJellyfinApiController
[ProducesResponseType(StatusCodes.Status403Forbidden)]
public ActionResult ShutdownApplication()
{
- Task.Run(async () =>
- {
- await Task.Delay(100).ConfigureAwait(false);
- await _appHost.Shutdown().ConfigureAwait(false);
- });
+ _systemManager.Shutdown();
return NoContent();
}
@@ -189,7 +177,7 @@ public class SystemController : BaseJellyfinApiController
return new EndPointInfo
{
IsLocal = HttpContext.IsLocal(),
- IsInNetwork = _network.IsInLocalNetwork(HttpContext.GetNormalizedRemoteIp())
+ IsInNetwork = _networkManager.IsInLocalNetwork(HttpContext.GetNormalizedRemoteIP())
};
}
@@ -227,7 +215,7 @@ public class SystemController : BaseJellyfinApiController
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<IEnumerable<WakeOnLanInfo>> GetWakeOnLanInfo()
{
- var result = _network.GetMacAddresses()
+ var result = _networkManager.GetMacAddresses()
.Select(i => new WakeOnLanInfo(i));
return Ok(result);
}
diff --git a/Jellyfin.Api/Controllers/TvShowsController.cs b/Jellyfin.Api/Controllers/TvShowsController.cs
index 7d23281f2..bdbbd1e0d 100644
--- a/Jellyfin.Api/Controllers/TvShowsController.cs
+++ b/Jellyfin.Api/Controllers/TvShowsController.cs
@@ -68,7 +68,8 @@ public class TvShowsController : BaseJellyfinApiController
/// <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>
- /// <param name="enableRewatching">Whether to include watched episode in next up results.</param>
+ /// <param name="enableResumable">Whether to include resumable episodes in next up results.</param>
+ /// <param name="enableRewatching">Whether to include watched episodes in next up results.</param>
/// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the next up episodes.</returns>
[HttpGet("NextUp")]
[ProducesResponseType(StatusCodes.Status200OK)]
@@ -86,6 +87,7 @@ public class TvShowsController : BaseJellyfinApiController
[FromQuery] DateTime? nextUpDateCutoff,
[FromQuery] bool enableTotalRecordCount = true,
[FromQuery] bool disableFirstEpisode = false,
+ [FromQuery] bool enableResumable = true,
[FromQuery] bool enableRewatching = false)
{
userId = RequestHelpers.GetUserId(User, userId);
@@ -104,6 +106,7 @@ public class TvShowsController : BaseJellyfinApiController
EnableTotalRecordCount = enableTotalRecordCount,
DisableFirstEpisode = disableFirstEpisode,
NextUpDateCutoff = nextUpDateCutoff ?? DateTime.MinValue,
+ EnableResumable = enableResumable,
EnableRewatching = enableRewatching
},
options);
diff --git a/Jellyfin.Api/Controllers/UniversalAudioController.cs b/Jellyfin.Api/Controllers/UniversalAudioController.cs
index 2e9035d24..7177a0440 100644
--- a/Jellyfin.Api/Controllers/UniversalAudioController.cs
+++ b/Jellyfin.Api/Controllers/UniversalAudioController.cs
@@ -138,7 +138,7 @@ public class UniversalAudioController : BaseJellyfinApiController
true,
true,
true,
- Request.HttpContext.GetNormalizedRemoteIp());
+ Request.HttpContext.GetNormalizedRemoteIP());
}
_mediaInfoHelper.SortMediaSources(info, maxStreamingBitrate);
diff --git a/Jellyfin.Api/Controllers/UserController.cs b/Jellyfin.Api/Controllers/UserController.cs
index 530bd9603..1be40111d 100644
--- a/Jellyfin.Api/Controllers/UserController.cs
+++ b/Jellyfin.Api/Controllers/UserController.cs
@@ -134,7 +134,7 @@ public class UserController : BaseJellyfinApiController
return NotFound("User not found");
}
- var result = _userManager.GetUserDto(user, HttpContext.GetNormalizedRemoteIp().ToString());
+ var result = _userManager.GetUserDto(user, HttpContext.GetNormalizedRemoteIP().ToString());
return result;
}
@@ -217,7 +217,7 @@ public class UserController : BaseJellyfinApiController
DeviceId = auth.DeviceId,
DeviceName = auth.Device,
Password = request.Pw,
- RemoteEndPoint = HttpContext.GetNormalizedRemoteIp().ToString(),
+ RemoteEndPoint = HttpContext.GetNormalizedRemoteIP().ToString(),
Username = request.Username
}).ConfigureAwait(false);
@@ -226,7 +226,7 @@ public class UserController : BaseJellyfinApiController
catch (SecurityException e)
{
// rethrow adding IP address to message
- throw new SecurityException($"[{HttpContext.GetNormalizedRemoteIp()}] {e.Message}", e);
+ throw new SecurityException($"[{HttpContext.GetNormalizedRemoteIP()}] {e.Message}", e);
}
}
@@ -248,7 +248,7 @@ public class UserController : BaseJellyfinApiController
catch (SecurityException e)
{
// rethrow adding IP address to message
- throw new SecurityException($"[{HttpContext.GetNormalizedRemoteIp()}] {e.Message}", e);
+ throw new SecurityException($"[{HttpContext.GetNormalizedRemoteIP()}] {e.Message}", e);
}
}
@@ -294,7 +294,7 @@ public class UserController : BaseJellyfinApiController
user.Username,
request.CurrentPw ?? string.Empty,
request.CurrentPw ?? string.Empty,
- HttpContext.GetNormalizedRemoteIp().ToString(),
+ HttpContext.GetNormalizedRemoteIP().ToString(),
false).ConfigureAwait(false);
if (success is null)
@@ -475,7 +475,7 @@ public class UserController : BaseJellyfinApiController
await _userManager.ChangePassword(newUser, request.Password).ConfigureAwait(false);
}
- var result = _userManager.GetUserDto(newUser, HttpContext.GetNormalizedRemoteIp().ToString());
+ var result = _userManager.GetUserDto(newUser, HttpContext.GetNormalizedRemoteIP().ToString());
return result;
}
@@ -490,11 +490,11 @@ public class UserController : BaseJellyfinApiController
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<ForgotPasswordResult>> ForgotPassword([FromBody, Required] ForgotPasswordDto forgotPasswordRequest)
{
- var ip = HttpContext.GetNormalizedRemoteIp();
+ var ip = HttpContext.GetNormalizedRemoteIP();
var isLocal = HttpContext.IsLocal()
|| _networkManager.IsInLocalNetwork(ip);
- if (isLocal)
+ if (!isLocal)
{
_logger.LogWarning("Password reset process initiated from outside the local network with IP: {IP}", ip);
}
@@ -571,7 +571,7 @@ public class UserController : BaseJellyfinApiController
if (filterByNetwork)
{
- if (!_networkManager.IsInLocalNetwork(HttpContext.GetNormalizedRemoteIp()))
+ if (!_networkManager.IsInLocalNetwork(HttpContext.GetNormalizedRemoteIP()))
{
users = users.Where(i => i.HasPermission(PermissionKind.EnableRemoteAccess));
}
@@ -579,7 +579,7 @@ public class UserController : BaseJellyfinApiController
var result = users
.OrderBy(u => u.Username)
- .Select(i => _userManager.GetUserDto(i, HttpContext.GetNormalizedRemoteIp().ToString()));
+ .Select(i => _userManager.GetUserDto(i, HttpContext.GetNormalizedRemoteIP().ToString()));
return result;
}