Finished multi-track subtitle support

This commit is contained in:
N00MKRAD
2021-01-16 14:42:47 +01:00
parent e18c25c694
commit 884bb6678e
8 changed files with 110 additions and 42 deletions

View File

@@ -224,7 +224,7 @@ namespace Flowframes
} }
} }
public static async Task ExtractSubtitles (string inputFile, string outFolder) public static async Task ExtractSubtitles (string inputFile, string outFolder, Interpolate.OutMode outMode)
{ {
Dictionary<int, string> subDict = await GetSubtitleTracks(inputFile); Dictionary<int, string> subDict = await GetSubtitleTracks(inputFile);
foreach (KeyValuePair<int, string> subTrack in subDict) foreach (KeyValuePair<int, string> subTrack in subDict)
@@ -234,14 +234,14 @@ namespace Flowframes
string args = $" -loglevel error -i {inputFile.Wrap()} -map 0:s:{subTrack.Key} {outPath.Wrap()}"; string args = $" -loglevel error -i {inputFile.Wrap()} -map 0:s:{subTrack.Key} {outPath.Wrap()}";
await AvProcess.RunFfmpeg(args, AvProcess.LogMode.Hidden); await AvProcess.RunFfmpeg(args, AvProcess.LogMode.Hidden);
if (AvProcess.lastOutputFfmpeg.Contains("matches no streams")) // Break if there are no more subtitle tracks if (AvProcess.lastOutputFfmpeg.Contains("matches no streams")) // Break if there are no more subtitle tracks
{
Logger.Log($"Extracted {subTrack.Key + 1} subtitle tracks from the input video.");
break; break;
} Logger.Log($"[FFCmds] Extracted subtitle track {subTrack.Key} to {outPath} ({FormatUtils.Bytes(IOUtils.GetFilesize(outPath))})", true, false, "ffmpeg");
Logger.Log($"[FFCmds] Extracted subtitle track {subTrack.Key} to {outPath} ({FormatUtils.Bytes(IOUtils.GetFilesize(outPath))})", true);
} }
if(subDict.Count > 0) if(subDict.Count > 0)
{
Logger.Log($"Extracted {subDict.Count} subtitle tracks from the input video."); Logger.Log($"Extracted {subDict.Count} subtitle tracks from the input video.");
Utils.ContainerSupportsSubs(Utils.GetExt(outMode), true);
}
} }
public static async Task<Dictionary<int, string>> GetSubtitleTracks (string inputFile) public static async Task<Dictionary<int, string>> GetSubtitleTracks (string inputFile)
@@ -257,41 +257,72 @@ namespace Flowframes
bool hasLangInfo = line.Contains("(") && line.Contains("): Subtitle: "); bool hasLangInfo = line.Contains("(") && line.Contains("): Subtitle: ");
if (hasLangInfo) if (hasLangInfo)
lang = line.Split('(')[1].Split(')')[0]; lang = line.Split('(')[1].Split(')')[0];
Logger.Log($"[FFCmds] Detected subtitle track {idx} '{lang}'", true);
subDict.Add(idx, lang); subDict.Add(idx, lang);
idx++; idx++;
} }
return subDict; return subDict;
} }
public static async Task MergeAudio(string inputFile, string audioPath, int looptimes = -1) // https://superuser.com/a/277667 public static async Task MergeAudioAndSubs(string inputFile, string audioPath, 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 tempPath = inputFile + "-temp" + Path.GetExtension(inputFile); string containerExt = Path.GetExtension(inputFile);
string args = $" -i {inputFile.Wrap()} -stream_loop {looptimes} -i {audioPath.Wrap()} -shortest -c copy {tempPath.Wrap()}"; string tempPath = Path.Combine(tempFolder, $"vid{containerExt}"); // inputFile + "-temp" + Path.GetExtension(inputFile);
await AvProcess.RunFfmpeg(args, AvProcess.LogMode.Hidden); string outPath = Path.Combine(tempFolder, $"muxed{containerExt}"); // inputFile + "-temp" + Path.GetExtension(inputFile);
if ((File.Exists(tempPath) && IOUtils.GetFilesize(tempPath) < 512) || AvProcess.lastOutputFfmpeg.Contains("Invalid data")) File.Move(inputFile, tempPath);
string inName = Path.GetFileName(tempPath);
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];
for (int subTrack = 0; subTrack < subTracks.Length; subTrack++)
{ {
Logger.Log("Failed to merge audio losslessly! Trying to re-encode."); subInputArgs += $" -i {Path.GetFileName(subTracks[subTrack])}";
subMapArgs += $" -map {subTrack+2}";
subMetaArgs += $" -metadata:s:s:{subTrack} language={Path.GetFileNameWithoutExtension(subTracks[subTrack]).Split('-').Last()}";
}
args = $" -i {inputFile.Wrap()} -stream_loop {looptimes} -i {audioPath.Wrap()} -shortest -c:v copy {Utils.GetAudioFallbackArgs(Path.GetExtension(inputFile))} {tempPath.Wrap()}"; string subCodec = Utils.GetSubCodecForContainer(containerExt);
await AvProcess.RunFfmpeg(args, AvProcess.LogMode.Hidden); 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}";
if ((File.Exists(tempPath) && IOUtils.GetFilesize(tempPath) < 512) || AvProcess.lastOutputFfmpeg.Contains("Invalid data")) await AvProcess.RunFfmpeg(args, tempFolder, AvProcess.LogMode.Hidden);
if ((File.Exists(outPath) && IOUtils.GetFilesize(outPath) < 1024) || AvProcess.lastOutputFfmpeg.Contains("Invalid data") || AvProcess.lastOutputFfmpeg.Contains("Error initializing output stream"))
{
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 AvProcess.RunFfmpeg(args, tempFolder, AvProcess.LogMode.Hidden);
if ((File.Exists(outPath) && IOUtils.GetFilesize(outPath) < 1024) || AvProcess.lastOutputFfmpeg.Contains("Invalid data") || AvProcess.lastOutputFfmpeg.Contains("Error initializing output stream"))
{ {
Logger.Log("Failed to merge audio, even with re-encoding. Output will not have audio."); Logger.Log("Failed to merge audio, even with re-encoding. Output will not have audio.", false, false, "ffmpeg");
IOUtils.TryDeleteIfExists(tempPath); IOUtils.TryDeleteIfExists(tempPath);
return; return;
} }
string audioExt = Path.GetExtension(audioPath).Remove(".").ToUpper(); string audioExt = Path.GetExtension(audioPath).Remove(".").ToUpper();
string containerExt = Path.GetExtension(inputFile).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");
Logger.Log($"Source audio ({audioExt}) has been re-encoded to fit into the target container ({containerExt}). This may decrease the quality slightly.", false, true); }
//string movePath = Path.ChangeExtension(inputFile, Path.GetExtension(tempPath));
//File.Delete(movePath);
if(File.Exists(outPath) && IOUtils.GetFilesize(outPath) > 512)
{
File.Delete(tempPath);
File.Move(outPath, inputFile);
}
else
{
File.Move(tempPath, inputFile);
} }
string movePath = Path.ChangeExtension(inputFile, Path.GetExtension(tempPath));
File.Delete(movePath);
File.Delete(inputFile);
File.Move(tempPath, movePath);
} }
public static float GetFramerate(string inputFile) public static float GetFramerate(string inputFile)

