mirror of https://github.com/ErsatzTV/ErsatzTV.git
				
				
			
				 17 changed files with 214 additions and 40 deletions
			
			
		@ -0,0 +1,12 @@
				@@ -0,0 +1,12 @@
					 | 
				
			||||
namespace ErsatzTV.Application.Streaming; | 
				
			||||
 | 
				
			||||
public record PtsAndDuration(long Pts, long Duration) | 
				
			||||
{ | 
				
			||||
    public static PtsAndDuration From(string ffprobeLine) | 
				
			||||
    { | 
				
			||||
        string[] split = ffprobeLine.Split("|"); | 
				
			||||
        var left = long.Parse(split[0]); | 
				
			||||
        var right = long.Parse(split[1]); | 
				
			||||
        return new PtsAndDuration(left, right); | 
				
			||||
    } | 
				
			||||
} | 
				
			||||
@ -0,0 +1,7 @@
				@@ -0,0 +1,7 @@
					 | 
				
			||||
using ErsatzTV.Core; | 
				
			||||
using LanguageExt; | 
				
			||||
using MediatR; | 
				
			||||
 | 
				
			||||
namespace ErsatzTV.Application.Streaming.Queries; | 
				
			||||
 | 
				
			||||
public record GetLastPtsDuration(string FileName) : IRequest<Either<BaseError, PtsAndDuration>>; | 
				
			||||
@ -0,0 +1,87 @@
				@@ -0,0 +1,87 @@
					 | 
				
			||||
using System.Diagnostics; | 
				
			||||
using System.IO; | 
				
			||||
using System.Linq; | 
				
			||||
using System.Text; | 
				
			||||
using System.Threading; | 
				
			||||
using System.Threading.Tasks; | 
				
			||||
using ErsatzTV.Core; | 
				
			||||
using ErsatzTV.Core.Domain; | 
				
			||||
using ErsatzTV.Core.Interfaces.Repositories; | 
				
			||||
using LanguageExt; | 
				
			||||
using MediatR; | 
				
			||||
 | 
				
			||||
namespace ErsatzTV.Application.Streaming.Queries; | 
				
			||||
 | 
				
			||||
public class GetLastPtsDurationHandler : IRequestHandler<GetLastPtsDuration, Either<BaseError, PtsAndDuration>> | 
				
			||||
{ | 
				
			||||
    private readonly IConfigElementRepository _configElementRepository; | 
				
			||||
 | 
				
			||||
    public GetLastPtsDurationHandler(IConfigElementRepository configElementRepository) | 
				
			||||
    { | 
				
			||||
        _configElementRepository = configElementRepository; | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    public async Task<Either<BaseError, PtsAndDuration>> Handle( | 
				
			||||
        GetLastPtsDuration request, | 
				
			||||
        CancellationToken cancellationToken) | 
				
			||||
    { | 
				
			||||
        Validation<BaseError, RequestParameters> validation = await Validate(request); | 
				
			||||
        return await validation.Match( | 
				
			||||
            Handle, | 
				
			||||
            error => Task.FromResult<Either<BaseError, PtsAndDuration>>(error.Join())); | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    private async Task<Validation<BaseError, RequestParameters>> Validate(GetLastPtsDuration request) => | 
				
			||||
        await ValidateFFprobePath() | 
				
			||||
            .MapT( | 
				
			||||
                ffprobePath => new RequestParameters( | 
				
			||||
                    request.FileName, | 
				
			||||
                    ffprobePath)); | 
				
			||||
 | 
				
			||||
    private async Task<Either<BaseError, PtsAndDuration>> Handle(RequestParameters parameters) | 
				
			||||
    { | 
				
			||||
        var startInfo = new ProcessStartInfo | 
				
			||||
        { | 
				
			||||
            FileName = parameters.FFprobePath, | 
				
			||||
            RedirectStandardOutput = true, | 
				
			||||
            RedirectStandardError = true, | 
				
			||||
            UseShellExecute = false, | 
				
			||||
            StandardOutputEncoding = Encoding.UTF8, | 
				
			||||
            StandardErrorEncoding = Encoding.UTF8 | 
				
			||||
        }; | 
				
			||||
 | 
				
			||||
        startInfo.ArgumentList.Add("-v"); | 
				
			||||
        startInfo.ArgumentList.Add("0"); | 
				
			||||
        startInfo.ArgumentList.Add("-show_entries"); | 
				
			||||
        startInfo.ArgumentList.Add("packet=pts,duration"); | 
				
			||||
        startInfo.ArgumentList.Add("-of"); | 
				
			||||
        startInfo.ArgumentList.Add("compact=p=0:nk=1"); | 
				
			||||
        startInfo.ArgumentList.Add("-read_intervals"); | 
				
			||||
        startInfo.ArgumentList.Add("-999999"); | 
				
			||||
        startInfo.ArgumentList.Add(parameters.FileName); | 
				
			||||
 | 
				
			||||
        var probe = new Process | 
				
			||||
        { | 
				
			||||
            StartInfo = startInfo | 
				
			||||
        }; | 
				
			||||
 | 
				
			||||
        probe.Start(); | 
				
			||||
        return await probe.StandardOutput.ReadToEndAsync().MapAsync<string, Either<BaseError, PtsAndDuration>>( | 
				
			||||
            async output => | 
				
			||||
            { | 
				
			||||
                await probe.WaitForExitAsync(); | 
				
			||||
                return probe.ExitCode == 0 | 
				
			||||
                    ? PtsAndDuration.From(output.Split("\n").Filter(s => !string.IsNullOrWhiteSpace(s)).Last().Trim()) | 
				
			||||
                    : BaseError.New($"FFprobe at {parameters.FFprobePath} exited with code {probe.ExitCode}"); | 
				
			||||
            }); | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    private Task<Validation<BaseError, string>> ValidateFFprobePath() => | 
				
			||||
        _configElementRepository.GetValue<string>(ConfigElementKey.FFprobePath) | 
				
			||||
            .FilterT(File.Exists) | 
				
			||||
            .Map( | 
				
			||||
                ffprobePath => | 
				
			||||
                    ffprobePath.ToValidation<BaseError>("FFprobe path does not exist on the file system")); | 
				
			||||
 | 
				
			||||
    private record RequestParameters(string FileName, string FFprobePath); | 
				
			||||
} | 
				
			||||
					Loading…
					
					
				
		Reference in new issue