1
mirror of https://github.com/n00mkrad/flowframes synced 2024-09-29 14:38:03 +02:00

Finished support for multiple audio streams - needs more testing

metadata transfer should be optimized (title/language)
This commit is contained in:
N00MKRAD 2021-02-23 23:55:03 +01:00
parent 71f3e583de
commit 6cfcfedbe6
6 changed files with 106 additions and 57 deletions

View File

@ -11,7 +11,7 @@ namespace Flowframes.Data
public AudioTrack(int streamNum, string titleStr, string codecStr)
{
streamIndex = streamNum;
title = titleStr.Trim();
title = titleStr.Trim().Replace(" ", ".");
codec = codecStr.Trim();
}
}

View File

@ -12,8 +12,8 @@ namespace Flowframes.Data
public SubtitleTrack(int streamNum, string langStr, string encodingStr)
{
streamIndex = streamNum;
lang = langStr;
langFriendly = CultureInfo.CurrentCulture.TextInfo.ToTitleCase(langStr.ToLower());
lang = langStr.Trim().Replace(" ", ".");
langFriendly = CultureInfo.CurrentCulture.TextInfo.ToTitleCase(langStr.ToLower().Trim().Replace(" ", "."));
encoding = encodingStr.Trim();
}
}

View File

@ -459,7 +459,6 @@ namespace Flowframes
if (trimCombox.SelectedIndex == 1)
{
trimStartBox.Text = "00:00:00";
Logger.Log("Setting trimEndBox text to FormatUtils.MsToTimestamp(currInDuration) = " + FormatUtils.MsToTimestamp(currInDuration));
trimEndBox.Text = FormatUtils.MsToTimestamp(currInDuration);
}
}

View File

