feat: add transcode workspace and media browser
This commit is contained in:
@@ -7,6 +7,7 @@ using LiveRecorder.Application.Abstractions.Platforms;
|
||||
using LiveRecorder.Application.Abstractions.Recording;
|
||||
using LiveRecorder.Application.Abstractions.Settings;
|
||||
using LiveRecorder.Application.Abstractions.Storage;
|
||||
using LiveRecorder.Application.Models.Media;
|
||||
using LiveRecorder.Application.Services;
|
||||
using LiveRecorder.Domain.Entities;
|
||||
using LiveRecorder.Domain.Enums;
|
||||
@@ -409,6 +410,128 @@ public sealed partial class FfmpegService : IFfmpegService
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<TranscodeMediaFileResultDto> StartManualFinalizeFileAsync(
|
||||
string sourceFilePath,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(sourceFilePath))
|
||||
{
|
||||
return new TranscodeMediaFileResultDto
|
||||
{
|
||||
Success = false,
|
||||
Message = "The selected source file is empty."
|
||||
};
|
||||
}
|
||||
|
||||
var absoluteSourcePath = NormalizeAbsolutePath(sourceFilePath);
|
||||
if (!File.Exists(absoluteSourcePath))
|
||||
{
|
||||
return new TranscodeMediaFileResultDto
|
||||
{
|
||||
Success = false,
|
||||
Message = "The selected .ts file does not exist.",
|
||||
SourcePath = absoluteSourcePath
|
||||
};
|
||||
}
|
||||
|
||||
if (!absoluteSourcePath.EndsWith(".ts", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return new TranscodeMediaFileResultDto
|
||||
{
|
||||
Success = false,
|
||||
Message = "Only .ts files can be transcoded to MP4.",
|
||||
SourcePath = absoluteSourcePath
|
||||
};
|
||||
}
|
||||
|
||||
var absoluteTargetPath = Path.ChangeExtension(absoluteSourcePath, ".mp4");
|
||||
if (File.Exists(absoluteTargetPath))
|
||||
{
|
||||
return new TranscodeMediaFileResultDto
|
||||
{
|
||||
Success = false,
|
||||
Message = "A target MP4 file already exists for the selected .ts file.",
|
||||
SourcePath = absoluteSourcePath,
|
||||
OutputPath = absoluteTargetPath
|
||||
};
|
||||
}
|
||||
|
||||
using var settingsScope = _serviceScopeFactory.CreateScope();
|
||||
var settingsService = settingsScope.ServiceProvider.GetRequiredService<ISystemSettingsService>();
|
||||
var settings = await settingsService.GetAsync(cancellationToken);
|
||||
|
||||
var syntheticSessionId = Guid.NewGuid();
|
||||
var syntheticTaskId = Guid.NewGuid();
|
||||
SetPostProcessState(
|
||||
syntheticSessionId,
|
||||
syntheticTaskId,
|
||||
"Queued",
|
||||
null,
|
||||
$"Manual file transcode queued for {Path.GetFileName(absoluteSourcePath)}");
|
||||
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await TryFinalizeMp4Async(
|
||||
settings.FfmpegPath,
|
||||
settings.MaxConcurrentFfmpegTranscodeTasks,
|
||||
settings.Mp4FinalizeTimeoutMinutes,
|
||||
syntheticSessionId,
|
||||
syntheticTaskId,
|
||||
absoluteSourcePath,
|
||||
absoluteTargetPath,
|
||||
expectedDurationSeconds: null,
|
||||
CancellationToken.None);
|
||||
|
||||
using var scope = _serviceScopeFactory.CreateScope();
|
||||
var logService = scope.ServiceProvider.GetRequiredService<ISystemLogService>();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(result.ErrorMessage))
|
||||
{
|
||||
await logService.WriteAsync(
|
||||
Domain.Enums.SystemLogLevel.Info,
|
||||
"FFmpeg",
|
||||
"Manual file transcode completed.",
|
||||
$"source={absoluteSourcePath}; output={result.OutputPath}",
|
||||
cancellationToken: CancellationToken.None);
|
||||
}
|
||||
else
|
||||
{
|
||||
await logService.WriteAsync(
|
||||
Domain.Enums.SystemLogLevel.Warning,
|
||||
"FFmpeg",
|
||||
"Manual file transcode failed.",
|
||||
$"source={absoluteSourcePath}; output={result.OutputPath}; error={result.ErrorMessage}",
|
||||
cancellationToken: CancellationToken.None);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
using var scope = _serviceScopeFactory.CreateScope();
|
||||
var logService = scope.ServiceProvider.GetRequiredService<ISystemLogService>();
|
||||
await logService.WriteAsync(
|
||||
Domain.Enums.SystemLogLevel.Error,
|
||||
"FFmpeg",
|
||||
"Manual file transcode crashed.",
|
||||
$"source={absoluteSourcePath}; output={absoluteTargetPath}; error={ex}",
|
||||
cancellationToken: CancellationToken.None);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ClearPostProcessState(syntheticTaskId);
|
||||
}
|
||||
}, CancellationToken.None);
|
||||
|
||||
return new TranscodeMediaFileResultDto
|
||||
{
|
||||
Success = true,
|
||||
Message = "Manual file transcode started. Refresh later to verify the output file.",
|
||||
SourcePath = absoluteSourcePath,
|
||||
OutputPath = absoluteTargetPath
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<int> ResumePausedFinalizationsAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var scope = _serviceScopeFactory.CreateScope();
|
||||
|
||||
Reference in New Issue
Block a user