View File

@@ -145,7 +145,7 @@ namespace Flowframes.AudioVideo
bitrate = $"{Config.GetInt("opusBitrate", 128)}"; bitrate = $"{Config.GetInt("opusBitrate", 128)}";
} }
return $"-c:a {codec} -b:a {bitrate} -ac 2"; return $"-c:a {codec} -b:a {bitrate}k -ac 2";
} }
public static string GetAudioExtForContainer(string containerExt) public static string GetAudioExtForContainer(string containerExt)
@@ -158,5 +158,25 @@ namespace Flowframes.AudioVideo
return ext; return ext;
} }
public static string GetSubCodecForContainer(string containerExt)
{
containerExt = containerExt.Remove(".");
if (containerExt == "mp4") return "mov_text";
if (containerExt == "webm") return "webvtt";
return "copy"; // Default: Copy SRT subs
}
public static bool ContainerSupportsSubs(string containerExt, bool showWarningIfNotSupported = true)
{
containerExt = containerExt.Remove(".");
bool supported = (containerExt == "mp4" || containerExt == "mkv" || containerExt == "webm" || containerExt == "mov");
Logger.Log($"Subtitles {(supported ? "are supported" : "not supported")} by {containerExt.ToUpper()}", true);
if (Config.GetBool("keepSubs") && !supported)
Logger.Log($"Warning: Subtitles are enabled, but {containerExt.ToUpper()} does not support them. MKV is recommended instead.");
return supported;
}
} }
} }

