Browse Source

use custom mpegts script

pull/2609/head
Jason Dove 9 months ago
parent
commit
49390ddabe
No known key found for this signature in database
  1. 5
      ErsatzTV.Application/Configuration/Queries/GetMpegTsScripts.cs
  2. 14
      ErsatzTV.Application/Configuration/Queries/GetMpegTsScriptsHandler.cs
  3. 4
      ErsatzTV.Application/FFmpegProfiles/Commands/UpdateFFmpegSettingsHandler.cs
  4. 1
      ErsatzTV.Application/FFmpegProfiles/FFmpegSettingsViewModel.cs
  5. 55
      ErsatzTV.Application/FFmpegProfiles/Queries/GetFFmpegSettingsHandler.cs
  6. 2
      ErsatzTV.Application/Streaming/HlsSessionWorker.cs
  7. 3
      ErsatzTV.Application/Streaming/Queries/GetWrappedProcessByChannelNumberHandler.cs
  8. 7
      ErsatzTV.Application/Troubleshooting/Queries/GetTroubleshootingInfoHandler.cs
  9. 1
      ErsatzTV.Core/Domain/ConfigElementKey.cs
  10. 8
      ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs
  11. 3
      ErsatzTV.Core/FFmpeg/MpegTsScript.cs
  12. 3
      ErsatzTV.Core/Interfaces/FFmpeg/IFFmpegProcessService.cs
  13. 1
      ErsatzTV.Infrastructure/FFmpeg/MpegTsScriptService.cs
  14. 17
      ErsatzTV/Pages/Settings/FFmpegSettings.razor

5
ErsatzTV.Application/Configuration/Queries/GetMpegTsScripts.cs

@ -0,0 +1,5 @@
using ErsatzTV.Core.FFmpeg;
namespace ErsatzTV.Application.Configuration;
public record GetMpegTsScripts : IRequest<List<MpegTsScript>>;

14
ErsatzTV.Application/Configuration/Queries/GetMpegTsScriptsHandler.cs

@ -0,0 +1,14 @@
using ErsatzTV.Core.FFmpeg;
using ErsatzTV.Core.Interfaces.FFmpeg;
namespace ErsatzTV.Application.Configuration;
public class GetMpegTsScriptsHandler(IMpegTsScriptService mpegTsScriptService)
: IRequestHandler<GetMpegTsScripts, List<MpegTsScript>>
{
public async Task<List<MpegTsScript>> Handle(GetMpegTsScripts request, CancellationToken cancellationToken)
{
await mpegTsScriptService.RefreshScripts();
return mpegTsScriptService.GetScripts().OrderBy(x => x.Name).ToList();
}
}

4
ErsatzTV.Application/FFmpegProfiles/Commands/UpdateFFmpegSettingsHandler.cs

@ -85,6 +85,10 @@ public class UpdateFFmpegSettingsHandler : IRequestHandler<UpdateFFmpegSettings,
ConfigElementKey.FFmpegHlsDirectOutputFormat, ConfigElementKey.FFmpegHlsDirectOutputFormat,
request.Settings.HlsDirectOutputFormat, request.Settings.HlsDirectOutputFormat,
cancellationToken); cancellationToken);
await _configElementRepository.Upsert(
ConfigElementKey.FFmpegDefaultMpegTsScript,
request.Settings.DefaultMpegTsScript,
cancellationToken);
if (request.Settings.SaveReports && !Directory.Exists(FileSystemLayout.FFmpegReportsFolder)) if (request.Settings.SaveReports && !Directory.Exists(FileSystemLayout.FFmpegReportsFolder))
{ {

1
ErsatzTV.Application/FFmpegProfiles/FFmpegSettingsViewModel.cs

@ -17,4 +17,5 @@ public class FFmpegSettingsViewModel
public int WorkAheadSegmenterLimit { get; set; } public int WorkAheadSegmenterLimit { get; set; }
public int InitialSegmentCount { get; set; } public int InitialSegmentCount { get; set; }
public OutputFormatKind HlsDirectOutputFormat { get; set; } public OutputFormatKind HlsDirectOutputFormat { get; set; }
public string DefaultMpegTsScript { get; set; }
} }

55
ErsatzTV.Application/FFmpegProfiles/Queries/GetFFmpegSettingsHandler.cs

