Пример #1
0
        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");
        }
Пример #2
0
        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");
        }
Пример #3
0
        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");
        }
Пример #4
0
    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);
    }
Пример #5
0
    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);
    }