private R <FfmpegInstance, string> StartFfmpegProcess(string url, TimeSpan?offsetOpt) { StopFfmpegProcess(); Log.Trace("Start request {0}", url); string arguments; var offset = offsetOpt ?? TimeSpan.Zero; if (offset > TimeSpan.Zero) { var seek = string.Format(CultureInfo.InvariantCulture, @"-ss {0:hh\:mm\:ss\.fff}", offset); arguments = string.Concat(seek, " ", PreLinkConf, url, PostLinkConf, " ", seek); } else { arguments = string.Concat(PreLinkConf, url, PostLinkConf); } var newInstance = new FfmpegInstance( url, new PreciseAudioTimer(this) { SongPositionOffset = offset, }); return(StartFfmpegProcessInternal(newInstance, arguments)); }
private (bool ret, bool trigger) OnReadEmpty(FfmpegInstance instance) { if (instance.FfmpegProcess.HasExitedSafe() && !instance.HasTriedToReconnect) { var expectedStopLength = GetCurrentSongLength(); Log.Trace("Expected song length {0}", expectedStopLength); if (expectedStopLength != TimeSpan.Zero) { var actualStopPosition = instance.AudioTimer.SongPosition; Log.Trace("Actual song position {0}", actualStopPosition); if (actualStopPosition + retryOnDropBeforeEnd < expectedStopLength) { Log.Debug("Connection to song lost, retrying at {0}", actualStopPosition); instance.HasTriedToReconnect = true; var newInstance = SetPosition(actualStopPosition); if (newInstance.Ok) { newInstance.Value.HasTriedToReconnect = true; return(true, false); } else { Log.Debug("Retry failed {0}", newInstance.Error); return(false, true); } } } } return(false, false); }
public HttpResponseMessage StartRecording([FromUri] VideoStartRequest startRequest) { if (startRequest == null || string.IsNullOrEmpty(startRequest.Source)) { return (GenerateJsonResponse( new { Started = false, StartRequest = startRequest, Error = "No source was specified. A source is required." })); } if (startRequest.MaxDurationSecs == 0) { startRequest.MaxDurationSecs = (5 * 60); } var existingKeys = FfmpegInstance.RunningInstances.Where(i => i.StartRequest.RecordingKey == startRequest.RecordingKey); foreach (var existingInstance in existingKeys) { existingInstance.Stop(null); } FfmpegInstance newInstance = new FfmpegInstance(); newInstance.Start(startRequest); Console.WriteLine("Starting recording: " + Newtonsoft.Json.JsonConvert.SerializeObject(startRequest)); return(GenerateJsonResponse(new { Started = true, StartRequest = startRequest })); }
public async Task <bool> Run(string inputFilePath, string outputFilePath, string animFramesDirPath, double framerate) { var videoProcess = new AnimationTaskCompileProcessVideo(FfmpegPath, new FfmpegCompatibilityOptions { TargetCompatibility = FfmpegCompatibilityOptions.OutputCompatibilityType.HighQualityLowCompatibility, OutputFramerate = (int)framerate }); string tmpOutputFilePath = $"{Path.GetDirectoryName(outputFilePath)}/{Path.GetFileName(outputFilePath)}.mp4"; if (File.Exists(tmpOutputFilePath)) { File.Delete(tmpOutputFilePath); } if (!await videoProcess.Run(inputFilePath, tmpOutputFilePath, animFramesDirPath, framerate)) { return(false); } string outputPaletteFile = $"{Path.GetDirectoryName(outputFilePath)}\\{Path.GetFileNameWithoutExtension(outputFilePath)}_palette.png"; var paletteFfmpeg = new FfmpegInstance(FfmpegPath); paletteFfmpeg.Options = new FfmpegRawOptions { RawParams = $"-i \"{tmpOutputFilePath}\" -vf palettegen \"{outputPaletteFile}\"" }; var paletteResult = await paletteFfmpeg.Start(null, null); if (paletteResult.ExitCode != 0) { Logger.Error("ffmpeg failed while generating a GIF palette for {InputFilePath}, where ffmpeg output {FfmpegConsoleOutputStream}", inputFilePath, string.Join("\n", paletteResult.OutputStreamData)); return(false); } var videoFfmpeg = new FfmpegInstance(FfmpegPath); videoFfmpeg.Options = new FfmpegRawOptions { RawParams = $"-i \"{tmpOutputFilePath}\" -i \"{outputPaletteFile}\" -lavfi \"paletteuse\" \"{outputFilePath}\"" }; var ffmpegResult = await videoFfmpeg.Start(null, outputFilePath); if (ffmpegResult.ExitCode != 0) { Logger.Error("ffmpeg failed while converting a temporary mp4 to gif for {InputFilePath}, where ffmpeg output {FfmpegConsoleOutputStream}", inputFilePath, string.Join("\n", ffmpegResult.OutputStreamData)); File.Delete(outputPaletteFile); return(false); } File.Delete(outputPaletteFile); File.Delete(tmpOutputFilePath); return(File.Exists(outputFilePath)); }
private async Task <R <FfmpegInstance, string> > StartFfmpegProcessIcy(string url) { StopFfmpegProcess(); Log.Trace("Start icy-stream request {0}", url); try { var response = await WebWrapper .Request(url) .WithHeader("Icy-MetaData", "1") .UnsafeResponse(); if (!int.TryParse(response.Headers.GetSingle("icy-metaint"), out var metaint)) { response.Dispose(); return("Invalid icy stream tags"); } var stream = await response.Content.ReadAsStreamAsync(); var newInstance = new FfmpegInstance( url, new PreciseAudioTimer(this), stream, metaint) { OnMetaUpdated = e => OnSongUpdated?.Invoke(this, e) }; new Thread(() => newInstance.ReadStreamLoop(id)) { Name = $"IcyStreamReader[{id}]", }.Start(); return(StartFfmpegProcessInternal(newInstance, LinkConfIcy)); } catch (Exception ex) { var error = $"Unable to create icy-stream ({ex.Message})"; Log.Warn(ex, error); return(error); } }
private R <FfmpegInstance, string> StartFfmpegProcessInternal(FfmpegInstance instance, string arguments) { try { instance.FfmpegProcess.StartInfo = new ProcessStartInfo { FileName = config.Path.Value, Arguments = arguments, RedirectStandardOutput = true, RedirectStandardInput = true, RedirectStandardError = true, UseShellExecute = false, CreateNoWindow = true, }; instance.FfmpegProcess.EnableRaisingEvents = true; Log.Debug("Starting ffmpeg with {0}", arguments); instance.FfmpegProcess.ErrorDataReceived += instance.FfmpegProcess_ErrorDataReceived; instance.FfmpegProcess.Start(); instance.FfmpegProcess.BeginErrorReadLine(); instance.AudioTimer.Start(); var oldInstance = Interlocked.Exchange(ref ffmpegInstance, instance); oldInstance?.Close(); return(instance); } catch (Exception ex) { var error = ex is Win32Exception ? $"Ffmpeg could not be found ({ex.Message})" : $"Unable to create stream ({ex.Message})"; Log.Error(ex, error); instance.Close(); StopFfmpegProcess(); return(error); } }
private (bool ret, bool trigger) OnReadEmptyIcy(FfmpegInstance instance) { AssertNotMainScheduler(); if (instance.FfmpegProcess.HasExitedSafe() && !instance.HasTriedToReconnect) { Log.Debug("Connection to stream lost, retrying..."); instance.HasTriedToReconnect = true; var newInstance = StartFfmpegProcessIcy(instance.ReconnectUrl).Result; if (newInstance.Ok) { newInstance.Value.HasTriedToReconnect = true; return(true, false); } else { Log.Debug("Retry failed {0}", newInstance.Error); return(false, true); } } return(false, false); }
public async Task <bool> Run(string inputFilePath, string outputFilePath, string animFramesDirPath, double framerate) { Logger.Verbose("Running ffmpeg to combine frames from {AnimFramesDir} into {OutputFile}", animFramesDirPath, outputFilePath); var ffmpegInstance = new FfmpegInstance(FfmpegPath); FfmpegOptions.OutputFramerate = (int)framerate; ffmpegInstance.Options = FfmpegOptions; string inputPathFormat = Path.Combine(animFramesDirPath, $"{Path.GetFileNameWithoutExtension(inputFilePath)}_%04d.png"); var ffmpegResult = await ffmpegInstance.Start(inputPathFormat, outputFilePath, () => this.Cancel); Logger.Verbose("Ran ffmpeg with parameters: {FfmpegParameters}", ffmpegResult.Args); if (ffmpegResult.ExitCode != 0 && !ffmpegResult.WasTerminated) { Logger.Error("Error occurred while running ffmpeg {@FfmpegOutput}", ffmpegResult.OutputStreamData); return(false); } return(true); }
public async Task <AnimationExtractionResult> ExtractFrames(string animationPath, string outputFolderPath, Func <bool> shouldTerminateDelegate = null) { string animationName = Path.GetFileNameWithoutExtension(animationPath); string outputFramesFormat = Path.Combine(outputFolderPath, animationName) + "_%04d.png"; double framerate = 0; using (MagickImageCollection gifFrames = new MagickImageCollection(animationPath)) { foreach (var frame in gifFrames) { if (shouldTerminateDelegate()) { return(null); } framerate += frame.AnimationDelay / 100.0 / gifFrames.Count; } } framerate = 1.0 / framerate; var ffmpegInstance = new FfmpegInstance(FfmpegPath); ffmpegInstance.Options = new FfmpegRawOptions { RawParams = $"-i \"{animationPath}\" -vf fps={framerate} \"{outputFramesFormat}\"" }; var ffmpegResult = await ffmpegInstance.Start(null, null, shouldTerminateDelegate); if (shouldTerminateDelegate()) { return(null); } if (ffmpegResult.ExitCode != 0) { Logger.Error("Failed to extract GIF frames for {InputAnimationPath} with ffmpeg, ffmpeg output was {@FfmpegOutput}", animationPath, ffmpegResult.OutputStreamData); return(null); } var animationFiles = Directory.EnumerateFiles(outputFolderPath).Where(f => Path.GetFileName(f).StartsWith(animationName + "_")).ToList(); List <string> outputFiles = new List <string>(); for (int i = 1; i <= animationFiles.Count; i++) { string originalIdxString = i.ToString(); originalIdxString = new string('0', 4 - originalIdxString.Length) + originalIdxString; string correctedIdxString = (i - 1).ToString(); correctedIdxString = new string('0', 4 - correctedIdxString.Length) + correctedIdxString; string originalFile = $"{outputFolderPath}\\{animationName}_{originalIdxString}.png"; string correctedFile = $"{outputFolderPath}\\{animationName}_{correctedIdxString}.png"; File.Move(originalFile, correctedFile); outputFiles.Add(correctedFile); } if (DenoiseAmount > 0) { Logger.Verbose("Frame extraction complete, denoising by {DenoiseAmount}x", DenoiseAmount); foreach (var frame in outputFiles) { if (shouldTerminateDelegate()) { return(null); } using (MagickImage img = new MagickImage(frame)) { for (int i = 0; i < DenoiseAmount; i++) { img.Despeckle(); } img.Write(frame); } } } return(new AnimationExtractionResult { Fps = framerate, ExtractedFiles = outputFiles }); }
public async Task <AnimationExtractionResult> ExtractFrames(string animationPath, string outputFolderPath, Func <bool> shouldTerminateDelegate) { string animationName = Path.GetFileNameWithoutExtension(animationPath); var ffmpegInstance = new FfmpegInstance(FfmpegPath); ffmpegInstance.Options = new FfmpegRawOptions { RawParams = $"-i \"{animationPath}\" \"{outputFolderPath}\\{animationName}_%04d.png\"" }; var runInfo = await ffmpegInstance.Start(null, null, shouldTerminateDelegate); if (shouldTerminateDelegate()) { return(null); } if (runInfo.ExitCode != 0) { Logger.Error("Running video frame extraction on {VideoPath} failed, where ffmpeg output: {FfmpegConsoleOutput}", animationPath, string.Join("\n", runInfo.OutputStreamData)); return(null); } var ffmpegOutput = string.Join("\n", runInfo.OutputStreamData); var fpsMatcher = new Regex(@"(\d+) fps"); var fpsMatch = fpsMatcher.Match(ffmpegOutput); var fpsString = fpsMatch.Groups[1].Value; double fps = double.Parse(fpsString); var durationMatcher = new Regex(@"Duration\: (\d+\:\d+\:\d+\.\d+)"); var durationString = durationMatcher.Match(ffmpegOutput).Captures[0].Value; var durationParts = durationString.Split(new[] { ':', '.' }); TimeSpan duration = new TimeSpan(0, int.Parse(durationParts[1]), int.Parse(durationParts[2]), int.Parse(durationParts[3]), int.Parse(durationParts[4])); int numFrames = (int)Math.Round(duration.TotalSeconds * fps); List <string> outputFiles = new List <string>(); for (int i = 1; i <= numFrames; i++) { string originalIdxString = i.ToString(); originalIdxString = new string('0', 4 - originalIdxString.Length) + originalIdxString; string correctedIdxString = (i - 1).ToString(); correctedIdxString = new string('0', 4 - correctedIdxString.Length) + correctedIdxString; string originalFile = $"{outputFolderPath}\\{animationName}_{originalIdxString}.png"; string correctedFile = $"{outputFolderPath}\\{animationName}_{correctedIdxString}.png"; File.Move(originalFile, correctedFile); outputFiles.Add(correctedFile); } return(new AnimationExtractionResult { Fps = fps, ExtractedFiles = outputFiles }); }