Пример #1
0
 /// <summary>
 /// Create a typical instance of DashEncodeResult.
 /// </summary>
 /// <param name="mpdPath">The exact path to the mpd file.</param>
 /// <param name="mpdContent">The exact mpd content deserialized from XML.</param>
 /// <param name="ffmpegCommand">The generated ffmpeg command used when </param>
 /// <param name="inputMetadata">Metadata about the DASHed input file.</param>
 public DashEncodeResult(string mpdPath, MPD mpdContent, FFmpegCommand ffmpegCommand, MediaMetadata inputMetadata)
 {
     DashFileContent = mpdContent;
     DashFilePath    = mpdPath;
     FFmpegCommand   = ffmpegCommand;
     InputMetadata   = inputMetadata;
 }
Пример #2
0
        /// <summary>
        /// Converts the input file into an MPEG DASH representations.
        /// This includes multiple bitrates, subtitle tracks, audio tracks, and an MPD manifest.
        /// </summary>
        /// <param name="config">A configuration specifying how DASHing should be performed.</param>
        /// <param name="probedInputData">The output from running <see cref="ProbeFile">ProbeFile</see> on the input file.</param>
        /// <param name="progress">Gives progress through the ffmpeg process, which takes the longest of all the parts of DASHing.</param>
        /// <param name="cancel">Allows the process to be ended part way through.</param>
        /// <returns>A value containing metadata about the artifacts of the DASHing process.</returns>
        /// <exception cref="ArgumentNullException">The probe data parameter is null.</exception>
        /// <exception cref="FFMpegFailedException">The ffmpeg process returned an error code other than 0 or threw an inner exception such as <see cref="OperationCanceledException"/>.</exception>
        /// <exception cref="Mp4boxFailedException">The MP4Box process returned an error code other than 0, threw an inner exception such as <see cref="OperationCanceledException"/>, or did not generate an MPD file.</exception>
        /// <exception cref="DashManifestNotCreatedException">Everything seemed to go okay until the final step with MP4Box, where an MPD file was not found.</exception>
        /// <exception cref="OperationCanceledException">The cancellation token was triggered.</exception>
        public DashEncodeResult GenerateDash(DashConfig config, MediaMetadata probedInputData, IProgress <double> progress = null, CancellationToken cancel = default)
        {
            cancel.ThrowIfCancellationRequested();

            if (probedInputData == null)
            {
                throw new ArgumentNullException(nameof(probedInputData), "Probe data cannot be null. Get this parameter from calling ProbeFile.");
            }

            //Field declarations
            IQuality compareQuality;
            bool     enableStreamCopy = false;

            config.Qualities = QualityCrusher.CrushQualities(config.Qualities, probedInputData.KBitrate, config.QualityCrushTolerance);
            compareQuality   = config.Qualities.First();

            if (config.EnableStreamCopying && compareQuality.Bitrate == 0)
            {
                enableStreamCopy = Copyable264Infer.DetermineCopyCanBeDone(compareQuality.PixelFormat, compareQuality.Level, compareQuality.Profile.ToString(), probedInputData.VideoStreams);
            }

            // Set the framerate interval to match input if user has not already set
            if (config.Framerate <= 0)
            {
                config.Framerate = (int)Math.Round(probedInputData.Framerate);
            }

            // Set the keyframe interval to match input if user has not already set
            if (config.KeyframeInterval <= 0)
            {
                config.KeyframeInterval = config.Framerate * 3;
            }

            cancel.ThrowIfCancellationRequested();

            FFmpegCommand ffmpegCommand = EncodeVideo(config, probedInputData, progress, cancel);

            Mp4BoxCommand mp4BoxCommand = GenerateDashManifest(config, ffmpegCommand.VideoCommands, ffmpegCommand.AudioCommands, cancel, ffmpegCommand);

            if (File.Exists(mp4BoxCommand.MpdPath))
            {
                int maxFileIndex = ffmpegCommand.AllStreamCommands.Max(x => x.Index);
                IEnumerable <SubtitleStreamCommand> allSubtitles = ProcessSubtitles(config, ffmpegCommand.SubtitleCommands, maxFileIndex + 1);
                MPD mpd = PostProcessMpdFile(mp4BoxCommand.MpdPath, allSubtitles);

                return(new DashEncodeResult(mp4BoxCommand.MpdPath, mpd, ffmpegCommand, probedInputData));
            }

            throw new DashManifestNotCreatedException(mp4BoxCommand.MpdPath, ffmpegCommand, mp4BoxCommand,
                                                      $"MP4Box did not produce the expected mpd file at path {mp4BoxCommand.MpdPath}. File: {config.InputFilePath}");
        }
