예제 #1
0
        public void GenerateDash_MidLevelApiOptions_ProducesCorrectDashEncodeResult()
        {
            Encoder encoder = new Encoder(ffmpegPath, ffprobePath, mp4boxPath,
                                          ffmpegCommandGenerator: (dashConfig, mediaMetadata) =>
            {
                DashConfig c    = dashConfig;
                MediaMetadata m = mediaMetadata;
                FFmpegCommand r = Encoder.GenerateFFmpegCommand(c, m);
                return(r);
            },
                                          mp4BoxCommandGenerator: (dashConfig, videoStreams, audioStreams) =>
            {
                DashConfig c = dashConfig;
                IEnumerable <VideoStreamCommand> v = videoStreams;
                IEnumerable <AudioStreamCommand> a = audioStreams;
                Mp4BoxCommand r = Encoder.GenerateMp4BoxCommand(c, v, a);
                return(r);
            });
            DashConfig config = new DashConfig(testFileName, RunPath, Qualities, "output");

            encodeResult = encoder.GenerateDash(config, encoder.ProbeFile(config.InputFilePath, out _));

            Assert.NotNull(encodeResult.DashFilePath);
            Assert.NotNull(encodeResult.DashFileContent);
            Assert.NotNull(encodeResult.MediaFiles);
            Assert.Equal(4, encodeResult.MediaFiles.Count());
        }
예제 #2
0
        public void GenerateDash_WithLiveStreamingProfile_ProducesCorrectDashEncodeResult()
        {
            string outputFilename = "outputlive";

            var options = new H264EncodeOptions
            {
                AdditionalMP4BoxFlags = new List <string>()
                {
                    "-profile \"dashavc264:live\"",
                    "-bs-switching no"
                }
            };

            Encoder    encoder = new Encoder(ffmpegPath, ffprobePath, mp4boxPath);
            DashConfig config  = new DashConfig("testfile.ogg", RunPath, SubtitleQualities, outputFilename)
            {
                Options = options
            };

            try
            {
                encodeResult = encoder.GenerateDash(config, encoder.ProbeFile(config.InputFilePath, out _));

                Assert.NotNull(encodeResult.DashFilePath);
                Assert.NotNull(encodeResult.DashFileContent);
            }
            finally
            {
                foreach (var file in Directory.EnumerateFiles(RunPath, $"{outputFilename}_*"))
                {
                    File.Delete(Path.Combine(RunPath, file));
                }
            }
        }
예제 #3
0
        public void GenerateDash_Downmixing_ProducesCorrectDashEncodeResult()
        {
            Encoder    encoder = new Encoder(ffmpegPath, ffprobePath, mp4boxPath);
            DashConfig config  = new DashConfig(multiLanguageTestFileName, RunPath, Qualities, "output")
            {
                AudioConfig = new AudioConfig()
                {
                    DownmixMode          = DownmixMode.Default,
                    MaxPerChannelBitrate = 1024 * 80
                }
            };

            encodeResult = encoder.GenerateDash(config, encoder.ProbeFile(config.InputFilePath, out _));

            var scans = encodeResult.MediaFiles.Select((x) => encoder.ProbeFile(x, out _));

            foreach (var outputStream in scans)
            {
                if (outputStream.VideoStreams.Count() > 0)
                {
                    continue;
                }
                foreach (var aStream in outputStream.AudioStreams)
                {
                    Assert.True(aStream.channels <= 2);
                    Assert.True(aStream.max_bit_rate <= config.AudioConfig.MaxPerChannelBitrate * aStream.channels);
                }
            }
        }
예제 #4
0
        public void Constructor_WithInvalidOutputCharacters_CleansCharacters()
        {
            string     outputName = "testfile*&:\\";
            DashConfig config     = new DashConfig(testFileName, Environment.CurrentDirectory, Qualities, outputName);

            Assert.Equal("testfile", config.OutputFileName);
        }
예제 #5
0
 public MainWindow()
 {
     InitializeComponent();
     this.config      = new DashConfig();
     this.languages   = config.getFeeds();
     this.Title       = Assembly.GetExecutingAssembly().GetName().Name;
     this.DataContext = this;
 }
