public void I_can_build_an_argument_string_from_multiple_strings() { // Arrange var builder = new ArgumentsBuilder(); // Act var arguments = builder .Add("foo") .Add("bar") .Add("two words") .Add(new[] { "array", "of", "many" }) .Build(); // Assert arguments.Should().Be("foo bar \"two words\" array of many"); }
public void I_can_build_an_argument_string_from_multiple_formattable_values() { // Arrange var builder = new ArgumentsBuilder(); // Act var arguments = builder .Add("foo") .Add(1234) .Add(3.14) .Add(TimeSpan.FromMinutes(1)) .Add(new IFormattable[] { -5, 89.13, 100.50M }) .Add("bar") .Build(); // Assert arguments.Should().Be("foo 1234 3.14 00:01:00 -5 89.13 100.50 bar"); }
public void Command_line_arguments_can_be_set_with_a_builder() { // Arrange var builder = new ArgumentsBuilder(); // Act var arguments = builder .Add("hello world") .Add("foo") .Add(1234) .Add(3.14) .Add(TimeSpan.FromMinutes(1)) .Add(new IFormattable[] { -5, 89.13, 100.50M }) .Add("bar") .Build(); // Assert arguments.Should().Be("\"hello world\" foo 1234 3.14 00:01:00 -5 89.13 100.50 bar"); }
private async Task <Unit> ExtractSubtitles( TvContext dbContext, int mediaItemId, string ffmpegPath, CancellationToken cancellationToken) { Option <MediaItem> maybeMediaItem = await dbContext.MediaItems .Include(mi => (mi as Episode).MediaVersions) .ThenInclude(mv => mv.MediaFiles) .Include(mi => (mi as Episode).MediaVersions) .ThenInclude(mv => mv.Streams) .Include(mi => (mi as Episode).EpisodeMetadata) .ThenInclude(em => em.Subtitles) .Include(mi => (mi as Movie).MediaVersions) .ThenInclude(mv => mv.MediaFiles) .Include(mi => (mi as Movie).MediaVersions) .ThenInclude(mv => mv.Streams) .Include(mi => (mi as Movie).MovieMetadata) .ThenInclude(em => em.Subtitles) .Include(mi => (mi as MusicVideo).MediaVersions) .ThenInclude(mv => mv.MediaFiles) .Include(mi => (mi as MusicVideo).MediaVersions) .ThenInclude(mv => mv.Streams) .Include(mi => (mi as MusicVideo).MusicVideoMetadata) .ThenInclude(em => em.Subtitles) .Include(mi => (mi as OtherVideo).MediaVersions) .ThenInclude(mv => mv.MediaFiles) .Include(mi => (mi as OtherVideo).MediaVersions) .ThenInclude(mv => mv.Streams) .Include(mi => (mi as OtherVideo).OtherVideoMetadata) .ThenInclude(em => em.Subtitles) .SelectOneAsync(e => e.Id, e => e.Id == mediaItemId); foreach (MediaItem mediaItem in maybeMediaItem) { foreach (List <Subtitle> allSubtitles in GetSubtitles(mediaItem)) { var subtitlesToExtract = new List <SubtitleToExtract>(); // find each subtitle that needs extraction IEnumerable <Subtitle> subtitles = allSubtitles .Filter( s => s.SubtitleKind == SubtitleKind.Embedded && s.IsExtracted == false && s.Codec != "hdmv_pgs_subtitle" && s.Codec != "dvd_subtitle"); // find cache paths for each subtitle foreach (Subtitle subtitle in subtitles) { Option <string> maybePath = GetRelativeOutputPath(mediaItem.Id, subtitle); foreach (string path in maybePath) { subtitlesToExtract.Add(new SubtitleToExtract(subtitle, path)); } } string mediaItemPath = await GetMediaItemPath(mediaItem); ArgumentsBuilder args = new ArgumentsBuilder() .Add("-nostdin") .Add("-hide_banner") .Add("-i").Add(mediaItemPath); foreach (SubtitleToExtract subtitle in subtitlesToExtract) { string fullOutputPath = Path.Combine(FileSystemLayout.SubtitleCacheFolder, subtitle.OutputPath); Directory.CreateDirectory(Path.GetDirectoryName(fullOutputPath)); if (_localFileSystem.FileExists(fullOutputPath)) { File.Delete(fullOutputPath); } args.Add("-map").Add($"0:{subtitle.Subtitle.StreamIndex}").Add("-c").Add("copy") .Add(fullOutputPath); } BufferedCommandResult result = await Cli.Wrap(ffmpegPath) .WithArguments(args.Build()) .WithValidation(CommandResultValidation.None) .ExecuteBufferedAsync(cancellationToken); if (result.ExitCode == 0) { foreach (SubtitleToExtract subtitle in subtitlesToExtract) { subtitle.Subtitle.IsExtracted = true; subtitle.Subtitle.Path = subtitle.OutputPath; } int count = await dbContext.SaveChangesAsync(cancellationToken); _logger.LogDebug("Successfully extracted {Count} subtitles", count); } else { _logger.LogError("Failed to extract subtitles. {Error}", result.StandardError); } } } return(Unit.Default); }
private async ValueTask ProcessAsync( string filePath, Container container, IReadOnlyList <StreamInput> streamInputs, IReadOnlyList <SubtitleInput> subtitleInputs, IProgress <double>?progress = null, CancellationToken cancellationToken = default) { var arguments = new ArgumentsBuilder(); // Stream inputs foreach (var streamInput in streamInputs) { arguments.Add("-i").Add(streamInput.FilePath); } // Subtitle inputs foreach (var subtitleInput in subtitleInputs) { arguments.Add("-i").Add(subtitleInput.FilePath); } // Format arguments.Add("-f").Add(container.Name); // Preset arguments.Add("-preset").Add(_preset); // Mapping for (var i = 0; i < streamInputs.Count + subtitleInputs.Count; i++) { arguments.Add("-map").Add(i); } // Avoid transcoding if possible if (streamInputs.All(s => s.Info.Container == container)) { arguments .Add("-c:a").Add("copy") .Add("-c:v").Add("copy"); } // MP4: specify subtitle codec manually, otherwise they're not injected if (container == Container.Mp4 && subtitleInputs.Any()) { arguments.Add("-c:s").Add("mov_text"); } // MP3: specify bitrate manually, otherwise the metadata will contain wrong duration // https://superuser.com/questions/892996/ffmpeg-is-doubling-audio-length-when-extracting-from-video if (container == Container.Mp3) { arguments.Add("-b:a").Add("165k"); } // Inject language metadata for subtitles for (var i = 0; i < subtitleInputs.Count; i++) { arguments .Add($"-metadata:s:s:{i}") .Add($"language={subtitleInputs[i].Info.Language.Code}") .Add($"-metadata:s:s:{i}") .Add($"title={subtitleInputs[i].Info.Language.Name}"); } // Misc settings arguments .Add("-threads").Add(Environment.ProcessorCount) .Add("-nostdin") .Add("-y"); // Output arguments.Add(filePath); // Run FFmpeg await _ffmpeg.ExecuteAsync(arguments.Build(), progress, cancellationToken); }