View File

@@ -188,7 +188,7 @@
this.outModeCombox.FormattingEnabled = true; this.outModeCombox.FormattingEnabled = true;
this.outModeCombox.Items.AddRange(new object[] { this.outModeCombox.Items.AddRange(new object[] {
"MP4 Video (h264, h265)", "MP4 Video (h264, h265)",
"MKV Video (h264, h265) (Supports more audio codecs than MP4)", "MKV Video (h264, h265) (Best Audio/Subtitles Support)",
"WEBM Video (Google VP9)", "WEBM Video (Google VP9)",
"MOV Video (Apple ProRes)", "MOV Video (Apple ProRes)",
"AVI Video (ffv1, huffyuv, rawvideo)", "AVI Video (ffv1, huffyuv, rawvideo)",

View File

@@ -75,7 +75,7 @@
this.label15 = new System.Windows.Forms.Label(); this.label15 = new System.Windows.Forms.Label();
this.dedupMode = new System.Windows.Forms.ComboBox(); this.dedupMode = new System.Windows.Forms.ComboBox();
this.label2 = new System.Windows.Forms.Label(); this.label2 = new System.Windows.Forms.Label();
this.enableAudio = new System.Windows.Forms.CheckBox(); this.keepAudio = new System.Windows.Forms.CheckBox();
this.label1 = new System.Windows.Forms.Label(); this.label1 = new System.Windows.Forms.Label();
this.aiOptsPage = new Cyotek.Windows.Forms.TabListPage(); this.aiOptsPage = new Cyotek.Windows.Forms.TabListPage();
this.label30 = new System.Windows.Forms.Label(); this.label30 = new System.Windows.Forms.Label();
@@ -152,6 +152,7 @@
this.toolTip1 = new System.Windows.Forms.ToolTip(this.components); this.toolTip1 = new System.Windows.Forms.ToolTip(this.components);
this.ncnnThreads = new System.Windows.Forms.NumericUpDown(); this.ncnnThreads = new System.Windows.Forms.NumericUpDown();
this.minOutVidLength = new System.Windows.Forms.NumericUpDown(); this.minOutVidLength = new System.Windows.Forms.NumericUpDown();
this.keepSubs = new System.Windows.Forms.CheckBox();
this.settingsTabList.SuspendLayout(); this.settingsTabList.SuspendLayout();
this.generalTab.SuspendLayout(); this.generalTab.SuspendLayout();
this.tabListPage2.SuspendLayout(); this.tabListPage2.SuspendLayout();
@@ -450,6 +451,7 @@
// tabListPage2 // tabListPage2
// //
this.tabListPage2.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(48)))), ((int)(((byte)(48)))), ((int)(((byte)(48))))); this.tabListPage2.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(48)))), ((int)(((byte)(48)))), ((int)(((byte)(48)))));
this.tabListPage2.Controls.Add(this.keepSubs);
this.tabListPage2.Controls.Add(this.scnDetectValue); this.tabListPage2.Controls.Add(this.scnDetectValue);
this.tabListPage2.Controls.Add(this.sbsAllowAutoEnc); this.tabListPage2.Controls.Add(this.sbsAllowAutoEnc);
this.tabListPage2.Controls.Add(this.label4); this.tabListPage2.Controls.Add(this.label4);
@@ -468,7 +470,7 @@
this.tabListPage2.Controls.Add(this.label15); this.tabListPage2.Controls.Add(this.label15);
this.tabListPage2.Controls.Add(this.dedupMode); this.tabListPage2.Controls.Add(this.dedupMode);
this.tabListPage2.Controls.Add(this.label2); this.tabListPage2.Controls.Add(this.label2);
this.tabListPage2.Controls.Add(this.enableAudio); this.tabListPage2.Controls.Add(this.keepAudio);
this.tabListPage2.Controls.Add(this.label1); this.tabListPage2.Controls.Add(this.label1);
this.tabListPage2.Name = "tabListPage2"; this.tabListPage2.Name = "tabListPage2";
this.tabListPage2.Size = new System.Drawing.Size(762, 419); this.tabListPage2.Size = new System.Drawing.Size(762, 419);
@@ -742,14 +744,15 @@
this.label2.TabIndex = 26; this.label2.TabIndex = 26;
this.label2.Text = "Frame De-Duplication Mode"; this.label2.Text = "Frame De-Duplication Mode";
// //
// enableAudio // keepAudio
// //
this.enableAudio.AutoSize = true; this.keepAudio.AutoSize = true;
this.enableAudio.Location = new System.Drawing.Point(280, 40); this.keepAudio.Location = new System.Drawing.Point(277, 39);
this.enableAudio.Name = "enableAudio"; this.keepAudio.Name = "keepAudio";
this.enableAudio.Size = new System.Drawing.Size(15, 14); this.keepAudio.Size = new System.Drawing.Size(81, 17);
this.enableAudio.TabIndex = 25; this.keepAudio.TabIndex = 25;
this.enableAudio.UseVisualStyleBackColor = true; this.keepAudio.Text = "Keep Audio";
this.keepAudio.UseVisualStyleBackColor = true;
// //
// label1 // label1
// //
@@ -757,9 +760,9 @@
this.label1.Location = new System.Drawing.Point(10, 40); this.label1.Location = new System.Drawing.Point(10, 40);
this.label1.Margin = new System.Windows.Forms.Padding(10, 10, 10, 7); this.label1.Margin = new System.Windows.Forms.Padding(10, 10, 10, 7);
this.label1.Name = "label1"; this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(166, 13); this.label1.Size = new System.Drawing.Size(124, 13);
this.label1.TabIndex = 24; this.label1.TabIndex = 24;
this.label1.Text = "Copy Audio To Interpolated Video"; this.label1.Text = "Input Media To Preserve";
// //
// aiOptsPage // aiOptsPage
// //
@@ -1722,6 +1725,16 @@
0, 0,
131072}); 131072});
// //
// keepSubs
//
this.keepSubs.AutoSize = true;
this.keepSubs.Location = new System.Drawing.Point(364, 39);
this.keepSubs.Name = "keepSubs";
this.keepSubs.Size = new System.Drawing.Size(94, 17);
this.keepSubs.TabIndex = 75;
this.keepSubs.Text = "Keep Subtitles";
this.keepSubs.UseVisualStyleBackColor = true;
//
// SettingsForm // SettingsForm
// //
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
@@ -1770,7 +1783,7 @@
private System.Windows.Forms.CheckBox delLogsOnStartup; private System.Windows.Forms.CheckBox delLogsOnStartup;
private System.Windows.Forms.Label label11; private System.Windows.Forms.Label label11;
private System.Windows.Forms.Label label2; private System.Windows.Forms.Label label2;
private System.Windows.Forms.CheckBox enableAudio; private System.Windows.Forms.CheckBox keepAudio;
private System.Windows.Forms.Label label1; private System.Windows.Forms.Label label1;
private System.Windows.Forms.ComboBox dedupMode; private System.Windows.Forms.ComboBox dedupMode;
private System.Windows.Forms.Label label3; private System.Windows.Forms.Label label3;
@@ -1884,5 +1897,6 @@
private System.Windows.Forms.NumericUpDown dedupThresh; private System.Windows.Forms.NumericUpDown dedupThresh;
private System.Windows.Forms.NumericUpDown ncnnThreads; private System.Windows.Forms.NumericUpDown ncnnThreads;
private System.Windows.Forms.NumericUpDown minOutVidLength; private System.Windows.Forms.NumericUpDown minOutVidLength;
private System.Windows.Forms.CheckBox keepSubs;
} }
} }