예제 #6
0
        public void GenerateDash_WithCancellationToken_ThrowsOperationCanceledException()
        {
            var tokenSource = new CancellationTokenSource(500);

            DEnc.Encoder encoder = new DEnc.Encoder(ffmpegPath, ffprobePath, mp4boxPath);
            DashConfig   config  = new DashConfig(testFileName, RunPath, Qualities, "output");

            Assert.Throws <OperationCanceledException>(() => encodeResult = encoder.GenerateDash(config, cancel: tokenSource.Token));
        }
예제 #7
0
        public void GenerateDash_NormalEncode_ProducesCorrectDashEncodeResult()
        {
            Encoder    encoder = new Encoder(ffmpegPath, ffprobePath, mp4boxPath);
            DashConfig config  = new DashConfig(testFileName, RunPath, Qualities, "output");

            encodeResult = encoder.GenerateDash(config, encoder.ProbeFile(config.InputFilePath, out _));

            Assert.NotNull(encodeResult.DashFilePath);
            Assert.NotNull(encodeResult.DashFileContent);
            Assert.NotNull(encodeResult.MediaFiles);
            Assert.Equal(4, encodeResult.MediaFiles.Count());
        }
예제 #8
0
        private Mp4BoxRenderedCommand GenerateDashManifest(DashConfig config, IEnumerable <StreamVideoFile> videoFiles, IEnumerable <StreamAudioFile> audioFiles, CancellationToken cancel)
        {
            string mpdOutputPath = Path.Combine(config.OutputDirectory, config.OutputFileName) + ".mpd";
            var    mp4boxCommand = Mp4BoxCommandBuilder.BuildMp4boxMpdCommand(
                videoFiles: videoFiles,
                audioFiles: audioFiles,
                mpdOutputPath: mpdOutputPath,
                keyInterval: (config.KeyframeInterval / config.Framerate) * 1000,
                additionalFlags: config.Options.AdditionalMP4BoxFlags);

            // Generate DASH files.
            ExecutionResult mpdResult;

            stderrLog.Invoke($"Running MP4Box with arguments: {mp4boxCommand.RenderedCommand}");
            try
            {
                mpdResult = ManagedExecution.Start(BoxPath, mp4boxCommand.RenderedCommand, stdoutLog, stderrLog, cancel);

                // Dash Failed TODO: Add in Progress report behavior that was excluded from this
                // Detect error in MP4Box process and cleanup, then return null.
                if (mpdResult.ExitCode != 0)
                {
                    MPD mpdFile   = MPD.LoadFromFile(mpdOutputPath);
                    var filePaths = mpdFile.GetFileNames().Select(x => Path.Combine(config.OutputDirectory, x));

                    stderrLog.Invoke($"ERROR: MP4Box returned code {mpdResult.ExitCode}. File: {config.InputFilePath}");
                    CleanOutputFiles(filePaths);
                    CleanOutputFiles(mpdResult.Output);

                    return(null);
                }
            }
            catch (Exception ex)
            {
                if (ex is OperationCanceledException)
                {
                    throw new OperationCanceledException($"Exception running MP4box on {config.InputFilePath}", ex);
                }
                else
                {
                    throw new Exception($"Exception running MP4box on {config.InputFilePath}", ex);
                }
            }
            finally
            {
                CleanOutputFiles(videoFiles.Select(x => x.Path));
                CleanOutputFiles(audioFiles.Select(x => x.Path));
            }

            return(mp4boxCommand);
        }
예제 #9
0
파일: Encoder.cs 프로젝트: tohoff82/DEnc
        /// <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}");
        }
예제 #10
0
파일: Encoder.cs 프로젝트: tohoff82/DEnc
        /// <summary>
        /// The default function for generating an MP4Box command.
        /// </summary>
        public static Mp4BoxCommand GenerateMp4BoxCommand(DashConfig config, IEnumerable <VideoStreamCommand> videoFiles, IEnumerable <AudioStreamCommand> audioFiles)
        {
            // Use a default key interval of 3s if a framerate or keyframe interval is not given.
            int    keyInterval   = (config.KeyframeInterval == 0 || config.Framerate == 0) ? 3000 : (config.KeyframeInterval / config.Framerate * 1000);
            string mpdOutputPath = Path.Combine(config.OutputDirectory, config.OutputFileName) + ".mpd";
            var    mp4boxCommand = Mp4BoxCommandBuilder.BuildMp4boxMpdCommand(
                videoFiles: videoFiles,
                audioFiles: audioFiles,
                mpdOutputPath: mpdOutputPath,
                keyInterval: keyInterval,
                additionalFlags: config.Options.AdditionalMP4BoxFlags);

            return(mp4boxCommand);
        }