Пример #3
0
        public FFmpegCommand EncodeVideo(DashConfig config, MediaMetadata inputStats, IProgress <double> progress = null, CancellationToken cancel = default)
        {
            FFmpegCommand ffmpegCommand = null;
            var           log           = new StringBuilder();

            try
            {
                ffmpegCommand = FFmpegCommandGeneratorMethod(config, inputStats);

                ExecutionResult ffResult;
                ffResult = ManagedExecution.Start(FFmpegPath, ffmpegCommand.RenderedCommand,
                                                  (x) =>
                {
                    log.AppendLine(x);
                    stdoutLog.Invoke(x);
                },
                                                  (x) =>
                {
                    log.AppendLine(x);
                    FFmpegProgressShim(x, inputStats.Duration, progress);
                }, cancel);

                // Detect error in ffmpeg process and cleanup, then return null.
                if (ffResult.ExitCode != 0)
                {
                    throw new FFMpegFailedException(ffmpegCommand, log, $"ERROR: ffmpeg returned code {ffResult.ExitCode}. File: {config.InputFilePath}");
                }
            }
            catch (Exception ex)
            {
                try
                {
                    CleanFiles(ffmpegCommand.AllStreamCommands.Select(x => x.Path));
                }
                catch (Exception)
                {
                }
                if (ex is FFMpegFailedException)
                {
                    throw;
                }
                throw new FFMpegFailedException(ffmpegCommand, log, ex.Message, ex);
            }
            finally
            {
            }

            return(ffmpegCommand);
        }
Пример #4
0
 /// <summary>
 /// The default function for generating an ffmpeg command.
 /// </summary>
 public static FFmpegCommand GenerateFFmpegCommand(DashConfig config, MediaMetadata inputStats)
 {
     return(new FFmpegCommandBuilder
            (
                inPath: config.InputFilePath,
                outDirectory: config.OutputDirectory,
                outBaseFilename: config.OutputFileName,
                options: config.Options,
                enableStreamCopying: config.EnableStreamCopying
            )
            .WithVideoCommands(inputStats.VideoStreams, config.Qualities, config.Framerate, config.KeyframeInterval, inputStats.KBitrate)
            .WithAudioCommands(inputStats.AudioStreams, config.AudioConfig)
            .WithSubtitleCommands(inputStats.SubtitleStreams)
            .Build());
 }
