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)); }
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 }); }