예제 #11
0
파일: Encoder.cs 프로젝트: tohoff82/DEnc
        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);
        }
예제 #12
0
        public void GenerateDash_WithManySubtitleLanguages_ProducesSubtitleFiles()
        {
            DEnc.Encoder encoder = new DEnc.Encoder(ffmpegPath, ffprobePath, mp4boxPath);
            encoder.EnableStreamCopying = true;
            DashConfig config = new DashConfig(multiLanguageTestFileName, RunPath, MultiLanguageQualities, "outputlang");

            encodeResult = encoder.GenerateDash(config);

            Assert.NotNull(encodeResult.DashFilePath);
            Assert.NotNull(encodeResult.DashFileContent);
            Assert.NotNull(encodeResult.MediaFiles);
            Assert.Equal("avc1.640028", encodeResult.DashFileContent.Period[0].AdaptationSet[0].Representation[0].Codecs);
            Assert.True(encodeResult.DashFileContent.Period[0].AdaptationSet.Where(x => x.Lang == "jpn" && x.MaxFrameRate == null).SingleOrDefault().Representation.Count() == 1);
            Assert.True(encodeResult.DashFileContent.Period[0].AdaptationSet.Where(x => x.Lang == "eng" && x.MaxFrameRate == null).Count() == 2);
        }
예제 #13
0
파일: Encoder.cs 프로젝트: tohoff82/DEnc
 /// <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());
 }
예제 #14
0
        public void GenerateDash_WithManySubtitles_ProducesSubtitleFiles()
        {
            Encoder    encoder = new Encoder(ffmpegPath, ffprobePath, mp4boxPath);
            DashConfig config  = new DashConfig(subtitleTestFileName, RunPath, SubtitleQualities, "outputmulti");

            encodeResult = encoder.GenerateDash(config, encoder.ProbeFile(config.InputFilePath, out _));

            Assert.NotNull(encodeResult.DashFilePath);
            Assert.NotNull(encodeResult.DashFileContent);
            Assert.NotNull(encodeResult.MediaFiles);
            Assert.Equal(16, encodeResult.MediaFiles.Count());
            Assert.Contains("outputmulti_audio_default_1_dashinit.mp4", encodeResult.MediaFiles);
            Assert.Contains("outputmulti_subtitle_eng_2.vtt", encodeResult.MediaFiles);
            Assert.Contains("outputmulti_subtitle_und_10.vtt", encodeResult.MediaFiles);
            Assert.Contains("outputmulti_subtitle_eng_12.vtt", encodeResult.MediaFiles);
        }
예제 #15
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);
        }
