Пример #1
0
        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));
        }
Пример #2
0
 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);
 }
Пример #3
0
        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));
        }
Пример #5
0
        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);
            }
        }
Пример #6
0
        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);
            }
        }
Пример #7
0
        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);
        }
Пример #8
0
        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
            });
        }
Пример #10
0
        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
            });
        }