View File

@@ -70,7 +70,8 @@ namespace Flowframes.Forms
ConfigParser.SaveGuiElement(clearLogOnInput); ConfigParser.SaveGuiElement(clearLogOnInput);
ConfigParser.SaveGuiElement(modelSuffix); ConfigParser.SaveGuiElement(modelSuffix);
// Interpolation // Interpolation
ConfigParser.SaveGuiElement(enableAudio); ConfigParser.SaveGuiElement(keepAudio);
ConfigParser.SaveGuiElement(keepSubs);
ConfigParser.SaveComboxIndex(dedupMode); ConfigParser.SaveComboxIndex(dedupMode);
ConfigParser.SaveComboxIndex(mpdecimateMode); ConfigParser.SaveComboxIndex(mpdecimateMode);
ConfigParser.SaveGuiElement(dedupThresh); ConfigParser.SaveGuiElement(dedupThresh);
@@ -117,7 +118,8 @@ namespace Flowframes.Forms
ConfigParser.LoadGuiElement(clearLogOnInput); ConfigParser.LoadGuiElement(clearLogOnInput);
ConfigParser.LoadGuiElement(modelSuffix); ConfigParser.LoadGuiElement(modelSuffix);
// Interpolation // Interpolation
ConfigParser.LoadGuiElement(enableAudio); ConfigParser.LoadGuiElement(keepAudio);
ConfigParser.LoadGuiElement(keepSubs);
ConfigParser.LoadComboxIndex(dedupMode); ConfigParser.LoadComboxIndex(dedupMode);
ConfigParser.LoadComboxIndex(mpdecimateMode); ConfigParser.LoadComboxIndex(mpdecimateMode);
ConfigParser.LoadGuiElement(dedupThresh); ConfigParser.LoadGuiElement(dedupThresh);