@ -210,6 +210,7 @@ namespace Flowframes.Main
public static async Task MergeAudio(string inputPath, string outVideo, int looptimes = -1)
{
if (!Config.GetBool("keepAudio")) return;
try
{
string audioFileBasePath = Path.Combine(I.current.tempFolder, "audio");
@ -217,16 +218,16 @@ namespace Flowframes.Main
if (inputPath != null && IOUtils.IsPathDirectory(inputPath) && !File.Exists(IOUtils.GetAudioFile(audioFileBasePath))) // Try loading out of same folder as input if input is a folder
audioFileBasePath = Path.Combine(I.current.tempFolder.GetParentDir(), "audio");
if (!File.Exists(IOUtils.GetAudioFile(audioFileBasePath)))
await FfmpegAudioAndMetadata.ExtractAudioTracks(inputPath, audioFileBasePath); // Extract from sourceVideo to audioFile unless it already exists
// if (!File.Exists(IOUtils.GetAudioFile(audioFileBasePath)))
// await FfmpegAudioAndMetadata.ExtractAudioTracks(inputPath, audioFileBasePath); // Extract from sourceVideo to audioFile unless it already exists
if (!File.Exists(IOUtils.GetAudioFile(audioFileBasePath)) || new FileInfo(IOUtils.GetAudioFile(audioFileBasePath)).Length < 4096)
{
Logger.Log("Can't merge audio as there's no extracted audio file in the temp folder.", true);
return;
}
// if (!File.Exists(IOUtils.GetAudioFile(audioFileBasePath)) || new FileInfo(IOUtils.GetAudioFile(audioFileBasePath)).Length < 4096)
// {
// Logger.Log("Can't merge audio as there's no extracted audio file in the temp folder.", true);
// return;
// }
await FfmpegAudioAndMetadata.MergeAudioAndSubs(outVideo, IOUtils.GetAudioFile(audioFileBasePath), I.current.tempFolder); // Merge from audioFile into outVideo
await FfmpegAudioAndMetadata.MergeAudioAndSubs(outVideo, I.current.tempFolder); // Merge from audioFile into outVideo
}
catch (Exception e)
{

View File

@ -95,13 +95,26 @@ namespace Flowframes.Media
return args;
}
public static string GetAudioEnc(Codec codec)
public static bool ContainerSupportsAudioFormat (Interpolate.OutMode outMode, string format)
{
switch (codec)
format = format.Remove(".");
string[] formatsMp4 = new string[] { "m4a", "ac3", "dts" };
string[] formatsMkv = new string[] { "m4a", "ac3", "dts", "ogg", "mp2", "wav" };
string[] formatsWebm = new string[] { "ogg" };
string[] formatsProres = new string[] { "m4a" };
string[] formatsAvi = new string[] { "m4a", "ac3", "dts" };
switch (outMode)
{
case Codec.VP9: return "libopus";
case Interpolate.OutMode.VidMp4: return formatsMp4.Contains(format);
case Interpolate.OutMode.VidMkv: return formatsMkv.Contains(format);
case Interpolate.OutMode.VidWebm: return formatsWebm.Contains(format);
case Interpolate.OutMode.VidProRes: return formatsProres.Contains(format);
case Interpolate.OutMode.VidAvi: return formatsAvi.Contains(format);
}
return "aac";
return false;
}
public static string GetExt(Interpolate.OutMode outMode, bool dot = true)

View File

@ -66,7 +66,7 @@ namespace Flowframes.Media
int streamIndex = line.Remove("Stream #0:").Split(':')[0].GetInt();
string title = "unknown";
string title = "";
string codec = "";
if ((i + 2 < outputLines.Length) && outputLines[i + 1].Contains("Metadata:") && outputLines[i + 2].Contains("title"))
@ -121,13 +121,14 @@ namespace Flowframes.Media
string args = $"-i {inputFile.Wrap()}";
Logger.Log("GetSubtitleTracks()", true, false, "ffmpeg");
string[] outputLines = (await GetFfmpegOutputAsync(args)).SplitIntoLines();
int idx = 0;
for (int i = 0; i < outputLines.Length; i++)
{
string line = outputLines[i];
if (!line.Contains(" Subtitle: ")) continue;
int streamIndex = line.Remove("Stream #0:").Split(':')[0].GetInt();
string lang = "unknown";
string subEnc = "UTF-8";
@ -137,9 +138,8 @@ namespace Flowframes.Media
if ((i + 2 < outputLines.Length) && outputLines[i + 1].Contains("Metadata:") && outputLines[i + 2].Contains("SUB_CHARENC")) // Subtitle encoding is in metadata!
subEnc = outputLines[i + 2].Remove("SUB_CHARENC").Remove(":").TrimWhitespaces();
Logger.Log($"Found subtitle track #{idx} with language '{lang}' and encoding '{subEnc}'", true, false, "ffmpeg");
subtitleTracks.Add(new SubtitleTrack(idx, lang, subEnc));
idx++;
Logger.Log($"Found subtitle track #{streamIndex} with language '{lang}' and encoding '{subEnc}'", true, false, "ffmpeg");
subtitleTracks.Add(new SubtitleTrack(streamIndex, lang, subEnc));
}
return subtitleTracks;
@ -147,56 +147,92 @@ namespace Flowframes.Media
#endregion
public static async Task MergeAudioAndSubs(string inputFile, string audioPath, string tempFolder, int looptimes = -1) // https://superuser.com/a/277667
public static async Task MergeAudioAndSubs(string inputFile, string tempFolder, int looptimes = -1) // https://superuser.com/a/277667
{
Logger.Log($"[FFCmds] Merging audio from {audioPath} into {inputFile}", true);
// Logger.Log($"[FFCmds] Merging audio from {audioPath} into {inputFile}", true);
string containerExt = Path.GetExtension(inputFile);
string tempPath = Path.Combine(tempFolder, $"vid{containerExt}"); // inputFile + "-temp" + Path.GetExtension(inputFile);
string outPath = Path.Combine(tempFolder, $"muxed{containerExt}"); // inputFile + "-temp" + Path.GetExtension(inputFile);
string tempPath = Path.Combine(tempFolder, $"vid{containerExt}");
string outPath = Path.Combine(tempFolder, $"muxed{containerExt}");
File.Move(inputFile, tempPath);
string inName = Path.GetFileName(tempPath);
string audioName = Path.GetFileName(audioPath);
// string audioName = Path.GetFileName(audioPath);
string outName = Path.GetFileName(outPath);
bool subs = Utils.ContainerSupportsSubs(containerExt, false) && Config.GetBool("keepSubs");
string subInputArgs = "";
string subMapArgs = "";
string subMetaArgs = "";
string[] subTracks = subs ? IOUtils.GetFilesSorted(tempFolder, false, "*.srt") : new string[0];
string[] audioTracks = IOUtils.GetFilesSorted(tempFolder, false, "*_audio.*"); // Find audio files
string[] subTracks = IOUtils.GetFilesSorted(tempFolder, false, "*.srt"); // Find subtitle files
for (int subTrack = 0; subTrack < subTracks.Length; subTrack++)
Dictionary<int, string> trackFiles = new Dictionary<int, string>(); // Dict holding all track files with their index
foreach (string audioTrack in audioTracks) // Loop through audio streams to add them to the dict
trackFiles[Path.GetFileNameWithoutExtension(audioTrack).Split('_')[0].GetInt()] = audioTrack; // Add file, dict key is stream index
foreach (string subTrack in subTracks) // Loop through subtitle streams to add them to the dict
trackFiles[Path.GetFileNameWithoutExtension(subTrack).Split('_')[0].GetInt()] = subTrack; // Add file, dict key is stream index
bool audio = Config.GetBool("keepSubs");
bool subs = Utils.ContainerSupportsSubs(containerExt, false) && Config.GetBool("keepSubs");
string trackInputArgs = "";
string trackMapArgs = "";
string trackMetaArgs = "";
// for (int subTrack = 0; subTrack < subTracks.Length; subTrack++)
// {
// trackInputArgs += $" -i {Path.GetFileName(subTracks[subTrack])}"; // Input filename
// trackMapArgs += $" -map {Path.GetFileNameWithoutExtension(subTracks[subTrack]).Split('_')[0].GetInt()}"; // Stream index
// trackMetaArgs += $" -metadata:s:s:{subTrack} language={Path.GetFileNameWithoutExtension(subTracks[subTrack]).Split('_')[1]}"; // Language
// }
SortedDictionary<int, string> sortedTrackFiles = new SortedDictionary<int, string>(trackFiles);
foreach (KeyValuePair<int, string> track in sortedTrackFiles)
{
subInputArgs += $" -i {Path.GetFileName(subTracks[subTrack])}"; // Input filename
subMapArgs += $" -map {Path.GetFileNameWithoutExtension(subTracks[subTrack]).Split('_')[0].GetInt()}"; // Stream index
subMetaArgs += $" -metadata:s:s:{subTrack} language={Path.GetFileNameWithoutExtension(subTracks[subTrack]).Split('_')[1]}"; // Language
int streamIndex = track.Key;
string trackFile = track.Value;
trackInputArgs += $" -i {Path.GetFileName(trackFile)}"; // Input filename
trackMapArgs += $" -map {Path.GetFileNameWithoutExtension(trackFile).Split('_')[0].GetInt()}"; // Set stream index
trackMetaArgs += $" -metadata:s:{streamIndex} title={Path.GetFileNameWithoutExtension(trackFile).Split('_')[1]}"; // Language or title
}
string subCodec = Utils.GetSubCodecForContainer(containerExt);
string args = $" -i {inName} -stream_loop {looptimes} -i {audioName.Wrap()}" +
$"{subInputArgs} -map 0:v -map 1:a {subMapArgs} -c:v copy -c:a copy -c:s {subCodec} {subMetaArgs} -shortest {outName}";
bool allAudioCodecsSupported = true;
foreach (string audioTrack in audioTracks)
if (!Utils.ContainerSupportsAudioFormat(Interpolate.current.outMode, Path.GetExtension(audioTrack)))
allAudioCodecsSupported = false;
if(!allAudioCodecsSupported)
Logger.Log("Warning: Input audio format(s) not fully support in output container. Audio transfer will not be lossless.", false, false, "ffmpeg");
string subArgs = "-c:s " + Utils.GetSubCodecForContainer(containerExt);
string audioArgs = allAudioCodecsSupported ? "-c:a copy" : Utils.GetAudioFallbackArgs(Path.GetExtension(inputFile));
string args = $" -i {inName} -stream_loop {looptimes}" +
$"{trackInputArgs} -map 0:v {trackMapArgs} -c:v copy {audioArgs} {subArgs} {trackMetaArgs} -shortest {outName}";
await RunFfmpeg(args, tempFolder, LogMode.Hidden);
if (File.Exists(outPath) && IOUtils.GetFilesize(outPath) < 1024)
{
Logger.Log("Failed to merge audio losslessly! Trying to re-encode.", false, false, "ffmpeg");
args = $" -i {inName} -stream_loop {looptimes} -i {audioName.Wrap()}" +
$"{subInputArgs} -map 0:v -map 1:a {subMapArgs} -c:v copy {Utils.GetAudioFallbackArgs(Path.GetExtension(inputFile))} -c:s {subCodec} {subMetaArgs} -shortest {outName}";
await RunFfmpeg(args, tempFolder, LogMode.Hidden);
if (File.Exists(outPath) && IOUtils.GetFilesize(outPath) < 1024)
{
Logger.Log("Failed to merge audio, even with re-encoding. Output will not have audio.", false, false, "ffmpeg");
IOUtils.TryMove(tempPath, inputFile); // Move temp file back
IOUtils.TryDeleteIfExists(tempPath);
return;
}
string audioExt = Path.GetExtension(audioPath).Remove(".").ToUpper();
Logger.Log($"Source audio ({audioExt}) has been re-encoded to fit into the target container ({containerExt.Remove(".").ToUpper()}). This may decrease the quality slightly.", false, true, "ffmpeg");
}
// if (File.Exists(outPath) && IOUtils.GetFilesize(outPath) < 1024)
// {
// Logger.Log("Failed to merge audio losslessly! Trying to re-encode.", false, false, "ffmpeg");
//
// args = $" -i {inName} -stream_loop {looptimes} -i {audioName.Wrap()}" +
// $"{trackInputArgs} -map 0:v -map 1:a {trackMapArgs} -c:v copy {Utils.GetAudioFallbackArgs(Path.GetExtension(inputFile))} -c:s {subCodec} {trackMetaArgs} -shortest {outName}";
//
// await RunFfmpeg(args, tempFolder, LogMode.Hidden);
//
// if (File.Exists(outPath) && IOUtils.GetFilesize(outPath) < 1024)
// {
// Logger.Log("Failed to merge audio, even with re-encoding. Output will not have audio.", false, false, "ffmpeg");
// IOUtils.TryMove(tempPath, inputFile); // Move temp file back
// IOUtils.TryDeleteIfExists(tempPath);
// return;
// }
//
// string audioExt = Path.GetExtension(audioPath).Remove(".").ToUpper();
// Logger.Log($"Source audio ({audioExt}) has been re-encoded to fit into the target container ({containerExt.Remove(".").ToUpper()}). This may decrease the quality slightly.", false, true, "ffmpeg");
// }
if (File.Exists(outPath) && IOUtils.GetFilesize(outPath) > 512)
{