@ -4,41 +4,55 @@ using ErsatzTV.FFmpeg.OutputFormat;
namespace ErsatzTV.Application.FFmpegProfiles; namespace ErsatzTV.Application.FFmpegProfiles;
public class GetFFmpegSettingsHandler : IRequestHandler<GetFFmpegSettings, FFmpegSettingsViewModel> public class GetFFmpegSettingsHandler(IConfigElementRepository configElementRepository)
: IRequestHandler<GetFFmpegSettings, FFmpegSettingsViewModel>
{ {
private readonly IConfigElementRepository _configElementRepository;
public GetFFmpegSettingsHandler(IConfigElementRepository configElementRepository) =>
_configElementRepository = configElementRepository;
public async Task<FFmpegSettingsViewModel> Handle( public async Task<FFmpegSettingsViewModel> Handle(
GetFFmpegSettings request, GetFFmpegSettings request,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
Option<string> ffmpegPath = await _configElementRepository.GetValue<string>(ConfigElementKey.FFmpegPath, cancellationToken); Option<string> ffmpegPath = await configElementRepository.GetValue<string>(
Option<string> ffprobePath = await _configElementRepository.GetValue<string>(ConfigElementKey.FFprobePath, cancellationToken); ConfigElementKey.FFmpegPath,
cancellationToken);
Option<string> ffprobePath = await configElementRepository.GetValue<string>(
ConfigElementKey.FFprobePath,
cancellationToken);
Option<int> defaultFFmpegProfileId = Option<int> defaultFFmpegProfileId =
await _configElementRepository.GetValue<int>(ConfigElementKey.FFmpegDefaultProfileId, cancellationToken); await configElementRepository.GetValue<int>(ConfigElementKey.FFmpegDefaultProfileId, cancellationToken);
Option<bool> saveReports = Option<bool> saveReports =
await _configElementRepository.GetValue<bool>(ConfigElementKey.FFmpegSaveReports, cancellationToken); await configElementRepository.GetValue<bool>(ConfigElementKey.FFmpegSaveReports, cancellationToken);
Option<string> preferredAudioLanguageCode = Option<string> preferredAudioLanguageCode =
await _configElementRepository.GetValue<string>(ConfigElementKey.FFmpegPreferredLanguageCode, cancellationToken); await configElementRepository.GetValue<string>(
ConfigElementKey.FFmpegPreferredLanguageCode,
cancellationToken);
Option<bool> useEmbeddedSubtitles = Option<bool> useEmbeddedSubtitles =
await _configElementRepository.GetValue<bool>(ConfigElementKey.FFmpegUseEmbeddedSubtitles, cancellationToken); await configElementRepository.GetValue<bool>(
ConfigElementKey.FFmpegUseEmbeddedSubtitles,
cancellationToken);
Option<bool> extractEmbeddedSubtitles = Option<bool> extractEmbeddedSubtitles =
await _configElementRepository.GetValue<bool>(ConfigElementKey.FFmpegExtractEmbeddedSubtitles, cancellationToken); await configElementRepository.GetValue<bool>(
ConfigElementKey.FFmpegExtractEmbeddedSubtitles,
cancellationToken);
Option<int> watermark = Option<int> watermark =
await _configElementRepository.GetValue<int>(ConfigElementKey.FFmpegGlobalWatermarkId, cancellationToken); await configElementRepository.GetValue<int>(ConfigElementKey.FFmpegGlobalWatermarkId, cancellationToken);
Option<int> fallbackFiller = Option<int> fallbackFiller =
await _configElementRepository.GetValue<int>(ConfigElementKey.FFmpegGlobalFallbackFillerId, cancellationToken); await configElementRepository.GetValue<int>(
ConfigElementKey.FFmpegGlobalFallbackFillerId,
cancellationToken);
Option<int> hlsSegmenterIdleTimeout = Option<int> hlsSegmenterIdleTimeout =
await _configElementRepository.GetValue<int>(ConfigElementKey.FFmpegSegmenterTimeout, cancellationToken); await configElementRepository.GetValue<int>(ConfigElementKey.FFmpegSegmenterTimeout, cancellationToken);
Option<int> workAheadSegmenterLimit = Option<int> workAheadSegmenterLimit =
await _configElementRepository.GetValue<int>(ConfigElementKey.FFmpegWorkAheadSegmenters, cancellationToken); await configElementRepository.GetValue<int>(ConfigElementKey.FFmpegWorkAheadSegmenters, cancellationToken);
Option<int> initialSegmentCount = Option<int> initialSegmentCount =
await _configElementRepository.GetValue<int>(ConfigElementKey.FFmpegInitialSegmentCount, cancellationToken); await configElementRepository.GetValue<int>(ConfigElementKey.FFmpegInitialSegmentCount, cancellationToken);
Option<OutputFormatKind> outputFormatKind = Option<OutputFormatKind> outputFormatKind =
await _configElementRepository.GetValue<OutputFormatKind>(ConfigElementKey.FFmpegHlsDirectOutputFormat, cancellationToken); await configElementRepository.GetValue<OutputFormatKind>(
ConfigElementKey.FFmpegHlsDirectOutputFormat,
cancellationToken);
Option<string> defaultMpegTsScript =
await configElementRepository.GetValue<string>(
ConfigElementKey.FFmpegDefaultMpegTsScript,
cancellationToken);
var result = new FFmpegSettingsViewModel var result = new FFmpegSettingsViewModel
{ {
@ -52,7 +66,8 @@ public class GetFFmpegSettingsHandler : IRequestHandler<GetFFmpegSettings, FFmpe
HlsSegmenterIdleTimeout = await hlsSegmenterIdleTimeout.IfNoneAsync(60), HlsSegmenterIdleTimeout = await hlsSegmenterIdleTimeout.IfNoneAsync(60),
WorkAheadSegmenterLimit = await workAheadSegmenterLimit.IfNoneAsync(1), WorkAheadSegmenterLimit = await workAheadSegmenterLimit.IfNoneAsync(1),
InitialSegmentCount = await initialSegmentCount.IfNoneAsync(1), InitialSegmentCount = await initialSegmentCount.IfNoneAsync(1),
HlsDirectOutputFormat = await outputFormatKind.IfNoneAsync(OutputFormatKind.MpegTs) HlsDirectOutputFormat = await outputFormatKind.IfNoneAsync(OutputFormatKind.MpegTs),
DefaultMpegTsScript = await defaultMpegTsScript.IfNoneAsync("Default"),
}; };
foreach (int watermarkId in watermark) foreach (int watermarkId in watermark)

2
ErsatzTV.Application/Streaming/HlsSessionWorker.cs

@ -791,7 +791,7 @@ public class HlsSessionWorker : IHlsSessionWorker
foreach (PtsTime pts in queryResult.RightToSeq()) foreach (PtsTime pts in queryResult.RightToSeq())
{ {
_logger.LogWarning("Last pts offset is {Pts}", pts.Value); _logger.LogDebug("Last pts offset is {Pts}", pts.Value);
result = pts.Value; result = pts.Value;
} }

3
ErsatzTV.Application/Streaming/Queries/GetWrappedProcessByChannelNumberHandler.cs

@ -37,7 +37,8 @@ public class GetWrappedProcessByChannelNumberHandler : FFmpegProcessHandler<GetW
channel, channel,
request.Scheme, request.Scheme,
request.Host, request.Host,
request.AccessToken); request.AccessToken,
cancellationToken);
return new PlayoutItemProcessModel( return new PlayoutItemProcessModel(
process, process,

7
ErsatzTV.Application/Troubleshooting/Queries/GetTroubleshootingInfoHandler.cs

@ -236,6 +236,10 @@ public class GetTroubleshootingInfoHandler : IRequestHandler<GetTroubleshootingI
await _configElementRepository.GetValue<OutputFormatKind>( await _configElementRepository.GetValue<OutputFormatKind>(
ConfigElementKey.FFmpegHlsDirectOutputFormat, ConfigElementKey.FFmpegHlsDirectOutputFormat,
cancellationToken); cancellationToken);
Option<string> defaultMpegTsScript =
await _configElementRepository.GetValue<string>(
ConfigElementKey.FFmpegDefaultMpegTsScript,
cancellationToken);
var result = new FFmpegSettingsViewModel var result = new FFmpegSettingsViewModel
{ {
@ -249,7 +253,8 @@ public class GetTroubleshootingInfoHandler : IRequestHandler<GetTroubleshootingI
HlsSegmenterIdleTimeout = await hlsSegmenterIdleTimeout.IfNoneAsync(60), HlsSegmenterIdleTimeout = await hlsSegmenterIdleTimeout.IfNoneAsync(60),
WorkAheadSegmenterLimit = await workAheadSegmenterLimit.IfNoneAsync(1), WorkAheadSegmenterLimit = await workAheadSegmenterLimit.IfNoneAsync(1),
InitialSegmentCount = await initialSegmentCount.IfNoneAsync(1), InitialSegmentCount = await initialSegmentCount.IfNoneAsync(1),
HlsDirectOutputFormat = await outputFormatKind.IfNoneAsync(OutputFormatKind.MpegTs) HlsDirectOutputFormat = await outputFormatKind.IfNoneAsync(OutputFormatKind.MpegTs),
DefaultMpegTsScript = await defaultMpegTsScript.IfNoneAsync("Default")
}; };
foreach (int watermarkId in watermark) foreach (int watermarkId in watermark)

1
ErsatzTV.Core/Domain/ConfigElementKey.cs

@ -26,6 +26,7 @@ public class ConfigElementKey
public static ConfigElementKey FFmpegWorkAheadSegmenters => new("ffmpeg.segmenter.work_ahead_limit"); public static ConfigElementKey FFmpegWorkAheadSegmenters => new("ffmpeg.segmenter.work_ahead_limit");
public static ConfigElementKey FFmpegInitialSegmentCount => new("ffmpeg.segmenter.initial_segment_count"); public static ConfigElementKey FFmpegInitialSegmentCount => new("ffmpeg.segmenter.initial_segment_count");
public static ConfigElementKey FFmpegHlsDirectOutputFormat => new("ffmpeg.hls_direct.output_format"); public static ConfigElementKey FFmpegHlsDirectOutputFormat => new("ffmpeg.hls_direct.output_format");
public static ConfigElementKey FFmpegDefaultMpegTsScript => new("ffmpeg.default_mpegts_script");
public static ConfigElementKey SearchIndexVersion => new("search_index.version"); public static ConfigElementKey SearchIndexVersion => new("search_index.version");
public static ConfigElementKey HDHRTunerCount => new("hdhr.tuner_count"); public static ConfigElementKey HDHRTunerCount => new("hdhr.tuner_count");
public static ConfigElementKey HDHRUUID => new("hdhr.uuid"); public static ConfigElementKey HDHRUUID => new("hdhr.uuid");

8
ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs

@ -826,7 +826,8 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
Channel channel, Channel channel,
string scheme, string scheme,
string host, string host,
string accessToken) string accessToken,
CancellationToken cancellationToken)
{ {
var resolution = new FrameSize(channel.FFmpegProfile.Resolution.Width, channel.FFmpegProfile.Resolution.Height); var resolution = new FrameSize(channel.FFmpegProfile.Resolution.Width, channel.FFmpegProfile.Resolution.Height);
@ -844,10 +845,13 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
} }
// TODO: save reports? // TODO: save reports?
string defaultScript = await _configElementRepository
.GetValue<string>(ConfigElementKey.FFmpegDefaultMpegTsScript, cancellationToken)
.IfNoneAsync("Default");
List<MpegTsScript> allScripts = _mpegTsScriptService.GetScripts(); List<MpegTsScript> allScripts = _mpegTsScriptService.GetScripts();
foreach (var script in allScripts.Where(s => string.Equals( foreach (var script in allScripts.Where(s => string.Equals(
s.Name, s.Name,
"Default", defaultScript,
StringComparison.OrdinalIgnoreCase))) StringComparison.OrdinalIgnoreCase)))
{ {
Option<Command> maybeCommand = await _mpegTsScriptService.Execute( Option<Command> maybeCommand = await _mpegTsScriptService.Execute(

3
ErsatzTV.Core/FFmpeg/MpegTsScript.cs

@ -4,6 +4,9 @@ namespace ErsatzTV.Core.FFmpeg;
public class MpegTsScript public class MpegTsScript
{ {
[YamlIgnore]
public string Id { get; set; }
[YamlMember(Alias = "name", ApplyNamingConventions = false)] [YamlMember(Alias = "name", ApplyNamingConventions = false)]
public string Name { get; set; } public string Name { get; set; }

3
ErsatzTV.Core/Interfaces/FFmpeg/IFFmpegProcessService.cs

@ -65,7 +65,8 @@ public interface IFFmpegProcessService
Channel channel, Channel channel,
string scheme, string scheme,
string host, string host,
string accessToken); string accessToken,
CancellationToken cancellationToken);
Task<Command> ResizeImage(string ffmpegPath, string inputFile, string outputFile, int height); Task<Command> ResizeImage(string ffmpegPath, string inputFile, string outputFile, int height);

1
ErsatzTV.Infrastructure/FFmpeg/MpegTsScriptService.cs

@ -31,6 +31,7 @@ public class MpegTsScriptService(
Option<MpegTsScript> maybeScript = FromYaml(await localFileSystem.ReadAllText(definition)); Option<MpegTsScript> maybeScript = FromYaml(await localFileSystem.ReadAllText(definition));
foreach (var script in maybeScript) foreach (var script in maybeScript)
{ {
script.Id = Path.GetFileName(folder);
Scripts[folder] = script; Scripts[folder] = script;
} }
} }

17
ErsatzTV/Pages/Settings/FFmpegSettings.razor

@ -1,10 +1,12 @@
@page "/settings/ffmpeg" @page "/settings/ffmpeg"
@using ErsatzTV.Application.Configuration
@using ErsatzTV.Application.FFmpegProfiles @using ErsatzTV.Application.FFmpegProfiles
@using ErsatzTV.Application.Filler @using ErsatzTV.Application.Filler
@using ErsatzTV.Application.MediaItems @using ErsatzTV.Application.MediaItems
@using ErsatzTV.Application.Resolutions @using ErsatzTV.Application.Resolutions
@using ErsatzTV.Application.Watermarks @using ErsatzTV.Application.Watermarks
@using ErsatzTV.Core.Domain.Filler @using ErsatzTV.Core.Domain.Filler
@using ErsatzTV.Core.FFmpeg
@using ErsatzTV.FFmpeg.OutputFormat @using ErsatzTV.FFmpeg.OutputFormat
@implements IDisposable @implements IDisposable
@inject IMediator Mediator @inject IMediator Mediator
@ -124,6 +126,17 @@
<MudSelectItem T="OutputFormatKind" Value="@OutputFormatKind.Mkv">MKV</MudSelectItem> <MudSelectItem T="OutputFormatKind" Value="@OutputFormatKind.Mkv">MKV</MudSelectItem>
</MudSelect> </MudSelect>
</MudStack> </MudStack>
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex">
<MudText>Default MPEG-TS Script</MudText>
</div>
<MudSelect @bind-Value="_ffmpegSettings.DefaultMpegTsScript" HelperText="The MPEG-TS script to use when streaming">
@foreach (MpegTsScript script in _mpegTsScripts)
{
<MudSelectItem Value="@script.Id">@script.Name</MudSelectItem>
}
</MudSelect>
</MudStack>
<MudText Typo="Typo.h5" Class="mb-2 mt-10">Custom Resolutions</MudText> <MudText Typo="Typo.h5" Class="mb-2 mt-10">Custom Resolutions</MudText>
<MudDivider Class="mb-6"/> <MudDivider Class="mb-6"/>
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="@(_ => AddCustomResolution())" StartIcon="@Icons.Material.Filled.Add"> <MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="@(_ => AddCustomResolution())" StartIcon="@Icons.Material.Filled.Add">
@ -162,6 +175,7 @@
private List<WatermarkViewModel> _watermarks = []; private List<WatermarkViewModel> _watermarks = [];
private List<FillerPresetViewModel> _fillerPresets = []; private List<FillerPresetViewModel> _fillerPresets = [];
private List<ResolutionViewModel> _customResolutions = []; private List<ResolutionViewModel> _customResolutions = [];
private readonly List<MpegTsScript> _mpegTsScripts = [];
public void Dispose() public void Dispose()
{ {
@ -187,6 +201,9 @@
_fillerPresets = await Mediator.Send(new GetAllFillerPresets(), token) _fillerPresets = await Mediator.Send(new GetAllFillerPresets(), token)
.Map(list => list.Filter(fp => fp.FillerKind == FillerKind.Fallback).ToList()); .Map(list => list.Filter(fp => fp.FillerKind == FillerKind.Fallback).ToList());
_mpegTsScripts.Clear();
_mpegTsScripts.AddRange(await Mediator.Send(new GetMpegTsScripts(), token));
await RefreshCustomResolutions(token); await RefreshCustomResolutions(token);
} }
catch (OperationCanceledException) catch (OperationCanceledException)

Loading…
Cancel
Save