mirror of
https://github.com/revenz/FileFlowsPlugins.git
synced 2025-12-21 09:39:49 -06:00
FF-2049: Fixing TVEpisodeLookup
This commit is contained in:
@@ -86,6 +86,12 @@ namespace FileFlows.AudioNodes
|
||||
variables.AddOrUpdate("audio.Disc", AudioInfo.Disc < 1 ? 1 : AudioInfo.Disc);
|
||||
variables.AddOrUpdate("audio.TotalDiscs", AudioInfo.TotalDiscs < 1 ? 1 : AudioInfo.TotalDiscs);
|
||||
|
||||
args.SetTraits(new string[]
|
||||
{
|
||||
AudioInfo.Codec,
|
||||
FormatBitrate(AudioInfo.Bitrate),
|
||||
FormatAudioChannels(AudioInfo.Channels)
|
||||
}.Where(x => x != null).ToArray());
|
||||
|
||||
var metadata = new Dictionary<string, object>();
|
||||
metadata.Add("Duration", AudioInfo.Duration);
|
||||
@@ -192,5 +198,47 @@ namespace FileFlows.AudioNodes
|
||||
SetAudioInfo(args, result.Value, Variables, filename);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts an audio bitrate (in bits per second) to a human-readable format.
|
||||
/// </summary>
|
||||
/// <param name="bitrate">The bitrate in bits per second.</param>
|
||||
/// <returns>
|
||||
/// A human-readable string representation of the bitrate, such as "128 kbps" or "1.4 Mbps".
|
||||
/// Returns <c>null</c> if the bitrate is 0.
|
||||
/// </returns>
|
||||
public static string? FormatBitrate(long bitrate)
|
||||
{
|
||||
if (bitrate < 1)
|
||||
return null;
|
||||
if (bitrate < 1000)
|
||||
return $"{bitrate} bps";
|
||||
if (bitrate < 1_000_000)
|
||||
return $"{bitrate / 1000.0:0.#} kbps";
|
||||
return $"{bitrate / 1_000_000.0:0.#} Mbps";
|
||||
}/// <summary>
|
||||
/// Converts the number of audio channels into a human-readable format.
|
||||
/// </summary>
|
||||
/// <param name="channels">The number of audio channels.</param>
|
||||
/// <returns>
|
||||
/// A human-readable string representation of the channels, such as "Mono", "Stereo", or "5.1".
|
||||
/// Returns <c>null</c> if the number of channels is 0.
|
||||
/// </returns>
|
||||
public static string? FormatAudioChannels(long channels)
|
||||
{
|
||||
return channels switch
|
||||
{
|
||||
0 => null,
|
||||
1 => "Mono",
|
||||
2 => "Stereo",
|
||||
3 => "2.1",
|
||||
4 => "Quad",
|
||||
5 => "4.1",
|
||||
6 => "5.1",
|
||||
7 => "6.1",
|
||||
8 => "7.1",
|
||||
_ => $"{channels} channels"
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.ComponentModel;
|
||||
using System.IO.Enumeration;
|
||||
using FileFlows.Plugin.Helpers;
|
||||
|
||||
namespace FileFlows.ComicNodes.Comics;
|
||||
@@ -174,6 +175,8 @@ public class ComicConverter: Node
|
||||
return -1;
|
||||
}
|
||||
|
||||
List<string> traits = [];
|
||||
|
||||
if (DeleteNonPageImages)
|
||||
{
|
||||
List<string> nonPages = new();
|
||||
@@ -228,6 +231,11 @@ public class ComicConverter: Node
|
||||
args.Logger?.ILog("Total Files: " + files.Length);
|
||||
args.PartPercentageUpdate?.Invoke(0);
|
||||
int count = 0;
|
||||
if (files.Length == 1)
|
||||
traits.Add("1 Page");
|
||||
else
|
||||
traits.Add($"{files.Length} Pages");
|
||||
traits.Add(Codec.ToLowerInvariant() == "webp" ? "WebP" : "JPEG");
|
||||
|
||||
for (int i = 0; i < files.Length; i++)
|
||||
{
|
||||
@@ -296,6 +304,9 @@ public class ComicConverter: Node
|
||||
args.Logger?.ELog(args.FailureReason);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(traits.Count > 0)
|
||||
args.SetTraits(traits.ToArray());
|
||||
|
||||
args.SetWorkingFile(newFileResult.Value);
|
||||
if(Format == "CBZ")
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -77,6 +77,7 @@ public class FfmpegBuilderTrackRemover: TrackSelectorFlowElement<FfmpegBuilderT
|
||||
{
|
||||
bool removing = false;
|
||||
int index = -1;
|
||||
List<string> removed = [];
|
||||
foreach (var track in tracks)
|
||||
{
|
||||
if (track.Deleted)
|
||||
@@ -89,10 +90,21 @@ public class FfmpegBuilderTrackRemover: TrackSelectorFlowElement<FfmpegBuilderT
|
||||
continue;
|
||||
}
|
||||
Args.Logger?.ILog($"Deleting Stream: {track}");
|
||||
removed.Add(" - Removed: " + track.ToString());
|
||||
track.Deleted = true;
|
||||
removing = true;
|
||||
}
|
||||
|
||||
return removing;
|
||||
if (removing == false)
|
||||
{
|
||||
Args.Logger?.ILog("Nothing removed!");
|
||||
return false;
|
||||
}
|
||||
|
||||
Args.Logger?.ILog("\n------------------------------ Removed Summary ------------------------------ \n" +
|
||||
string.Join("\n", removed) +
|
||||
"\n-----------------------------------------------------------------------------");
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -26,4 +26,33 @@ public class ChannelHelper
|
||||
// Otherwise, round to the nearest integer
|
||||
return (int)Math.Round(channels);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the number of audio channels (as a float) into a human-readable format,
|
||||
/// handling floating-point precision issues.
|
||||
/// </summary>
|
||||
/// <param name="channels">The number of audio channels as a float.</param>
|
||||
/// <returns>
|
||||
/// A human-readable string representation of the channels, such as "Mono", "Stereo", or "5.1".
|
||||
/// Returns <c>null</c> if the number of channels is 0.
|
||||
/// </returns>
|
||||
public static string? FormatAudioChannels(float channels)
|
||||
{
|
||||
if (channels == 0)
|
||||
return null;
|
||||
|
||||
if (Approximately(channels, 1f)) return "Mono";
|
||||
if (Approximately(channels, 2f)) return "Stereo";
|
||||
if (Approximately(channels, 2.1f)) return "2.1";
|
||||
if (Approximately(channels, 4f)) return "Quad";
|
||||
if (Approximately(channels, 4.1f)) return "4.1";
|
||||
if (channels is >= 5f and <= 6f) return "5.1"; // Covers 5, 5.1, 5.0999, 6
|
||||
if (Approximately(channels, 6.1f)) return "6.1";
|
||||
if (channels is >= 7f and <= 8f) return "7.1"; // Covers 7, 7.1, 8
|
||||
|
||||
return $"{channels} channels";
|
||||
|
||||
static bool Approximately(float a, float b, float epsilon = 0.05f)
|
||||
=> Math.Abs(a - b) < epsilon;
|
||||
}
|
||||
}
|
||||
55
VideoNodes/Helpers/VideoHelper.cs
Normal file
55
VideoNodes/Helpers/VideoHelper.cs
Normal file
@@ -0,0 +1,55 @@
|
||||
namespace FileFlows.VideoNodes.Helpers;
|
||||
|
||||
/// <summary>
|
||||
/// Video Helper
|
||||
/// </summary>
|
||||
public class VideoHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Determines the closest standard video resolution label based on width and height.
|
||||
/// </summary>
|
||||
/// <param name="width">The width of the video.</param>
|
||||
/// <param name="height">The height of the video.</param>
|
||||
/// <returns>
|
||||
/// A resolution label such as "SD", "720p", "1080p", or "4K".
|
||||
/// Returns <c>null</c> if the resolution does not match any standard.
|
||||
/// </returns>
|
||||
public static string FormatResolution(int width, int height)
|
||||
{
|
||||
if (width <= 0 || height <= 0)
|
||||
return null;
|
||||
|
||||
if (Approximately(width, 7680) || Approximately(height, 4320))
|
||||
return "8K";
|
||||
|
||||
if (Approximately(width, 3840) || Approximately(height, 2160))
|
||||
return "4K";
|
||||
|
||||
if (Approximately(width, 1920) || Approximately(height, 1080))
|
||||
return "1080p";
|
||||
|
||||
if (Approximately(width, 1280) || Approximately(height, 720))
|
||||
return "720p";
|
||||
|
||||
if (Approximately(width, 640) || Approximately(height, 360))
|
||||
return "360p";
|
||||
|
||||
if (Approximately(width, 480) || Approximately(height, 360))
|
||||
return "480p";
|
||||
|
||||
if (Approximately(width, 720) || Approximately(height, 480))
|
||||
return "SD";
|
||||
|
||||
if (Approximately(width, 426) || Approximately(height, 240))
|
||||
return "240p";
|
||||
|
||||
return null;
|
||||
|
||||
static bool Approximately(int value, int target)
|
||||
{
|
||||
int tolerance = (int)(target * 0.1); // 10% tolerance
|
||||
return Math.Abs(value - target) <= tolerance;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -80,7 +80,7 @@ public class VideoFile : VideoNode
|
||||
return -1;
|
||||
}
|
||||
|
||||
var videoInfoResult = new VideoInfoHelper(FFMPEG, args.Logger).Read(file);
|
||||
var videoInfoResult = new VideoInfoHelper(FFMPEG, args.Logger, args).Read(file);
|
||||
if (videoInfoResult.Failed(out string error))
|
||||
{
|
||||
args.FailureReason = error;
|
||||
|
||||
@@ -13,10 +13,15 @@ public class FFmpegBuilder_TrackRemoverTests : VideoTestBase
|
||||
VideoInfo vii;
|
||||
NodeParameters args;
|
||||
FfmpegModel Model;
|
||||
|
||||
|
||||
protected override void TestStarting()
|
||||
{
|
||||
args = GetVideoNodeParameters();
|
||||
InitializeModel();
|
||||
}
|
||||
|
||||
private void InitializeModel(string filename = null)
|
||||
{
|
||||
args = GetVideoNodeParameters(filename);
|
||||
VideoFile vf = new VideoFile();
|
||||
vf.PreExecute(args);
|
||||
vf.Execute(args);
|
||||
@@ -94,6 +99,40 @@ public class FFmpegBuilder_TrackRemoverTests : VideoTestBase
|
||||
Assert.AreEqual("fre", nonDeletedSubtitles[0].Language);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test German is matched and removed
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void RemoveNonGerman()
|
||||
{
|
||||
InitializeModel(VideoEngGerAudio);
|
||||
vii.AudioStreams[1].Language = "ger";
|
||||
Model.AudioStreams[1].Language = "ger";
|
||||
int originaluNonDeletedSubtitles = Model.AudioStreams.Count(x => x.Deleted == false);
|
||||
FfmpegBuilderTrackRemover ffRemover = new();
|
||||
ffRemover.CustomTrackSelection = true;
|
||||
ffRemover.TrackSelectionOptions = new();
|
||||
ffRemover.TrackSelectionOptions.Add(new ("Language", "!deu"));
|
||||
ffRemover.StreamType = "Audio";
|
||||
ffRemover.PreExecute(args);
|
||||
Assert.AreEqual(1, ffRemover.Execute(args));
|
||||
List<FfmpegAudioStream> nonDeletedAudio = new();
|
||||
|
||||
Logger.ILog(new string('-', 100));
|
||||
foreach (var stream in Model.AudioStreams.Where(x => x.Deleted))
|
||||
{
|
||||
Logger.ILog("Deleted audio: " + stream);
|
||||
}
|
||||
Logger.ILog(new string('-', 100));
|
||||
foreach (var stream in Model.AudioStreams.Where(x => x.Deleted == false))
|
||||
{
|
||||
Logger.ILog("Non audio subtitle: " + stream);
|
||||
nonDeletedAudio.Add(stream);
|
||||
}
|
||||
Assert.AreNotEqual(originaluNonDeletedSubtitles, nonDeletedAudio.Count);
|
||||
Assert.AreEqual(nonDeletedAudio.Count, 1);
|
||||
Assert.IsTrue("deu" == nonDeletedAudio[0].Language || nonDeletedAudio[0].Language == "ger");
|
||||
}
|
||||
/// <summary>
|
||||
/// Tests english subtitles are removed
|
||||
/// </summary>
|
||||
|
||||
@@ -168,9 +168,9 @@ public class FfmpegBuilder_AudioConverterTests: VideoTestBase
|
||||
[TestCategory("Slow")]
|
||||
public void FfmpegBuilder_AudioConverter_Opus_All()
|
||||
{
|
||||
var vi = new VideoInfoHelper(FFmpeg, Logger);
|
||||
var vii = vi.Read(VideoMkv);
|
||||
var args = GetVideoNodeParameters(VideoMkv);
|
||||
var vi = new VideoInfoHelper(FFmpeg, Logger, args);
|
||||
var vii = vi.Read(VideoMkv);
|
||||
args.Parameters.Add("VideoInfo", vii);
|
||||
|
||||
FfmpegBuilderStart ffStart = new();
|
||||
|
||||
@@ -50,6 +50,11 @@ public abstract class VideoTestBase : TestBase
|
||||
/// Video with many subtitles file
|
||||
/// </summary>
|
||||
protected static readonly string VideoSubtitles = ResourcesTestFilesDir + "/subtitles.mkv";
|
||||
|
||||
/// <summary>
|
||||
/// Video with english and german audio
|
||||
/// </summary>
|
||||
protected static readonly string VideoEngGerAudio = ResourcesTestFilesDir + "/eng_ger_audio.mp4";
|
||||
|
||||
/// <summary>
|
||||
/// Audio MP3 file
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using FileFlows.VideoNodes.Helpers;
|
||||
|
||||
namespace FileFlows.VideoNodes;
|
||||
|
||||
public class VideoInfoHelper
|
||||
public class VideoInfoHelper(string ffMpegExe, ILogger logger, NodeParameters args)
|
||||
{
|
||||
private string ffMpegExe;
|
||||
private ILogger Logger;
|
||||
private ILogger Logger = logger;
|
||||
|
||||
static Regex rgxTitle = new Regex(@"(?<=((^[\s]+title[\s]+:[\s])))(.*?)$", RegexOptions.Multiline);
|
||||
static Regex rgxDuration = new Regex(@"(?<=((^[\s]+DURATION(\-[\w]+)?[\s]+:[\s])))([\d]+:?)+\.[\d]{1,7}", RegexOptions.Multiline);
|
||||
@@ -45,17 +45,25 @@ public class VideoInfoHelper
|
||||
set => _AnalyzeDuration = Math.Max(32, value);
|
||||
}
|
||||
|
||||
public VideoInfoHelper(string ffMpegExe, ILogger logger)
|
||||
{
|
||||
this.ffMpegExe = ffMpegExe;
|
||||
this.Logger = logger;
|
||||
}
|
||||
|
||||
public static string GetFFMpegPath(NodeParameters args) => args.GetToolPath("FFMpeg");
|
||||
|
||||
public Result<VideoInfo> Read(string filename)
|
||||
=> ReadStatic(Logger, ffMpegExe, filename);
|
||||
|
||||
{
|
||||
var result = ReadStatic(Logger, ffMpegExe, filename);
|
||||
if (result.IsFailed == false)
|
||||
{
|
||||
var vi = result.Value;
|
||||
args.SetTraits(new string[]
|
||||
{
|
||||
vi.VideoStreams?.FirstOrDefault()?.Codec,
|
||||
vi.AudioStreams?.FirstOrDefault()?.Codec,
|
||||
ChannelHelper.FormatAudioChannels(vi.AudioStreams?.FirstOrDefault()?.Channels ?? 0),
|
||||
VideoHelper.FormatResolution(vi?.VideoStreams?.FirstOrDefault()?.Width ?? 0 , vi?.VideoStreams?.FirstOrDefault()?.Height ?? 0),
|
||||
}.Where(x => string.IsNullOrWhiteSpace(x) == false).ToArray());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
internal static Result<VideoInfo> ReadStatic(ILogger logger, string ffMpegExe, string filename)
|
||||
{
|
||||
#if(DEBUG) // UNIT TESTING
|
||||
|
||||
@@ -87,7 +87,7 @@ namespace FileFlows.VideoNodes
|
||||
args.SetWorkingFile(outputFile);
|
||||
|
||||
// get the new video info
|
||||
var videoInfo = new VideoInfoHelper(ffmpegExe, args.Logger).Read(outputFile).ValueOrDefault;
|
||||
var videoInfo = new VideoInfoHelper(ffmpegExe, args.Logger, args).Read(outputFile).ValueOrDefault;
|
||||
SetVideoInfo(args, videoInfo, this.Variables ?? new Dictionary<string, object>());
|
||||
}
|
||||
else if (success.successs == false)
|
||||
|
||||
@@ -66,7 +66,7 @@ public class ReadVideoInfo: EncodingNode
|
||||
return -1;
|
||||
}
|
||||
|
||||
var videoInfoResult = new VideoInfoHelper(FFMPEG, args.Logger).Read(localFileResult.Value);
|
||||
var videoInfoResult = new VideoInfoHelper(FFMPEG, args.Logger, args).Read(localFileResult.Value);
|
||||
if (videoInfoResult.Failed(out string error))
|
||||
{
|
||||
args.Logger.ELog(error);
|
||||
|
||||
@@ -318,7 +318,7 @@ namespace FileFlows.VideoNodes
|
||||
return null;
|
||||
}
|
||||
|
||||
var viResult = new VideoInfoHelper(FFMPEG, args.Logger).Read(local);
|
||||
var viResult = new VideoInfoHelper(FFMPEG, args.Logger, args).Read(local);
|
||||
if (viResult.Failed(out string error))
|
||||
{
|
||||
args.Logger?.ELog(error);
|
||||
|
||||
Reference in New Issue
Block a user