예제 #16
0
        /// <summary>
        /// Processes the media subtitles and finds and handles external subtitle files
        /// </summary>
        /// <param name="config">The <see cref="DashConfig"/></param>
        /// <param name="subtitleFiles">The subtitle stream files</param>
        /// <param name="startFileIndex">The index additional subtitles need to start at. This should be the max index of the ffmpeg pieces +1</param>
        private List <StreamSubtitleFile> ProcessSubtitles(DashConfig config, IEnumerable <StreamSubtitleFile> subtitleFiles, int startFileIndex)
        {
            // Move subtitles found in media
            List <StreamSubtitleFile> subtitles = new List <StreamSubtitleFile>();

            foreach (var subFile in subtitleFiles)
            {
                string oldPath = subFile.Path;
                subFile.Path = Path.Combine(config.OutputDirectory, Path.GetFileName(subFile.Path));
                subtitles.Add(subFile);
                if (oldPath != subFile.Path)
                {
                    if (File.Exists(subFile.Path))
                    {
                        File.Delete(subFile.Path);
                    }
                    File.Move(oldPath, subFile.Path);
                }
            }

            // Add external subtitles
            string baseFilename = Path.GetFileNameWithoutExtension(config.InputFilePath);

            foreach (var vttFile in Directory.EnumerateFiles(Path.GetDirectoryName(config.InputFilePath), baseFilename + "*", SearchOption.TopDirectoryOnly))
            {
                if (vttFile.EndsWith(".vtt"))
                {
                    string vttFilename   = Path.GetFileName(vttFile);
                    string vttName       = GetSubtitleName(vttFilename);
                    string vttOutputPath = Path.Combine(config.OutputDirectory, $"{config.OutputFileName}_subtitle_{vttName}_{startFileIndex}.vtt");

                    var subFile = new StreamSubtitleFile()
                    {
                        Type     = StreamType.Subtitle,
                        Index    = startFileIndex,
                        Path     = vttOutputPath,
                        Language = $"{vttName}_{startFileIndex}"
                    };
                    startFileIndex++;
                    File.Copy(vttFile, vttOutputPath, true);
                    subtitles.Add(subFile);
                }
            }

            return(subtitles);
        }
예제 #17
0
        public void GenerateDash_WithCancellationToken_ThrowsOperationCanceledException()
        {
            var        tokenSource = new CancellationTokenSource(250);
            Encoder    encoder     = new Encoder(ffmpegPath, ffprobePath, mp4boxPath);
            DashConfig config      = new DashConfig(testFileName, RunPath, Qualities, "output");

            Exception thrown = null;

            try
            {
                encodeResult = encoder.GenerateDash(config, encoder.ProbeFile(config.InputFilePath, out _), cancel: tokenSource.Token);
            }
            catch (Exception ex)
            {
                thrown = ex;
            }
            Assert.NotNull(thrown);
            Assert.IsType <OperationCanceledException>(thrown.InnerException);
        }
예제 #18
0
        protected override void Process(IConversionItem item)
        {
            if (item.CancellationToken.IsCancellationRequested)
            {
                HandleCancel(item);
                return;
            }

            var options = new H264EncodeOptions();

            switch (item.Tune)
            {
            case Tune.Film:
                options.AdditionalVideoFlags.Add("-tune film");
                break;

            case Tune.Grain:
                options.AdditionalVideoFlags.Add("-tune grain");
                break;

            case Tune.Animation:
                options.AdditionalVideoFlags.Add("-tune animation");
                break;
            }

            log.LogInformation($"Processing item {item.SourcePath}");

            try
            {
                var probeData  = encoder.ProbeFile(item.SourcePath, out _);
                var dashConfig = new DashConfig(item.SourcePath, item.OutputPath, item.Quality, item.OutputBaseFilename)
                {
                    EnableStreamCopying = true,
                    Framerate           = item.Framerate,
                    Options             = options
                };
                if (item.DownmixAudio)
                {
                    dashConfig.AudioConfig = new AudioConfig()
                    {
                        DownmixMode = DownmixMode.Default
                    };
                }

                var dashResult = encoder.GenerateDash(dashConfig, probeData,
                                                      progress: new NaiveProgress <double>((x) =>
                {
                    item.Progress = new[] { new DescribedProgress("Conversion", x) };
                }),
                                                      cancel: item.CancellationToken.Token);

                if (dashResult == null)
                {
                    throw new Exception("Failed to convert item. Got null from generator. Check the ffmpeg/mp4box log.");
                }

                item.CompletionAction.Invoke(item, dashResult);

                completeItems.Add(item);
            }
            catch (FFMpegFailedException ex) when(ex is FFMpegFailedException || ex is Mp4boxFailedException || ex is DashManifestNotCreatedException)
            {
                if (ex.InnerException is OperationCanceledException)
                {
                    log.LogInformation($"Task Cancelled: {item.SourcePath}");
                    HandleCancel(item);
                    return;
                }

                var logItems = new List <string>()
                {
                    $"Failed to convert {item.SourcePath}",
                    $"ffmpeg command: {ex.FFmpegCommand?.RenderedCommand ?? "Unavailable"}"
                };
                string failureStage = "Unknown";

                switch (ex)
                {
                case DashManifestNotCreatedException dex:
                    failureStage = "Manifest generation";
                    break;

                case Mp4boxFailedException mpex:
                    failureStage = "DASHing/MP4Box";
                    logItems.Add($"MP4Box command: {mpex.MP4BoxCommand.RenderedCommand}");
                    break;

                case FFMpegFailedException ffex:
                    failureStage = "Encoding/ffmpeg";
                    break;

                default:
                    break;
                }

                logItems.Add($"Stack trace: {ex}");
                if (ex.Log != null && ex.Log.Length > 0)
                {
                    logItems.Add($"Process log: {ex.Log}");
                }

                string fullLog = string.Join('\n', logItems);

                log.LogWarning(fullLog);

                item.ErrorReason = $"Failed at step: {failureStage}. Message: {ex.Message}";
                item.ErrorDetail = fullLog;
                completeItems.Add(item);
            }
            catch (OperationCanceledException)
            {
                log.LogInformation($"Task Cancelled: {item.SourcePath}");
                HandleCancel(item);
            }
        }
