mirror of
https://github.com/jellyfin/jellyfin.git
synced 2026-05-30 12:28:27 +01:00
Merge branch 'master' into trickplay
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
if (EncodingHelper.IsCopyCodec(audioCodec))
|
||||
{
|
||||
var bitStreamArgs = EncodingHelper.GetAudioBitStreamArguments(state, state.Request.SegmentContainer, state.MediaSource.Container);
|
||||
|
||||
return "-acodec copy -strict -2" + bitStreamArgs;
|
||||
}
|
||||
|
||||
var audioTranscodeParams = string.Empty;
|
||||
|
||||
audioTranscodeParams += "-acodec " + audioCodec;
|
||||
// -vn to drop any video streams
|
||||
audioTranscodeParams += "-vn";
|
||||
|
||||
if (EncodingHelper.IsCopyCodec(audioCodec))
|
||||
{
|
||||
return audioTranscodeParams + " -acodec copy" + bitStreamArgs + strictArgs;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -184,7 +184,7 @@ public class MediaInfoController : BaseJellyfinApiController
|
||||
enableTranscoding.Value,
|
||||
allowVideoStreamCopy.Value,
|
||||
allowAudioStreamCopy.Value,
|
||||
Request.HttpContext.GetNormalizedRemoteIp());
|
||||
Request.HttpContext.GetNormalizedRemoteIP());
|
||||
}
|
||||
|
||||
_mediaInfoHelper.SortMediaSources(info, maxStreamingBitrate);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
_appHost = appHost;
|
||||
_fileSystem = fileSystem;
|
||||
_network = network;
|
||||
_logger = logger;
|
||||
_appHost = appHost;
|
||||
_appPaths = appPaths;
|
||||
_fileSystem = fileSystem;
|
||||
_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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -138,7 +138,7 @@ public class UniversalAudioController : BaseJellyfinApiController
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
Request.HttpContext.GetNormalizedRemoteIp());
|
||||
Request.HttpContext.GetNormalizedRemoteIP());
|
||||
}
|
||||
|
||||
_mediaInfoHelper.SortMediaSources(info, maxStreamingBitrate);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user