private static bool HasAcceptableAudio(DConf dc, IMediaInfo mediaInfo) { if (dc.AcceptedAudioCodecs == null) { throw new ArgumentNullException(nameof(dc.AcceptedAudioCodecs)); } if (mediaInfo == null) { throw new ArgumentNullException(nameof(mediaInfo)); } if (mediaInfo.AudioCodec == null) { throw new ArgumentNullException(nameof(mediaInfo.AudioCodec)); } return(dc.AcceptedAudioCodecs.Contains(mediaInfo.AudioCodec)); }
private static VideoCompatibilityFlags HasAcceptableVideo(DConf dc, SoFakingConfiguration sc, IMediaInfo mediaInfo) { if (dc.AcceptedVideoCodecs == null) { throw new ArgumentNullException(nameof(dc.AcceptedVideoCodecs)); } if (mediaInfo == null) { throw new ArgumentNullException(nameof(mediaInfo)); } if (mediaInfo.VideoCodec == null) { throw new ArgumentNullException(nameof(mediaInfo.VideoCodec)); } if (mediaInfo.FileInfo == null) { throw new ArgumentNullException(nameof(mediaInfo.FileInfo)); } if (mediaInfo.HorizontalVideoResolution == -1) { throw new ArgumentException($"Horizontal resolution invalid: {nameof(mediaInfo.HorizontalVideoResolution)}"); } var acceptableCodec = false; foreach (var vc in dc.AcceptedVideoCodecs) { if (mediaInfo.VideoCodec.IndexOf(vc) >= 0) { acceptableCodec = true; break; } } var acceptableResolution = mediaInfo.HorizontalVideoResolution <= sc.MaxHorizontalVideoResolution; var acceptableSize = mediaInfo.FileInfo.Length > (sc.MaxSizeGb * 1024 * 1024); // TODO: Fixing getting the video bitrate right would speed up the program significantly. // Unfortunately, FFMPEG can't return bitrate of only the video stream. So we will ONLY stream copy if video and all the audio streams combined have a lower bitrate than level 4.2 h264 video bitrate compatible with PS4 (6,25Mbit/s) var acceptableBitrate = (mediaInfo.AVBitrateKbs == null || mediaInfo.AVBitrateKbs <= TargetVideoBitrateKbs); var flags = VideoCompatibilityFlags.Compatible; if (!acceptableCodec) { flags |= VideoCompatibilityFlags.IncompatibleCodec; } if (!acceptableResolution) { flags |= VideoCompatibilityFlags.IncompatibleResolution; } if (!acceptableSize) { flags |= VideoCompatibilityFlags.IncompatibleSize; } if (!acceptableBitrate) { flags |= VideoCompatibilityFlags.IncompatibleBitrate; } return(flags); }
static async Task Main(string[] args) { if (args.Length == 0) { throw new Exception("No file to convert provided"); } Console.WriteLine("Encoding: " + args[0]); var builder = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) .AddJsonFile("appsettings.Development.json", optional: true, reloadOnChange: true) // TODO: Change the Production with Enviroment .AddEnvironmentVariables(); IConfigurationRoot configuration = builder.Build(); var encoderConfiguration = new EncoderConfiguration(); configuration.GetSection("Encoder").Bind(encoderConfiguration); var sofakingConfiguration = new SoFakingConfiguration(); configuration.GetSection("Sofaking").Bind(sofakingConfiguration); var dc = new DConf(); configuration.GetSection("DownloadFinishedWorker").Bind(dc); var builder1 = new HostBuilder() .ConfigureServices((hostContext, services) => { services .AddSingleton(sofakingConfiguration) .AddSingleton(encoderConfiguration) .AddSingleton(new SoFakingContextFactory()); }).UseConsoleLifetime(); var host = builder1.Build(); using (var serviceScope = host.Services.CreateScope()) { var done = false; var serviceProvider = serviceScope.ServiceProvider; var logger = new NullLogger <FFMPEGEncoderService>(); var flags = EncodingTargetFlags.None; var videoFile = args[0]; IMediaInfo mediaInfo = null; var useCuda = true; Console.Clear(); Console.WriteLine($"Is this a film (vs. cartoon)? [y/n]"); if (Console.ReadKey().KeyChar == 'n') { flags |= EncodingTargetFlags.VideoIsAnimation; } var encoderTranscodingInstance = new FFMPEGEncoderService(logger, encoderConfiguration, sofakingConfiguration); try { mediaInfo = await encoderTranscodingInstance.GetMediaInfo(videoFile); } catch (Exception ex) { Console.Clear(); Console.WriteLine("Can not read media info: " + ex.Message); Console.ReadKey(); return; } // Find subtitles var filenameBase = Path.GetFileNameWithoutExtension(videoFile); var path = Path.GetDirectoryName(videoFile); var subtitles = new Dictionary <string, string>(); foreach (var sl in sofakingConfiguration.SubtitleLanguages) { var subPath = Path.Combine(path, filenameBase + $".{sl}.srt"); if (File.Exists(subPath)) { subtitles.Add(sl, subPath); } } if (subtitles.Count > 0) { Console.Clear(); Console.WriteLine($"Found {subtitles.Count} subtitles to embed."); Thread.Sleep(3 * 1000); flags |= EncodingTargetFlags.ExternalSubtitles; } try { var videoAudit = HasAcceptableVideo(dc, sofakingConfiguration, mediaInfo); if (videoAudit != VideoCompatibilityFlags.Compatible) { Console.Clear(); Console.WriteLine("Video details:"); if (videoAudit.HasFlag(VideoCompatibilityFlags.IncompatibleCodec)) { Console.ForegroundColor = ConsoleColor.Red; } Console.WriteLine($" Codec: {mediaInfo.VideoCodec}, (Accepted: {string.Join(", ", dc.AcceptedVideoCodecs)})"); Console.ResetColor(); if (videoAudit.HasFlag(VideoCompatibilityFlags.IncompatibleResolution)) { Console.ForegroundColor = ConsoleColor.Red; } Console.WriteLine($" H. Resolution: {mediaInfo.HorizontalVideoResolution}px, (Max: {sofakingConfiguration.MaxHorizontalVideoResolution}px)"); Console.ResetColor(); if (videoAudit.HasFlag(VideoCompatibilityFlags.IncompatibleBitrate)) { Console.ForegroundColor = ConsoleColor.Red; } Console.WriteLine($" Avg bitrate: {(mediaInfo.AVBitrateKbs.HasValue ? ByteSize.FromKiloBytes(mediaInfo.AVBitrateKbs.Value).ToString() : "?")}/s, (Max: {ByteSize.FromKiloBytes(TargetVideoBitrateKbs)}/s"); Console.ResetColor(); if (videoAudit.HasFlag(VideoCompatibilityFlags.IncompatibleSize)) { Console.ForegroundColor = ConsoleColor.Red; } Console.WriteLine($" Size: {ByteSize.FromBytes(mediaInfo.FileInfo.Length)} (Max: {ByteSize.FromGigaBytes(sofakingConfiguration.MaxSizeGb)})"); Console.ResetColor(); Console.WriteLine(); Console.WriteLine($"Video needs converting. Continue? [y/n]"); if (Console.ReadKey().KeyChar == 'n') { return; } if (encoderConfiguration.CanUseCuda) { Console.Clear(); Console.WriteLine($"Use CUDA? [y/n]"); if (Console.ReadKey().KeyChar == 'n') { useCuda = false;; } } flags |= EncodingTargetFlags.NeedsNewVideo; } } catch (ArgumentException ex) { Console.Clear(); Console.WriteLine("Incompatible video: " + ex.Message); Console.ReadKey(); return; } try { if (!HasAcceptableAudio(dc, mediaInfo)) { Console.Clear(); Console.WriteLine($"Audio ({mediaInfo.AudioCodec}, (Accepted: {string.Join(", ", dc.AcceptedAudioCodecs)})) needs converting. Continue? [y/n]"); if (Console.ReadKey().KeyChar == 'n') { return; } flags |= EncodingTargetFlags.NeedsNewAudio; } } catch (ArgumentException ex) { Console.Clear(); Console.WriteLine("Incompatible audio: " + ex.Message); Console.ReadKey(); return; } if (flags == EncodingTargetFlags.None) { Console.Clear(); Console.WriteLine($"Video file {videoFile} doesn't need transcoding, adding to files to move."); Console.ReadKey(); return; } Console.Clear(); Console.WriteLine("Converting..."); encoderTranscodingInstance.OnSuccess += (object sender, EncodingSuccessEventArgs e) => { done = true; }; encoderTranscodingInstance.OnProgress += (object sender, EncodingProgressEventArgs e) => { Console.Clear(); Console.WriteLine($"Transcoding progress: {e.ProgressPercent:0.##}%"); }; // non-blocking, only starts the external engine await encoderTranscodingInstance.StartTranscodingAsync(new TranscodingJob { SourceFile = videoFile, Action = flags, Duration = mediaInfo.Duration, Subtitles = subtitles, UseCuda = useCuda }); while (!done) { // wait until encoding finished } Console.Clear(); Console.WriteLine("Done!"); Thread.Sleep(3 * 1000); } }