예제 #19
0
        public void Constructor_WithNullOutputFileName_UsesInputName()
        {
            DashConfig config = new DashConfig(testFileName, Environment.CurrentDirectory, Qualities);

            Assert.Equal("testfile", config.OutputFileName);
        }
예제 #20
0
파일: Encoder.cs 프로젝트: tohoff82/DEnc
        /// <summary>
        /// This method takes configuration, and a set of video and audio streams, and assemb
        /// </summary>
        /// <param name="config">The config to use to generate the MP4Box command.</param>
        /// <param name="videoFiles">A set of video files to include in the DASH process and manifest.</param>
        /// <param name="audioFiles">A set of audio files to include in the DASH process and manifest.</param>
        /// <param name="cancel">A cancel token to pass to the process.</param>
        /// <param name="originalFFmpegCommand">The ffmpeg command used to create the input files. This is for exception logging only, and may be left null.</param>
        protected virtual Mp4BoxCommand GenerateDashManifest(DashConfig config, IEnumerable <VideoStreamCommand> videoFiles, IEnumerable <AudioStreamCommand> audioFiles, CancellationToken cancel, FFmpegCommand originalFFmpegCommand = null)
        {
            Mp4BoxCommand   mp4boxCommand = null;
            ExecutionResult mpdResult;
            var             log = new StringBuilder();

            try
            {
                mp4boxCommand = Mp4BoxCommandGeneratorMethod(config, videoFiles, audioFiles);
                mpdResult     = ManagedExecution.Start(Mp4BoxPath, mp4boxCommand.RenderedCommand,
                                                       (x) =>
                {
                    log.AppendLine(x);
                    stdoutLog.Invoke(x);
                },
                                                       (x) =>
                {
                    log.AppendLine(x);
                    stderrLog.Invoke(x);
                }, cancel);

                if (mpdResult.ExitCode != 0)
                {
                    try
                    {
                        // Error in MP4Box.
                        if (File.Exists(mp4boxCommand.MpdPath))
                        {
                            MPD mpdFile   = MPD.LoadFromFile(mp4boxCommand.MpdPath);
                            var filePaths = mpdFile.GetFileNames().Select(x => Path.Combine(config.OutputDirectory, x));

                            CleanFiles(filePaths);
                            CleanFiles(mpdResult.Output);
                        }
                    }
                    catch (Exception ex)
                    {
                        throw new Mp4boxFailedException(originalFFmpegCommand, mp4boxCommand, log, $"MP4Box returned code {mpdResult.ExitCode}.", ex);
                    }
                    throw new Mp4boxFailedException(originalFFmpegCommand, mp4boxCommand, log, $"MP4Box returned code {mpdResult.ExitCode}.");
                }
                else if (!File.Exists(mp4boxCommand.MpdPath))
                {
                    throw new Mp4boxFailedException(originalFFmpegCommand, mp4boxCommand, log, $"MP4Box appeared to succeed, but no MPD file was created.");
                }
            }
            catch (Exception ex)
            {
                if (ex is Mp4boxFailedException)
                {
                    throw;
                }
                throw new Mp4boxFailedException(originalFFmpegCommand, mp4boxCommand, log, ex.Message, ex);
            }
            finally
            {
                CleanFiles(videoFiles.Select(x => x.Path));
                CleanFiles(audioFiles.Select(x => x.Path));
            }

            return(mp4boxCommand);
        }
예제 #21
0
        /// <summary>
        /// oOnverts the input file into an MPEG DASH representations.
        /// This includes multiple bitrates, subtitle tracks, audio tracks, and an MPD manifest.
        /// </summary>
        /// <param name="config"></param>
        /// <param name="progress"></param>
        /// <param name="cancel"></param>
        /// <returns></returns>
        public DashEncodeResult GenerateDash(DashConfig config, IProgress <Dictionary <EncodingStage, double> > progress = null, CancellationToken cancel = default(CancellationToken))
        {
            cancel.ThrowIfCancellationRequested();
            if (!Directory.Exists(WorkingDirectory))
            {
                throw new DirectoryNotFoundException("The given path for the working directory doesn't exist.");
            }

            //Field declarations
            MediaMetadata inputStats;
            IQuality      compareQuality;
            int           inputBitrate;
            bool          enableStreamCopy = false;

            inputStats = ProbeFile(config.InputFilePath);
            if (inputStats == null)
            {
                throw new NullReferenceException("ffprobe query returned a null result.");
            }

            inputBitrate = (int)(inputStats.Bitrate / 1024);
            if (!DisableQualityCrushing)
            {
                config.Qualities = QualityCrusher.CrushQualities(config.Qualities, inputBitrate);
            }
            compareQuality = config.Qualities.First();


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

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

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

            //This is not really the proper place to have this
            // Logging shim for ffmpeg to get progress info
            var ffmpegLogShim = new Action <string>(x =>
            {
                if (x != null)
                {
                    var match = Encode.Regexes.ParseProgress.Match(x);
                    if (match.Success && TimeSpan.TryParse(match.Value, out TimeSpan p))
                    {
                        stdoutLog(x);
                        float progressFloat = Math.Min(1, (float)(p.TotalMilliseconds / 1000) / inputStats.Duration);
                        if (progress != null)
                        {
                            ReportProgress(progress, EncodingStage.Encode, progressFloat);
                        }
                    }
                    else
                    {
                        stderrLog(x);
                    }
                }
                else
                {
                    stderrLog(x);
                }
            });

            cancel.ThrowIfCancellationRequested();
            FfmpegRenderedCommand ffmpgCommand = EncodeVideo(config, inputStats, inputBitrate, enableStreamCopy, ffmpegLogShim, cancel);

            if (ffmpgCommand is null)
            {
                return(null);
            }

            Mp4BoxRenderedCommand mp4BoxCommand = GenerateDashManifest(config, ffmpgCommand.VideoPieces, ffmpgCommand.AudioPieces, cancel);

            if (mp4BoxCommand is null)
            {
                return(null);
            }

            ReportProgress(progress, EncodingStage.DASHify, 1);
            ReportProgress(progress, EncodingStage.PostProcess, 0.3);

            int maxFileIndex = ffmpgCommand.AllPieces.Max(x => x.Index);
            List <StreamSubtitleFile> allSubtitles = ProcessSubtitles(config, ffmpgCommand.SubtitlePieces, maxFileIndex + 1);

            ReportProgress(progress, EncodingStage.PostProcess, 0.66);

            try
            {
                string mpdFilepath = mp4BoxCommand.MpdPath;
                if (File.Exists(mpdFilepath))
                {
                    MPD mpd = PostProcessMpdFile(mpdFilepath, allSubtitles);

                    var result = new DashEncodeResult(mpd, inputStats.Metadata, TimeSpan.FromMilliseconds((inputStats.VideoStreams.FirstOrDefault()?.duration ?? 0) * 1000), mpdFilepath);

                    // Success.
                    return(result);
                }

                stderrLog.Invoke($"ERROR: MP4Box did not produce the expected mpd file at path {mpdFilepath}. File: {config.InputFilePath}");
                return(null);
            }
            finally
            {
                ReportProgress(progress, EncodingStage.PostProcess, 1);
            }
        }