added MusicNodes plugin

This commit is contained in:
reven
2022-01-12 12:49:01 +13:00
parent 221ad25438
commit 7b1058666c
13 changed files with 770 additions and 72 deletions

View File

@@ -0,0 +1,17 @@
namespace FileFlows.MusicNodes
{
internal static class ExtensionMethods
{
public static void AddOrUpdate(this Dictionary<string, object> dict, string key, object value)
{
if (dict.ContainsKey(key))
dict[key] = value;
else
dict.Add(key, value);
}
public static string? EmptyAsNull(this string str)
{
return str == string.Empty ? null : str;
}
}
}

View File

@@ -0,0 +1,62 @@
namespace FileFlows.MusicNodes
{
using System.ComponentModel;
using FileFlows.Plugin;
using FileFlows.Plugin.Attributes;
public class MusicFile : MusicNode
{
public override int Outputs => 1;
public override FlowElementType Type => FlowElementType.Input;
private Dictionary<string, object> _Variables;
public override Dictionary<string, object> Variables => _Variables;
public MusicFile()
{
_Variables = new Dictionary<string, object>()
{
{ "mi.Album", "Album" },
{ "mi.BitRate", 845 },
{ "mi.Channels", 2 },
{ "mi.Codec", "flac" },
{ "mi.Date", new DateTime(2020, 05, 23) },
{ "mi.Duration", 256 },
{ "mi.Encoder", "FLAC 1.2.1" },
{ "mi.Frequency", 44100 },
{ "mi.Genres", new [] { "Pop", "Rock" } },
{ "mi.Language", "English" },
{ "mi.Title", "Song Title" },
{ "mi.Track", 2 }
};
}
public override int Execute(NodeParameters args)
{
string ffmpegExe = GetFFMpegExe(args);
if (string.IsNullOrEmpty(ffmpegExe))
return -1;
try
{
var videoInfo = new MusicInfoHelper(ffmpegExe, args.Logger).Read(args.WorkingFile);
if (videoInfo.Duration == 0)
{
args.Logger.ILog("Failed to load music information.");
return 0;
}
SetMusicInfo(args, videoInfo, Variables);
return 1;
}
catch (Exception ex)
{
args.Logger.ELog("Failed processing MusicFile: " + ex.Message);
return -1;
}
}
}
}

18
MusicNodes/MusicInfo.cs Normal file
View File

@@ -0,0 +1,18 @@
namespace FileFlows.MusicNodes
{
public class MusicInfo
{
public string Language { get; set; }
public int Track { get; set; }
public string Title { get; set; }
public string Album { get; set; }
public DateTime Date { get; set; }
public string[] Genres { get; set; }
public string Encoder { get; set; }
public long Duration { get; set; }
public long BitRate { get; set; }
public string Codec { get; set; }
public long Channels { get; set; }
public long Frequency { get; set; }
}
}

View File

