将多个视频文件与延迟的音频文件连接起来

Concatenate multiple video files alongside delayed audio files

我目前正在开发一个实用程序,负责从云端提取音频和视频文件并通过 FFMPEG 将它们合并在一起。由于我是 FFMPEG 的新手,我将把问题分成一个 FFMPEG 部分和一个 C# 部分,这样人们就可以回答一个部分或另一个部分(或两个部分!)。

FFMPEG 部分

目前,如果只有 1 个视频文件需要与多个文件合并,我有一个有效的 FFMPEG arg。

ffmpeg -i input1.mkv -i input1.mka -i input2.mka -i input3.mka -i input4.mka -filter_complex "[1:a]adelay=0s:all=1[a1pad];[2:a]adelay=20s:all=1[a2pad];[3:a]adelay=30s:all=1[a3pad];[4:a]adelay=40s:all=1[a4pad];[a1pad][a2pad][a3pad][a4pad]amix=inputs=4:weights=1|1|1|1[aout]" -map [aout] -map 0:0 output4.mkv

您在其中看到的延迟是通过从最早创建的音频或视频文件的开始时间减去每个文件的开始时间来确定的。我知道如果我想创建多个视频的水平堆叠,我可以这样做

ffmpeg -i input1.mkv -i input1.mka -i input2.mkv -i input2.mka -i input3.mka -i input4.mka
-filter_complex 
"[2:v]tpad=start_duration=120:color=black[vpad]; 
 [3:a]adelay=120000:all=1[a2pad]; 
 [4:a]adelay=180000:all=1[a3pad];
 [5:a]adelay=200000:all=1[a4pad]; 
 [0:v][vpad]hstack=inputs=2[vout]; 
 [1:a][a2pad][a3pad][a4pad]amix=inputs=4:weights=1|1|1|1[aout]" 
 -map [vout] -map [aout] 
 output.mkv

但我想做的是保留音频和视频文件的延迟并连接(而不是堆叠)这些视频,我该怎么做?

C# 部分

你看到上面那个巨大的 arg 了吗?该实用程序应该根据录音列表生成它。这是模型。

List<FileModel> _records;
public class FileModel {
  public string Id { get; set; }
  public string FileType { get; set; }
  public string StartTime { get; set; }
}

然后该实用程序必须遍历该列表并创建要由 Xabe.FFMPEG 包执行的 arg(如 FFMPEG 部分所示)。我想解决这个问题的方法是基本上创建 2 个字符串生成器。 1 个字符串生成器将负责处理输入,另一个字符串生成器。这是我目前所拥有的

private async Task CombineAsync()
    {
        var minTime = _records.Min(y => Convert.ToDateTime(y.StartTime));
        var frontBuilder = new StringBuilder("-y ");
        var middleBuilder = new StringBuilder("-filter_complex \"");
        var endString = $" -map [vout] -map [aout] {_folderPath}\CombinedOutput.mkv";

        for (var i = 0; i < _records.Count; i++)
        {
            var type = _records[i].FileType.ToLower();
            var delay = (Convert.ToDateTime(_records[i].StartTime).Subtract(minTime)).TotalSeconds;
            frontBuilder.Append($"-i {_folderPath + "\" + _records[i].Id} ");
            var addColon = i != _records.Count - 1 ? ";" : "";
            middleBuilder.Append(type.Equals("video") ? $"[{i}:v]tpad=start_duration={delay}:color=black[v{i}pad]{addColon} " : $"[{i}:a]adelay={delay}s:all=1[a{i}pad]{addColon} ");
        }
        middleBuilder.Append("\"");
        Console.WriteLine(frontBuilder.ToString() + middleBuilder.ToString() + endString);
        // var args = frontBuilder + middleBuilder + endString;
        // try
        // {
        //     var conversionResult = await FFmpeg.Conversions.New().Start(args);
        //     Console.WriteLine(JsonConvert.SerializeObject(conversionResult));
        // }
        // catch (Exception e)
        // {
        //     Console.WriteLine(e);
        // }
    }
  1. 这是构建论点的正确方法吗?

  2. 天哪,我是怎么弄到这样的东西的,因为它依赖于管道和输入的命名和总计数=

      [0:v][vpad]hstack=inputs=2[vout]; // This part will change for video concatenation depending on what gets answered above
      [1:a][a2pad][a3pad][a4pad]amix=inputs=4:weights=1|1|1|1[aout]
    

混音document
Note that this filter only supports float samples(the amerge and pan audio filters support many formats).
也许你的文件格式很多,试试amerge

为了使用这么多过滤器轻松生成参数,请尝试 FFmpegArgs

FFmpegArg ffmpegArg = new FFmpegArg().OverWriteOutput();
List<ImageMap> imageMaps = new List<ImageMap>();
List<AudioMap> audioMaps = new List<AudioMap>();
foreach (var item in _records)
{
    if (item.IsVideo)
    {
        imageMaps.Add(ffmpegArg.AddImageInput(new ImageFileInput(item.FilePath))
            .TpadFilter().StartDuration(item.Delay).MapOut);
    }
    else
    {
        audioMaps.Add(ffmpegArg.AddAudioInput(new AudioFileInput(item.FilePath))
            .AdelayFilter().Delays(item.Delay).All(true).MapOut);
    }
}
            
var imageMap = imageMaps.HstackFilter().MapOut;
var audioMap = audioMaps.AmergeFilter().MapOut;

ffmpegArg.AddOutput(new VideoFileOutput("out.mp4", imageMap, audioMap));
var result = ffmpegArg
    .Render(c => c
        .WithFFmpegBinaryPath("path to ffmpeg.exe")
        .WithWorkingDirectory("working dir"))
    .Execute();

result.EnsureSuccess();

或通过 kesh 评论

FFmpegArg ffmpegArg = new FFmpegArg().OverWriteOutput();
List<ImageMap> imageMaps = new List<ImageMap>();
List<AudioMap> audioMaps = new List<AudioMap>();
foreach (var item in _records)
{
    if (item.IsVideo)
    {
        imageMaps.Add(ffmpegArg.AddImageInput(new ImageFileInput(item.FilePath))
            .TpadFilter().StartDuration(item.Delay).MapOut);
    }
    else
    {
        audioMaps.Add(ffmpegArg.AddAudioInput(new AudioFileInput(item.FilePath)));
        //audioMaps.Add(ffmpegArg.AddAudioInput(new AudioFileInput(item.FilePath).SsPosition(item.Skip)));
    }
}
var imageMap = imageMaps.HstackFilter().MapOut;
var concatFilter = audioMaps.Select(x => new ConcatGroup(x)).ConcatFilter();

ffmpegArg.AddOutput(new VideoFileOutput("out.mp4", imageMap, concatFilter.AudioMapsOut.First()));
var result = ffmpegArg
    .Render(c => c
        .WithFFmpegBinaryPath("path to ffmpeg.exe")
        .WithWorkingDirectory("working dir"))
    .Execute();

result.EnsureSuccess();