Boah ist das rechtsbündig beim Schreiben irritierend, dachte gerade schon der Browser hängt/wär abgeschmiert, weils ich im Textfeld beim Tippen nicht links nichts getan hat. xD
Hier ist mein krasses Tool: https://1drv.ms/u/s!AlNGd4g1Vh9rmwvmLCE4CiTnhHS_?e=2L0HIo
Wie gesagt es macht nix anderes als ffmpeg zu steuern. In der Config (FFmpegFileMergerSettings.json) muss Du eintragen, wo sich Dein ffmpeg und MediaInfo CLI (Command Line MediaInfo ohne GUI) befindet. Das MediaInfo nutze ich um die FPS auszugeben, weil ich mal ne zeitlang kontrollieren wollte ob das Video 60 fps oder 59,97 fps hat. Sollte eine Audiodatei mal nicht die richtige Endung haben, kann man die in der Config auch noch auf eine andere Endung mappen.
Einfach die exe und die Config Datei in einen Ordner ablegen und ein Videofile auf die exe droppen, dann fragt er was er damit machen soll.
Als erste ob er die gedropte Datei in den Ordner der exe kopieren oder verschieben soll. Zu beantworten mit c, m oder n, also copy, move oder nothing.
Dann fragt er ob er audio extrahieren soll. Zu beantworten mit y oder n, also yes oder no.
Und zuletzt fragt, ob die Dateien umbenannt werden soll, so dass die dem Ordernamen entsprechen. Auch zu beantworten mit y oder n, also yes oder no.
Der Name des Tools kommt btw daher, weil ich die exe ursprünglich dazu benutzt habe, um 2 Video Dateien zusammen zu mergen, wenn man 2 Videodateien auf die exe dropt. Die Funktion benutze ich schon lange nicht mehr, könnte aber noch funktionieren. xD
FFMpeg Download:
https://ffmpeg.org/download.html#build-windows
https://www.gyan.dev/ffmpeg/builds/
https://github.com/BtbN/FFmpeg-Builds/releases
MediaInfo CLI Download:
https://mediaarea.net/de/MediaInfo/Download/Windows
Hier ist auch noch der Quick&Dirty Code zum selbst compilen:
using System.Diagnostics;
using Microsoft.Extensions.Configuration;
namespace FFmpegFileMerger
private static string targetPath = AppDomain.CurrentDomain.BaseDirectory;
private static IConfigurationRoot config;
static void Main(string[] args)
//args = new string[] { @"E:\Lets Play\MHGU\2 Edited\39-40\20180913_212456.MOV", @"E:\Lets Play\MHGU\2 Edited\39-40\20180913_215119.MOV", @"E:\Lets Play\MHGU\2 Edited\39-40\20180913_221741.MOV" };
//args = new string[] { @"D:\LPAufnahmen\2022-02-26 13-57-08.mkv" };
config = new ConfigurationBuilder().AddJsonFile("FFmpegFileMergerSettings.json").Build();
endAndOutputResult(new List<string>() { "No arguments given. Press any key to exit." }, ConsoleColor.Red);
List<string> messages = new List<string>();
char action = acknowledgeMoveOrCopy(args[0]);
bool extract = acknowledgeAudioExtract();
if (action == 'n' && extract == false)
getVideoFps(args[0], messages);
messages.Add("Nothing done. Press any key to exit.");
endAndOutputResult(messages, ConsoleColor.Red);
string fileSuffix = string.Empty;
bool rename = acknowledgeRename();
fileSuffix = acknowledgeFileSuffix();
targetFile = Path.Combine(targetPath, new DirectoryInfo(targetPath).Name + fileSuffix + Path.GetExtension(file));
targetFile = Path.Combine(targetPath, Path.GetFileNameWithoutExtension(file) + fileSuffix + Path.GetExtension(file));
extractAudioFromVideoFile(file, Path.Combine(Path.GetDirectoryName(targetFile), Path.GetFileNameWithoutExtension(targetFile)), fileSuffix, messages);
if (action == 'c' || action == 'm')
processFile(args[0], action, targetFile, fileSuffix);
messages.Add(getMessage(action, rename));
getVideoFps(targetFile, messages);
messages.Add("Done. Press any key to exit.");
endAndOutputResult(messages, ConsoleColor.Green);
if (acknowledgeMerge(args))
mergeVideoFiles(args, acknowledgeAudioExtractAfterVideoMerge(), acknowledgeFileSuffix(), messages);
endAndOutputResult(messages, ConsoleColor.Green);
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine(e.ToString());
Console.WriteLine("Error. Press any key to exit.");
private static string acknowledgeFileSuffix()
string suffix = string.Empty;
Console.WriteLine("File Suffix for file name?");
suffix= Console.ReadLine().ToLower();
if (!string.IsNullOrWhiteSpace(suffix))
private static string getMessage(char action, bool rename)
message = "File " + actionWord;
message += " and renamed";
private static bool acknowledgeRename()
while (choice != "y" && choice != "n")
Console.WriteLine("Rename output file(s) to directory name? (y/n)");
choice = Console.ReadLine().ToLower();
private static void processFile(string file, char action, string targetFile, string fileSuffix)
CopyFileExWrapper.CopyFile(file, targetFile, CopyFileOptions.FailIfDestinationExists, outputCopyProgress);
private static char acknowledgeMoveOrCopy(string file)
Console.WriteLine("File: " + file);
while (choice != "c" && choice != "m" && choice != "n")
Console.WriteLine("Copy or move file? (c/m/n)");
choice = Console.ReadLine().ToLower();
private static bool acknowledgeAudioExtract()
while (choice != "y" && choice != "n")
Console.WriteLine("Exctract audio from file? (y/n)");
choice = Console.ReadLine().ToLower();
private static bool acknowledgeMerge(string[] files)
foreach (string file in files)
Console.WriteLine("Adding file " + file);
while (choice != "y" && choice != "n")
Console.WriteLine("Merge files to current directoy? (y/n)");
choice = Console.ReadLine().ToLower();
endAndOutputResult(new List<string>() { "Nothing done. Press any key to exit." }, ConsoleColor.Red);
private static bool acknowledgeAudioExtractAfterVideoMerge()
while (choice != "y" && choice != "n")
Console.WriteLine("Extract audio after merge? (y/n)");
choice = Console.ReadLine().ToLower();
private static void extractAudioFromVideoFile(string file, string targetFileWithoutExtension, string fileSuffix, List<string> messages)
List<string> audioExtensions = getAudioFileExtensions(file);
string argumentString = "";
foreach (string audioExtension in audioExtensions)
string targetFile = targetFileWithoutExtension + "-audioextract-" + (audioCount + 1) + audioExtension;
argumentString += " -map a:" + audioCount + " -c copy \"" + targetFile + "\"";
ProcessStartInfo info = new ProcessStartInfo(config["FFmpegFilePath"], "-i \"" + file + "\"" + argumentString);
info.UseShellExecute = false;
using (Process p = Process.Start(info))
messages.Add("Command used for audio extract: " + info.FileName + " " + info.Arguments);
private static List<string> getAudioFileExtensions(string file)
List<string> extensions = new List<string>();
ProcessStartInfo info = new ProcessStartInfo(config["MediaInfoCliFilePath"], $"--output=Audio;\"%Format% \" \"{ file }\"");
info.UseShellExecute = false;
info.RedirectStandardOutput = true;
Dictionary<string, string> audioCodecsFileExtensions = new Dictionary<string, string>();
foreach (IConfigurationSection child in config.GetSection("AudioCodecsFileExtensions").GetChildren())
//don't add sections to config.
if(child.Key != null && child.Value!=null)
audioCodecsFileExtensions.Add(child.Key, child.Value);
using (Process p = Process.Start(info))
output = p.StandardOutput.ReadLine();
output = output.Substring(0, output.Length - 1);
parts = output.Split(' ');
foreach (string format in parts)
string formatLower = format.ToLower();
if (audioCodecsFileExtensions.ContainsKey(formatLower))
extensions.Add("." + audioCodecsFileExtensions[formatLower]);
extensions.Add("." + formatLower);
throw new ApplicationException("No audio codec information found.");
private static void getVideoFps(string file, List<string> messages)
ProcessStartInfo info = new ProcessStartInfo(config["MediaInfoCliFilePath"], $"--output=Video;\"%FrameRate_Mode% %FrameRate% \" \"{ file }\"");
info.UseShellExecute = false;
info.RedirectStandardOutput = true;
messages.Add("Command used go get fps info r_frame_rate: " + info.FileName + " " + info.Arguments);
using (Process p = Process.Start(info))
output = p.StandardOutput.ReadLine();
output = output.Substring(0, output.Length - 1);
parts = output.Split(' ');
for (int i = 0; i < parts.Length / 2; i++)
messages.Add($"Video stream {i} frame rate: {parts[i * 2]} {parts[i * 2 + 1]}");
throw new ApplicationException("No video fps information found.");
private static void endAndOutputResult(List<string> messages, ConsoleColor color)
Console.ForegroundColor = color;
Console.WriteLine(string.Empty);
foreach (string message in messages)
if (message.Contains("frame rate") && !message.Contains("CFR 60.000"))
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine(message);
Console.ForegroundColor = color;
Console.WriteLine(message);
private static void mergeVideoFiles(string[] files, bool extractAudioAfter, string fileSuffix, List<string> messages)
string inputFile = Path.Combine(targetPath, "input.txt");
using (StreamWriter parameterFile = new StreamWriter(inputFile))
foreach (string file in files)
parameterFile.WriteLine("file '" + file + "'");
string targetFile = Path.Combine(targetPath, new DirectoryInfo(targetPath).Name) + fileSuffix + Path.GetExtension(files[0]);
ProcessStartInfo info = new ProcessStartInfo(config["FFmpegFilePath"], "-f concat -safe 0 -i \""
+ inputFile + "\" -c copy \"" + targetFile + "\"");
info.UseShellExecute = false;
using (Process p = Process.Start(info))
messages.Add("Command used for file merge: " + info.FileName + " " + info.Arguments);
extractAudioFromVideoFile(targetFile, Path.Combine(Path.GetFullPath(targetFile), Path.GetFileNameWithoutExtension(targetFile)), fileSuffix, messages);
getVideoFps(targetFile, messages);
messages.Add("Done. Press any key to exit.");
public static void Move(string inputFilePath, string outputFilePath)
FileInfo inputFileInfo = new FileInfo(inputFilePath);
FileInfo outputFileInfo = new FileInfo(outputFilePath);
if (inputFileInfo.DirectoryName[0] == outputFileInfo.DirectoryName[0])
File.Move(inputFilePath, outputFilePath);
CopyFileExWrapper.CopyFile(inputFilePath, outputFilePath, CopyFileOptions.FailIfDestinationExists, outputCopyProgress);
File.Delete(inputFilePath);
/*public static void Copy(string inputFilePath, string outputFilePath)
int bufferSize = 1024 * 1024;
using (FileStream targetFileStream = new FileStream(outputFilePath, FileMode.CreateNew, FileAccess.Write, FileShare.None))
using (FileStream sourceFileStream = new FileStream(inputFilePath, FileMode.Open, FileAccess.ReadWrite))
targetFileStream.SetLength(sourceFileStream.Length);
byte[] bytes = new byte[bufferSize];
double totalLength = sourceFileStream.Length;
while ((bytesRead = sourceFileStream.Read(bytes, 0, bufferSize)) > 0)
targetFileStream.Write(bytes, 0, bytesRead);
double percentage = Math.Round((targetFileStream.Position / totalLength) * 100, 2);
Console.Write("\rCopy/move progress: " + percentage + "% ");
public static int outputCopyProgress (long totalFileSize, long totalBytesTransferred,
long streamSize, long streamBytesTransferred,
int streamNumber, int callbackReason,
IntPtr sourceFile, IntPtr destinationFile, IntPtr data)
double copyProgress = Math.Round((totalBytesTransferred / (double)totalFileSize) * 100.0, 2);
Console.Write("\rCopy/move progress: " + copyProgress + "% ");
return (int)CopyAction.Continue;
Alles anzeigen
Könnte man auch mit was einfacherem wie PowerShell machen, aber in C# bin ich mehr im Thema und schneller.