@@ -0,0 +1,139 @@
namespace FileFlows.MusicNodes
{
using System.Diagnostics;
using System.IO;
using System.Text.RegularExpressions;
using FileFlows.Plugin;
public class MusicInfoHelper
{
private string ffMpegExe;
private ILogger Logger;
public MusicInfoHelper(string ffMpegExe, ILogger logger)
{
this.ffMpegExe = ffMpegExe;
this.Logger = logger;
}
public static string GetFFMpegPath(NodeParameters args) => args.GetToolPath("FFMpeg");
public MusicInfo Read(string filename)
{
var mi = new MusicInfo();
if (File.Exists(filename) == false)
{
Logger.ELog("File not found: " + filename);
return mi;
}
if (string.IsNullOrEmpty(ffMpegExe) || File.Exists(ffMpegExe) == false)
{
Logger.ELog("FFMpeg not found: " + (ffMpegExe ?? "not passed in"));
return mi;
}
try
{
using (var process = new Process())
{
process.StartInfo = new ProcessStartInfo();
process.StartInfo.FileName = ffMpegExe;
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardError = true;
process.StartInfo.CreateNoWindow = true;
process.StartInfo.Arguments = $"-hide_banner -i \"{filename}\"";
process.Start();
string output = process.StandardError.ReadToEnd();
string error = process.StandardError.ReadToEnd();
process.WaitForExit();
if (string.IsNullOrEmpty(error) == false && error != "At least one output file must be specified")
{
Logger.ELog("Failed reading ffmpeg info: " + error);
return mi;
}
Logger.ILog("Music Information:" + Environment.NewLine + output);
if(output.IndexOf("Input #0") < 0)
{
Logger.ELog("Failed to read audio information for file");
return mi;
}
foreach(string line in output.Split('\n'))
{
int colonIndex = line.IndexOf(":");
if(colonIndex < 1)
continue;
if(line.Trim().StartsWith("Language"))
mi.Language = line.Substring(colonIndex + 1).Trim();
else if (line.Trim().StartsWith("track"))
{
if (int.TryParse(line.Substring(colonIndex + 1).Trim(), out int value))
mi.Track = value;
}
else if (line.Trim().StartsWith("Title"))
mi.Title = line.Substring(colonIndex + 1).Trim();
else if (line.Trim().StartsWith("Album"))
mi.Album = line.Substring(colonIndex + 1).Trim();
else if (line.Trim().StartsWith("Date") && mi.Date < new DateTime(1900, 1, 1))
{
if (int.TryParse(line.Substring(colonIndex + 1).Trim(), out int value))
mi.Date = new DateTime(value, 1, 1);
}
else if (line.Trim().StartsWith("Retail Date"))
{
if (DateTime.TryParse(line.Substring(colonIndex + 1).Trim(), out DateTime value))
mi.Date = value;
}
else if (line.Trim().StartsWith("Genre"))
mi.Genres = line.Substring(colonIndex + 1).Trim().Split(' ');
else if (line.Trim().StartsWith("Encoder"))
mi.Encoder = line.Substring(colonIndex + 1).Trim();
else if (line.Trim().StartsWith("Duration"))
{
string temp = line.Substring(colonIndex + 1).Trim();
if(temp.IndexOf(",") > 0)
{
temp = temp.Substring(0, temp.IndexOf(","));
if (TimeSpan.TryParse(temp, out TimeSpan value))
mi.Duration = (long)value.TotalSeconds;
}
}
if (line.IndexOf("bitrate:") > 0)
{
string br = line.Substring(line.IndexOf("bitrate:") + "bitrate:".Length).Trim();
if (br.IndexOf(" ") > 0)
{
br = br.Substring(0, br.IndexOf(" "));
if (long.TryParse(br, out long value))
mi.BitRate = value;
}
}
var match = Regex.Match(line, @"([\d]+) Hz");
if (match.Success)
{
mi.Frequency = int.Parse(match.Groups[1].Value);
}
if (line.IndexOf(" stereo,") > 0)
mi.Channels = 2;
}
}
}
catch (Exception ex)
{
Logger.ELog(ex.Message, ex.StackTrace.ToString());
}
return mi;
}
}
}

Binary file not shown.

View File