Пример #5
0
        private FfmpegRenderedCommand EncodeVideo(DashConfig config, MediaMetadata inputStats, int inputBitrate, bool enableStreamCopying, Action <string> progressCallback, CancellationToken cancel)
        {
            FfmpegRenderedCommand ffmpegCommand = FFmpegCommandBuilder
                                                  .Initilize(
                inPath: config.InputFilePath,
                outDirectory: config.OutputDirectory,
                outBaseFilename: config.OutputFileName,
                options: config.Options,
                enableStreamCopying: enableStreamCopying
                )
                                                  .WithVideoCommands(inputStats.VideoStreams, config.Qualities, config.Framerate, config.KeyframeInterval, inputBitrate)
                                                  .WithAudioCommands(inputStats.AudioStreams)
                                                  .WithSubtitleCommands(inputStats.SubtitleStreams)
                                                  .Build();

            // Generate intermediates
            try
            {
                ExecutionResult ffResult;
                stderrLog.Invoke($"Running ffmpeg with arguments: {ffmpegCommand.RenderedCommand}");
                ffResult = ManagedExecution.Start(FFmpegPath, ffmpegCommand.RenderedCommand, stdoutLog, progressCallback, cancel); //TODO: Use a better log/error callback mechanism? Also use a better progress mechanism

                // Detect error in ffmpeg process and cleanup, then return null.
                if (ffResult.ExitCode != 0)
                {
                    stderrLog.Invoke($"ERROR: ffmpeg returned code {ffResult.ExitCode}. File: {config.InputFilePath}");
                    CleanOutputFiles(ffmpegCommand.AllPieces.Select(x => x.Path));
                    return(null);
                }
            }
            catch (Exception ex)
            {
                CleanOutputFiles(ffmpegCommand.AllPieces.Select(x => x.Path));

                if (ex is OperationCanceledException)
                {
                    throw new OperationCanceledException($"Exception running ffmpeg on {config.InputFilePath}", ex);
                }
                else
                {
                    throw new Exception($"Exception running ffmpeg on {config.InputFilePath}", ex);
                }
            }

            return(ffmpegCommand);
        }
Пример #6
0
        /// <summary>
        /// Builds the arguments for the CLI version of ffmpeg.
        /// </summary>
        /// <param name="inPath">The source file to encode.</param>
        /// <param name="outDirectory">The directory to place output file in.</param>
        /// <param name="outFilename">The base filename to use when naming output files (format is [outFilename]_[qualityBitrate].mp4).</param>
        /// <param name="options">Flags to pass to the encoder.</param>
        /// <param name="framerate">The output framerate.</param>
        /// <param name="keyframeInterval">The output key interval. For best results should be a multiple of framerate.</param>
        /// <param name="metadata">Metadata on the encoded stream.</param>
        /// <param name="qualities">A collection of qualities to encode to. Entries in the collection must have a distinct bitrate, otherwise behavior is undefined.</param>
        /// <param name="defaultBitrate">The bitrate to use for the copy quality. Typically the input file bitrate.</param>
        /// <param name="enableStreamCopying">Set true to enable -vcodec copy for the copy quality.</param>
        internal static CommandBuildResult BuildFfmpegCommand(
            string inPath,
            string outDirectory,
            string outFilename,
            IEncodeOptions options,
            IEnumerable <IQuality> qualities,
            int framerate,
            int keyframeInterval,
            MediaMetadata metadata,
            int defaultBitrate,
            bool enableStreamCopying)
        {
            var videoCommand    = BuildVideoCommands(metadata.VideoStreams, qualities, options.AdditionalVideoFlags, framerate, keyframeInterval, defaultBitrate, enableStreamCopying, outDirectory, outFilename);
            var audioCommand    = BuildAudioCommands(metadata.AudioStreams, options.AdditionalAudioFlags, outDirectory, outFilename);
            var subtitleCommand = BuildSubtitleCommands(metadata.SubtitleStreams, outDirectory, outFilename);

            var allCommands = videoCommand.Concat(audioCommand).Concat(subtitleCommand);

            var    additionalFlags = options.AdditionalFlags ?? new List <string>();
            string parameters      = $"-i \"{inPath}\" -y -hide_banner {string.Join(" ", additionalFlags.Concat(allCommands.Select(x => x.Argument)))}";

            return(new CommandBuildResult(parameters, allCommands));
        }
