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);
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()}";
await AvProcess.RunFfmpeg(args, AvProcess.LogMode.Hidden);
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;
}
Logger.Log($"[FFCmds] Extracted subtitle track {subTrack.Key} to {outPath} ({FormatUtils.Bytes(IOUtils.GetFilesize(outPath))})", true);
Logger.Log($"[FFCmds] Extracted subtitle track {subTrack.Key} to {outPath} ({FormatUtils.Bytes(IOUtils.GetFilesize(outPath))})", true, false, "ffmpeg");
}
if(subDict.Count > 0)
{
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)
@@ -257,41 +257,72 @@ namespace Flowframes
bool hasLangInfo = line.Contains("(") && line.Contains("): Subtitle: ");
if (hasLangInfo)
lang = line.Split('(')[1].Split(')')[0];
Logger.Log($"[FFCmds] Detected subtitle track {idx} '{lang}'", true);
subDict.Add(idx, lang);
idx++;
}
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);
string tempPath = inputFile + "-temp" + Path.GetExtension(inputFile);
string args = $" -i {inputFile.Wrap()} -stream_loop {looptimes} -i {audioPath.Wrap()} -shortest -c copy {tempPath.Wrap()}";
await AvProcess.RunFfmpeg(args, AvProcess.LogMode.Hidden);
if ((File.Exists(tempPath) && IOUtils.GetFilesize(tempPath) < 512) || AvProcess.lastOutputFfmpeg.Contains("Invalid data"))
{
Logger.Log("Failed to merge audio losslessly! Trying to re-encode.");
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);
File.Move(inputFile, tempPath);
string inName = Path.GetFileName(tempPath);
string audioName = Path.GetFileName(audioPath);
string outName = Path.GetFileName(outPath);
args = $" -i {inputFile.Wrap()} -stream_loop {looptimes} -i {audioPath.Wrap()} -shortest -c:v copy {Utils.GetAudioFallbackArgs(Path.GetExtension(inputFile))} {tempPath.Wrap()}";
await AvProcess.RunFfmpeg(args, AvProcess.LogMode.Hidden);
if ((File.Exists(tempPath) && IOUtils.GetFilesize(tempPath) < 512) || AvProcess.lastOutputFfmpeg.Contains("Invalid data"))
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, even with re-encoding. Output will not have audio.");
subInputArgs += $" -i {Path.GetFileName(subTracks[subTrack])}";
subMapArgs += $" -map {subTrack+2}";
subMetaArgs += $" -metadata:s:s:{subTrack} language={Path.GetFileNameWithoutExtension(subTracks[subTrack]).Split('-').Last()}";
}
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}";
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.", false, false, "ffmpeg");
IOUtils.TryDeleteIfExists(tempPath);
return;
}
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}). This may decrease the quality slightly.", false, true);
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");
}
//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)

View File

@@ -145,7 +145,7 @@ namespace Flowframes.AudioVideo
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)
@@ -158,5 +158,25 @@ namespace Flowframes.AudioVideo
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.Items.AddRange(new object[] {
"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)",
"MOV Video (Apple ProRes)",
"AVI Video (ffv1, huffyuv, rawvideo)",

View File

@@ -75,7 +75,7 @@
this.label15 = new System.Windows.Forms.Label();
this.dedupMode = new System.Windows.Forms.ComboBox();
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.aiOptsPage = new Cyotek.Windows.Forms.TabListPage();
this.label30 = new System.Windows.Forms.Label();
@@ -152,6 +152,7 @@
this.toolTip1 = new System.Windows.Forms.ToolTip(this.components);
this.ncnnThreads = new System.Windows.Forms.NumericUpDown();
this.minOutVidLength = new System.Windows.Forms.NumericUpDown();
this.keepSubs = new System.Windows.Forms.CheckBox();
this.settingsTabList.SuspendLayout();
this.generalTab.SuspendLayout();
this.tabListPage2.SuspendLayout();
@@ -450,6 +451,7 @@
// tabListPage2
//
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.sbsAllowAutoEnc);
this.tabListPage2.Controls.Add(this.label4);
@@ -468,7 +470,7 @@
this.tabListPage2.Controls.Add(this.label15);
this.tabListPage2.Controls.Add(this.dedupMode);
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.Name = "tabListPage2";
this.tabListPage2.Size = new System.Drawing.Size(762, 419);
@@ -742,14 +744,15 @@
this.label2.TabIndex = 26;
this.label2.Text = "Frame De-Duplication Mode";
//
// enableAudio
// keepAudio
//
this.enableAudio.AutoSize = true;
this.enableAudio.Location = new System.Drawing.Point(280, 40);
this.enableAudio.Name = "enableAudio";
this.enableAudio.Size = new System.Drawing.Size(15, 14);
this.enableAudio.TabIndex = 25;
this.enableAudio.UseVisualStyleBackColor = true;
this.keepAudio.AutoSize = true;
this.keepAudio.Location = new System.Drawing.Point(277, 39);
this.keepAudio.Name = "keepAudio";
this.keepAudio.Size = new System.Drawing.Size(81, 17);
this.keepAudio.TabIndex = 25;
this.keepAudio.Text = "Keep Audio";
this.keepAudio.UseVisualStyleBackColor = true;
//
// label1
//
@@ -757,9 +760,9 @@
this.label1.Location = new System.Drawing.Point(10, 40);
this.label1.Margin = new System.Windows.Forms.Padding(10, 10, 10, 7);
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.Text = "Copy Audio To Interpolated Video";
this.label1.Text = "Input Media To Preserve";
//
// aiOptsPage
//
@@ -1722,6 +1725,16 @@
0,
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
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
@@ -1770,7 +1783,7 @@
private System.Windows.Forms.CheckBox delLogsOnStartup;
private System.Windows.Forms.Label label11;
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.ComboBox dedupMode;
private System.Windows.Forms.Label label3;
@@ -1884,5 +1897,6 @@
private System.Windows.Forms.NumericUpDown dedupThresh;
private System.Windows.Forms.NumericUpDown ncnnThreads;
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(modelSuffix);
// Interpolation
ConfigParser.SaveGuiElement(enableAudio);
ConfigParser.SaveGuiElement(keepAudio);
ConfigParser.SaveGuiElement(keepSubs);
ConfigParser.SaveComboxIndex(dedupMode);
ConfigParser.SaveComboxIndex(mpdecimateMode);
ConfigParser.SaveGuiElement(dedupThresh);
@@ -117,7 +118,8 @@ namespace Flowframes.Forms
ConfigParser.LoadGuiElement(clearLogOnInput);
ConfigParser.LoadGuiElement(modelSuffix);
// Interpolation
ConfigParser.LoadGuiElement(enableAudio);
ConfigParser.LoadGuiElement(keepAudio);
ConfigParser.LoadGuiElement(keepSubs);
ConfigParser.LoadComboxIndex(dedupMode);
ConfigParser.LoadComboxIndex(mpdecimateMode);
ConfigParser.LoadGuiElement(dedupThresh);

View File

@@ -122,7 +122,8 @@ namespace Flowframes.IO
// Interpolation
if (key == "dedupMode") 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 == "vfrDedupe") return WriteDefault(key, "True");
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)
{
if (!Config.GetBool("enableAudio")) return;
if (!Config.GetBool("keepAudio")) return;
try
{
string audioFileBasePath = Path.Combine(i.current.tempFolder, "audio");
@@ -221,7 +221,7 @@ namespace Flowframes.Main
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)
{

View File

@@ -98,7 +98,7 @@ namespace Flowframes
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)