FF-411 - added forced only option to subtitle extractor

This commit is contained in:
John Andrews
2023-06-14 08:12:18 +12:00
parent 53c637b037
commit 32aeae0be4
4 changed files with 185 additions and 136 deletions
+2
View File
@@ -154,6 +154,8 @@ public class VideoFile : VideoNode
/// <param name="args">the node parameters</param>
private void PrintFFmpegVersion(NodeParameters args)
{
if(string.IsNullOrEmpty(FFMPEG))
return;
string firstLine = string.Empty;
try
+5 -2
View File
@@ -45,9 +45,10 @@ namespace VideoNodes.Tests
var vi = new VideoInfoHelper(FfmpegPath, new TestLogger());
var vii = vi.Read(file);
foreach (string ext in new[] { String.Empty, ".srt", ".sup" })
foreach (string ext in new[] { string.Empty, ".srt", ".sup" })
{
SubtitleExtractor node = new();
node.ForcedOnly = true;
node.OutputFile = Path.Combine(TempPath, "subtitle.en" + ext);
node.Language = "eng";
@@ -55,7 +56,9 @@ namespace VideoNodes.Tests
args.GetToolPathActual = (string tool) => FfmpegPath;
args.TempPath = TempPath;
Assert.AreEqual(1, new VideoFile().Execute(args));
var vf = new VideoFile();
vf.PreExecute(args);
Assert.AreEqual(1, vf.Execute(args));
int output = node.Execute(args);
+3 -1
View File
@@ -498,7 +498,9 @@
"OutputFile": "Output File",
"OutputFile-Help": "Where to save the the output file to, e.g. \"'{folder.Orig.FullName}\\{file.Orig.FileName}.srt'\" to save it with the original file as a srt output.\nIf left blank an srt subtitle will be created in the same folder as the original input file.",
"SetWorkingFile": "Set as Working File",
"SetWorkingFile-Help": "When this is checked, if a subtitle is extracted, the working file will be changed to this extracted subtitle. The original working file will NOT be deleted."
"SetWorkingFile-Help": "When this is checked, if a subtitle is extracted, the working file will be changed to this extracted subtitle. The original working file will NOT be deleted.",
"ForcedOnly": "Forced Only",
"ForcedOnly-Help": "If only forced subtitles should be extracted."
}
},
"VideoHasStream": {
+175 -133
View File
@@ -1,145 +1,187 @@
namespace FileFlows.VideoNodes
using System.Net;
namespace FileFlows.VideoNodes;
/// <summary>
/// Element that extracts subtitles
/// </summary>
public class SubtitleExtractor : EncodingNode
{
using FileFlows.Plugin;
using FileFlows.Plugin.Attributes;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
public class SubtitleExtractor : EncodingNode
/// <summary>
/// Gets the number of outputs
/// </summary>
public override int Outputs => 2;
/// <summary>
/// Gets the icon for the element
/// </summary>
public override string Icon => "fas fa-comment-dots";
/// <summary>
/// Gets or sets the language to extract
/// </summary>
[Text(1)]
public string Language { get; set; }
/// <summary>
/// Gets or sets the destination output file
/// </summary>
[File(2)]
public string OutputFile { get; set; }
/// <summary>
/// Gets or sets if the new file should be set as the current working file
/// </summary>
[Boolean(3)]
public bool SetWorkingFile { get; set; }
/// <summary>
/// Gets or sets if only forced subtitles should be extracted
/// </summary>
[Boolean(4)]
public bool ForcedOnly { get; set; }
private Dictionary<string, object> _Variables;
/// <summary>
/// Gets or sets the variables
/// </summary>
public override Dictionary<string, object> Variables => _Variables;
/// <summary>
/// Creates an instance of the subtitle extractor
/// </summary>
public SubtitleExtractor()
{
public override int Outputs => 2;
public override string Icon => "fas fa-comment-dots";
[Text(1)]
public string Language { get; set; }
[File(2)]
public string OutputFile { get; set; }
[Boolean(3)]
public bool SetWorkingFile { get; set; }
private Dictionary<string, object> _Variables;
public override Dictionary<string, object> Variables => _Variables;
public SubtitleExtractor()
_Variables = new Dictionary<string, object>()
{
_Variables = new Dictionary<string, object>()
{
{ "sub.FileName", "/path/to/subtitle.sub" }
};
}
{ "sub.FileName", "/path/to/subtitle.sub" }
};
}
public override int Execute(NodeParameters args)
/// <summary>
/// Executes the subtitle extractor
/// </summary>
/// <param name="args">the node parameters</param>
/// <returns>the output to call next</returns>
public override int Execute(NodeParameters args)
{
try
{
try
{
VideoInfo videoInfo = GetVideoInfo(args);
if (videoInfo == null)
return -1;
// ffmpeg -i input.mkv -map "0:m:language:eng" -map "-0:v" -map "-0:a" output.srt
var subTrack = videoInfo.SubtitleStreams?.Where(x => string.IsNullOrEmpty(Language) || x.Language?.ToLower() == Language.ToLower()).FirstOrDefault();
if (subTrack == null)
{
args.Logger?.ILog("No subtitles found to extract");
return 2;
}
if (string.IsNullOrEmpty(OutputFile) == false)
{
OutputFile = args.ReplaceVariables(OutputFile, true);
}
else
{
var file = new FileInfo(args.FileName);
OutputFile = file.FullName.Substring(0, file.FullName.LastIndexOf(file.Extension));
}
OutputFile = args.MapPath(OutputFile);
string extension = "srt";
if (subTrack.Codec?.ToLower()?.Contains("pgs") == true)
extension = "sup";
if (OutputFile.ToLower().EndsWith(".srt") || OutputFile.ToLower().EndsWith(".sup"))
OutputFile = OutputFile[0..^4];
OutputFile += "." + extension;
//bool textSubtitles = Regex.IsMatch(OutputFile, @"\.(sup)$") == false;
var extracted = ExtractSubtitle(args, FFMPEG, "0:s:" + subTrack.TypeIndex, OutputFile);
if(extracted)
{
args.UpdateVariables(new Dictionary<string, object>
{
{ "sub.FileName", OutputFile }
});
if (SetWorkingFile)
args.SetWorkingFile(OutputFile, dontDelete: true);
return 1;
}
VideoInfo videoInfo = GetVideoInfo(args);
if (videoInfo == null)
return -1;
}
catch (Exception ex)
// ffmpeg -i input.mkv -map "0:m:language:eng" -map "-0:v" -map "-0:a" output.srt
var subTrack = videoInfo.SubtitleStreams?.Where(x =>
{
args.Logger?.ELog("Failed processing VideoFile: " + ex.Message);
return -1;
}
}
internal bool ExtractSubtitle(NodeParameters args, string ffmpegExe, string subtitleStream, string output)
{
if (File.Exists(OutputFile))
{
args.Logger?.ILog("File already exists, deleting file: " + OutputFile);
File.Delete(OutputFile);
}
bool textSubtitles = Regex.IsMatch(OutputFile.ToLower(), @"\.(sup)$") == false;
// -y means it will overwrite a file if output already exists
var result = args.Process.ExecuteShellCommand(new ExecuteArgs
{
Command = ffmpegExe,
ArgumentList = textSubtitles ?
new[] {
"-i", args.WorkingFile,
"-map", subtitleStream,
output
} :
new[] {
"-i", args.WorkingFile,
"-map", subtitleStream,
"-c:s", "copy",
output
}
}).Result;
var of = new FileInfo(OutputFile);
if (result.ExitCode != 0)
{
args.Logger?.ELog("FFMPEG process failed to extract subtitles");
args.Logger?.ILog("Unexpected exit code: " + result.ExitCode);
args.Logger?.ILog(result.StandardOutput ?? String.Empty);
args.Logger?.ILog(result.StandardError ?? String.Empty);
if (of.Exists && of.Length == 0)
{
// delete the output file if it created an empty file
try
{
of.Delete();
}
catch (Exception) { }
}
if (ForcedOnly && x.Forced == false)
return false;
if(string.IsNullOrEmpty(Language))
return true;
if (x.Language?.ToLowerInvariant() == Language.ToLowerInvariant())
return true;
return false;
}).FirstOrDefault();
if (subTrack == null)
{
args.Logger?.ILog("No subtitles found to extract");
return 2;
}
return of.Exists;
if (string.IsNullOrEmpty(OutputFile) == false)
{
OutputFile = args.ReplaceVariables(OutputFile, true);
}
else
{
var file = new FileInfo(args.FileName);
OutputFile = file.FullName.Substring(0, file.FullName.LastIndexOf(file.Extension));
}
OutputFile = args.MapPath(OutputFile);
string extension = "srt";
if (subTrack.Codec?.ToLower()?.Contains("pgs") == true)
extension = "sup";
if (OutputFile.ToLower().EndsWith(".srt") || OutputFile.ToLower().EndsWith(".sup"))
OutputFile = OutputFile[0..^4];
OutputFile += "." + extension;
var extracted = ExtractSubtitle(args, FFMPEG, "0:s:" + subTrack.TypeIndex, OutputFile);
if(extracted)
{
args.UpdateVariables(new Dictionary<string, object>
{
{ "sub.FileName", OutputFile }
});
if (SetWorkingFile)
args.SetWorkingFile(OutputFile, dontDelete: true);
return 1;
}
return -1;
}
catch (Exception ex)
{
args.Logger?.ELog("Failed processing VideoFile: " + ex.Message);
return -1;
}
}
/// <summary>
/// Extracts a subtitle
/// </summary>
/// <param name="args">the node parameters</param>
/// <param name="ffmpegExe">the FFmpeg executable</param>
/// <param name="subtitleStream">the FFmpeg subtitle stream to extract</param>
/// <param name="output">the destination file of the extracted subtitle</param>
/// <returns>if it was successful or not</returns>
internal bool ExtractSubtitle(NodeParameters args, string ffmpegExe, string subtitleStream, string output)
{
if (File.Exists(OutputFile))
{
args.Logger?.ILog("File already exists, deleting file: " + OutputFile);
File.Delete(OutputFile);
}
bool textSubtitles = Regex.IsMatch(OutputFile.ToLower(), @"\.(sup)$") == false;
// -y means it will overwrite a file if output already exists
var result = args.Process.ExecuteShellCommand(new ExecuteArgs
{
Command = ffmpegExe,
ArgumentList = textSubtitles ?
new[] {
"-i", args.WorkingFile,
"-map", subtitleStream,
output
} :
new[] {
"-i", args.WorkingFile,
"-map", subtitleStream,
"-c:s", "copy",
output
}
}).Result;
var of = new FileInfo(OutputFile);
if (result.ExitCode != 0)
{
args.Logger?.ELog("FFMPEG process failed to extract subtitles");
args.Logger?.ILog("Unexpected exit code: " + result.ExitCode);
args.Logger?.ILog(result.StandardOutput ?? String.Empty);
args.Logger?.ILog(result.StandardError ?? String.Empty);
if (of.Exists && of.Length == 0)
{
// delete the output file if it created an empty file
try
{
of.Delete();
}
catch (Exception) { }
}
return false;
}
return of.Exists;
}
}