@@ -1,76 +1,44 @@
{
"Flow":{
"Parts": {
"AudioTrackReorder": {
"Description": "Allows you to reorder audio tracks in the preferred order.\n\nEnter the audio codecs in the order you want. Any not listed will be ordered after the ones entered in their original order.\nIf there are multiple tracks with same codec, they will be ordered first by the order you entered, then in their original order.\n\nOutput 1: Audio tracks were reordered\nOutput 2: Audio tracks did not need reordering",
"Fields": {
"OrderedTracks": "Ordered Audio Codecs"
}
},
"VideoFile": {
"Description": "An input video file that has had its VideoInformation read and can be processed"
},
"DetectBlackBars": {
"Description": "Processes a video file and scans for black bars in the video.\n\nIf found a parameter \"VideoCrop\" will be added.\n\nOutput 1: Black bars detected\nOutput 2: Not detected",
"Fields": {
"CroppingThreshold": "Threshold",
"CroppingThreshold-Help": "The amount of pixels that must be greater than to crop. E.g. if there's only 5 pixels detected as black space, you may consider this too small to crop."
}
},
"SubtitleRemover": {
"Description": "Removes subtitles from a video file if found..\n\nOutput 1: Subtitles were removed\nOutput 2: No subtitles found that needed to be removed",
"Fields": {
"SubtitlesToRemove": "Subtitles To Remove"
}
},
"VideoCodec": {
"Description": "This node will check the codecs in the input file, and trigger when matched.\n\nOutput 1: Matches\nOutput 2: Does not match",
"Fields": {
"Codecs": "Codecs",
"Codecs-Help": "Enter a list of case insensitive video or audio codecs.\nEg hevc, h265, mpeg4, ac3"
}
},
"VideoEncode": {
"Description": "A generic video encoding node, this lets you customize how to encode a video file using ffmpeg.\n\nOutput 1: Video was processed\nOutput 2: No processing required",
"Fields": {
"Extension": "Extension",
"Extension-Help": "The file extension to use on the newly created file",
"VideoCodec": "Video Codec",
"VideoCodec-Help": "The video codec the video should be in, for example hevc, h264",
"VideoCodecParameters": "Video Codec Parameters",
"VideoCodecParameters-Help": "The parameters to use to encode the video, eg. \"hevc_nvenc -preset hq -crf 23\" to encode into hevc using the HQ preset a constant rate factor of 23 and using NVIDIA hardware acceleration.",
"AudioCodec": "Audio Codec",
"AudioCodec-Help": "The audio codec to encode the video with",
"Language": "Language",
"Language-Help": "Optional ISO 639-2 language code to use. Will attempt to find an audio track with this language code if not the best audio track will be used.\nhttps://en.wikipedia.org/wiki/List_of_ISO_639-2_codes"
}
},
"Video_H265_AC3": {
"Description": "This will ensure all videos are encoded in H265 (if not already encoded) and that AC3 audio is the first audio channel\n\nOutput 1: Video was processed\nOutput 2: No processing required",
"Fields": {
"Language": "Language",
"Language-Help": "Optional ISO 639-2 language code to use. Will attempt to find an audio track with this language code if not the best audio track will be used.\nhttps://en.wikipedia.org/wiki/List_of_ISO_639-2_codes",
"Crf": "Constant Rate Factor",
"Crf-Help": "Refer to ffmpeg for more details, the lower the value the bigger the file. A good value is around 19-23. Default is 21.",
"NvidiaEncoding": "NVIDIA Encoding",
"NvidiaEncoding-Help": "If NVIDIA hardware encoding should be used. If you do not have a supported NVIDIA card the encoding will fail.",
"Threads": "Threads",
"Threads-Help": "Only used if not using NVIDIA. If set to 0, the threads will use FFMpegs defaults.",
"NormalizeAudio": "Normalize Audio",
"NormalizeAudio-Help": "If the audio track should have its volume level normalized",
"ForceRencode": "Force Re-Encode",
"ForceRencode-Help": "If the video should always be re-encoded regardless if it already is in H265/AC3"
}
},
"FFMPEG": {
"Description": "The node lets you run any FFMPEG command you like. Giving you full control over what it can do.\n\nFor more information refer to the FFMPEG documentation",
"Fields": {
"Extension": "Extension",
"Extension-Help": "The file extension to use on the newly created file",
"CommandLine": "Command Line",
"CommandLine-Help": "The command line to run with FFMPEG.\n'{WorkingFile}': the working file of the flow\n'{Output}': The output file that will be passed as the last parameter to FFMPEG including the extension defined above."
}
"Flow":{
"Parts": {
"MusicFile": {
"Description": "An input music file that has had its MusicInformation read and can be processed"
},
"ConvertToAAC": {
"Description": "Convert a music file to AAC",
"Fields": {
"Bitrate": "Bitrate",
"Bitrate-Help": "The bitrate for the new AAC file, the higher the bitrate the better the quality but larger the file. 192 Kbps is the recommended rate."
}
},
"ConvertToFLAC": {
"Description": "Convert a music file to FLAC",
"Fields": {
"Bitrate": "Bitrate",
"Bitrate-Help": "The bitrate for the new FLAC file, the higher the bitrate the better the quality but larger the file. 128 Kbps is the recommended rate."
}
},
"ConvertToMP3": {
"Description": "Convert a music file to MP3",
"Fields": {
"Bitrate": "Bitrate",
"Bitrate-Help": "The bitrate for the new MP3 file, the higher the bitrate the better the quality but larger the file. 192 Kbps is the recommended rate."
}
},
"ConvertToOGG": {
"Description": "Convert a music file to OGG",
"Fields": {
"Bitrate": "Bitrate",
"Bitrate-Help": "The bitrate for the new OGG file, the higher the bitrate the better the quality but larger the file. 128 Kbps is the recommended rate."
}
},
"ConvertToWAV": {
"Description": "Convert a music file to WAV",
"Fields": {
"Bitrate": "Bitrate",
"Bitrate-Help": "The bitrate for the new WAV file, the higher the bitrate the better the quality but larger the file. 128 Kbps is the recommended rate."
}
}
}
}
}

View File

@@ -0,0 +1,231 @@
using FileFlows.Plugin;
using FileFlows.Plugin.Attributes;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace FileFlows.MusicNodes
{
public class ConvertToMP3 : ConvertNode
{
protected override string Extension => "mp3";
public static List<ListOption> BitrateOptions => ConvertNode.BitrateOptions;
protected override List<string> GetArguments()
{
return new List<string>
{
"-c:a",
"mp3",
"-ab",
Bitrate + "k"
};
}
}
public class ConvertToWAV : ConvertNode
{
protected override string Extension => "wav";
public static List<ListOption> BitrateOptions => ConvertNode.BitrateOptions;
protected override List<string> GetArguments()
{
return new List<string>
{
"-c:a",
"pcm_s16le",
"-ab",
Bitrate + "k"
};
}
}
public class ConvertToAAC : ConvertNode
{
protected override string Extension => "aac";
public static List<ListOption> BitrateOptions => ConvertNode.BitrateOptions;
protected override bool SetId3Tags => true;
protected override List<string> GetArguments()
{
return new List<string>
{
"-c:a",
"aac",
"-ab",
Bitrate + "k"
};
}
}
public class ConvertToOGG: ConvertNode
{
protected override string Extension => "ogg";
public static List<ListOption> BitrateOptions => ConvertNode.BitrateOptions;
protected override List<string> GetArguments()
{
return new List<string>
{
"-c:a",
"libvorbis",
"-ab",
Bitrate + "k"
};
}
}
//public class ConvertToFLAC : ConvertNode
//{
// protected override string Extension => "flac";
// public static List<ListOption> BitrateOptions => ConvertNode.BitrateOptions;
// protected override List<string> GetArguments()
// {
// return new List<string>
// {
// "-c:a",
// "flac",
// "-ab",
// Bitrate + "k"
// };
// }
//}
public abstract class ConvertNode:MusicNode
{
protected abstract string Extension { get; }
protected virtual bool SetId3Tags => false;
public override int Inputs => 1;
public override int Outputs => 1;
protected virtual List<string> GetArguments()
{
return new List<string>
{
"-map_metadata",
"0:0",
"-ab",
Bitrate + "k"
};
}
public override FlowElementType Type => FlowElementType.Process;
[Select(nameof(BitrateOptions), 1)]
public int Bitrate { get; set; }
private static List<ListOption> _BitrateOptions;
public static List<ListOption> BitrateOptions
{
get
{
if (_BitrateOptions == null)
{
_BitrateOptions = new List<ListOption>
{
new ListOption { Label = "64 Kbps", Value = 64},
new ListOption { Label = "96 Kbps", Value = 96},
new ListOption { Label = "128 Kbps", Value = 128},
new ListOption { Label = "160 Kbps", Value = 160},
new ListOption { Label = "192 Kbps", Value = 192},
new ListOption { Label = "224 Kbps", Value = 224},
new ListOption { Label = "256 Kbps", Value = 256},
new ListOption { Label = "288 Kbps", Value = 288},
new ListOption { Label = "320 Kbps", Value = 320},
};
}
return _BitrateOptions;
}
}
public override int Execute(NodeParameters args)
{
string ffmpegExe = GetFFMpegExe(args);
if (string.IsNullOrEmpty(ffmpegExe))
return -1;
//MusicInfo musicInfo = GetMusicInfo(args);
//if (musicInfo == null)
// return -1;
if (Bitrate < 64 || Bitrate > 320)
{
args.Logger?.ILog("Bitrate not set or invalid, setting to 192kbps");
Bitrate = 192;
}
string outputFile = Path.Combine(args.TempPath, Guid.NewGuid().ToString() + "." + Extension);
var ffArgs = GetArguments();
ffArgs.Insert(0, "-hide_banner");
ffArgs.Insert(1, "-y"); // tells ffmpeg to replace the file if already exists, which it shouldnt but just incase
ffArgs.Insert(2, "-i");
ffArgs.Insert(3, args.WorkingFile);
ffArgs.Add(outputFile);
args.Logger?.ILog("FFArgs: " + String.Join(", ", ffArgs));
var result = args.Execute(new ExecuteArgs
{
Command = ffmpegExe,
ArgumentList = ffArgs.ToArray()
});
if(result.ExitCode != 0)
{
args.Logger?.ELog("Invalid exit code detected: " + result.ExitCode);
return -1;
}
//CopyMetaData(outputFile, args.FileName);
args.SetWorkingFile(outputFile);
return 1;
}
//private void CopyMetaData(string outputFile, string originalFile)
//{
// Track original = new Track(originalFile);
// Track dest = new Track(outputFile);
// dest.Album = original.Album;
// dest.AlbumArtist = original.AlbumArtist;
// dest.Artist = original.Artist;
// dest.Comment = original.Comment;
// dest.Composer= original.Composer;
// dest.Conductor = original.Conductor;
// dest.Copyright = original.Copyright;
// dest.Date = original.Date;
// dest.Description= original.Description;
// dest.DiscNumber= original.DiscNumber;
// dest.DiscTotal = original.DiscTotal;
// if (original.EmbeddedPictures?.Any() == true)
// {
// foreach (var pic in original.EmbeddedPictures)
// dest.EmbeddedPictures.Add(pic);
// }
// dest.Genre= original.Genre;
// dest.Lyrics= original.Lyrics;
// dest.OriginalAlbum= original.OriginalAlbum;
// dest.OriginalArtist = original.OriginalArtist;
// dest.Popularity= original.Popularity;
// dest.Publisher= original.Publisher;
// dest.PublishingDate= original.PublishingDate;
// dest.Title= original.Title;
// dest.TrackNumber= original.TrackNumber;
// dest.TrackTotal= original.TrackTotal;
// dest.Year= original.Year;
// foreach (var key in original.AdditionalFields.Keys)
// {
// if(dest.AdditionalFields.ContainsKey(key))
// dest.AdditionalFields[key] = original.AdditionalFields[key];
// else
// dest.AdditionalFields.Add(key, original.AdditionalFields[key]);
// }
// dest.Save();
//}
}
}

View File

@@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace FileFlows.MusicNodes.Nodes
{
internal class ID3Tagger
{
}
}

View File

@@ -0,0 +1,82 @@
namespace FileFlows.MusicNodes
{
using FileFlows.Plugin;
public abstract class MusicNode : Node
{
public override string Icon => "fas fa-music";
protected string GetFFMpegExe(NodeParameters args)
{
string ffmpeg = args.GetToolPath("FFMpeg");
if (string.IsNullOrEmpty(ffmpeg))
{
args.Logger.ELog("FFMpeg tool not found.");
return "";
}
var fileInfo = new FileInfo(ffmpeg);
if (fileInfo.Exists == false)
{
args.Logger.ELog("FFMpeg tool configured by ffmpeg file does not exist.");
return "";
}
return fileInfo.FullName;
}
protected string GetFFMpegPath(NodeParameters args)
{
string ffmpeg = args.GetToolPath("FFMpeg");
if (string.IsNullOrEmpty(ffmpeg))
{
args.Logger.ELog("FFMpeg tool not found.");
return "";
}
var fileInfo = new FileInfo(ffmpeg);
if (fileInfo.Exists == false)
{
args.Logger.ELog("FFMpeg tool configured by ffmpeg file does not exist.");
return "";
}
return fileInfo.DirectoryName;
}
private const string MUSIC_INFO = "MusicInfo";
protected void SetMusicInfo(NodeParameters args, MusicInfo musicInfo, Dictionary<string, object> variables)
{
if (args.Parameters.ContainsKey(MUSIC_INFO))
args.Parameters[MUSIC_INFO] = musicInfo;
else
args.Parameters.Add(MUSIC_INFO, musicInfo);
variables.AddOrUpdate("mi.Album", musicInfo.Album);
variables.AddOrUpdate("mi.BitRate", musicInfo.BitRate);
variables.AddOrUpdate("mi.Channels", musicInfo.Channels);
variables.AddOrUpdate("mi.Codec", musicInfo.Codec);
variables.AddOrUpdate("mi.Date", musicInfo.Date);
variables.AddOrUpdate("mi.Duration", musicInfo.Duration);
variables.AddOrUpdate("mi.Encoder", musicInfo.Encoder);
variables.AddOrUpdate("mi.Frequency", musicInfo.Frequency);
variables.AddOrUpdate("mi.Genres", musicInfo.Genres);
variables.AddOrUpdate("mi.Language", musicInfo.Language);
variables.AddOrUpdate("mi.Title", musicInfo.Title);
variables.AddOrUpdate("mi.Track", musicInfo.Track);
args.UpdateVariables(variables);
}
protected MusicInfo GetMusicInfo(NodeParameters args)
{
if (args.Parameters.ContainsKey(MUSIC_INFO) == false)
{
args.Logger.WLog("No codec information loaded, use a 'Music File' node first");
return null;
}
var result = args.Parameters[MUSIC_INFO] as MusicInfo;
if (result == null)
{
args.Logger.WLog("MusicInfo not found for file");
return null;
}
return result;
}
}
}

15
MusicNodes/Plugin.cs Normal file
View File

@@ -0,0 +1,15 @@
namespace FileFlows.MusicNodes
{
using System.ComponentModel.DataAnnotations;
using FileFlows.Plugin.Attributes;
public class Plugin : FileFlows.Plugin.IPlugin
{
public string Name => "Music Nodes";
public string MinimumVersion => string.Empty;
public void Init()
{
}
}
}

View File

@@ -0,0 +1,99 @@
#if(DEBUG)
namespace FileFlows.MusicNodes.Tests
{
using FileFlows.MusicNodes;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
[TestClass]
public class ConvertTests
{
[TestMethod]
public void Convert_FlacToAac()
{
const string file = @"D:\music\unprocessed\01-billy_joel-you_may_be_right.flac";
ConvertToAAC node = new ();
var args = new FileFlows.Plugin.NodeParameters(file, new TestLogger(), false, string.Empty);
args.GetToolPath = (string tool) => @"C:\utils\ffmpeg\ffmpeg.exe";
args.TempPath = @"D:\music\temp";
new MusicFile().Execute(args); // need to read the music info and set it
int output = node.Execute(args);
Assert.AreEqual(1, output);
}
[TestMethod]
public void Convert_FlacToMp3()
{
const string file = @"D:\music\unprocessed\01-billy_joel-you_may_be_right.flac";
ConvertToMP3 node = new();
var args = new FileFlows.Plugin.NodeParameters(file, new TestLogger(), false, string.Empty);
args.GetToolPath = (string tool) => @"C:\utils\ffmpeg\ffmpeg.exe";
args.TempPath = @"D:\music\temp";
new MusicFile().Execute(args); // need to read the music info and set it
int output = node.Execute(args);
Assert.AreEqual(1, output);
}
[TestMethod]
public void Convert_Mp3ToWAV()
{
const string file = @"D:\music\unprocessed\04-billy_joel-scenes_from_an_italian_restaurant-b2125758.mp3";
ConvertToWAV node = new();
var args = new FileFlows.Plugin.NodeParameters(file, new TestLogger(), false, string.Empty);
args.GetToolPath = (string tool) => @"C:\utils\ffmpeg\ffmpeg.exe";
args.TempPath = @"D:\music\temp";
new MusicFile().Execute(args); // need to read the music info and set it
int output = node.Execute(args);
Assert.AreEqual(1, output);
}
[TestMethod]
public void Convert_Mp3ToOgg()
{
const string file = @"D:\music\unprocessed\04-billy_joel-scenes_from_an_italian_restaurant-b2125758.mp3";
ConvertToOGG node = new();
var args = new FileFlows.Plugin.NodeParameters(file, new TestLogger(), false, string.Empty);
args.GetToolPath = (string tool) => @"C:\utils\ffmpeg\ffmpeg.exe";
args.TempPath = @"D:\music\temp";
new MusicFile().Execute(args); // need to read the music info and set it
int output = node.Execute(args);
Assert.AreEqual(1, output);
}
[TestMethod]
public void Convert_AacToMp3()
{
const string file = @"D:\music\temp\37f315a0-4afc-4a72-a0b4-eb7eb681b9b3.aac";
ConvertToMP3 node = new();
var args = new FileFlows.Plugin.NodeParameters(file, new TestLogger(), false, string.Empty);
args.GetToolPath = (string tool) => @"C:\utils\ffmpeg\ffmpeg.exe";
args.TempPath = @"D:\music\temp";
new MusicFile().Execute(args); // need to read the music info and set it
int output = node.Execute(args);
Assert.AreEqual(1, output);
}
}
}
#endif

View File

@@ -0,0 +1,47 @@
#if(DEBUG)
namespace FileFlows.MusicNodes.Tests
{
using FileFlows.Plugin;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
internal class TestLogger : ILogger
{
private List<string> Messages = new List<string>();
public void DLog(params object[] args) => Log("DBUG", args);
public void ELog(params object[] args) => Log("ERRR", args);
public void ILog(params object[] args) => Log("INFO", args);
public void WLog(params object[] args) => Log("WARN", args);
private void Log(string type, object[] args)
{
if (args == null || args.Length == 0)
return;
string message = type + " -> " +
string.Join(", ", args.Select(x =>
x == null ? "null" :
x.GetType().IsPrimitive || x is string ? x.ToString() :
System.Text.Json.JsonSerializer.Serialize(x)));
Messages.Add(message);
}
public bool Contains(string message)
{
if (string.IsNullOrWhiteSpace(message))
return false;
string log = string.Join(Environment.NewLine, Messages);
return log.Contains(message);
}
}
}
#endif

View File

@@ -46,4 +46,12 @@ Remuxes
remuxed
srt
ssa
rescaled
rescaled
Bitrate
bitrate
MusicInformation
Kbps
AAC
WAV
OGG
FLAC