mirror of
https://github.com/jellyfin/jellyfin.git
synced 2026-01-23 23:20:51 +01:00
Merge branch 'master' into master
This commit is contained in:
commit
4b6fb6c4bb
956 changed files with 43950 additions and 11532 deletions
|
|
@ -3,7 +3,8 @@ using System.Security.Claims;
|
|||
using System.Text.Encodings.Web;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Api.Constants;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Data;
|
||||
using Jellyfin.Database.Implementations.Enums;
|
||||
using MediaBrowser.Controller.Authentication;
|
||||
using MediaBrowser.Controller.Net;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
using System.Threading.Tasks;
|
||||
using Jellyfin.Api.Constants;
|
||||
using Jellyfin.Api.Extensions;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Data;
|
||||
using Jellyfin.Database.Implementations.Enums;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Common.Net;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
using System.Threading.Tasks;
|
||||
using Jellyfin.Api.Extensions;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Database.Implementations.Enums;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.SyncPlay;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using System.Threading.Tasks;
|
||||
using Jellyfin.Api.Extensions;
|
||||
using Jellyfin.Data;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Controller.Library;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
using Jellyfin.Api.Auth.DefaultAuthorizationPolicy;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Database.Implementations.Enums;
|
||||
|
||||
namespace Jellyfin.Api.Auth.UserPermissionPolicy
|
||||
{
|
||||
|
|
|
|||
|
|
@ -4,8 +4,9 @@ using System.Linq;
|
|||
using Jellyfin.Api.Extensions;
|
||||
using Jellyfin.Api.Helpers;
|
||||
using Jellyfin.Api.ModelBinders;
|
||||
using Jellyfin.Data.Entities;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Database.Implementations.Entities;
|
||||
using Jellyfin.Database.Implementations.Enums;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
|
|
|
|||
|
|
@ -92,18 +92,18 @@ public class AudioController : BaseJellyfinApiController
|
|||
[ProducesAudioFile]
|
||||
public async Task<ActionResult> GetAudioStream(
|
||||
[FromRoute, Required] Guid itemId,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? container,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? container,
|
||||
[FromQuery] bool? @static,
|
||||
[FromQuery] string? @params,
|
||||
[FromQuery] string? tag,
|
||||
[FromQuery, ParameterObsolete] string? deviceProfileId,
|
||||
[FromQuery] string? playSessionId,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? segmentContainer,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? segmentContainer,
|
||||
[FromQuery] int? segmentLength,
|
||||
[FromQuery] int? minSegments,
|
||||
[FromQuery] string? mediaSourceId,
|
||||
[FromQuery] string? deviceId,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? audioCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? audioCodec,
|
||||
[FromQuery] bool? enableAutoStreamCopy,
|
||||
[FromQuery] bool? allowVideoStreamCopy,
|
||||
[FromQuery] bool? allowAudioStreamCopy,
|
||||
|
|
@ -114,7 +114,7 @@ public class AudioController : BaseJellyfinApiController
|
|||
[FromQuery] int? audioChannels,
|
||||
[FromQuery] int? maxAudioChannels,
|
||||
[FromQuery] string? profile,
|
||||
[FromQuery] string? level,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.LevelValidationRegex)] string? level,
|
||||
[FromQuery] float? framerate,
|
||||
[FromQuery] float? maxFramerate,
|
||||
[FromQuery] bool? copyTimestamps,
|
||||
|
|
@ -133,8 +133,8 @@ public class AudioController : BaseJellyfinApiController
|
|||
[FromQuery] int? cpuCoreLimit,
|
||||
[FromQuery] string? liveStreamId,
|
||||
[FromQuery] bool? enableMpegtsM2TsMode,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? videoCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? subtitleCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? videoCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? subtitleCodec,
|
||||
[FromQuery] string? transcodeReasons,
|
||||
[FromQuery] int? audioStreamIndex,
|
||||
[FromQuery] int? videoStreamIndex,
|
||||
|
|
@ -259,18 +259,18 @@ public class AudioController : BaseJellyfinApiController
|
|||
[ProducesAudioFile]
|
||||
public async Task<ActionResult> GetAudioStreamByContainer(
|
||||
[FromRoute, Required] Guid itemId,
|
||||
[FromRoute, Required] string container,
|
||||
[FromRoute, Required] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string container,
|
||||
[FromQuery] bool? @static,
|
||||
[FromQuery] string? @params,
|
||||
[FromQuery] string? tag,
|
||||
[FromQuery, ParameterObsolete] string? deviceProfileId,
|
||||
[FromQuery] string? playSessionId,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? segmentContainer,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? segmentContainer,
|
||||
[FromQuery] int? segmentLength,
|
||||
[FromQuery] int? minSegments,
|
||||
[FromQuery] string? mediaSourceId,
|
||||
[FromQuery] string? deviceId,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? audioCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? audioCodec,
|
||||
[FromQuery] bool? enableAutoStreamCopy,
|
||||
[FromQuery] bool? allowVideoStreamCopy,
|
||||
[FromQuery] bool? allowAudioStreamCopy,
|
||||
|
|
@ -281,7 +281,7 @@ public class AudioController : BaseJellyfinApiController
|
|||
[FromQuery] int? audioChannels,
|
||||
[FromQuery] int? maxAudioChannels,
|
||||
[FromQuery] string? profile,
|
||||
[FromQuery] string? level,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.LevelValidationRegex)] string? level,
|
||||
[FromQuery] float? framerate,
|
||||
[FromQuery] float? maxFramerate,
|
||||
[FromQuery] bool? copyTimestamps,
|
||||
|
|
@ -300,8 +300,8 @@ public class AudioController : BaseJellyfinApiController
|
|||
[FromQuery] int? cpuCoreLimit,
|
||||
[FromQuery] string? liveStreamId,
|
||||
[FromQuery] bool? enableMpegtsM2TsMode,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? videoCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? subtitleCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? videoCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? subtitleCodec,
|
||||
[FromQuery] string? transcodeReasons,
|
||||
[FromQuery] int? audioStreamIndex,
|
||||
[FromQuery] int? videoStreamIndex,
|
||||
|
|
|
|||
127
Jellyfin.Api/Controllers/BackupController.cs
Normal file
127
Jellyfin.Api/Controllers/BackupController.cs
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Server.Implementations.SystemBackupService;
|
||||
using MediaBrowser.Common.Api;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Controller.SystemBackupService;
|
||||
using Microsoft.AspNetCore.Authentication.OAuth.Claims;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
|
||||
namespace Jellyfin.Api.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// The backup controller.
|
||||
/// </summary>
|
||||
[Authorize(Policy = Policies.RequiresElevation)]
|
||||
public class BackupController : BaseJellyfinApiController
|
||||
{
|
||||
private readonly IBackupService _backupService;
|
||||
private readonly IApplicationPaths _applicationPaths;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BackupController"/> class.
|
||||
/// </summary>
|
||||
/// <param name="backupService">Instance of the <see cref="IBackupService"/> interface.</param>
|
||||
/// <param name="applicationPaths">Instance of the <see cref="IApplicationPaths"/> interface.</param>
|
||||
public BackupController(IBackupService backupService, IApplicationPaths applicationPaths)
|
||||
{
|
||||
_backupService = backupService;
|
||||
_applicationPaths = applicationPaths;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new Backup.
|
||||
/// </summary>
|
||||
/// <param name="backupOptions">The backup options.</param>
|
||||
/// <response code="200">Backup created.</response>
|
||||
/// <response code="403">User does not have permission to retrieve information.</response>
|
||||
/// <returns>The created backup manifest.</returns>
|
||||
[HttpPost("Create")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||
public async Task<ActionResult<BackupManifestDto>> CreateBackup([FromBody] BackupOptionsDto backupOptions)
|
||||
{
|
||||
return Ok(await _backupService.CreateBackupAsync(backupOptions ?? new()).ConfigureAwait(false));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Restores to a backup by restarting the server and applying the backup.
|
||||
/// </summary>
|
||||
/// <param name="archiveRestoreDto">The data to start a restore process.</param>
|
||||
/// <response code="204">Backup restore started.</response>
|
||||
/// <response code="403">User does not have permission to retrieve information.</response>
|
||||
/// <returns>No-Content.</returns>
|
||||
[HttpPost("Restore")]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||
public IActionResult StartRestoreBackup([FromBody, BindRequired] BackupRestoreRequestDto archiveRestoreDto)
|
||||
{
|
||||
var archivePath = SanitizePath(archiveRestoreDto.ArchiveFileName);
|
||||
if (!System.IO.File.Exists(archivePath))
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
_backupService.ScheduleRestoreAndRestartServer(archivePath);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of all currently present backups in the backup directory.
|
||||
/// </summary>
|
||||
/// <response code="200">Backups available.</response>
|
||||
/// <response code="403">User does not have permission to retrieve information.</response>
|
||||
/// <returns>The list of backups.</returns>
|
||||
[HttpGet]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||
public async Task<ActionResult<BackupManifestDto[]>> ListBackups()
|
||||
{
|
||||
return Ok(await _backupService.EnumerateBackups().ConfigureAwait(false));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the descriptor from an existing archive is present.
|
||||
/// </summary>
|
||||
/// <param name="path">The data to start a restore process.</param>
|
||||
/// <response code="200">Backup archive manifest.</response>
|
||||
/// <response code="204">Not a valid jellyfin Archive.</response>
|
||||
/// <response code="404">Not a valid path.</response>
|
||||
/// <response code="403">User does not have permission to retrieve information.</response>
|
||||
/// <returns>The backup manifest.</returns>
|
||||
[HttpGet("Manifest")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||
public async Task<ActionResult<BackupManifestDto>> GetBackup([BindRequired] string path)
|
||||
{
|
||||
var backupPath = SanitizePath(path);
|
||||
|
||||
if (!System.IO.File.Exists(backupPath))
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var manifest = await _backupService.GetBackupManifest(backupPath).ConfigureAwait(false);
|
||||
if (manifest is null)
|
||||
{
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
return Ok(manifest);
|
||||
}
|
||||
|
||||
[NonAction]
|
||||
private string SanitizePath(string path)
|
||||
{
|
||||
// sanitize path
|
||||
var archiveRestorePath = Path.GetFileName(Path.GetFullPath(path));
|
||||
var archivePath = Path.Combine(_applicationPaths.BackupPath, archiveRestorePath);
|
||||
return archivePath;
|
||||
}
|
||||
}
|
||||
|
|
@ -29,9 +29,18 @@ public class BrandingController : BaseJellyfinApiController
|
|||
/// <returns>An <see cref="OkResult"/> containing the branding configuration.</returns>
|
||||
[HttpGet("Configuration")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public ActionResult<BrandingOptions> GetBrandingOptions()
|
||||
public ActionResult<BrandingOptionsDto> GetBrandingOptions()
|
||||
{
|
||||
return _serverConfigurationManager.GetConfiguration<BrandingOptions>("branding");
|
||||
var brandingOptions = _serverConfigurationManager.GetConfiguration<BrandingOptions>("branding");
|
||||
|
||||
var brandingOptionsDto = new BrandingOptionsDto
|
||||
{
|
||||
LoginDisclaimer = brandingOptions.LoginDisclaimer,
|
||||
CustomCss = brandingOptions.CustomCss,
|
||||
SplashscreenEnabled = brandingOptions.SplashscreenEnabled
|
||||
};
|
||||
|
||||
return brandingOptionsDto;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ using System.Threading.Tasks;
|
|||
using Jellyfin.Api.Helpers;
|
||||
using Jellyfin.Api.ModelBinders;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Database.Implementations.Enums;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Controller.Channels;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ using Jellyfin.Extensions.Json;
|
|||
using MediaBrowser.Common.Api;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.MediaEncoding;
|
||||
using MediaBrowser.Model.Branding;
|
||||
using MediaBrowser.Model.Configuration;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
|
@ -119,6 +120,30 @@ public class ConfigurationController : BaseJellyfinApiController
|
|||
return new MetadataOptions();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates branding configuration.
|
||||
/// </summary>
|
||||
/// <param name="configuration">Branding configuration.</param>
|
||||
/// <response code="204">Branding configuration updated.</response>
|
||||
/// <returns>Update status.</returns>
|
||||
[HttpPost("Configuration/Branding")]
|
||||
[Authorize(Policy = Policies.RequiresElevation)]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
public ActionResult UpdateBrandingConfiguration([FromBody, Required] BrandingOptionsDto configuration)
|
||||
{
|
||||
// Get the current branding configuration to preserve SplashscreenLocation
|
||||
var currentBranding = (BrandingOptions)_configurationManager.GetConfiguration("branding");
|
||||
|
||||
// Update only the properties from BrandingOptionsDto
|
||||
currentBranding.LoginDisclaimer = configuration.LoginDisclaimer;
|
||||
currentBranding.CustomCss = configuration.CustomCss;
|
||||
currentBranding.SplashscreenEnabled = configuration.SplashscreenEnabled;
|
||||
|
||||
_configurationManager.SaveConfiguration("branding", currentBranding);
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the path to the media encoder.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ using System.Diagnostics.CodeAnalysis;
|
|||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Jellyfin.Api.Helpers;
|
||||
using Jellyfin.Data.Entities;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Database.Implementations.Entities;
|
||||
using Jellyfin.Database.Implementations.Enums;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Model.Dto;
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ public class DynamicHlsController : BaseJellyfinApiController
|
|||
|
||||
private readonly Version _minFFmpegFlacInMp4 = new Version(6, 0);
|
||||
private readonly Version _minFFmpegX265BframeInFmp4 = new Version(7, 0, 1);
|
||||
private readonly Version _minFFmpegHlsSegmentOptions = new Version(5, 0);
|
||||
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly IUserManager _userManager;
|
||||
|
|
@ -166,18 +167,18 @@ public class DynamicHlsController : BaseJellyfinApiController
|
|||
[ProducesPlaylistFile]
|
||||
public async Task<ActionResult> GetLiveHlsStream(
|
||||
[FromRoute, Required] Guid itemId,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? container,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? container,
|
||||
[FromQuery] bool? @static,
|
||||
[FromQuery] string? @params,
|
||||
[FromQuery] string? tag,
|
||||
[FromQuery, ParameterObsolete] string? deviceProfileId,
|
||||
[FromQuery] string? playSessionId,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? segmentContainer,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? segmentContainer,
|
||||
[FromQuery] int? segmentLength,
|
||||
[FromQuery] int? minSegments,
|
||||
[FromQuery] string? mediaSourceId,
|
||||
[FromQuery] string? deviceId,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? audioCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? audioCodec,
|
||||
[FromQuery] bool? enableAutoStreamCopy,
|
||||
[FromQuery] bool? allowVideoStreamCopy,
|
||||
[FromQuery] bool? allowAudioStreamCopy,
|
||||
|
|
@ -188,7 +189,7 @@ public class DynamicHlsController : BaseJellyfinApiController
|
|||
[FromQuery] int? audioChannels,
|
||||
[FromQuery] int? maxAudioChannels,
|
||||
[FromQuery] string? profile,
|
||||
[FromQuery] string? level,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.LevelValidationRegex)] string? level,
|
||||
[FromQuery] float? framerate,
|
||||
[FromQuery] float? maxFramerate,
|
||||
[FromQuery] bool? copyTimestamps,
|
||||
|
|
@ -207,8 +208,8 @@ public class DynamicHlsController : BaseJellyfinApiController
|
|||
[FromQuery] int? cpuCoreLimit,
|
||||
[FromQuery] string? liveStreamId,
|
||||
[FromQuery] bool? enableMpegtsM2TsMode,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? videoCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? subtitleCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? videoCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? subtitleCodec,
|
||||
[FromQuery] string? transcodeReasons,
|
||||
[FromQuery] int? audioStreamIndex,
|
||||
[FromQuery] int? videoStreamIndex,
|
||||
|
|
@ -415,12 +416,12 @@ public class DynamicHlsController : BaseJellyfinApiController
|
|||
[FromQuery] string? tag,
|
||||
[FromQuery, ParameterObsolete] string? deviceProfileId,
|
||||
[FromQuery] string? playSessionId,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? segmentContainer,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? segmentContainer,
|
||||
[FromQuery] int? segmentLength,
|
||||
[FromQuery] int? minSegments,
|
||||
[FromQuery, Required] string mediaSourceId,
|
||||
[FromQuery] string? deviceId,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? audioCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? audioCodec,
|
||||
[FromQuery] bool? enableAutoStreamCopy,
|
||||
[FromQuery] bool? allowVideoStreamCopy,
|
||||
[FromQuery] bool? allowAudioStreamCopy,
|
||||
|
|
@ -431,7 +432,7 @@ public class DynamicHlsController : BaseJellyfinApiController
|
|||
[FromQuery] int? audioChannels,
|
||||
[FromQuery] int? maxAudioChannels,
|
||||
[FromQuery] string? profile,
|
||||
[FromQuery] string? level,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.LevelValidationRegex)] string? level,
|
||||
[FromQuery] float? framerate,
|
||||
[FromQuery] float? maxFramerate,
|
||||
[FromQuery] bool? copyTimestamps,
|
||||
|
|
@ -452,8 +453,8 @@ public class DynamicHlsController : BaseJellyfinApiController
|
|||
[FromQuery] int? cpuCoreLimit,
|
||||
[FromQuery] string? liveStreamId,
|
||||
[FromQuery] bool? enableMpegtsM2TsMode,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? videoCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? subtitleCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? videoCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? subtitleCodec,
|
||||
[FromQuery] string? transcodeReasons,
|
||||
[FromQuery] int? audioStreamIndex,
|
||||
[FromQuery] int? videoStreamIndex,
|
||||
|
|
@ -591,12 +592,12 @@ public class DynamicHlsController : BaseJellyfinApiController
|
|||
[FromQuery] string? tag,
|
||||
[FromQuery, ParameterObsolete] string? deviceProfileId,
|
||||
[FromQuery] string? playSessionId,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? segmentContainer,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? segmentContainer,
|
||||
[FromQuery] int? segmentLength,
|
||||
[FromQuery] int? minSegments,
|
||||
[FromQuery, Required] string mediaSourceId,
|
||||
[FromQuery] string? deviceId,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? audioCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? audioCodec,
|
||||
[FromQuery] bool? enableAutoStreamCopy,
|
||||
[FromQuery] bool? allowVideoStreamCopy,
|
||||
[FromQuery] bool? allowAudioStreamCopy,
|
||||
|
|
@ -608,7 +609,7 @@ public class DynamicHlsController : BaseJellyfinApiController
|
|||
[FromQuery] int? audioChannels,
|
||||
[FromQuery] int? maxAudioChannels,
|
||||
[FromQuery] string? profile,
|
||||
[FromQuery] string? level,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.LevelValidationRegex)] string? level,
|
||||
[FromQuery] float? framerate,
|
||||
[FromQuery] float? maxFramerate,
|
||||
[FromQuery] bool? copyTimestamps,
|
||||
|
|
@ -627,8 +628,8 @@ public class DynamicHlsController : BaseJellyfinApiController
|
|||
[FromQuery] int? cpuCoreLimit,
|
||||
[FromQuery] string? liveStreamId,
|
||||
[FromQuery] bool? enableMpegtsM2TsMode,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? videoCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? subtitleCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? videoCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? subtitleCodec,
|
||||
[FromQuery] string? transcodeReasons,
|
||||
[FromQuery] int? audioStreamIndex,
|
||||
[FromQuery] int? videoStreamIndex,
|
||||
|
|
@ -761,12 +762,12 @@ public class DynamicHlsController : BaseJellyfinApiController
|
|||
[FromQuery] string? tag,
|
||||
[FromQuery, ParameterObsolete] string? deviceProfileId,
|
||||
[FromQuery] string? playSessionId,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? segmentContainer,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? segmentContainer,
|
||||
[FromQuery] int? segmentLength,
|
||||
[FromQuery] int? minSegments,
|
||||
[FromQuery] string? mediaSourceId,
|
||||
[FromQuery] string? deviceId,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? audioCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? audioCodec,
|
||||
[FromQuery] bool? enableAutoStreamCopy,
|
||||
[FromQuery] bool? allowVideoStreamCopy,
|
||||
[FromQuery] bool? allowAudioStreamCopy,
|
||||
|
|
@ -777,7 +778,7 @@ public class DynamicHlsController : BaseJellyfinApiController
|
|||
[FromQuery] int? audioChannels,
|
||||
[FromQuery] int? maxAudioChannels,
|
||||
[FromQuery] string? profile,
|
||||
[FromQuery] string? level,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.LevelValidationRegex)] string? level,
|
||||
[FromQuery] float? framerate,
|
||||
[FromQuery] float? maxFramerate,
|
||||
[FromQuery] bool? copyTimestamps,
|
||||
|
|
@ -798,8 +799,8 @@ public class DynamicHlsController : BaseJellyfinApiController
|
|||
[FromQuery] int? cpuCoreLimit,
|
||||
[FromQuery] string? liveStreamId,
|
||||
[FromQuery] bool? enableMpegtsM2TsMode,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? videoCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? subtitleCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? videoCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? subtitleCodec,
|
||||
[FromQuery] string? transcodeReasons,
|
||||
[FromQuery] int? audioStreamIndex,
|
||||
[FromQuery] int? videoStreamIndex,
|
||||
|
|
@ -933,12 +934,12 @@ public class DynamicHlsController : BaseJellyfinApiController
|
|||
[FromQuery] string? tag,
|
||||
[FromQuery, ParameterObsolete] string? deviceProfileId,
|
||||
[FromQuery] string? playSessionId,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? segmentContainer,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? segmentContainer,
|
||||
[FromQuery] int? segmentLength,
|
||||
[FromQuery] int? minSegments,
|
||||
[FromQuery] string? mediaSourceId,
|
||||
[FromQuery] string? deviceId,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? audioCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? audioCodec,
|
||||
[FromQuery] bool? enableAutoStreamCopy,
|
||||
[FromQuery] bool? allowVideoStreamCopy,
|
||||
[FromQuery] bool? allowAudioStreamCopy,
|
||||
|
|
@ -950,7 +951,7 @@ public class DynamicHlsController : BaseJellyfinApiController
|
|||
[FromQuery] int? audioChannels,
|
||||
[FromQuery] int? maxAudioChannels,
|
||||
[FromQuery] string? profile,
|
||||
[FromQuery] string? level,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.LevelValidationRegex)] string? level,
|
||||
[FromQuery] float? framerate,
|
||||
[FromQuery] float? maxFramerate,
|
||||
[FromQuery] bool? copyTimestamps,
|
||||
|
|
@ -969,8 +970,8 @@ public class DynamicHlsController : BaseJellyfinApiController
|
|||
[FromQuery] int? cpuCoreLimit,
|
||||
[FromQuery] string? liveStreamId,
|
||||
[FromQuery] bool? enableMpegtsM2TsMode,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? videoCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? subtitleCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? videoCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? subtitleCodec,
|
||||
[FromQuery] string? transcodeReasons,
|
||||
[FromQuery] int? audioStreamIndex,
|
||||
[FromQuery] int? videoStreamIndex,
|
||||
|
|
@ -1106,7 +1107,7 @@ public class DynamicHlsController : BaseJellyfinApiController
|
|||
[FromRoute, Required] Guid itemId,
|
||||
[FromRoute, Required] string playlistId,
|
||||
[FromRoute, Required] int segmentId,
|
||||
[FromRoute, Required] string container,
|
||||
[FromRoute, Required] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string container,
|
||||
[FromQuery, Required] long runtimeTicks,
|
||||
[FromQuery, Required] long actualSegmentLengthTicks,
|
||||
[FromQuery] bool? @static,
|
||||
|
|
@ -1114,12 +1115,12 @@ public class DynamicHlsController : BaseJellyfinApiController
|
|||
[FromQuery] string? tag,
|
||||
[FromQuery, ParameterObsolete] string? deviceProfileId,
|
||||
[FromQuery] string? playSessionId,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? segmentContainer,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? segmentContainer,
|
||||
[FromQuery] int? segmentLength,
|
||||
[FromQuery] int? minSegments,
|
||||
[FromQuery] string? mediaSourceId,
|
||||
[FromQuery] string? deviceId,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? audioCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? audioCodec,
|
||||
[FromQuery] bool? enableAutoStreamCopy,
|
||||
[FromQuery] bool? allowVideoStreamCopy,
|
||||
[FromQuery] bool? allowAudioStreamCopy,
|
||||
|
|
@ -1130,7 +1131,7 @@ public class DynamicHlsController : BaseJellyfinApiController
|
|||
[FromQuery] int? audioChannels,
|
||||
[FromQuery] int? maxAudioChannels,
|
||||
[FromQuery] string? profile,
|
||||
[FromQuery] string? level,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.LevelValidationRegex)] string? level,
|
||||
[FromQuery] float? framerate,
|
||||
[FromQuery] float? maxFramerate,
|
||||
[FromQuery] bool? copyTimestamps,
|
||||
|
|
@ -1151,8 +1152,8 @@ public class DynamicHlsController : BaseJellyfinApiController
|
|||
[FromQuery] int? cpuCoreLimit,
|
||||
[FromQuery] string? liveStreamId,
|
||||
[FromQuery] bool? enableMpegtsM2TsMode,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? videoCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? subtitleCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? videoCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? subtitleCodec,
|
||||
[FromQuery] string? transcodeReasons,
|
||||
[FromQuery] int? audioStreamIndex,
|
||||
[FromQuery] int? videoStreamIndex,
|
||||
|
|
@ -1291,7 +1292,7 @@ public class DynamicHlsController : BaseJellyfinApiController
|
|||
[FromRoute, Required] Guid itemId,
|
||||
[FromRoute, Required] string playlistId,
|
||||
[FromRoute, Required] int segmentId,
|
||||
[FromRoute, Required] string container,
|
||||
[FromRoute, Required] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string container,
|
||||
[FromQuery, Required] long runtimeTicks,
|
||||
[FromQuery, Required] long actualSegmentLengthTicks,
|
||||
[FromQuery] bool? @static,
|
||||
|
|
@ -1299,12 +1300,12 @@ public class DynamicHlsController : BaseJellyfinApiController
|
|||
[FromQuery] string? tag,
|
||||
[FromQuery, ParameterObsolete] string? deviceProfileId,
|
||||
[FromQuery] string? playSessionId,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? segmentContainer,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? segmentContainer,
|
||||
[FromQuery] int? segmentLength,
|
||||
[FromQuery] int? minSegments,
|
||||
[FromQuery] string? mediaSourceId,
|
||||
[FromQuery] string? deviceId,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? audioCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? audioCodec,
|
||||
[FromQuery] bool? enableAutoStreamCopy,
|
||||
[FromQuery] bool? allowVideoStreamCopy,
|
||||
[FromQuery] bool? allowAudioStreamCopy,
|
||||
|
|
@ -1316,7 +1317,7 @@ public class DynamicHlsController : BaseJellyfinApiController
|
|||
[FromQuery] int? audioChannels,
|
||||
[FromQuery] int? maxAudioChannels,
|
||||
[FromQuery] string? profile,
|
||||
[FromQuery] string? level,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.LevelValidationRegex)] string? level,
|
||||
[FromQuery] float? framerate,
|
||||
[FromQuery] float? maxFramerate,
|
||||
[FromQuery] bool? copyTimestamps,
|
||||
|
|
@ -1335,8 +1336,8 @@ public class DynamicHlsController : BaseJellyfinApiController
|
|||
[FromQuery] int? cpuCoreLimit,
|
||||
[FromQuery] string? liveStreamId,
|
||||
[FromQuery] bool? enableMpegtsM2TsMode,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? videoCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? subtitleCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? videoCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? subtitleCodec,
|
||||
[FromQuery] string? transcodeReasons,
|
||||
[FromQuery] int? audioStreamIndex,
|
||||
[FromQuery] int? videoStreamIndex,
|
||||
|
|
@ -1419,8 +1420,9 @@ public class DynamicHlsController : BaseJellyfinApiController
|
|||
TranscodingJobType,
|
||||
cancellationTokenSource.Token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
var mediaSourceId = state.BaseRequest.MediaSourceId;
|
||||
var request = new CreateMainPlaylistRequest(
|
||||
mediaSourceId is null ? null : Guid.Parse(mediaSourceId),
|
||||
state.MediaPath,
|
||||
state.SegmentLength * 1000,
|
||||
state.RunTimeTicks ?? 0,
|
||||
|
|
@ -1605,6 +1607,7 @@ public class DynamicHlsController : BaseJellyfinApiController
|
|||
var segmentFormat = string.Empty;
|
||||
var segmentContainer = outputExtension.TrimStart('.');
|
||||
var inputModifier = _encodingHelper.GetInputModifier(state, _encodingOptions, segmentContainer);
|
||||
var hlsArguments = $"-hls_playlist_type {(isEventPlaylist ? "event" : "vod")} -hls_list_size 0";
|
||||
|
||||
if (string.Equals(segmentContainer, "ts", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
|
|
@ -1620,6 +1623,11 @@ public class DynamicHlsController : BaseJellyfinApiController
|
|||
false => " -hls_fmp4_init_filename \"" + outputFileNameWithoutExtension + "-1" + outputExtension + "\""
|
||||
};
|
||||
|
||||
var useLegacySegmentOption = _mediaEncoder.EncoderVersion < _minFFmpegHlsSegmentOptions;
|
||||
|
||||
// fMP4 needs this flag to write the audio packet DTS/PTS including the initial delay into MOOF::TRAF::TFDT
|
||||
hlsArguments += $" {(useLegacySegmentOption ? "-hls_ts_options" : "-hls_segment_options")} movflags=+frag_discont";
|
||||
|
||||
segmentFormat = "fmp4" + outputFmp4HeaderArg;
|
||||
}
|
||||
else
|
||||
|
|
@ -1641,8 +1649,6 @@ public class DynamicHlsController : BaseJellyfinApiController
|
|||
Path.GetFileNameWithoutExtension(outputPath));
|
||||
}
|
||||
|
||||
var hlsArguments = $"-hls_playlist_type {(isEventPlaylist ? "event" : "vod")} -hls_list_size 0";
|
||||
|
||||
return string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"{0} {1} -map_metadata -1 -map_chapters -1 -threads {2} {3} {4} {5} -copyts -avoid_negative_ts disabled -max_muxing_queue_size {6} -f hls -max_delay 5000000 -hls_time {7} -hls_segment_type {8} -start_number {9}{10} -hls_segment_filename \"{11}\" {12} -y \"{13}\"",
|
||||
|
|
@ -1675,7 +1681,7 @@ public class DynamicHlsController : BaseJellyfinApiController
|
|||
}
|
||||
|
||||
var audioCodec = _encodingHelper.GetAudioEncoder(state);
|
||||
var bitStreamArgs = EncodingHelper.GetAudioBitStreamArguments(state, state.Request.SegmentContainer, state.MediaSource.Container);
|
||||
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;
|
||||
|
|
@ -1753,7 +1759,7 @@ public class DynamicHlsController : BaseJellyfinApiController
|
|||
|
||||
if (channels.HasValue
|
||||
&& (channels.Value != 2
|
||||
|| (state.AudioStream?.Channels != null && !useDownMixAlgorithm)))
|
||||
|| (state.AudioStream?.Channels is not null && !useDownMixAlgorithm)))
|
||||
{
|
||||
args += " -ac " + channels.Value;
|
||||
}
|
||||
|
|
@ -1822,10 +1828,11 @@ public class DynamicHlsController : BaseJellyfinApiController
|
|||
// Clients reporting Dolby Vision capabilities with fallbacks may only support the fallback layer.
|
||||
// Only enable Dolby Vision remuxing if the client explicitly declares support for profiles without fallbacks.
|
||||
var clientSupportsDoVi = requestedRange.Contains(VideoRangeType.DOVI.ToString(), StringComparison.OrdinalIgnoreCase);
|
||||
var videoIsDoVi = state.VideoStream.VideoRangeType is VideoRangeType.DOVI or VideoRangeType.DOVIWithHDR10 or VideoRangeType.DOVIWithHLG or VideoRangeType.DOVIWithSDR;
|
||||
var videoIsDoVi = EncodingHelper.IsDovi(state.VideoStream);
|
||||
|
||||
if (EncodingHelper.IsCopyCodec(codec)
|
||||
&& (videoIsDoVi && clientSupportsDoVi))
|
||||
&& (videoIsDoVi && clientSupportsDoVi)
|
||||
&& !_encodingHelper.IsDoviRemoved(state))
|
||||
{
|
||||
if (isActualOutputVideoCodecHevc)
|
||||
{
|
||||
|
|
@ -1855,7 +1862,7 @@ public class DynamicHlsController : BaseJellyfinApiController
|
|||
// If h264_mp4toannexb is ever added, do not use it for live tv.
|
||||
if (state.VideoStream is not null && !string.Equals(state.VideoStream.NalLengthSize, "0", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
string bitStreamArgs = EncodingHelper.GetBitStreamArgs(state.VideoStream);
|
||||
string bitStreamArgs = _encodingHelper.GetBitStreamArgs(state, MediaStreamType.Video);
|
||||
if (!string.IsNullOrEmpty(bitStreamArgs))
|
||||
{
|
||||
args += " " + bitStreamArgs;
|
||||
|
|
|
|||
|
|
@ -4,8 +4,9 @@ using System.Linq;
|
|||
using Jellyfin.Api.Extensions;
|
||||
using Jellyfin.Api.Helpers;
|
||||
using Jellyfin.Api.ModelBinders;
|
||||
using Jellyfin.Data.Entities;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Database.Implementations.Entities;
|
||||
using Jellyfin.Database.Implementations.Enums;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
|
|
|
|||
|
|
@ -130,7 +130,7 @@ public class ImageController : BaseJellyfinApiController
|
|||
await _userManager.ClearProfileImageAsync(user).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
user.ProfileImage = new Data.Entities.ImageInfo(Path.Combine(userDataPath, "profile" + extension));
|
||||
user.ProfileImage = new Database.Implementations.Entities.ImageInfo(Path.Combine(userDataPath, "profile" + extension));
|
||||
|
||||
await _providerManager
|
||||
.SaveImage(stream, mimeType, user.ProfileImage.Path)
|
||||
|
|
@ -1727,7 +1727,8 @@ public class ImageController : BaseJellyfinApiController
|
|||
[FromQuery, Range(0, 100)] int quality = 90)
|
||||
{
|
||||
var brandingOptions = _serverConfigurationManager.GetConfiguration<BrandingOptions>("branding");
|
||||
if (!brandingOptions.SplashscreenEnabled)
|
||||
var isAdmin = User.IsInRole(Constants.UserRoles.Administrator);
|
||||
if (!brandingOptions.SplashscreenEnabled && !isAdmin)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ using System.Linq;
|
|||
using Jellyfin.Api.Extensions;
|
||||
using Jellyfin.Api.Helpers;
|
||||
using Jellyfin.Api.ModelBinders;
|
||||
using Jellyfin.Data.Entities;
|
||||
using Jellyfin.Database.Implementations.Entities;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
|
|
|
|||
|
|
@ -299,7 +299,7 @@ public class ItemUpdateController : BaseJellyfinApiController
|
|||
|
||||
if (!season.LockedFields.Contains(MetadataField.Tags))
|
||||
{
|
||||
season.Tags = season.Tags.Concat(addedTags).Except(removedTags).Distinct().ToArray();
|
||||
season.Tags = season.Tags.Concat(addedTags).Except(removedTags).Distinct(StringComparer.OrdinalIgnoreCase).ToArray();
|
||||
}
|
||||
|
||||
season.OnMetadataChanged();
|
||||
|
|
@ -316,7 +316,7 @@ public class ItemUpdateController : BaseJellyfinApiController
|
|||
|
||||
if (!ep.LockedFields.Contains(MetadataField.Tags))
|
||||
{
|
||||
ep.Tags = ep.Tags.Concat(addedTags).Except(removedTags).Distinct().ToArray();
|
||||
ep.Tags = ep.Tags.Concat(addedTags).Except(removedTags).Distinct(StringComparer.OrdinalIgnoreCase).ToArray();
|
||||
}
|
||||
|
||||
ep.OnMetadataChanged();
|
||||
|
|
@ -337,7 +337,7 @@ public class ItemUpdateController : BaseJellyfinApiController
|
|||
|
||||
if (!ep.LockedFields.Contains(MetadataField.Tags))
|
||||
{
|
||||
ep.Tags = ep.Tags.Concat(addedTags).Except(removedTags).Distinct().ToArray();
|
||||
ep.Tags = ep.Tags.Concat(addedTags).Except(removedTags).Distinct(StringComparer.OrdinalIgnoreCase).ToArray();
|
||||
}
|
||||
|
||||
ep.OnMetadataChanged();
|
||||
|
|
@ -357,7 +357,7 @@ public class ItemUpdateController : BaseJellyfinApiController
|
|||
|
||||
if (!track.LockedFields.Contains(MetadataField.Tags))
|
||||
{
|
||||
track.Tags = track.Tags.Concat(addedTags).Except(removedTags).Distinct().ToArray();
|
||||
track.Tags = track.Tags.Concat(addedTags).Except(removedTags).Distinct(StringComparer.OrdinalIgnoreCase).ToArray();
|
||||
}
|
||||
|
||||
track.OnMetadataChanged();
|
||||
|
|
|
|||
|
|
@ -4,7 +4,9 @@ using System.Linq;
|
|||
using Jellyfin.Api.Extensions;
|
||||
using Jellyfin.Api.Helpers;
|
||||
using Jellyfin.Api.ModelBinders;
|
||||
using Jellyfin.Data;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Database.Implementations.Enums;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
|
|
@ -446,13 +448,13 @@ public class ItemsController : BaseJellyfinApiController
|
|||
// Min official rating
|
||||
if (!string.IsNullOrWhiteSpace(minOfficialRating))
|
||||
{
|
||||
query.MinParentalRating = _localization.GetRatingLevel(minOfficialRating);
|
||||
query.MinParentalRating = _localization.GetRatingScore(minOfficialRating);
|
||||
}
|
||||
|
||||
// Max official rating
|
||||
if (!string.IsNullOrWhiteSpace(maxOfficialRating))
|
||||
{
|
||||
query.MaxParentalRating = _localization.GetRatingLevel(maxOfficialRating);
|
||||
query.MaxParentalRating = _localization.GetRatingScore(maxOfficialRating);
|
||||
}
|
||||
|
||||
// Artists
|
||||
|
|
|
|||
|
|
@ -11,8 +11,9 @@ using Jellyfin.Api.Extensions;
|
|||
using Jellyfin.Api.Helpers;
|
||||
using Jellyfin.Api.ModelBinders;
|
||||
using Jellyfin.Api.Models.LibraryDtos;
|
||||
using Jellyfin.Data.Entities;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Database.Implementations.Entities;
|
||||
using Jellyfin.Database.Implementations.Enums;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Common.Api;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ using Jellyfin.Api.Helpers;
|
|||
using Jellyfin.Api.ModelBinders;
|
||||
using Jellyfin.Api.Models.LiveTvDtos;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Database.Implementations.Enums;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Common.Api;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
|
|
@ -698,6 +699,7 @@ public class LiveTvController : BaseJellyfinApiController
|
|||
/// Gets recommended live tv epgs.
|
||||
/// </summary>
|
||||
/// <param name="userId">Optional. filter by user id.</param>
|
||||
/// <param name="startIndex">Optional. The record index to start at. All items with a lower index will be dropped from the results.</param>
|
||||
/// <param name="limit">Optional. The maximum number of records to return.</param>
|
||||
/// <param name="isAiring">Optional. Filter by programs that are currently airing, or not.</param>
|
||||
/// <param name="hasAired">Optional. Filter by programs that have completed airing, or not.</param>
|
||||
|
|
@ -720,6 +722,7 @@ public class LiveTvController : BaseJellyfinApiController
|
|||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<QueryResult<BaseItemDto>>> GetRecommendedPrograms(
|
||||
[FromQuery] Guid? userId,
|
||||
[FromQuery] int? startIndex,
|
||||
[FromQuery] int? limit,
|
||||
[FromQuery] bool? isAiring,
|
||||
[FromQuery] bool? hasAired,
|
||||
|
|
@ -744,6 +747,7 @@ public class LiveTvController : BaseJellyfinApiController
|
|||
var query = new InternalItemsQuery(user)
|
||||
{
|
||||
IsAiring = isAiring,
|
||||
StartIndex = startIndex,
|
||||
Limit = limit,
|
||||
HasAired = hasAired,
|
||||
IsSeries = isSeries,
|
||||
|
|
@ -1186,7 +1190,9 @@ public class LiveTvController : BaseJellyfinApiController
|
|||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesVideoFile]
|
||||
public ActionResult GetLiveStreamFile([FromRoute, Required] string streamId, [FromRoute, Required] string container)
|
||||
public ActionResult GetLiveStreamFile(
|
||||
[FromRoute, Required] string streamId,
|
||||
[FromRoute, Required] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string container)
|
||||
{
|
||||
var liveStreamInfo = _mediaSourceManager.GetLiveStreamInfoByUniqueId(streamId);
|
||||
if (liveStreamInfo is null)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
using System.Collections.Generic;
|
||||
using Jellyfin.Api.Constants;
|
||||
using MediaBrowser.Common.Api;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Globalization;
|
||||
|
|
@ -45,7 +44,7 @@ public class LocalizationController : BaseJellyfinApiController
|
|||
/// <returns>An <see cref="OkResult"/> containing the list of countries.</returns>
|
||||
[HttpGet("Countries")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public ActionResult<IEnumerable<CountryInfo>> GetCountries()
|
||||
public ActionResult<IReadOnlyList<CountryInfo>> GetCountries()
|
||||
{
|
||||
return Ok(_localization.GetCountries());
|
||||
}
|
||||
|
|
@ -57,7 +56,7 @@ public class LocalizationController : BaseJellyfinApiController
|
|||
/// <returns>An <see cref="OkResult"/> containing the list of parental ratings.</returns>
|
||||
[HttpGet("ParentalRatings")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public ActionResult<IEnumerable<ParentalRating>> GetParentalRatings()
|
||||
public ActionResult<IReadOnlyList<ParentalRating>> GetParentalRatings()
|
||||
{
|
||||
return Ok(_localization.GetParentalRatings());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,10 +4,10 @@ using System.ComponentModel.DataAnnotations;
|
|||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Api.Extensions;
|
||||
using Jellyfin.Data.Enums;
|
||||
using MediaBrowser.Controller;
|
||||
using Jellyfin.Database.Implementations.Enums;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.MediaSegments;
|
||||
using MediaBrowser.Model.MediaSegments;
|
||||
using MediaBrowser.Model.Querying;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
|
|
@ -55,7 +55,8 @@ public class MediaSegmentsController : BaseJellyfinApiController
|
|||
return NotFound();
|
||||
}
|
||||
|
||||
var items = await _mediaSegmentManager.GetSegmentsAsync(item, includeSegmentTypes).ConfigureAwait(false);
|
||||
var libraryOptions = _libraryManager.GetLibraryOptions(item);
|
||||
var items = await _mediaSegmentManager.GetSegmentsAsync(item, includeSegmentTypes, libraryOptions).ConfigureAwait(false);
|
||||
return Ok(new QueryResult<MediaSegmentDto>(items.ToArray()));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,8 +5,9 @@ using System.Linq;
|
|||
using Jellyfin.Api.Extensions;
|
||||
using Jellyfin.Api.Helpers;
|
||||
using Jellyfin.Api.ModelBinders;
|
||||
using Jellyfin.Data.Entities;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Database.Implementations.Entities;
|
||||
using Jellyfin.Database.Implementations.Enums;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
|
|
|
|||
|
|
@ -4,8 +4,9 @@ using System.Linq;
|
|||
using Jellyfin.Api.Extensions;
|
||||
using Jellyfin.Api.Helpers;
|
||||
using Jellyfin.Api.ModelBinders;
|
||||
using Jellyfin.Data.Entities;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Database.Implementations.Entities;
|
||||
using Jellyfin.Database.Implementations.Enums;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ using System.Linq;
|
|||
using Jellyfin.Api.Extensions;
|
||||
using Jellyfin.Api.Helpers;
|
||||
using Jellyfin.Api.ModelBinders;
|
||||
using Jellyfin.Data.Entities;
|
||||
using Jellyfin.Database.Implementations.Entities;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
|
|
|
|||
|
|
@ -450,22 +450,41 @@ public class PlaylistsController : BaseJellyfinApiController
|
|||
{
|
||||
var callingUserId = User.GetUserId();
|
||||
|
||||
var playlist = _playlistManager.GetPlaylistForUser(Guid.Parse(playlistId), callingUserId);
|
||||
if (playlist is null)
|
||||
if (!callingUserId.IsEmpty())
|
||||
{
|
||||
return NotFound("Playlist not found");
|
||||
var playlist = _playlistManager.GetPlaylistForUser(Guid.Parse(playlistId), callingUserId);
|
||||
if (playlist is null)
|
||||
{
|
||||
return NotFound("Playlist not found");
|
||||
}
|
||||
|
||||
var isPermitted = playlist.OwnerUserId.Equals(callingUserId)
|
||||
|| playlist.Shares.Any(s => s.CanEdit && s.UserId.Equals(callingUserId));
|
||||
|
||||
if (!isPermitted)
|
||||
{
|
||||
return Forbid();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var isApiKey = User.GetIsApiKey();
|
||||
|
||||
if (!isApiKey)
|
||||
{
|
||||
return Forbid();
|
||||
}
|
||||
}
|
||||
|
||||
var isPermitted = playlist.OwnerUserId.Equals(callingUserId)
|
||||
|| playlist.Shares.Any(s => s.CanEdit && s.UserId.Equals(callingUserId));
|
||||
|
||||
if (!isPermitted)
|
||||
try
|
||||
{
|
||||
return Forbid();
|
||||
await _playlistManager.RemoveItemFromPlaylistAsync(playlistId, entryIds).ConfigureAwait(false);
|
||||
return NoContent();
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
await _playlistManager.RemoveItemFromPlaylistAsync(playlistId, entryIds).ConfigureAwait(false);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ using System.Threading.Tasks;
|
|||
using Jellyfin.Api.Extensions;
|
||||
using Jellyfin.Api.Helpers;
|
||||
using Jellyfin.Api.ModelBinders;
|
||||
using Jellyfin.Data.Entities;
|
||||
using Jellyfin.Database.Implementations.Entities;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
|
|
@ -272,6 +272,7 @@ public class PlaystateController : BaseJellyfinApiController
|
|||
/// <returns>A <see cref="NoContentResult"/>.</returns>
|
||||
[HttpPost("PlayingItems/{itemId}")]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
[Obsolete("This endpoint is obsolete. Use ReportPlaybackStart instead")]
|
||||
public async Task<ActionResult> OnPlaybackStart(
|
||||
[FromRoute, Required] Guid itemId,
|
||||
[FromQuery] string? mediaSourceId,
|
||||
|
|
@ -350,6 +351,7 @@ public class PlaystateController : BaseJellyfinApiController
|
|||
/// <returns>A <see cref="NoContentResult"/>.</returns>
|
||||
[HttpPost("PlayingItems/{itemId}/Progress")]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
[Obsolete("This endpoint is obsolete. Use ReportPlaybackProgress instead")]
|
||||
public async Task<ActionResult> OnPlaybackProgress(
|
||||
[FromRoute, Required] Guid itemId,
|
||||
[FromQuery] string? mediaSourceId,
|
||||
|
|
@ -438,6 +440,7 @@ public class PlaystateController : BaseJellyfinApiController
|
|||
/// <returns>A <see cref="NoContentResult"/>.</returns>
|
||||
[HttpDelete("PlayingItems/{itemId}")]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
[Obsolete("This endpoint is obsolete. Use ReportPlaybackStop instead")]
|
||||
public async Task<ActionResult> OnPlaybackStopped(
|
||||
[FromRoute, Required] Guid itemId,
|
||||
[FromQuery] string? mediaSourceId,
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@ public class StartupController : BaseJellyfinApiController
|
|||
{
|
||||
return new StartupConfigurationDto
|
||||
{
|
||||
ServerName = _config.Configuration.ServerName,
|
||||
UICulture = _config.Configuration.UICulture,
|
||||
MetadataCountryCode = _config.Configuration.MetadataCountryCode,
|
||||
PreferredMetadataLanguage = _config.Configuration.PreferredMetadataLanguage
|
||||
|
|
@ -75,6 +76,7 @@ public class StartupController : BaseJellyfinApiController
|
|||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
public ActionResult UpdateInitialConfiguration([FromBody, Required] StartupConfigurationDto startupConfiguration)
|
||||
{
|
||||
_config.Configuration.ServerName = startupConfiguration.ServerName ?? string.Empty;
|
||||
_config.Configuration.UICulture = startupConfiguration.UICulture ?? string.Empty;
|
||||
_config.Configuration.MetadataCountryCode = startupConfiguration.MetadataCountryCode ?? string.Empty;
|
||||
_config.Configuration.PreferredMetadataLanguage = startupConfiguration.PreferredMetadataLanguage ?? string.Empty;
|
||||
|
|
@ -113,8 +115,7 @@ public class StartupController : BaseJellyfinApiController
|
|||
var user = _userManager.Users.First();
|
||||
return new StartupUserDto
|
||||
{
|
||||
Name = user.Username,
|
||||
Password = user.Password
|
||||
Name = user.Username
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ using System.ComponentModel.DataAnnotations;
|
|||
using Jellyfin.Api.Extensions;
|
||||
using Jellyfin.Api.Helpers;
|
||||
using Jellyfin.Api.ModelBinders;
|
||||
using Jellyfin.Data.Entities;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Database.Implementations.Entities;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
|
|
|
|||
|
|
@ -3,8 +3,9 @@ using System.ComponentModel.DataAnnotations;
|
|||
using Jellyfin.Api.Extensions;
|
||||
using Jellyfin.Api.Helpers;
|
||||
using Jellyfin.Api.ModelBinders;
|
||||
using Jellyfin.Data.Entities;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Database.Implementations.Entities;
|
||||
using Jellyfin.Database.Implementations.Enums;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Api.Constants;
|
||||
using Jellyfin.Api.Helpers;
|
||||
using Jellyfin.Api.Models.SyncPlayDtos;
|
||||
using MediaBrowser.Common.Api;
|
||||
|
|
@ -50,17 +50,16 @@ public class SyncPlayController : BaseJellyfinApiController
|
|||
/// </summary>
|
||||
/// <param name="requestData">The settings of the new group.</param>
|
||||
/// <response code="204">New group created.</response>
|
||||
/// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
|
||||
/// <returns>An <see cref="GroupInfoDto"/> for the created group.</returns>
|
||||
[HttpPost("New")]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[Authorize(Policy = Policies.SyncPlayCreateGroup)]
|
||||
public async Task<ActionResult> SyncPlayCreateGroup(
|
||||
public async Task<ActionResult<GroupInfoDto>> SyncPlayCreateGroup(
|
||||
[FromBody, Required] NewGroupRequestDto requestData)
|
||||
{
|
||||
var currentSession = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false);
|
||||
var syncPlayRequest = new NewGroupRequest(requestData.GroupName);
|
||||
_syncPlayManager.NewGroup(currentSession, syncPlayRequest, CancellationToken.None);
|
||||
return NoContent();
|
||||
return Ok(_syncPlayManager.NewGroup(currentSession, syncPlayRequest, CancellationToken.None));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -112,6 +111,23 @@ public class SyncPlayController : BaseJellyfinApiController
|
|||
return Ok(_syncPlayManager.ListGroups(currentSession, syncPlayRequest).AsEnumerable());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a SyncPlay group by id.
|
||||
/// </summary>
|
||||
/// <param name="id">The id of the group.</param>
|
||||
/// <response code="200">Group returned.</response>
|
||||
/// <returns>An <see cref="GroupInfoDto"/> for the requested group.</returns>
|
||||
[HttpGet("{id:guid}")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[Authorize(Policy = Policies.SyncPlayJoinGroup)]
|
||||
public async Task<ActionResult<GroupInfoDto>> SyncPlayGetGroup([FromRoute] Guid id)
|
||||
{
|
||||
var currentSession = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false);
|
||||
var group = _syncPlayManager.GetGroup(currentSession, id);
|
||||
return group is null ? NotFound() : Ok(group);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request to set new playlist in SyncPlay group.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ using System.IO;
|
|||
using System.Linq;
|
||||
using System.Net.Mime;
|
||||
using Jellyfin.Api.Attributes;
|
||||
using Jellyfin.Api.Constants;
|
||||
using Jellyfin.Api.Models.SystemInfoDtos;
|
||||
using MediaBrowser.Common.Api;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
|
|
@ -71,6 +71,19 @@ public class SystemController : BaseJellyfinApiController
|
|||
public ActionResult<SystemInfo> GetSystemInfo()
|
||||
=> _systemManager.GetSystemInfo(Request);
|
||||
|
||||
/// <summary>
|
||||
/// Gets information about the server.
|
||||
/// </summary>
|
||||
/// <response code="200">Information retrieved.</response>
|
||||
/// <response code="403">User does not have permission to retrieve information.</response>
|
||||
/// <returns>A <see cref="SystemInfo"/> with info about the system.</returns>
|
||||
[HttpGet("Info/Storage")]
|
||||
[Authorize(Policy = Policies.RequiresElevation)]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||
public ActionResult<SystemStorageDto> GetSystemStorage()
|
||||
=> Ok(SystemStorageDto.FromSystemStorageInfo(_systemManager.GetSystemStorageInfo()));
|
||||
|
||||
/// <summary>
|
||||
/// Gets public information about the server.
|
||||
/// </summary>
|
||||
|
|
@ -212,20 +225,4 @@ public class SystemController : BaseJellyfinApiController
|
|||
FileStream stream = new FileStream(file.FullName, FileMode.Open, FileAccess.Read, fileShare, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous);
|
||||
return File(stream, "text/plain; charset=utf-8");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets wake on lan information.
|
||||
/// </summary>
|
||||
/// <response code="200">Information retrieved.</response>
|
||||
/// <returns>An <see cref="IEnumerable{WakeOnLanInfo}"/> with the WakeOnLan infos.</returns>
|
||||
[HttpGet("WakeOnLanInfo")]
|
||||
[Authorize]
|
||||
[Obsolete("This endpoint is obsolete.")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public ActionResult<IEnumerable<WakeOnLanInfo>> GetWakeOnLanInfo()
|
||||
{
|
||||
var result = _networkManager.GetMacAddresses()
|
||||
.Select(i => new WakeOnLanInfo(i));
|
||||
return Ok(result);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using Jellyfin.Api.ModelBinders;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Database.Implementations.Enums;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Querying;
|
||||
|
|
|
|||
|
|
@ -2,10 +2,12 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using Jellyfin.Api.Attributes;
|
||||
using Jellyfin.Api.Extensions;
|
||||
using Jellyfin.Api.Helpers;
|
||||
using Jellyfin.Api.ModelBinders;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Database.Implementations.Enums;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
|
|
@ -86,7 +88,7 @@ public class TvShowsController : BaseJellyfinApiController
|
|||
[FromQuery] bool? enableUserData,
|
||||
[FromQuery] DateTime? nextUpDateCutoff,
|
||||
[FromQuery] bool enableTotalRecordCount = true,
|
||||
[FromQuery] bool disableFirstEpisode = false,
|
||||
[FromQuery][ParameterObsolete] bool disableFirstEpisode = false,
|
||||
[FromQuery] bool enableResumable = true,
|
||||
[FromQuery] bool enableRewatching = false)
|
||||
{
|
||||
|
|
@ -109,7 +111,6 @@ public class TvShowsController : BaseJellyfinApiController
|
|||
StartIndex = startIndex,
|
||||
User = user,
|
||||
EnableTotalRecordCount = enableTotalRecordCount,
|
||||
DisableFirstEpisode = disableFirstEpisode,
|
||||
NextUpDateCutoff = nextUpDateCutoff ?? DateTime.MinValue,
|
||||
EnableResumable = enableResumable,
|
||||
EnableRewatching = enableRewatching
|
||||
|
|
|
|||
|
|
@ -102,13 +102,13 @@ public class UniversalAudioController : BaseJellyfinApiController
|
|||
[FromQuery] string? mediaSourceId,
|
||||
[FromQuery] string? deviceId,
|
||||
[FromQuery] Guid? userId,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? audioCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? audioCodec,
|
||||
[FromQuery] int? maxAudioChannels,
|
||||
[FromQuery] int? transcodingAudioChannels,
|
||||
[FromQuery] int? maxStreamingBitrate,
|
||||
[FromQuery] int? audioBitRate,
|
||||
[FromQuery] long? startTimeTicks,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? transcodingContainer,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? transcodingContainer,
|
||||
[FromQuery] MediaStreamProtocol? transcodingProtocol,
|
||||
[FromQuery] int? maxAudioSampleRate,
|
||||
[FromQuery] int? maxAudioBitDepth,
|
||||
|
|
|
|||
|
|
@ -7,7 +7,8 @@ using Jellyfin.Api.Constants;
|
|||
using Jellyfin.Api.Extensions;
|
||||
using Jellyfin.Api.Helpers;
|
||||
using Jellyfin.Api.Models.UserDtos;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Data;
|
||||
using Jellyfin.Database.Implementations.Enums;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Common.Api;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ using System.Threading.Tasks;
|
|||
using Jellyfin.Api.Extensions;
|
||||
using Jellyfin.Api.Helpers;
|
||||
using Jellyfin.Api.ModelBinders;
|
||||
using Jellyfin.Data.Entities;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Database.Implementations.Entities;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
|
|
|
|||
|
|
@ -315,18 +315,18 @@ public class VideosController : BaseJellyfinApiController
|
|||
[ProducesVideoFile]
|
||||
public async Task<ActionResult> GetVideoStream(
|
||||
[FromRoute, Required] Guid itemId,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? container,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? container,
|
||||
[FromQuery] bool? @static,
|
||||
[FromQuery] string? @params,
|
||||
[FromQuery] string? tag,
|
||||
[FromQuery, ParameterObsolete] string? deviceProfileId,
|
||||
[FromQuery] string? playSessionId,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? segmentContainer,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? segmentContainer,
|
||||
[FromQuery] int? segmentLength,
|
||||
[FromQuery] int? minSegments,
|
||||
[FromQuery] string? mediaSourceId,
|
||||
[FromQuery] string? deviceId,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? audioCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? audioCodec,
|
||||
[FromQuery] bool? enableAutoStreamCopy,
|
||||
[FromQuery] bool? allowVideoStreamCopy,
|
||||
[FromQuery] bool? allowAudioStreamCopy,
|
||||
|
|
@ -337,7 +337,7 @@ public class VideosController : BaseJellyfinApiController
|
|||
[FromQuery] int? audioChannels,
|
||||
[FromQuery] int? maxAudioChannels,
|
||||
[FromQuery] string? profile,
|
||||
[FromQuery] string? level,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.LevelValidationRegex)] string? level,
|
||||
[FromQuery] float? framerate,
|
||||
[FromQuery] float? maxFramerate,
|
||||
[FromQuery] bool? copyTimestamps,
|
||||
|
|
@ -358,8 +358,8 @@ public class VideosController : BaseJellyfinApiController
|
|||
[FromQuery] int? cpuCoreLimit,
|
||||
[FromQuery] string? liveStreamId,
|
||||
[FromQuery] bool? enableMpegtsM2TsMode,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? videoCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? subtitleCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? videoCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? subtitleCodec,
|
||||
[FromQuery] string? transcodeReasons,
|
||||
[FromQuery] int? audioStreamIndex,
|
||||
[FromQuery] int? videoStreamIndex,
|
||||
|
|
@ -556,18 +556,18 @@ public class VideosController : BaseJellyfinApiController
|
|||
[ProducesVideoFile]
|
||||
public Task<ActionResult> GetVideoStreamByContainer(
|
||||
[FromRoute, Required] Guid itemId,
|
||||
[FromRoute, Required] string container,
|
||||
[FromRoute, Required] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string container,
|
||||
[FromQuery] bool? @static,
|
||||
[FromQuery] string? @params,
|
||||
[FromQuery] string? tag,
|
||||
[FromQuery] string? deviceProfileId,
|
||||
[FromQuery] string? playSessionId,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? segmentContainer,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? segmentContainer,
|
||||
[FromQuery] int? segmentLength,
|
||||
[FromQuery] int? minSegments,
|
||||
[FromQuery] string? mediaSourceId,
|
||||
[FromQuery] string? deviceId,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? audioCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? audioCodec,
|
||||
[FromQuery] bool? enableAutoStreamCopy,
|
||||
[FromQuery] bool? allowVideoStreamCopy,
|
||||
[FromQuery] bool? allowAudioStreamCopy,
|
||||
|
|
@ -578,7 +578,7 @@ public class VideosController : BaseJellyfinApiController
|
|||
[FromQuery] int? audioChannels,
|
||||
[FromQuery] int? maxAudioChannels,
|
||||
[FromQuery] string? profile,
|
||||
[FromQuery] string? level,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.LevelValidationRegex)] string? level,
|
||||
[FromQuery] float? framerate,
|
||||
[FromQuery] float? maxFramerate,
|
||||
[FromQuery] bool? copyTimestamps,
|
||||
|
|
@ -599,8 +599,8 @@ public class VideosController : BaseJellyfinApiController
|
|||
[FromQuery] int? cpuCoreLimit,
|
||||
[FromQuery] string? liveStreamId,
|
||||
[FromQuery] bool? enableMpegtsM2TsMode,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? videoCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ValidationRegex)] string? subtitleCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? videoCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? subtitleCodec,
|
||||
[FromQuery] string? transcodeReasons,
|
||||
[FromQuery] int? audioStreamIndex,
|
||||
[FromQuery] int? videoStreamIndex,
|
||||
|
|
|
|||
|
|
@ -6,8 +6,9 @@ using System.Linq;
|
|||
using Jellyfin.Api.Extensions;
|
||||
using Jellyfin.Api.Helpers;
|
||||
using Jellyfin.Api.ModelBinders;
|
||||
using Jellyfin.Data.Entities;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Database.Implementations.Entities;
|
||||
using Jellyfin.Database.Implementations.Enums;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
|
|
@ -222,6 +223,6 @@ public class YearsController : BaseJellyfinApiController
|
|||
.Select(i => i.ProductionYear ?? 0)
|
||||
.Where(i => i > 0)
|
||||
.Distinct()
|
||||
.Select(year => _libraryManager.GetYear(year));
|
||||
.Select(_libraryManager.GetYear);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ using System.Text;
|
|||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Api.Extensions;
|
||||
using Jellyfin.Data.Entities;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Database.Implementations.Entities;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
|
|
@ -345,13 +345,15 @@ public class DynamicHlsHelper
|
|||
|
||||
if (videoRange == VideoRange.HDR)
|
||||
{
|
||||
if (videoRangeType == VideoRangeType.HLG)
|
||||
switch (videoRangeType)
|
||||
{
|
||||
builder.Append(",VIDEO-RANGE=HLG");
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.Append(",VIDEO-RANGE=PQ");
|
||||
case VideoRangeType.HLG:
|
||||
case VideoRangeType.DOVIWithHLG:
|
||||
builder.Append(",VIDEO-RANGE=HLG");
|
||||
break;
|
||||
default:
|
||||
builder.Append(",VIDEO-RANGE=PQ");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -418,36 +420,67 @@ public class DynamicHlsHelper
|
|||
/// <param name="state">StreamState of the current stream.</param>
|
||||
private void AppendPlaylistSupplementalCodecsField(StringBuilder builder, StreamState state)
|
||||
{
|
||||
// Dolby Vision currently cannot exist when transcoding
|
||||
// HDR dynamic metadata currently cannot exist when transcoding
|
||||
if (!EncodingHelper.IsCopyCodec(state.OutputVideoCodec))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var dvProfile = state.VideoStream.DvProfile;
|
||||
var dvLevel = state.VideoStream.DvLevel;
|
||||
var dvRangeString = state.VideoStream.VideoRangeType switch
|
||||
if (EncodingHelper.IsDovi(state.VideoStream) && !_encodingHelper.IsDoviRemoved(state))
|
||||
{
|
||||
VideoRangeType.DOVIWithHDR10 => "db1p",
|
||||
VideoRangeType.DOVIWithHLG => "db4h",
|
||||
_ => string.Empty
|
||||
};
|
||||
|
||||
if (dvProfile is null || dvLevel is null || string.IsNullOrEmpty(dvRangeString))
|
||||
AppendDvString();
|
||||
}
|
||||
else if (EncodingHelper.IsHdr10Plus(state.VideoStream) && !_encodingHelper.IsHdr10PlusRemoved(state))
|
||||
{
|
||||
return;
|
||||
AppendHdr10PlusString();
|
||||
}
|
||||
|
||||
var dvFourCc = string.Equals(state.ActualOutputVideoCodec, "av1", StringComparison.OrdinalIgnoreCase) ? "dav1" : "dvh1";
|
||||
builder.Append(",SUPPLEMENTAL-CODECS=\"")
|
||||
.Append(dvFourCc)
|
||||
.Append('.')
|
||||
.Append(dvProfile.Value.ToString("D2", CultureInfo.InvariantCulture))
|
||||
.Append('.')
|
||||
.Append(dvLevel.Value.ToString("D2", CultureInfo.InvariantCulture))
|
||||
.Append('/')
|
||||
.Append(dvRangeString)
|
||||
.Append('"');
|
||||
return;
|
||||
|
||||
void AppendDvString()
|
||||
{
|
||||
var dvProfile = state.VideoStream.DvProfile;
|
||||
var dvLevel = state.VideoStream.DvLevel;
|
||||
var dvRangeString = state.VideoStream.VideoRangeType switch
|
||||
{
|
||||
VideoRangeType.DOVIWithHDR10 => "db1p",
|
||||
VideoRangeType.DOVIWithHLG => "db4h",
|
||||
VideoRangeType.DOVIWithHDR10Plus => "db1p", // The HDR10+ metadata would be removed if Dovi metadata is not removed
|
||||
_ => string.Empty // Don't label Dovi with EL and SDR due to compatability issues, ignore invalid configurations
|
||||
};
|
||||
|
||||
if (dvProfile is null || dvLevel is null || string.IsNullOrEmpty(dvRangeString))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var dvFourCc = string.Equals(state.ActualOutputVideoCodec, "av1", StringComparison.OrdinalIgnoreCase) ? "dav1" : "dvh1";
|
||||
builder.Append(",SUPPLEMENTAL-CODECS=\"")
|
||||
.Append(dvFourCc)
|
||||
.Append('.')
|
||||
.Append(dvProfile.Value.ToString("D2", CultureInfo.InvariantCulture))
|
||||
.Append('.')
|
||||
.Append(dvLevel.Value.ToString("D2", CultureInfo.InvariantCulture))
|
||||
.Append('/')
|
||||
.Append(dvRangeString)
|
||||
.Append('"');
|
||||
}
|
||||
|
||||
void AppendHdr10PlusString()
|
||||
{
|
||||
var videoCodecLevel = GetOutputVideoCodecLevel(state);
|
||||
if (string.IsNullOrEmpty(state.ActualOutputVideoCodec) || videoCodecLevel is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var videoCodecString = GetPlaylistVideoCodecs(state, state.ActualOutputVideoCodec, videoCodecLevel.Value);
|
||||
builder.Append(",SUPPLEMENTAL-CODECS=\"")
|
||||
.Append(videoCodecString)
|
||||
.Append('/')
|
||||
.Append("cdm4")
|
||||
.Append('"');
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -7,8 +7,10 @@ using System.Text.Json;
|
|||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Api.Extensions;
|
||||
using Jellyfin.Data.Entities;
|
||||
using Jellyfin.Data;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Database.Implementations.Entities;
|
||||
using Jellyfin.Database.Implementations.Enums;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Common.Net;
|
||||
|
|
@ -127,6 +129,13 @@ public class MediaInfoHelper
|
|||
var mediaSourcesClone = JsonSerializer.Deserialize<MediaSourceInfo[]>(JsonSerializer.SerializeToUtf8Bytes(mediaSources));
|
||||
if (mediaSourcesClone is not null)
|
||||
{
|
||||
// Carry over the default audio index source.
|
||||
// This field is not intended to be exposed to API clients, but it is used internally by the server
|
||||
for (int i = 0; i < mediaSourcesClone.Length && i < mediaSources.Length; i++)
|
||||
{
|
||||
mediaSourcesClone[i].DefaultAudioIndexSource = mediaSources[i].DefaultAudioIndexSource;
|
||||
}
|
||||
|
||||
result.MediaSources = mediaSourcesClone;
|
||||
}
|
||||
|
||||
|
|
@ -288,9 +297,7 @@ public class MediaInfoHelper
|
|||
mediaSource.SupportsDirectPlay = false;
|
||||
mediaSource.SupportsDirectStream = false;
|
||||
|
||||
mediaSource.TranscodingUrl = streamInfo.ToUrl("-", claimsPrincipal.GetToken()).TrimStart('-');
|
||||
mediaSource.TranscodingUrl += "&allowVideoStreamCopy=false";
|
||||
mediaSource.TranscodingUrl += "&allowAudioStreamCopy=false";
|
||||
mediaSource.TranscodingUrl = streamInfo.ToUrl(null, claimsPrincipal.GetToken(), "&allowVideoStreamCopy=false&allowAudioStreamCopy=false");
|
||||
mediaSource.TranscodingContainer = streamInfo.Container;
|
||||
mediaSource.TranscodingSubProtocol = streamInfo.SubProtocol;
|
||||
if (streamInfo.AlwaysBurnInSubtitleWhenTranscoding)
|
||||
|
|
@ -303,7 +310,7 @@ public class MediaInfoHelper
|
|||
if (!mediaSource.SupportsDirectPlay && (mediaSource.SupportsTranscoding || mediaSource.SupportsDirectStream))
|
||||
{
|
||||
streamInfo.PlayMethod = PlayMethod.Transcode;
|
||||
mediaSource.TranscodingUrl = streamInfo.ToUrl("-", claimsPrincipal.GetToken()).TrimStart('-');
|
||||
mediaSource.TranscodingUrl = streamInfo.ToUrl(null, claimsPrincipal.GetToken(), null);
|
||||
|
||||
if (!allowVideoStreamCopy)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -5,8 +5,9 @@ using System.Security.Claims;
|
|||
using System.Threading.Tasks;
|
||||
using Jellyfin.Api.Constants;
|
||||
using Jellyfin.Api.Extensions;
|
||||
using Jellyfin.Data.Entities;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Database.Implementations.Entities;
|
||||
using Jellyfin.Database.Implementations.Enums;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
|
|
@ -110,7 +111,16 @@ public static class RequestHelpers
|
|||
return user.EnableUserPreferenceAccess;
|
||||
}
|
||||
|
||||
internal static async Task<SessionInfo> GetSession(ISessionManager sessionManager, IUserManager userManager, HttpContext httpContext, Guid? userId = null)
|
||||
/// <summary>
|
||||
/// Get the session based on http request.
|
||||
/// </summary>
|
||||
/// <param name="sessionManager">The session manager.</param>
|
||||
/// <param name="userManager">The user manager.</param>
|
||||
/// <param name="httpContext">The http context.</param>
|
||||
/// <param name="userId">The optional userid.</param>
|
||||
/// <returns>The session.</returns>
|
||||
/// <exception cref="ResourceNotFoundException">Session not found.</exception>
|
||||
public static async Task<SessionInfo> GetSession(ISessionManager sessionManager, IUserManager userManager, HttpContext httpContext, Guid? userId = null)
|
||||
{
|
||||
userId ??= httpContext.User.GetUserId();
|
||||
User? user = null;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Common.Net;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Jellyfin.Api.Middleware;
|
||||
|
||||
|
|
@ -12,14 +14,17 @@ namespace Jellyfin.Api.Middleware;
|
|||
public class IPBasedAccessValidationMiddleware
|
||||
{
|
||||
private readonly RequestDelegate _next;
|
||||
private readonly ILogger<IPBasedAccessValidationMiddleware> _logger;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="IPBasedAccessValidationMiddleware"/> class.
|
||||
/// </summary>
|
||||
/// <param name="next">The next delegate in the pipeline.</param>
|
||||
public IPBasedAccessValidationMiddleware(RequestDelegate next)
|
||||
/// <param name="logger">The logger to log to.</param>
|
||||
public IPBasedAccessValidationMiddleware(RequestDelegate next, ILogger<IPBasedAccessValidationMiddleware> logger)
|
||||
{
|
||||
_next = next;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -32,16 +37,23 @@ public class IPBasedAccessValidationMiddleware
|
|||
{
|
||||
if (httpContext.IsLocal())
|
||||
{
|
||||
// Running locally.
|
||||
// Accessing from the same machine as the server.
|
||||
await _next(httpContext).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
var remoteIP = httpContext.Connection.RemoteIpAddress ?? IPAddress.Loopback;
|
||||
var remoteIP = httpContext.GetNormalizedRemoteIP();
|
||||
|
||||
if (!networkManager.HasRemoteAccess(remoteIP))
|
||||
var result = networkManager.ShouldAllowServerAccess(remoteIP);
|
||||
if (result != RemoteAccessPolicyResult.Allow)
|
||||
{
|
||||
// No access from network, respond with 503 instead of 200.
|
||||
_logger.LogWarning(
|
||||
"Blocking request to {Path} by {RemoteIP} due to IP filtering rule, reason: {Reason}",
|
||||
// url-encode to block log injection
|
||||
HttpUtility.UrlEncode(httpContext.Request.Path),
|
||||
remoteIP,
|
||||
result);
|
||||
httpContext.Response.StatusCode = StatusCodes.Status503ServiceUnavailable;
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,51 +0,0 @@
|
|||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Jellyfin.Api.Middleware;
|
||||
|
||||
/// <summary>
|
||||
/// Validates the LAN host IP based on application configuration.
|
||||
/// </summary>
|
||||
public class LanFilteringMiddleware
|
||||
{
|
||||
private readonly RequestDelegate _next;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LanFilteringMiddleware"/> class.
|
||||
/// </summary>
|
||||
/// <param name="next">The next delegate in the pipeline.</param>
|
||||
public LanFilteringMiddleware(RequestDelegate next)
|
||||
{
|
||||
_next = next;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes the middleware action.
|
||||
/// </summary>
|
||||
/// <param name="httpContext">The current HTTP context.</param>
|
||||
/// <param name="networkManager">The network manager.</param>
|
||||
/// <param name="serverConfigurationManager">The server configuration manager.</param>
|
||||
/// <returns>The async task.</returns>
|
||||
public async Task Invoke(HttpContext httpContext, INetworkManager networkManager, IServerConfigurationManager serverConfigurationManager)
|
||||
{
|
||||
if (serverConfigurationManager.GetNetworkConfiguration().EnableRemoteAccess)
|
||||
{
|
||||
await _next(httpContext).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
var host = httpContext.GetNormalizedRemoteIP();
|
||||
if (!networkManager.IsInLocalNetwork(host))
|
||||
{
|
||||
// No access from network, respond with 503 instead of 200.
|
||||
httpContext.Response.StatusCode = StatusCodes.Status503ServiceUnavailable;
|
||||
return;
|
||||
}
|
||||
|
||||
await _next(httpContext).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||
using System.ComponentModel;
|
||||
using System.Text.Json.Serialization;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Database.Implementations.Enums;
|
||||
using Jellyfin.Extensions.Json.Converters;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Querying;
|
||||
|
|
|
|||
|
|
@ -5,6 +5,11 @@ namespace Jellyfin.Api.Models.StartupDtos;
|
|||
/// </summary>
|
||||
public class StartupConfigurationDto
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the server name.
|
||||
/// </summary>
|
||||
public string? ServerName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets UI language culture.
|
||||
/// </summary>
|
||||
|
|
|
|||
46
Jellyfin.Api/Models/SystemInfoDtos/FolderStorageDto.cs
Normal file
46
Jellyfin.Api/Models/SystemInfoDtos/FolderStorageDto.cs
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
using MediaBrowser.Model.System;
|
||||
|
||||
namespace Jellyfin.Api.Models.SystemInfoDtos;
|
||||
|
||||
/// <summary>
|
||||
/// Contains information about a specific folder.
|
||||
/// </summary>
|
||||
public record FolderStorageDto
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the path of the folder in question.
|
||||
/// </summary>
|
||||
public required string Path { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the free space of the underlying storage device of the <see cref="Path"/>.
|
||||
/// </summary>
|
||||
public long FreeSpace { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the used space of the underlying storage device of the <see cref="Path"/>.
|
||||
/// </summary>
|
||||
public long UsedSpace { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the kind of storage device of the <see cref="Path"/>.
|
||||
/// </summary>
|
||||
public string? StorageType { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Device Identifier.
|
||||
/// </summary>
|
||||
public string? DeviceId { get; init; }
|
||||
|
||||
internal static FolderStorageDto FromFolderStorageInfo(FolderStorageInfo model)
|
||||
{
|
||||
return new()
|
||||
{
|
||||
Path = model.Path,
|
||||
FreeSpace = model.FreeSpace,
|
||||
UsedSpace = model.UsedSpace,
|
||||
StorageType = model.StorageType,
|
||||
DeviceId = model.DeviceId
|
||||
};
|
||||
}
|
||||
}
|
||||
37
Jellyfin.Api/Models/SystemInfoDtos/LibraryStorageDto.cs
Normal file
37
Jellyfin.Api/Models/SystemInfoDtos/LibraryStorageDto.cs
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using MediaBrowser.Model.System;
|
||||
|
||||
namespace Jellyfin.Api.Models.SystemInfoDtos;
|
||||
|
||||
/// <summary>
|
||||
/// Contains informations about a libraries storage informations.
|
||||
/// </summary>
|
||||
public record LibraryStorageDto
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the Library Id.
|
||||
/// </summary>
|
||||
public required Guid Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the library.
|
||||
/// </summary>
|
||||
public required string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the storage informations about the folders used in a library.
|
||||
/// </summary>
|
||||
public required IReadOnlyCollection<FolderStorageDto> Folders { get; set; }
|
||||
|
||||
internal static LibraryStorageDto FromLibraryStorageModel(LibraryStorageInfo model)
|
||||
{
|
||||
return new()
|
||||
{
|
||||
Id = model.Id,
|
||||
Name = model.Name,
|
||||
Folders = model.Folders.Select(FolderStorageDto.FromFolderStorageInfo).ToArray()
|
||||
};
|
||||
}
|
||||
}
|
||||
67
Jellyfin.Api/Models/SystemInfoDtos/SystemStorageDto.cs
Normal file
67
Jellyfin.Api/Models/SystemInfoDtos/SystemStorageDto.cs
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using MediaBrowser.Model.System;
|
||||
|
||||
namespace Jellyfin.Api.Models.SystemInfoDtos;
|
||||
|
||||
/// <summary>
|
||||
/// Contains informations about the systems storage.
|
||||
/// </summary>
|
||||
public record SystemStorageDto
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the Storage information of the program data folder.
|
||||
/// </summary>
|
||||
public required FolderStorageDto ProgramDataFolder { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Storage information of the web UI resources folder.
|
||||
/// </summary>
|
||||
public required FolderStorageDto WebFolder { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Storage information of the folder where images are cached.
|
||||
/// </summary>
|
||||
public required FolderStorageDto ImageCacheFolder { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Storage information of the cache folder.
|
||||
/// </summary>
|
||||
public required FolderStorageDto CacheFolder { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Storage information of the folder where logfiles are saved to.
|
||||
/// </summary>
|
||||
public required FolderStorageDto LogFolder { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Storage information of the folder where metadata is stored.
|
||||
/// </summary>
|
||||
public required FolderStorageDto InternalMetadataFolder { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Storage information of the transcoding cache.
|
||||
/// </summary>
|
||||
public required FolderStorageDto TranscodingTempFolder { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the storage informations of all libraries.
|
||||
/// </summary>
|
||||
public required IReadOnlyCollection<LibraryStorageDto> Libraries { get; set; }
|
||||
|
||||
internal static SystemStorageDto FromSystemStorageInfo(SystemStorageInfo model)
|
||||
{
|
||||
return new SystemStorageDto()
|
||||
{
|
||||
ProgramDataFolder = FolderStorageDto.FromFolderStorageInfo(model.ProgramDataFolder),
|
||||
WebFolder = FolderStorageDto.FromFolderStorageInfo(model.WebFolder),
|
||||
ImageCacheFolder = FolderStorageDto.FromFolderStorageInfo(model.ImageCacheFolder),
|
||||
CacheFolder = FolderStorageDto.FromFolderStorageInfo(model.CacheFolder),
|
||||
LogFolder = FolderStorageDto.FromFolderStorageInfo(model.LogFolder),
|
||||
InternalMetadataFolder = FolderStorageDto.FromFolderStorageInfo(model.InternalMetadataFolder),
|
||||
TranscodingTempFolder = FolderStorageDto.FromFolderStorageInfo(model.TranscodingTempFolder),
|
||||
Libraries = model.Libraries.Select(LibraryStorageDto.FromLibraryStorageModel).ToArray()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,8 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Data;
|
||||
using Jellyfin.Data.Events;
|
||||
using Jellyfin.Database.Implementations.Enums;
|
||||
using MediaBrowser.Controller.Authentication;
|
||||
using MediaBrowser.Controller.Net;
|
||||
using MediaBrowser.Model.Activity;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Data;
|
||||
using Jellyfin.Database.Implementations.Enums;
|
||||
using MediaBrowser.Controller.Authentication;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Net;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue