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