135 lines
4.8 KiB
C#
135 lines
4.8 KiB
C#
using LiveRecorder.Application.Abstractions.Persistence;
|
|
using LiveRecorder.Application.Abstractions.Recording;
|
|
using LiveRecorder.Application.Models.RecordTasks;
|
|
using LiveRecorder.Domain.Entities;
|
|
using LiveRecorder.Domain.Enums;
|
|
|
|
namespace LiveRecorder.Application.Services;
|
|
|
|
public sealed class TranscodeTaskService
|
|
{
|
|
private readonly IRecordTaskRepository _recordTaskRepository;
|
|
private readonly IFfmpegService _ffmpegService;
|
|
|
|
public TranscodeTaskService(
|
|
IRecordTaskRepository recordTaskRepository,
|
|
IFfmpegService ffmpegService)
|
|
{
|
|
_recordTaskRepository = recordTaskRepository;
|
|
_ffmpegService = ffmpegService;
|
|
}
|
|
|
|
public async Task<IReadOnlyList<TranscodeTaskItemDto>> ListAsync(CancellationToken cancellationToken = default)
|
|
{
|
|
var tasks = await _recordTaskRepository.ListAsync(null, cancellationToken);
|
|
if (tasks.Count == 0)
|
|
{
|
|
return [];
|
|
}
|
|
|
|
var runtimeStates = _ffmpegService.GetTaskRuntimeStates(tasks.Select(static item => item.Id).ToArray());
|
|
|
|
return tasks
|
|
.Select(task =>
|
|
{
|
|
runtimeStates.TryGetValue(task.Id, out var runtimeState);
|
|
var mappedTask = RecordModelMapper.MapTask(task, runtimeState);
|
|
var sourceFilePath = ResolveManualTranscodeSourcePath(task);
|
|
var canManualTranscode = CanManuallyTranscode(task, runtimeState, sourceFilePath);
|
|
|
|
return new TranscodeTaskItemDto
|
|
{
|
|
Task = mappedTask,
|
|
Result = task.Result is null ? null : RecordModelMapper.MapResult(task.Result),
|
|
SourceFilePath = sourceFilePath,
|
|
CanManualTranscode = canManualTranscode
|
|
};
|
|
})
|
|
.Where(static item => ShouldInclude(item))
|
|
.OrderByDescending(static item => item.Task.CreatedAt)
|
|
.ToArray();
|
|
}
|
|
|
|
private static bool ShouldInclude(TranscodeTaskItemDto item)
|
|
{
|
|
if (!string.IsNullOrWhiteSpace(item.Task.PostProcessStage))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (item.CanManualTranscode)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return item.Task.OutputFormat == RecordOutputFormat.Mp4 &&
|
|
item.Task.Status == RecordTaskStatus.Processing;
|
|
}
|
|
|
|
private static bool CanManuallyTranscode(
|
|
RecordTask recordTask,
|
|
RecordTaskRuntimeState? runtimeState,
|
|
string? sourceFilePath)
|
|
{
|
|
if (recordTask.OutputFormat != RecordOutputFormat.Mp4 ||
|
|
runtimeState is not null ||
|
|
IsActiveStatus(recordTask.Status))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!string.IsNullOrWhiteSpace(sourceFilePath) && File.Exists(sourceFilePath))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
var resultPath = recordTask.Result?.FilePath?.ToLowerInvariant() ?? string.Empty;
|
|
var errorText = $"{recordTask.ErrorMessage ?? string.Empty} {recordTask.Result?.ErrorMessage ?? string.Empty}".ToLowerInvariant();
|
|
return resultPath.EndsWith(".ts", StringComparison.OrdinalIgnoreCase) ||
|
|
errorText.Contains("finaliz", StringComparison.Ordinal) ||
|
|
errorText.Contains("intermediate ts", StringComparison.Ordinal);
|
|
}
|
|
|
|
private static string? ResolveManualTranscodeSourcePath(RecordTask recordTask)
|
|
{
|
|
if (!string.IsNullOrWhiteSpace(recordTask.Result?.FilePath) &&
|
|
recordTask.Result.FilePath.EndsWith(".ts", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return recordTask.Result.FilePath;
|
|
}
|
|
|
|
if (!string.IsNullOrWhiteSpace(recordTask.OutputFilePath))
|
|
{
|
|
var normalizedOutput = NormalizeAbsolutePath(recordTask.OutputFilePath);
|
|
if (normalizedOutput.EndsWith(".ts", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return normalizedOutput;
|
|
}
|
|
|
|
var candidate = Path.ChangeExtension(normalizedOutput, ".ts");
|
|
if (File.Exists(candidate))
|
|
{
|
|
return candidate;
|
|
}
|
|
|
|
var singleFileCandidate = Path.Combine(
|
|
Path.GetDirectoryName(normalizedOutput) ?? string.Empty,
|
|
$"{Path.GetFileNameWithoutExtension(normalizedOutput)}.recording.ts");
|
|
if (File.Exists(singleFileCandidate))
|
|
{
|
|
return singleFileCandidate;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private static string NormalizeAbsolutePath(string path) =>
|
|
Path.IsPathRooted(path)
|
|
? path
|
|
: Path.GetFullPath(path, AppContext.BaseDirectory);
|
|
|
|
private static bool IsActiveStatus(RecordTaskStatus status) =>
|
|
status is RecordTaskStatus.Starting or RecordTaskStatus.Running or RecordTaskStatus.Stopping or RecordTaskStatus.Processing;
|
|
}
|