mirror of
https://github.com/revenz/FileFlowsPlugins.git
synced 2026-05-09 07:39:49 -05:00
FF-411 - added forced only option to subtitle extractor
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user