diff --git a/ErsatzTV.Application/FFmpegProfiles/Mapper.cs b/ErsatzTV.Application/FFmpegProfiles/Mapper.cs index 4799f0fa7..612433dc2 100644 --- a/ErsatzTV.Application/FFmpegProfiles/Mapper.cs +++ b/ErsatzTV.Application/FFmpegProfiles/Mapper.cs @@ -47,19 +47,25 @@ internal static class Mapper ffmpegProfile.Id, ffmpegProfile.Name, ffmpegProfile.ThreadCount, - (int)ffmpegProfile.HardwareAcceleration, + ffmpegProfile.HardwareAcceleration, ffmpegProfile.VaapiDisplay, - (int)ffmpegProfile.VaapiDriver, + ffmpegProfile.VaapiDriver, ffmpegProfile.VaapiDevice, - ffmpegProfile.ResolutionId, - (int)ffmpegProfile.VideoFormat, + ffmpegProfile.QsvExtraHardwareFrames, + ffmpegProfile.Resolution.Name, + ffmpegProfile.ScalingBehavior, + ffmpegProfile.VideoFormat, + ffmpegProfile.VideoProfile, + ffmpegProfile.VideoPreset, + ffmpegProfile.AllowBFrames, + ffmpegProfile.BitDepth, ffmpegProfile.VideoBitrate, ffmpegProfile.VideoBufferSize, - (int)ffmpegProfile.TonemapAlgorithm, - (int)ffmpegProfile.AudioFormat, + ffmpegProfile.TonemapAlgorithm, + ffmpegProfile.AudioFormat, ffmpegProfile.AudioBitrate, ffmpegProfile.AudioBufferSize, - (int)ffmpegProfile.NormalizeLoudnessMode, + ffmpegProfile.NormalizeLoudnessMode, ffmpegProfile.AudioChannels, ffmpegProfile.AudioSampleRate, ffmpegProfile.NormalizeFramerate, diff --git a/ErsatzTV.Application/FFmpegProfiles/Queries/GetAllFFmpegProfilesForApi.cs b/ErsatzTV.Application/FFmpegProfiles/Queries/GetAllFFmpegProfilesForApi.cs index a020ff83f..7f1a9d2d4 100644 --- a/ErsatzTV.Application/FFmpegProfiles/Queries/GetAllFFmpegProfilesForApi.cs +++ b/ErsatzTV.Application/FFmpegProfiles/Queries/GetAllFFmpegProfilesForApi.cs @@ -2,4 +2,4 @@ namespace ErsatzTV.Application.FFmpegProfiles; -public record GetAllFFmpegProfilesForApi : IRequest>; +public record GetAllFFmpegProfilesForApi : IRequest>; diff --git a/ErsatzTV.Application/FFmpegProfiles/Queries/GetAllFFmpegProfilesForApiHandler.cs b/ErsatzTV.Application/FFmpegProfiles/Queries/GetAllFFmpegProfilesForApiHandler.cs index 9e620e55a..ec9c8b934 100644 --- a/ErsatzTV.Application/FFmpegProfiles/Queries/GetAllFFmpegProfilesForApiHandler.cs +++ b/ErsatzTV.Application/FFmpegProfiles/Queries/GetAllFFmpegProfilesForApiHandler.cs @@ -6,23 +6,18 @@ using static ErsatzTV.Application.FFmpegProfiles.Mapper; namespace ErsatzTV.Application.FFmpegProfiles; -public class - GetAllFFmpegProfilesForApiHandler : IRequestHandler> +public class GetAllFFmpegProfilesForApiHandler(IDbContextFactory dbContextFactory) + : IRequestHandler> { - private readonly IDbContextFactory _dbContextFactory; - - public GetAllFFmpegProfilesForApiHandler(IDbContextFactory dbContextFactory) => - _dbContextFactory = dbContextFactory; - - public async Task> Handle( + public async Task> Handle( GetAllFFmpegProfilesForApi request, CancellationToken cancellationToken) { - await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken); + await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken); List ffmpegProfiles = await dbContext.FFmpegProfiles .AsNoTracking() .Include(p => p.Resolution) .ToListAsync(cancellationToken); - return ffmpegProfiles.Map(ProjectToResponseModel).ToList(); + return ffmpegProfiles.Map(ProjectToFullResponseModel).ToList(); } } diff --git a/ErsatzTV.Application/FFmpegProfiles/Queries/GetResolutionByName.cs b/ErsatzTV.Application/FFmpegProfiles/Queries/GetResolutionByName.cs new file mode 100644 index 000000000..2e770542d --- /dev/null +++ b/ErsatzTV.Application/FFmpegProfiles/Queries/GetResolutionByName.cs @@ -0,0 +1,5 @@ +using ErsatzTV.Core; + +namespace ErsatzTV.Application.FFmpegProfiles; + +public record GetResolutionByName(string Name) : IRequest>; diff --git a/ErsatzTV.Application/FFmpegProfiles/Queries/GetResolutionByNameHandler.cs b/ErsatzTV.Application/FFmpegProfiles/Queries/GetResolutionByNameHandler.cs new file mode 100644 index 000000000..724d85160 --- /dev/null +++ b/ErsatzTV.Application/FFmpegProfiles/Queries/GetResolutionByNameHandler.cs @@ -0,0 +1,18 @@ +using ErsatzTV.Infrastructure.Data; +using ErsatzTV.Infrastructure.Extensions; +using Microsoft.EntityFrameworkCore; + +namespace ErsatzTV.Application.FFmpegProfiles; + +public class GetResolutionByNameHandler(IDbContextFactory dbContextFactory) + : IRequestHandler> +{ + public async Task> Handle(GetResolutionByName request, CancellationToken cancellationToken) + { + await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken); + return await dbContext.Resolutions + .AsNoTracking() + .SelectOneAsync(r => r.Name, r => r.Name == request.Name, cancellationToken) + .MapT(r => r.Id); + } +} diff --git a/ErsatzTV.Core/Api/FFmpegProfiles/FFmpegFullProfileResponseModel.cs b/ErsatzTV.Core/Api/FFmpegProfiles/FFmpegFullProfileResponseModel.cs index 5848819eb..706063a86 100644 --- a/ErsatzTV.Core/Api/FFmpegProfiles/FFmpegFullProfileResponseModel.cs +++ b/ErsatzTV.Core/Api/FFmpegProfiles/FFmpegFullProfileResponseModel.cs @@ -1,22 +1,31 @@ -namespace ErsatzTV.Core.Api.FFmpegProfiles; +using ErsatzTV.Core.Domain; +using ErsatzTV.Core.FFmpeg; + +namespace ErsatzTV.Core.Api.FFmpegProfiles; public record FFmpegFullProfileResponseModel( int Id, string Name, int ThreadCount, - int HardwareAcceleration, + HardwareAccelerationKind HardwareAcceleration, string VaapiDisplay, - int VaapiDriver, + VaapiDriver VaapiDriver, string VaapiDevice, - int ResolutionId, - int VideoFormat, + int? QsvExtraHardwareFrames, + string Resolution, + ScalingBehavior ScalingBehavior, + FFmpegProfileVideoFormat VideoFormat, + string VideoProfile, + string VideoPreset, + bool AllowBFrames, + FFmpegProfileBitDepth BitDepth, int VideoBitrate, int VideoBufferSize, - int TonemapAlgorithm, - int AudioFormat, + FFmpegProfileTonemapAlgorithm TonemapAlgorithm, + FFmpegProfileAudioFormat AudioFormat, int AudioBitrate, int AudioBufferSize, - int NormalizeLoudnessMode, + NormalizeLoudnessMode NormalizeLoudnessMode, int AudioChannels, int AudioSampleRate, bool NormalizeFramerate, diff --git a/ErsatzTV.Core/Domain/FFmpegProfileAudioFormat.cs b/ErsatzTV.Core/Domain/FFmpegProfileAudioFormat.cs index 9ac4caf75..143e7f4e1 100644 --- a/ErsatzTV.Core/Domain/FFmpegProfileAudioFormat.cs +++ b/ErsatzTV.Core/Domain/FFmpegProfileAudioFormat.cs @@ -1,5 +1,8 @@ -namespace ErsatzTV.Core.Domain; +using System.Text.Json.Serialization; +namespace ErsatzTV.Core.Domain; + +[JsonConverter(typeof(JsonStringEnumConverter))] public enum FFmpegProfileAudioFormat { None = 0, diff --git a/ErsatzTV.Core/Domain/FFmpegProfileBitDepth.cs b/ErsatzTV.Core/Domain/FFmpegProfileBitDepth.cs index 8f67c71dd..09dadb59d 100644 --- a/ErsatzTV.Core/Domain/FFmpegProfileBitDepth.cs +++ b/ErsatzTV.Core/Domain/FFmpegProfileBitDepth.cs @@ -1,5 +1,8 @@ +using System.Text.Json.Serialization; + namespace ErsatzTV.Core.Domain; +[JsonConverter(typeof(JsonStringEnumConverter))] public enum FFmpegProfileBitDepth { EightBit = 0, diff --git a/ErsatzTV.Core/Domain/FFmpegProfileTonemapAlgorithm.cs b/ErsatzTV.Core/Domain/FFmpegProfileTonemapAlgorithm.cs index 419d9d90c..470554d3e 100644 --- a/ErsatzTV.Core/Domain/FFmpegProfileTonemapAlgorithm.cs +++ b/ErsatzTV.Core/Domain/FFmpegProfileTonemapAlgorithm.cs @@ -1,5 +1,8 @@ +using System.Text.Json.Serialization; + namespace ErsatzTV.Core.Domain; +[JsonConverter(typeof(JsonStringEnumConverter))] public enum FFmpegProfileTonemapAlgorithm { Linear = 0, diff --git a/ErsatzTV.Core/Domain/FFmpegProfileVideoFormat.cs b/ErsatzTV.Core/Domain/FFmpegProfileVideoFormat.cs index 665faec71..b96f332d9 100644 --- a/ErsatzTV.Core/Domain/FFmpegProfileVideoFormat.cs +++ b/ErsatzTV.Core/Domain/FFmpegProfileVideoFormat.cs @@ -1,5 +1,8 @@ -namespace ErsatzTV.Core.Domain; +using System.Text.Json.Serialization; +namespace ErsatzTV.Core.Domain; + +[JsonConverter(typeof(JsonStringEnumConverter))] public enum FFmpegProfileVideoFormat { None = 0, diff --git a/ErsatzTV.Core/Domain/HardwareAccelerationKind.cs b/ErsatzTV.Core/Domain/HardwareAccelerationKind.cs index 9cb312d52..2dd7251e1 100644 --- a/ErsatzTV.Core/Domain/HardwareAccelerationKind.cs +++ b/ErsatzTV.Core/Domain/HardwareAccelerationKind.cs @@ -1,5 +1,8 @@ -namespace ErsatzTV.Core.Domain; +using System.Text.Json.Serialization; +namespace ErsatzTV.Core.Domain; + +[JsonConverter(typeof(JsonStringEnumConverter))] public enum HardwareAccelerationKind { None = 0, diff --git a/ErsatzTV.Core/Domain/NormalizeLoudnessMode.cs b/ErsatzTV.Core/Domain/NormalizeLoudnessMode.cs index fb9c6f341..d9f9211f3 100644 --- a/ErsatzTV.Core/Domain/NormalizeLoudnessMode.cs +++ b/ErsatzTV.Core/Domain/NormalizeLoudnessMode.cs @@ -1,5 +1,8 @@ +using System.Text.Json.Serialization; + namespace ErsatzTV.Core.Domain; +[JsonConverter(typeof(JsonStringEnumConverter))] public enum NormalizeLoudnessMode { Off = 0, diff --git a/ErsatzTV.Core/Domain/ScalingBehavior.cs b/ErsatzTV.Core/Domain/ScalingBehavior.cs index b9283e53e..e48b36942 100644 --- a/ErsatzTV.Core/Domain/ScalingBehavior.cs +++ b/ErsatzTV.Core/Domain/ScalingBehavior.cs @@ -1,5 +1,8 @@ +using System.Text.Json.Serialization; + namespace ErsatzTV.Core.Domain; +[JsonConverter(typeof(JsonStringEnumConverter))] public enum ScalingBehavior { ScaleAndPad = 0, diff --git a/ErsatzTV.Core/FFmpeg/VaapiDriver.cs b/ErsatzTV.Core/FFmpeg/VaapiDriver.cs index b5c85bf99..0081100ef 100644 --- a/ErsatzTV.Core/FFmpeg/VaapiDriver.cs +++ b/ErsatzTV.Core/FFmpeg/VaapiDriver.cs @@ -1,8 +1,10 @@ using System.Diagnostics.CodeAnalysis; +using System.Text.Json.Serialization; namespace ErsatzTV.Core.FFmpeg; [SuppressMessage("ReSharper", "InconsistentNaming")] +[JsonConverter(typeof(JsonStringEnumConverter))] public enum VaapiDriver { Default = 0, diff --git a/ErsatzTV/Controllers/Api/FFmpegProfileController.cs b/ErsatzTV/Controllers/Api/FFmpegProfileController.cs index 2cd5f2371..b0256bf31 100644 --- a/ErsatzTV/Controllers/Api/FFmpegProfileController.cs +++ b/ErsatzTV/Controllers/Api/FFmpegProfileController.cs @@ -2,34 +2,43 @@ using ErsatzTV.Application.FFmpegProfiles; using ErsatzTV.Core; using ErsatzTV.Core.Api.FFmpegProfiles; -using ErsatzTV.Filters; using MediatR; using Microsoft.AspNetCore.Mvc; namespace ErsatzTV.Controllers.Api; [ApiController] -[V2ApiActionFilter] -public class FFmpegProfileController(IMediator mediator) +[EndpointGroupName("general")] +public class FFmpegProfileController(IMediator mediator) : ControllerBase { - [HttpGet("/api/ffmpeg/profiles")] - public async Task> GetAll() => - await mediator.Send(new GetAllFFmpegProfilesForApi()); + [HttpGet("/api/ffmpeg/profiles", Name="GetFFmpegProfiles")] + public async Task> GetAll(CancellationToken cancellationToken) => + await mediator.Send(new GetAllFFmpegProfilesForApi(), cancellationToken); - [HttpPost("/api/ffmpeg/profiles/new")] - public async Task> AddOne( + [HttpPost("/api/ffmpeg/profiles/new", Name="CreateFFmpegProfile")] + public async Task AddOne( [Required] [FromBody] - CreateFFmpegProfile request) => await mediator.Send(request); + CreateFFmpegProfile request, + CancellationToken cancellationToken) + { + Either result = await mediator.Send(request, cancellationToken); + return result.Match(Ok, error => Problem(error.ToString())); + } - [HttpPut("/api/ffmpeg/profiles/update")] - public async Task> UpdateOne( + [HttpPut("/api/ffmpeg/profiles/update", Name="UpdateFFmpegProfile")] + public async Task UpdateOne( [Required] [FromBody] - UpdateFFmpegProfile request) => await mediator.Send(request); + UpdateFFmpegProfile request, + CancellationToken cancellationToken) + { + Either result = await mediator.Send(request, cancellationToken); + return result.Match(Ok, error => Problem(error.ToString())); + } - [HttpGet("/api/ffmpeg/profiles/{id:int}")] - public async Task> GetOne(int id) => - await mediator.Send(new GetFFmpegFullProfileByIdForApi(id)); - - [HttpDelete("/api/ffmpeg/delete/{id:int}")] - public async Task DeleteProfileAsync(int id) => await mediator.Send(new DeleteFFmpegProfile(id)); + [HttpDelete("/api/ffmpeg/delete/{id:int}", Name="DeleteFFmpegProfile")] + public async Task DeleteProfileAsync(int id, CancellationToken cancellationToken) + { + Either result = await mediator.Send(new DeleteFFmpegProfile(id), cancellationToken); + return result.Match(_ => Ok(), error => Problem(error.ToString())); + } } diff --git a/ErsatzTV/Controllers/Api/ResolutionController.cs b/ErsatzTV/Controllers/Api/ResolutionController.cs new file mode 100644 index 000000000..423266646 --- /dev/null +++ b/ErsatzTV/Controllers/Api/ResolutionController.cs @@ -0,0 +1,18 @@ +using ErsatzTV.Application.FFmpegProfiles; +using MediatR; +using Microsoft.AspNetCore.Mvc; + +namespace ErsatzTV.Controllers.Api; + +[ApiController] +[EndpointGroupName("general")] +public class ResolutionController(IMediator mediator) : ControllerBase +{ + [HttpGet("/api/ffmpeg/resolution/by-name/{name}", Name="GetResolutionByName")] + public async Task> GetResolutionByName(string name, CancellationToken cancellationToken) + { + Option result = await mediator.Send(new GetResolutionByName(name), cancellationToken); + return result.Match>(i => Ok(i), () => NotFound()); + } +} + diff --git a/ErsatzTV/Controllers/Api/VersionController.cs b/ErsatzTV/Controllers/Api/VersionController.cs index 4a7dafef2..5804177a8 100644 --- a/ErsatzTV/Controllers/Api/VersionController.cs +++ b/ErsatzTV/Controllers/Api/VersionController.cs @@ -11,7 +11,7 @@ public class VersionController static VersionController() => Version = new CombinedVersion( - 1, + 2, Assembly.GetEntryAssembly()? .GetCustomAttribute()? .InformationalVersion ?? "unknown"); diff --git a/ErsatzTV/wwwroot/openapi/v1.json b/ErsatzTV/wwwroot/openapi/v1.json index c949e9538..c5c9e80e5 100644 --- a/ErsatzTV/wwwroot/openapi/v1.json +++ b/ErsatzTV/wwwroot/openapi/v1.json @@ -66,6 +66,145 @@ } } }, + "/api/ffmpeg/profiles": { + "get": { + "tags": [ + "FFmpegProfile" + ], + "operationId": "GetFFmpegProfiles", + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FFmpegFullProfileResponseModel" + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FFmpegFullProfileResponseModel" + } + } + }, + "text/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FFmpegFullProfileResponseModel" + } + } + } + } + } + } + } + }, + "/api/ffmpeg/profiles/new": { + "post": { + "tags": [ + "FFmpegProfile" + ], + "operationId": "CreateFFmpegProfile", + "requestBody": { + "content": { + "application/json-patch+json": { + "schema": { + "$ref": "#/components/schemas/CreateFFmpegProfile" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateFFmpegProfile" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/CreateFFmpegProfile" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/CreateFFmpegProfile" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/api/ffmpeg/profiles/update": { + "put": { + "tags": [ + "FFmpegProfile" + ], + "operationId": "UpdateFFmpegProfile", + "requestBody": { + "content": { + "application/json-patch+json": { + "schema": { + "$ref": "#/components/schemas/UpdateFFmpegProfile" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateFFmpegProfile" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/UpdateFFmpegProfile" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/UpdateFFmpegProfile" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/api/ffmpeg/delete/{id}": { + "delete": { + "tags": [ + "FFmpegProfile" + ], + "operationId": "DeleteFFmpegProfile", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, "/api/libraries/{id}/scan": { "post": { "tags": [ @@ -175,6 +314,49 @@ } } }, + "/api/ffmpeg/resolution/by-name/{name}": { + "get": { + "tags": [ + "Resolution" + ], + "operationId": "GetResolutionByName", + "parameters": [ + { + "name": "name", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "integer", + "format": "int32" + } + }, + "application/json": { + "schema": { + "type": "integer", + "format": "int32" + } + }, + "text/json": { + "schema": { + "type": "integer", + "format": "int32" + } + } + } + } + } + } + }, "/api/sessions": { "get": { "tags": [ @@ -464,6 +646,128 @@ } } }, + "CreateFFmpegProfile": { + "required": [ + "name", + "threadCount", + "hardwareAcceleration", + "vaapiDisplay", + "vaapiDriver", + "vaapiDevice", + "qsvExtraHardwareFrames", + "resolutionId", + "scalingBehavior", + "videoFormat", + "videoProfile", + "videoPreset", + "allowBFrames", + "bitDepth", + "videoBitrate", + "videoBufferSize", + "tonemapAlgorithm", + "audioFormat", + "audioBitrate", + "audioBufferSize", + "normalizeLoudnessMode", + "audioChannels", + "audioSampleRate", + "normalizeFramerate", + "deinterlaceVideo" + ], + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true + }, + "threadCount": { + "type": "integer", + "format": "int32" + }, + "hardwareAcceleration": { + "$ref": "#/components/schemas/HardwareAccelerationKind" + }, + "vaapiDisplay": { + "type": "string", + "nullable": true + }, + "vaapiDriver": { + "$ref": "#/components/schemas/VaapiDriver" + }, + "vaapiDevice": { + "type": "string", + "nullable": true + }, + "qsvExtraHardwareFrames": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "resolutionId": { + "type": "integer", + "format": "int32" + }, + "scalingBehavior": { + "$ref": "#/components/schemas/ScalingBehavior" + }, + "videoFormat": { + "$ref": "#/components/schemas/FFmpegProfileVideoFormat" + }, + "videoProfile": { + "type": "string", + "nullable": true + }, + "videoPreset": { + "type": "string", + "nullable": true + }, + "allowBFrames": { + "type": "boolean" + }, + "bitDepth": { + "$ref": "#/components/schemas/FFmpegProfileBitDepth" + }, + "videoBitrate": { + "type": "integer", + "format": "int32" + }, + "videoBufferSize": { + "type": "integer", + "format": "int32" + }, + "tonemapAlgorithm": { + "$ref": "#/components/schemas/FFmpegProfileTonemapAlgorithm" + }, + "audioFormat": { + "$ref": "#/components/schemas/FFmpegProfileAudioFormat" + }, + "audioBitrate": { + "type": "integer", + "format": "int32" + }, + "audioBufferSize": { + "type": "integer", + "format": "int32" + }, + "normalizeLoudnessMode": { + "$ref": "#/components/schemas/NormalizeLoudnessMode" + }, + "audioChannels": { + "type": "integer", + "format": "int32" + }, + "audioSampleRate": { + "type": "integer", + "format": "int32" + }, + "normalizeFramerate": { + "type": "boolean" + }, + "deinterlaceVideo": { + "type": "boolean" + } + } + }, "CreateSmartCollection": { "required": [ "query", @@ -481,6 +785,180 @@ } } }, + "FFmpegFullProfileResponseModel": { + "required": [ + "id", + "name", + "threadCount", + "hardwareAcceleration", + "vaapiDisplay", + "vaapiDriver", + "vaapiDevice", + "qsvExtraHardwareFrames", + "resolution", + "scalingBehavior", + "videoFormat", + "videoProfile", + "videoPreset", + "allowBFrames", + "bitDepth", + "videoBitrate", + "videoBufferSize", + "tonemapAlgorithm", + "audioFormat", + "audioBitrate", + "audioBufferSize", + "normalizeLoudnessMode", + "audioChannels", + "audioSampleRate", + "normalizeFramerate", + "deinterlaceVideo" + ], + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int32" + }, + "name": { + "type": "string", + "nullable": true + }, + "threadCount": { + "type": "integer", + "format": "int32" + }, + "hardwareAcceleration": { + "$ref": "#/components/schemas/HardwareAccelerationKind" + }, + "vaapiDisplay": { + "type": "string", + "nullable": true + }, + "vaapiDriver": { + "$ref": "#/components/schemas/VaapiDriver" + }, + "vaapiDevice": { + "type": "string", + "nullable": true + }, + "qsvExtraHardwareFrames": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "resolution": { + "type": "string", + "nullable": true + }, + "scalingBehavior": { + "$ref": "#/components/schemas/ScalingBehavior" + }, + "videoFormat": { + "$ref": "#/components/schemas/FFmpegProfileVideoFormat" + }, + "videoProfile": { + "type": "string", + "nullable": true + }, + "videoPreset": { + "type": "string", + "nullable": true + }, + "allowBFrames": { + "type": "boolean" + }, + "bitDepth": { + "$ref": "#/components/schemas/FFmpegProfileBitDepth" + }, + "videoBitrate": { + "type": "integer", + "format": "int32" + }, + "videoBufferSize": { + "type": "integer", + "format": "int32" + }, + "tonemapAlgorithm": { + "$ref": "#/components/schemas/FFmpegProfileTonemapAlgorithm" + }, + "audioFormat": { + "$ref": "#/components/schemas/FFmpegProfileAudioFormat" + }, + "audioBitrate": { + "type": "integer", + "format": "int32" + }, + "audioBufferSize": { + "type": "integer", + "format": "int32" + }, + "normalizeLoudnessMode": { + "$ref": "#/components/schemas/NormalizeLoudnessMode" + }, + "audioChannels": { + "type": "integer", + "format": "int32" + }, + "audioSampleRate": { + "type": "integer", + "format": "int32" + }, + "normalizeFramerate": { + "type": "boolean" + }, + "deinterlaceVideo": { + "type": "boolean", + "nullable": true + } + } + }, + "FFmpegProfileAudioFormat": { + "enum": [ + "None", + "Aac", + "Ac3", + "Copy" + ] + }, + "FFmpegProfileBitDepth": { + "enum": [ + "EightBit", + "TenBit" + ] + }, + "FFmpegProfileTonemapAlgorithm": { + "enum": [ + "Linear", + "Clip", + "Gamma", + "Reinhard", + "Mobius", + "Hable" + ] + }, + "FFmpegProfileVideoFormat": { + "enum": [ + "None", + "H264", + "Hevc", + "Mpeg2Video", + "Av1", + "Copy" + ] + }, + "HardwareAccelerationKind": { + "enum": [ + "None", + "Qsv", + "Nvenc", + "Vaapi", + "VideoToolbox", + "Amf", + "V4l2m2m", + "Rkmpp" + ] + }, "HlsSessionModel": { "required": [ "channelNumber", @@ -508,6 +986,19 @@ } } }, + "NormalizeLoudnessMode": { + "enum": [ + "Off", + "LoudNorm" + ] + }, + "ScalingBehavior": { + "enum": [ + "ScaleAndPad", + "Stretch", + "Crop" + ] + }, "ScanShowRequest": { "required": [ "showTitle" @@ -546,6 +1037,133 @@ } } }, + "UpdateFFmpegProfile": { + "required": [ + "fFmpegProfileId", + "name", + "threadCount", + "hardwareAcceleration", + "vaapiDisplay", + "vaapiDriver", + "vaapiDevice", + "qsvExtraHardwareFrames", + "resolutionId", + "scalingBehavior", + "videoFormat", + "videoProfile", + "videoPreset", + "allowBFrames", + "bitDepth", + "videoBitrate", + "videoBufferSize", + "tonemapAlgorithm", + "audioFormat", + "audioBitrate", + "audioBufferSize", + "normalizeLoudnessMode", + "audioChannels", + "audioSampleRate", + "normalizeFramerate", + "deinterlaceVideo" + ], + "type": "object", + "properties": { + "fFmpegProfileId": { + "type": "integer", + "format": "int32" + }, + "name": { + "type": "string", + "nullable": true + }, + "threadCount": { + "type": "integer", + "format": "int32" + }, + "hardwareAcceleration": { + "$ref": "#/components/schemas/HardwareAccelerationKind" + }, + "vaapiDisplay": { + "type": "string", + "nullable": true + }, + "vaapiDriver": { + "$ref": "#/components/schemas/VaapiDriver" + }, + "vaapiDevice": { + "type": "string", + "nullable": true + }, + "qsvExtraHardwareFrames": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "resolutionId": { + "type": "integer", + "format": "int32" + }, + "scalingBehavior": { + "$ref": "#/components/schemas/ScalingBehavior" + }, + "videoFormat": { + "$ref": "#/components/schemas/FFmpegProfileVideoFormat" + }, + "videoProfile": { + "type": "string", + "nullable": true + }, + "videoPreset": { + "type": "string", + "nullable": true + }, + "allowBFrames": { + "type": "boolean" + }, + "bitDepth": { + "$ref": "#/components/schemas/FFmpegProfileBitDepth" + }, + "videoBitrate": { + "type": "integer", + "format": "int32" + }, + "videoBufferSize": { + "type": "integer", + "format": "int32" + }, + "tonemapAlgorithm": { + "$ref": "#/components/schemas/FFmpegProfileTonemapAlgorithm" + }, + "audioFormat": { + "$ref": "#/components/schemas/FFmpegProfileAudioFormat" + }, + "audioBitrate": { + "type": "integer", + "format": "int32" + }, + "audioBufferSize": { + "type": "integer", + "format": "int32" + }, + "normalizeLoudnessMode": { + "$ref": "#/components/schemas/NormalizeLoudnessMode" + }, + "audioChannels": { + "type": "integer", + "format": "int32" + }, + "audioSampleRate": { + "type": "integer", + "format": "int32" + }, + "normalizeFramerate": { + "type": "boolean" + }, + "deinterlaceVideo": { + "type": "boolean" + } + } + }, "UpdateSmartCollection": { "required": [ "id", @@ -567,6 +1185,15 @@ "nullable": true } } + }, + "VaapiDriver": { + "enum": [ + "Default", + "iHD", + "i965", + "RadeonSI", + "Nouveau" + ] } } }, @@ -577,12 +1204,18 @@ { "name": "Channels" }, + { + "name": "FFmpegProfile" + }, { "name": "Libraries" }, { "name": "Maintenance" }, + { + "name": "Resolution" + }, { "name": "Sessions" },