* remove readalltext * remove unused method * remove fileexists * remove folderexists * remove readalllines * remove fake local file system * show playlist name in playout build errors * add basic sequential schedule validator tests * work around sequential schedule validation limit
139 lines
4.7 KiB
C#
139 lines
4.7 KiB
C#
using System.Collections.Concurrent;
|
|
using System.IO.Abstractions;
|
|
using System.Runtime.InteropServices;
|
|
using CliWrap;
|
|
using ErsatzTV.Core;
|
|
using ErsatzTV.Core.Domain;
|
|
using ErsatzTV.Core.FFmpeg;
|
|
using ErsatzTV.Core.Interfaces.FFmpeg;
|
|
using ErsatzTV.Core.Interfaces.Metadata;
|
|
using Microsoft.Extensions.Logging;
|
|
using Scriban;
|
|
using Scriban.Runtime;
|
|
using YamlDotNet.Serialization;
|
|
using YamlDotNet.Serialization.NamingConventions;
|
|
|
|
namespace ErsatzTV.Infrastructure.FFmpeg;
|
|
|
|
public class MpegTsScriptService(
|
|
IFileSystem fileSystem,
|
|
ILocalFileSystem localFileSystem,
|
|
ITempFilePool tempFilePool,
|
|
ILogger<MpegTsScriptService> logger) : IMpegTsScriptService
|
|
{
|
|
private static readonly ConcurrentDictionary<string, MpegTsScript> Scripts = new();
|
|
|
|
public async Task RefreshScripts()
|
|
{
|
|
foreach (string folder in localFileSystem.ListSubdirectories(FileSystemLayout.MpegTsScriptsFolder))
|
|
{
|
|
string definition = Path.Combine(folder, "mpegts.yml");
|
|
if (!Scripts.ContainsKey(folder) && fileSystem.File.Exists(definition))
|
|
{
|
|
Option<MpegTsScript> maybeScript = FromYaml(await fileSystem.File.ReadAllTextAsync(definition));
|
|
foreach (var script in maybeScript)
|
|
{
|
|
script.Id = Path.GetFileName(folder);
|
|
Scripts[folder] = script;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public List<MpegTsScript> GetScripts() => Scripts.Values.ToList();
|
|
|
|
public async Task<Option<Command>> Execute(MpegTsScript script, Channel channel, string hlsUrl, string ffmpegPath)
|
|
{
|
|
string scriptFolder = string.Empty;
|
|
foreach (KeyValuePair<string, MpegTsScript> kvp in Scripts.Where(kvp => kvp.Value == script))
|
|
{
|
|
scriptFolder = kvp.Key;
|
|
}
|
|
|
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
|
{
|
|
string scriptInput = Path.Combine(scriptFolder, script.WindowsScript);
|
|
if (File.Exists(scriptInput))
|
|
{
|
|
Option<string> maybeScript = await GetTemplatedScript(
|
|
scriptInput,
|
|
hlsUrl,
|
|
channel.Name,
|
|
ffmpegPath);
|
|
foreach (string finalScript in maybeScript)
|
|
{
|
|
var fileName = $"{tempFilePool.GetNextTempFile(TempFileCategory.MpegTsScript)}.bat";
|
|
await File.WriteAllTextAsync(fileName, finalScript);
|
|
return Cli.Wrap(fileName);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
string scriptInput = Path.Combine(scriptFolder, script.LinuxScript);
|
|
if (File.Exists(scriptInput))
|
|
{
|
|
Option<string> maybeScript = await GetTemplatedScript(
|
|
scriptInput,
|
|
hlsUrl,
|
|
channel.Name,
|
|
ffmpegPath);
|
|
foreach (string finalScript in maybeScript)
|
|
{
|
|
string fileName = tempFilePool.GetNextTempFile(TempFileCategory.MpegTsScript);
|
|
await File.WriteAllTextAsync(fileName, finalScript);
|
|
return Cli.Wrap("bash").WithArguments([fileName]);
|
|
}
|
|
}
|
|
}
|
|
|
|
return [];
|
|
}
|
|
|
|
private async Task<Option<string>> GetTemplatedScript(
|
|
string fileName,
|
|
string hlsUrl,
|
|
string channelName,
|
|
string ffmpegPath)
|
|
{
|
|
string script = await fileSystem.File.ReadAllTextAsync(fileName);
|
|
try
|
|
{
|
|
var data = new Dictionary<string, string>
|
|
{
|
|
["HlsUrl"] = hlsUrl,
|
|
["ChannelName"] = channelName,
|
|
["FFmpegPath"] = ffmpegPath
|
|
};
|
|
|
|
var scriptObject = new ScriptObject();
|
|
scriptObject.Import(data, renamer: member => member.Name);
|
|
var context = new TemplateContext { MemberRenamer = member => member.Name };
|
|
context.PushGlobal(scriptObject);
|
|
return await Template.Parse(script).RenderAsync(context);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
logger.LogWarning(ex, "Failed to render mpegts script as scriban template");
|
|
return Option<string>.None;
|
|
}
|
|
}
|
|
|
|
private Option<MpegTsScript> FromYaml(string yaml)
|
|
{
|
|
try
|
|
{
|
|
IDeserializer deserializer = new DeserializerBuilder()
|
|
.WithNamingConvention(CamelCaseNamingConvention.Instance)
|
|
.Build();
|
|
|
|
return deserializer.Deserialize<MpegTsScript>(yaml);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
logger.LogWarning(ex, "Failed to load mpegts script YAML definition");
|
|
return Option<MpegTsScript>.None;
|
|
}
|
|
}
|
|
}
|