diff --git a/Code/Data/AudioTrack.cs b/Code/Data/AudioTrack.cs index 9b8a469..356d2a8 100644 --- a/Code/Data/AudioTrack.cs +++ b/Code/Data/AudioTrack.cs @@ -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(); } } diff --git a/Code/Data/SubtitleTrack.cs b/Code/Data/SubtitleTrack.cs index 56a3b92..f91f90d 100644 --- a/Code/Data/SubtitleTrack.cs +++ b/Code/Data/SubtitleTrack.cs @@ -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(); } } diff --git a/Code/Form1.cs b/Code/Form1.cs index 9ac7bf0..64e1bb6 100644 --- a/Code/Form1.cs +++ b/Code/Form1.cs @@ -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); } } diff --git a/Code/Main/CreateVideo.cs b/Code/Main/CreateVideo.cs index c757963..a7e301f 100644 --- a/Code/Main/CreateVideo.cs +++ b/Code/Main/CreateVideo.cs @@ -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) { diff --git a/Code/Media/FFmpegUtils.cs b/Code/Media/FFmpegUtils.cs index e3a7ac4..e849a50 100644 --- a/Code/Media/FFmpegUtils.cs +++ b/Code/Media/FFmpegUtils.cs @@ -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) diff --git a/Code/Media/FfmpegAudioAndMetadata.cs b/Code/Media/FfmpegAudioAndMetadata.cs index 057b5ef..079679f 100644 --- a/Code/Media/FfmpegAudioAndMetadata.cs +++ b/Code/Media/FfmpegAudioAndMetadata.cs @@ -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 trackFiles = new Dictionary(); // 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 sortedTrackFiles = new SortedDictionary(trackFiles); + + foreach (KeyValuePair 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) {