View File

@@ -122,7 +122,8 @@ namespace Flowframes.IO
// Interpolation // Interpolation
if (key == "dedupMode") return WriteDefault(key, "2"); if (key == "dedupMode") return WriteDefault(key, "2");
if (key == "dedupThresh") return WriteDefault(key, "2"); if (key == "dedupThresh") return WriteDefault(key, "2");
if (key == "enableAudio") return WriteDefault(key, "True"); if (key == "keepAudio") return WriteDefault(key, "True");
if (key == "keepSubs") return WriteDefault(key, "True");
if (key == "autoDedupFrames") return WriteDefault(key, "100"); if (key == "autoDedupFrames") return WriteDefault(key, "100");
if (key == "vfrDedupe") return WriteDefault(key, "True"); if (key == "vfrDedupe") return WriteDefault(key, "True");
if (key == "scnDetectValue") return WriteDefault(key, "0.2"); if (key == "scnDetectValue") return WriteDefault(key, "0.2");

View File

@@ -204,7 +204,7 @@ namespace Flowframes.Main
public static async Task MergeAudio(string inputPath, string outVideo, int looptimes = -1) public static async Task MergeAudio(string inputPath, string outVideo, int looptimes = -1)
{ {
if (!Config.GetBool("enableAudio")) return; if (!Config.GetBool("keepAudio")) return;
try try
{ {
string audioFileBasePath = Path.Combine(i.current.tempFolder, "audio"); string audioFileBasePath = Path.Combine(i.current.tempFolder, "audio");
@@ -221,7 +221,7 @@ namespace Flowframes.Main
return; return;
} }
await FFmpegCommands.MergeAudio(outVideo, IOUtils.GetAudioFile(audioFileBasePath)); // Merge from audioFile into outVideo await FFmpegCommands.MergeAudioAndSubs(outVideo, IOUtils.GetAudioFile(audioFileBasePath), i.current.tempFolder); // Merge from audioFile into outVideo
} }
catch (Exception e) catch (Exception e)
{ {

View File

@@ -98,7 +98,7 @@ namespace Flowframes
await FFmpegCommands.ExtractAudio(inPath, audioFile); await FFmpegCommands.ExtractAudio(inPath, audioFile);
} }
await FFmpegCommands.ExtractSubtitles(inPath, current.tempFolder); await FFmpegCommands.ExtractSubtitles(inPath, current.tempFolder, current.outMode);
} }
public static async Task PostProcessFrames (bool sbsMode = false) public static async Task PostProcessFrames (bool sbsMode = false)