Пример #7
0
        private MediaMetadata ProbeFile(string inFile)
        {
            string args     = $"-print_format xml=fully_qualified=1 -show_format -show_streams -- \"{inFile}\"";
            var    exResult = ManagedExecution.Start(FFprobePath, args);

            string xmlData = string.Join("\n", exResult.Output);

            if (FFprobeData.Deserialize(xmlData, out FFprobeData t))
            {
                List <MediaStream> audioStreams    = new List <MediaStream>();
                List <MediaStream> videoStreams    = new List <MediaStream>();
                List <MediaStream> subtitleStreams = new List <MediaStream>();
                foreach (var s in t.streams)
                {
                    switch (s.codec_type)
                    {
                    case "audio":
                        audioStreams.Add(s);
                        break;

                    case "video":
                        videoStreams.Add(s);
                        break;

                    case "subtitle":
                        subtitleStreams.Add(s);
                        break;

                    default:
                        break;
                    }
                }

                var metadata = new Dictionary <string, string>();
                if (t.format.tag != null)
                {
                    foreach (var item in t.format.tag)
                    {
                        if (!metadata.ContainsKey(item.key))
                        {
                            metadata.Add(item.key.ToLower(System.Globalization.CultureInfo.InvariantCulture), item.value);
                        }
                    }
                }

                var firstVideoStream = videoStreams.FirstOrDefault(x => CommandBuilder.SupportedCodecs.ContainsKey(x.codec_name));
                var firstAudioStream = audioStreams.FirstOrDefault(x => CommandBuilder.SupportedCodecs.ContainsKey(x.codec_name));

                if (!decimal.TryParse(firstVideoStream?.r_frame_rate, out decimal framerate))
                {
                    framerate = 24;
                }

                float duration = t.format != null ? t.format.duration : 0;

                var meta = new MediaMetadata(videoStreams, audioStreams, subtitleStreams, metadata, firstVideoStream?.bit_rate ?? t.format.bit_rate, framerate, duration);
                return(meta);
            }

            return(null);
        }
Пример #8
0
        public MediaMetadata ProbeFile(string inFile, out FFprobeData rawProbe)
        {
            string args     = $"-print_format xml=fully_qualified=1 -show_format -show_streams -- \"{inFile}\"";
            var    exResult = ManagedExecution.Start(FFprobePath, args);

            string xmlData = string.Join("\n", exResult.Output);

            rawProbe = FFprobeData.Deserialize(xmlData);
            List <MediaStream> audioStreams    = new List <MediaStream>();
            List <MediaStream> videoStreams    = new List <MediaStream>();
            List <MediaStream> subtitleStreams = new List <MediaStream>();

            foreach (var s in rawProbe.streams)
            {
                switch (s.codec_type)
                {
                case "audio":
                    audioStreams.Add(s);
                    break;

                case "video":
                    videoStreams.Add(s);
                    break;

                case "subtitle":
                    subtitleStreams.Add(s);
                    break;

                default:
                    break;
                }
            }

            var metadata = new Dictionary <string, string>();

            if (rawProbe.format.tag != null)
            {
                foreach (var item in rawProbe.format.tag)
                {
                    if (!metadata.ContainsKey(item.key))
                    {
                        metadata.Add(item.key.ToLower(System.Globalization.CultureInfo.InvariantCulture), item.value);
                    }
                }
            }

            var firstVideoStream = videoStreams.FirstOrDefault(x => Constants.SupportedInputCodecs.ContainsKey(x.codec_name)) ?? videoStreams.FirstOrDefault();

            decimal framerate = 0;
            long    bitrate   = 0;

            if (firstVideoStream == null)
            {
                // Leave them as zero.
            }
            else
            {
                if (decimal.TryParse(firstVideoStream.r_frame_rate, out framerate))
                {
                }
                else if (firstVideoStream.r_frame_rate.Contains("/"))
                {
                    try
                    {
                        framerate = firstVideoStream.r_frame_rate
                                    .Split('/')
                                    .Select(component => decimal.Parse(component))
                                    .Aggregate((dividend, divisor) => dividend / divisor);
                    }
                    catch (Exception)
                    {
                        // Leave it as zero.
                    }
                }

                bitrate = firstVideoStream.bit_rate != 0 ? firstVideoStream.bit_rate : (rawProbe.format?.bit_rate ?? 0);
            }

            float duration = rawProbe.format != null ? rawProbe.format.duration : 0;

            var meta = new MediaMetadata(inFile, videoStreams, audioStreams, subtitleStreams, metadata, bitrate, framerate, duration);

            return(meta);
        }