mirror of
https://github.com/revenz/FileFlowsPlugins.git
synced 2026-02-12 10:10:47 -06:00
FF-1155 - audio nodes now copies metadata when converting
This commit is contained in:
@@ -59,6 +59,9 @@ public class CreateAudioBook: AudioNode
|
||||
var ffmpeg = GetFFmpeg(args);
|
||||
if (string.IsNullOrEmpty(ffmpeg))
|
||||
return -1;
|
||||
var ffprobe = GetFFprobe(args);
|
||||
if (string.IsNullOrEmpty(ffprobe))
|
||||
return -1;
|
||||
|
||||
var dir = args.IsDirectory ? args.WorkingFile : FileHelper.GetDirectory(args.WorkingFile);
|
||||
|
||||
@@ -121,7 +124,7 @@ public class CreateAudioBook: AudioNode
|
||||
string extension = FileHelper.GetExtension(x);
|
||||
string name = FileHelper.GetShortFileName(x);
|
||||
string chapterName = GetChapterName(bookName, name[..^extension.Length], chapterCount);
|
||||
TimeSpan length = GetChapterLength(args, ffmpeg, x);
|
||||
TimeSpan length = GetChapterLength(args, ffmpeg, ffprobe, x);
|
||||
var end = current.Add(length);
|
||||
var chapter = "[CHAPTER]\n" +
|
||||
"TIMEBASE=1/1000\n" +
|
||||
@@ -257,9 +260,9 @@ public class CreateAudioBook: AudioNode
|
||||
// return string.Empty;
|
||||
// }
|
||||
|
||||
private TimeSpan GetChapterLength(NodeParameters args, string ffmpeg, string filename)
|
||||
private TimeSpan GetChapterLength(NodeParameters args, string ffmpeg, string ffprobe, string filename)
|
||||
{
|
||||
var info = new AudioInfoHelper(ffmpeg, args.Logger).Read(filename);
|
||||
var info = new AudioInfoHelper(ffmpeg, ffprobe, args.Logger).Read(filename);
|
||||
return TimeSpan.FromSeconds(info.Duration);
|
||||
}
|
||||
|
||||
|
||||
207
AudioNodes/AudioFormatInfo.cs
Normal file
207
AudioNodes/AudioFormatInfo.cs
Normal file
@@ -0,0 +1,207 @@
|
||||
using System.Globalization;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace FileFlows.AudioNodes;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the JSON structure returned by ffprobe for an audio file.
|
||||
/// </summary>
|
||||
public class FFprobeAudioInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the format information extracted from ffprobe.
|
||||
/// </summary>
|
||||
public AudioFormatInfo Format { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Parses JSON output from FFprobe
|
||||
/// </summary>
|
||||
/// <param name="json">the json output from FFprobe</param>
|
||||
/// <returns>the AudioFormatInfo parsed</returns>
|
||||
public static Result<AudioFormatInfo> Parse(string json)
|
||||
{
|
||||
try
|
||||
{
|
||||
var ffAudioFormatInfo = System.Text.Json.JsonSerializer.Deserialize<FFprobeAudioInfo>(json,
|
||||
new JsonSerializerOptions()
|
||||
{
|
||||
PropertyNameCaseInsensitive = true
|
||||
});
|
||||
return ffAudioFormatInfo.Format;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Result<AudioFormatInfo>.Fail(ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents the format information extracted from ffprobe for an audio file.
|
||||
/// </summary>
|
||||
public class AudioFormatInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the filename of the audio file.
|
||||
/// </summary>
|
||||
public string Filename { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the number of streams in the audio file.
|
||||
/// </summary>
|
||||
public int NbStreams { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the number of programs in the audio file.
|
||||
/// </summary>
|
||||
public int NbPrograms { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the format name (e.g., flac).
|
||||
/// </summary>
|
||||
public string FormatName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the long format name (e.g., raw FLAC).
|
||||
/// </summary>
|
||||
public string FormatLongName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the duration of the audio file.
|
||||
/// </summary>
|
||||
[JsonPropertyName("duration")]
|
||||
[JsonConverter(typeof(FFprobeTimeSpanConverter))]
|
||||
public TimeSpan Duration { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the start time of the audio file.
|
||||
/// </summary>
|
||||
[JsonPropertyName("start_time")]
|
||||
[JsonConverter(typeof(FFprobeTimeSpanConverter))]
|
||||
public TimeSpan StartTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the size of the audio file.
|
||||
/// </summary>
|
||||
[JsonPropertyName("size")]
|
||||
[JsonConverter(typeof(LongConverter))]
|
||||
public long Size { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the bit rate of the audio file.
|
||||
/// </summary>
|
||||
[JsonPropertyName("bit_rate")]
|
||||
[JsonConverter(typeof(LongConverter))]
|
||||
public long Bitrate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the probe score.
|
||||
/// </summary>
|
||||
public int ProbeScore { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the tags associated with the audio file.
|
||||
/// </summary>
|
||||
public AudioTags Tags { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents the tags associated with an audio file.
|
||||
/// </summary>
|
||||
public class AudioTags
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the title of the audio file.
|
||||
/// </summary>
|
||||
public string Title { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the artist of the audio file.
|
||||
/// </summary>
|
||||
public string Artist { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the album of the audio file.
|
||||
/// </summary>
|
||||
public string Album { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the track number of the audio file.
|
||||
/// </summary>
|
||||
public string Track { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the release date of the audio file.
|
||||
/// </summary>
|
||||
public string Date { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the genre of the audio file.
|
||||
/// </summary>
|
||||
public string Genre { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the total number of tracks in the album.
|
||||
/// </summary>
|
||||
public string TotalTracks { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the disc number of the audio file.
|
||||
/// </summary>
|
||||
public string Disc { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the total number of discs in the album.
|
||||
/// </summary>
|
||||
public string TotalDiscs { get; set; }
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Custom converter for TimeSpan to handle string representation.
|
||||
/// </summary>
|
||||
public class FFprobeTimeSpanConverter : JsonConverter<TimeSpan>
|
||||
{
|
||||
public override TimeSpan Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
string stringValue = reader.GetString();
|
||||
if (double.TryParse(stringValue, out double seconds) == false)
|
||||
return default;
|
||||
|
||||
return TimeSpan.FromSeconds(seconds);
|
||||
}
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, TimeSpan value, JsonSerializerOptions options)
|
||||
{
|
||||
writer.WriteStringValue(value.TotalSeconds.ToString(CultureInfo.InvariantCulture));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Custom converter for long to handle string representation.
|
||||
/// </summary>
|
||||
public class LongConverter : JsonConverter<long>
|
||||
{
|
||||
public override long Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
if (reader.TokenType == JsonTokenType.String)
|
||||
{
|
||||
if (long.TryParse(reader.GetString(), out long result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
else if (reader.TokenType == JsonTokenType.Number)
|
||||
{
|
||||
return reader.GetInt64();
|
||||
}
|
||||
|
||||
throw new JsonException($"Invalid long format: {reader.GetString()}");
|
||||
}
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, long value, JsonSerializerOptions options)
|
||||
{
|
||||
writer.WriteNumberValue(value);
|
||||
}
|
||||
}
|
||||
@@ -2,16 +2,48 @@ namespace FileFlows.AudioNodes
|
||||
{
|
||||
public class AudioInfo
|
||||
{
|
||||
public string Language { get; set; }
|
||||
public int Track { get; set; }
|
||||
public int Disc { get; set; }
|
||||
public int TotalDiscs { get; set; }
|
||||
public string Artist { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the title of the audio file.
|
||||
/// </summary>
|
||||
public string Title { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the artist of the audio file.
|
||||
/// </summary>
|
||||
public string Artist { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the album of the audio file.
|
||||
/// </summary>
|
||||
public string Album { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the track number of the audio file.
|
||||
/// </summary>
|
||||
public int Track { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the language number of the audio file.
|
||||
/// </summary>
|
||||
public string Language { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the disc number of the audio file.
|
||||
/// </summary>
|
||||
public int Disc { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the total number of discs in the album.
|
||||
/// </summary>
|
||||
public int TotalDiscs { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the total number of tracks in the album.
|
||||
/// </summary>
|
||||
public int TotalTracks { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the release date of the audio file.
|
||||
/// </summary>
|
||||
public DateTime Date { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the genre of the audio file.
|
||||
/// </summary>
|
||||
public string[] Genres { get; set; }
|
||||
public string Encoder { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets duration in SECONDS
|
||||
/// </summary>
|
||||
@@ -20,6 +52,7 @@ namespace FileFlows.AudioNodes
|
||||
/// Gets or sets the bitrate (in bytes per second)
|
||||
/// </summary>
|
||||
public long Bitrate { get; set; }
|
||||
public string Encoder { get; set; }
|
||||
public string Codec { get; set; }
|
||||
public long Channels { get; set; }
|
||||
public long Frequency { get; set; }
|
||||
|
||||
@@ -1,21 +1,23 @@
|
||||
using System.Diagnostics;
|
||||
//using System.IO;
|
||||
using System.Text.RegularExpressions;
|
||||
using TagLib.Matroska;
|
||||
|
||||
namespace FileFlows.AudioNodes;
|
||||
|
||||
public class AudioInfoHelper
|
||||
{
|
||||
private string ffMpegExe;
|
||||
private string ffprobe;
|
||||
private ILogger Logger;
|
||||
|
||||
public AudioInfoHelper(string ffMpegExe, ILogger logger)
|
||||
public AudioInfoHelper(string ffMpegExe, string ffprobe, ILogger logger)
|
||||
{
|
||||
this.ffMpegExe = ffMpegExe;
|
||||
this.ffprobe = ffprobe;
|
||||
this.Logger = logger;
|
||||
}
|
||||
|
||||
public static string GetFFMpegPath(NodeParameters args) => args.GetToolPath("FFMpeg");
|
||||
public AudioInfo Read(string filename)
|
||||
{
|
||||
var mi = new AudioInfo();
|
||||
@@ -27,11 +29,15 @@ public class AudioInfoHelper
|
||||
}
|
||||
if (string.IsNullOrEmpty(ffMpegExe) || System.IO.File.Exists(ffMpegExe) == false)
|
||||
{
|
||||
Logger.ELog("FFMpeg not found: " + (ffMpegExe ?? "not passed in"));
|
||||
Logger.ELog("FFmpeg not found: " + (ffMpegExe ?? "not passed in"));
|
||||
return mi;
|
||||
}
|
||||
|
||||
mi = ReadMetaData(filename);
|
||||
var result = ReadFromFFprobe(filename);
|
||||
if (result.IsFailed == false)
|
||||
mi = result.Value;
|
||||
else
|
||||
mi = ReadMetaData(filename);
|
||||
|
||||
try
|
||||
{
|
||||
@@ -196,6 +202,75 @@ public class AudioInfoHelper
|
||||
return mi;
|
||||
}
|
||||
|
||||
public Result<AudioInfo> ReadFromFFprobe(string file)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var process = new Process())
|
||||
{
|
||||
process.StartInfo = new ProcessStartInfo();
|
||||
process.StartInfo.FileName = ffprobe;
|
||||
process.StartInfo.UseShellExecute = false;
|
||||
process.StartInfo.RedirectStandardOutput = true;
|
||||
process.StartInfo.RedirectStandardError = true;
|
||||
process.StartInfo.CreateNoWindow = true;
|
||||
process.StartInfo.ArgumentList.Add("-v");
|
||||
process.StartInfo.ArgumentList.Add("error");
|
||||
process.StartInfo.ArgumentList.Add("-select_streams");
|
||||
process.StartInfo.ArgumentList.Add("a:0");
|
||||
process.StartInfo.ArgumentList.Add("-show_format");
|
||||
process.StartInfo.ArgumentList.Add("-of");
|
||||
process.StartInfo.ArgumentList.Add("json");
|
||||
process.StartInfo.ArgumentList.Add(file);
|
||||
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 Result<AudioInfo>.Fail("Failed reading ffmpeg info: " + error);
|
||||
}
|
||||
|
||||
var result = FFprobeAudioInfo.Parse(output);
|
||||
if (result.IsFailed)
|
||||
return Result<AudioInfo>.Fail(result.Error);
|
||||
|
||||
var audioInfo = new AudioInfo();
|
||||
audioInfo.Album = result.Value.Tags.Album;
|
||||
audioInfo.Artist = result.Value.Tags.Artist;
|
||||
audioInfo.Bitrate = result.Value.Bitrate;
|
||||
audioInfo.Codec = result.Value.FormatName;
|
||||
if (DateTime.TryParse(result.Value.Tags.Date ?? string.Empty, out DateTime date))
|
||||
audioInfo.Date = date;
|
||||
else if (int.TryParse(result.Value.Tags.Date ?? string.Empty, out int year))
|
||||
audioInfo.Date = new DateTime(year, 1, 1);
|
||||
|
||||
if (int.TryParse(result.Value.Tags.Disc ?? string.Empty, out int disc))
|
||||
audioInfo.Disc = disc;
|
||||
audioInfo.Duration = (long)result.Value.Duration.TotalSeconds;
|
||||
audioInfo.Genres = result.Value.Tags?.Genre
|
||||
.Split(new string[] { ";", "," }, StringSplitOptions.RemoveEmptyEntries)?.Select(x => x.Trim())
|
||||
?.ToArray();
|
||||
audioInfo.Title = result.Value.Tags.Title;
|
||||
if (int.TryParse(result.Value.Tags.Track, out int track))
|
||||
audioInfo.Track = track;
|
||||
if (int.TryParse(result.Value.Tags.TotalDiscs, out int totalDiscs))
|
||||
audioInfo.TotalDiscs = totalDiscs;
|
||||
if (int.TryParse(result.Value.Tags.TotalTracks, out int totalTracks))
|
||||
audioInfo.TotalTracks = totalTracks;
|
||||
|
||||
return audioInfo;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Result<AudioInfo>.Fail(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public AudioInfo ReadMetaData(string file)
|
||||
{
|
||||
using var tfile = TagLib.File.Create(file);
|
||||
@@ -213,6 +288,7 @@ public class AudioInfoHelper
|
||||
info.Artist = String.Join(", ", tfile.Tag.AlbumArtists);
|
||||
info.Album = tfile.Tag.Album;
|
||||
info.Track = Convert.ToInt32(tfile.Tag.Track);
|
||||
info.TotalTracks = Convert.ToInt32(tfile.Tag.TrackCount);
|
||||
if(tfile.Tag.Year > 1900)
|
||||
{
|
||||
info.Date = new DateTime(Convert.ToInt32(tfile.Tag.Year), 1, 1);
|
||||
|
||||
69
AudioNodes/Helpers/MetadataHelper.cs
Normal file
69
AudioNodes/Helpers/MetadataHelper.cs
Normal file
@@ -0,0 +1,69 @@
|
||||
namespace FileFlows.AudioNodes.Helpers;
|
||||
|
||||
/// <summary>
|
||||
/// Provides methods for working with audio metadata.
|
||||
/// </summary>
|
||||
public class MetadataHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets an array of metadata parameters based on the provided AudioTags.
|
||||
/// </summary>
|
||||
/// <param name="audioInfo">The audio metadata.</param>
|
||||
/// <returns>An array of metadata parameters.</returns>
|
||||
public static string[] GetMetadataParameters(AudioInfo audioInfo)
|
||||
{
|
||||
if (audioInfo == null)
|
||||
return new string[] { };
|
||||
var parameters = new List<string>();
|
||||
|
||||
AddMetadataParameter(parameters, "TITLE", audioInfo.Title);
|
||||
AddMetadataParameter(parameters, "ARTIST", audioInfo.Artist);
|
||||
AddMetadataParameter(parameters, "ALBUM", audioInfo.Album);
|
||||
AddMetadataParameter(parameters, "GENRE", string.Join(";", audioInfo?.Genres ?? new string[] { }));
|
||||
|
||||
if (audioInfo.Date.Year > 1900)
|
||||
{
|
||||
if (audioInfo.Date.Month == 1 && audioInfo.Date.Day == 1)
|
||||
AddMetadataParameter(parameters, "DATE", audioInfo.Date.Year.ToString());
|
||||
else
|
||||
AddMetadataParameter(parameters, "DATE", audioInfo.Date.ToString("yyyy-M-d"));
|
||||
}
|
||||
|
||||
if (audioInfo.Track > 0 && audioInfo.TotalTracks > 0)
|
||||
AddMetadataParameter(parameters, "TRACK", audioInfo.Track + "/" + audioInfo.TotalTracks);
|
||||
else if(audioInfo.Track > 0)
|
||||
AddMetadataParameter(parameters, "TRACK", audioInfo.Track.ToString());
|
||||
|
||||
if(audioInfo.Disc > 0)
|
||||
AddMetadataParameter(parameters, "DISC", audioInfo.Disc.ToString());
|
||||
if(audioInfo.TotalDiscs > 0)
|
||||
AddMetadataParameter(parameters, "TOTALDISCS", audioInfo.TotalDiscs.ToString());
|
||||
AddMetadataParameter(parameters, "comment", "Created by FileFlows\nhttps://fileflows.com");
|
||||
|
||||
return parameters.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a metadata parameter to the list if the value is valid.
|
||||
/// </summary>
|
||||
/// <param name="parameters">The list of parameters.</param>
|
||||
/// <param name="key">The metadata key.</param>
|
||||
/// <param name="value">The metadata value.</param>
|
||||
private static void AddMetadataParameter(List<string> parameters, string key, string value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value) == false && value != "0")
|
||||
{
|
||||
parameters.AddRange(new[] { "-metadata", $"{key}={EscapeForShell(value)}" });
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Escapes a string for use in a command-line shell.
|
||||
/// </summary>
|
||||
/// <param name="input">The input string to escape.</param>
|
||||
/// <returns>The escaped string.</returns>
|
||||
private static string EscapeForShell(string input)
|
||||
{
|
||||
return input.Replace("\"", "\\\"");
|
||||
}
|
||||
}
|
||||
@@ -42,6 +42,9 @@ namespace FileFlows.AudioNodes
|
||||
string ffmpegExe = GetFFmpeg(args);
|
||||
if (string.IsNullOrEmpty(ffmpegExe))
|
||||
return -1;
|
||||
string ffprobe = GetFFprobe(args);
|
||||
if (string.IsNullOrEmpty(ffprobe))
|
||||
return -1;
|
||||
|
||||
|
||||
if (args.FileService.FileCreationTimeUtc(args.WorkingFile).Success(out DateTime createTime))
|
||||
@@ -52,7 +55,7 @@ namespace FileFlows.AudioNodes
|
||||
|
||||
try
|
||||
{
|
||||
if (ReadAudioFileInfo(args, ffmpegExe, LocalWorkingFile))
|
||||
if (ReadAudioFileInfo(args, ffmpegExe, ffprobe, LocalWorkingFile))
|
||||
return 1;
|
||||
|
||||
var AudioInfo = GetAudioInfo(args);
|
||||
|
||||
@@ -37,24 +37,24 @@ namespace FileFlows.AudioNodes
|
||||
}
|
||||
return fileInfo.FullName;
|
||||
}
|
||||
|
||||
protected string GetFFMpegPath(NodeParameters args)
|
||||
protected string GetFFprobe(NodeParameters args)
|
||||
{
|
||||
string ffmpeg = args.GetToolPath("FFMpeg");
|
||||
string ffmpeg = args.GetToolPath("FFprobe");
|
||||
if (string.IsNullOrEmpty(ffmpeg))
|
||||
{
|
||||
args.Logger.ELog("FFMpeg tool not found.");
|
||||
args.Logger.ELog("FFprobe tool not found.");
|
||||
return "";
|
||||
}
|
||||
var fileInfo = new System.IO.FileInfo(ffmpeg);
|
||||
if (fileInfo.Exists == false)
|
||||
{
|
||||
args.Logger.ELog("FFmpeg tool configured by ffmpeg file does not exist.");
|
||||
args.Logger.ELog("FFprobe tool configured by ffmpeg file does not exist.");
|
||||
return "";
|
||||
}
|
||||
return fileInfo.DirectoryName;
|
||||
return fileInfo.FullName;
|
||||
}
|
||||
|
||||
|
||||
private const string Audio_INFO = "AudioInfo";
|
||||
internal void SetAudioInfo(NodeParameters args, AudioInfo AudioInfo, Dictionary<string, object> variables)
|
||||
{
|
||||
@@ -139,9 +139,9 @@ namespace FileFlows.AudioNodes
|
||||
return result;
|
||||
}
|
||||
|
||||
protected bool ReadAudioFileInfo(NodeParameters args, string ffmpegExe, string filename)
|
||||
protected bool ReadAudioFileInfo(NodeParameters args, string ffmpegExe, string ffprobe, string filename)
|
||||
{
|
||||
var AudioInfo = new AudioInfoHelper(ffmpegExe, args.Logger).Read(filename);
|
||||
var AudioInfo = new AudioInfoHelper(ffmpegExe, ffprobe, args.Logger).Read(filename);
|
||||
if (AudioInfo.Duration == 0)
|
||||
{
|
||||
args.Logger?.ILog("Failed to load Audio information.");
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using FileFlows.Plugin;
|
||||
using FileFlows.AudioNodes.Helpers;
|
||||
using FileFlows.Plugin;
|
||||
using FileFlows.Plugin.Attributes;
|
||||
|
||||
namespace FileFlows.AudioNodes
|
||||
@@ -310,6 +311,10 @@ namespace FileFlows.AudioNodes
|
||||
if (string.IsNullOrEmpty(ffmpegExe))
|
||||
return -1;
|
||||
|
||||
string ffprobe = GetFFprobe(args);
|
||||
if (string.IsNullOrEmpty(ffprobe))
|
||||
return -1;
|
||||
|
||||
if(Normalize == false && AudioInfo.Codec?.ToLower() == Extension?.ToLower())
|
||||
{
|
||||
if (SkipIfCodecMatches)
|
||||
@@ -356,6 +361,10 @@ namespace FileFlows.AudioNodes
|
||||
}
|
||||
}
|
||||
|
||||
var metadata = MetadataHelper.GetMetadataParameters(AudioInfo);
|
||||
if (metadata?.Any() == true)
|
||||
ffArgs.AddRange(metadata);
|
||||
|
||||
ffArgs.Add(outputFile);
|
||||
|
||||
args.Logger?.ILog("FFArgs: " + string.Join(" ", ffArgs.Select(x => x.IndexOf(" ") > 0 ? "\"" + x + "\"" : x).ToArray()));
|
||||
@@ -372,12 +381,10 @@ namespace FileFlows.AudioNodes
|
||||
return -1;
|
||||
}
|
||||
|
||||
//CopyMetaData(outputFile, args.FileName);
|
||||
|
||||
args.SetWorkingFile(outputFile);
|
||||
|
||||
// update the Audio file info
|
||||
if (ReadAudioFileInfo(args, ffmpegExe, args.WorkingFile))
|
||||
if (ReadAudioFileInfo(args, ffmpegExe, ffprobe, args.WorkingFile))
|
||||
return 1;
|
||||
|
||||
return -1;
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
#if(DEBUG)
|
||||
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace FileFlows.AudioNodes.Tests;
|
||||
[TestClass]
|
||||
@@ -9,6 +11,7 @@ public class AudioInfoTests
|
||||
{
|
||||
const string file = @"/home/john/Music/test/test.mp3";
|
||||
readonly string ffmpegExe = (OperatingSystem.IsLinux() ? "/usr/bin/ffmpeg" : @"C:\utils\ffmpeg\ffmpeg.exe");
|
||||
readonly string ffprobe = (OperatingSystem.IsLinux() ? "/usr/bin/ffprobe" : @"C:\utils\ffmpeg\ffprobe.exe");
|
||||
|
||||
[TestMethod]
|
||||
public void AudioInfo_SplitTrack()
|
||||
@@ -17,7 +20,7 @@ public class AudioInfoTests
|
||||
args.GetToolPathActual = (string tool) => ffmpegExe;
|
||||
args.TempPath = @"D:\music\temp";
|
||||
|
||||
var AudioInfo = new AudioInfoHelper(ffmpegExe, args.Logger).Read(args.WorkingFile);
|
||||
var AudioInfo = new AudioInfoHelper(ffmpegExe, ffprobe, args.Logger).Read(args.WorkingFile);
|
||||
|
||||
Assert.AreEqual(9, AudioInfo.Track);
|
||||
}
|
||||
@@ -33,7 +36,7 @@ public class AudioInfoTests
|
||||
args.GetToolPathActual = (string tool) => ffmpegExe;
|
||||
args.TempPath = @"D:\music\temp";
|
||||
|
||||
var AudioInfo = new AudioInfoHelper(ffmpegExe, args.Logger).Read(args.WorkingFile);
|
||||
var AudioInfo = new AudioInfoHelper(ffmpegExe, ffprobe, args.Logger).Read(args.WorkingFile);
|
||||
|
||||
Assert.AreEqual(8, AudioInfo.Track);
|
||||
}
|
||||
@@ -50,7 +53,7 @@ public class AudioInfoTests
|
||||
// laod the variables
|
||||
Assert.AreEqual(1, new AudioFile().Execute(args));
|
||||
|
||||
var audio = new AudioInfoHelper(ffmpegExe, args.Logger).Read(args.WorkingFile);
|
||||
var audio = new AudioInfoHelper(ffmpegExe, ffprobe, args.Logger).Read(args.WorkingFile);
|
||||
|
||||
string folder = args.ReplaceVariables("{audio.ArtistThe} ({audio.Year})");
|
||||
Assert.AreEqual($"{audio.Artist} ({audio.Date.Year})", folder);
|
||||
@@ -69,7 +72,7 @@ public class AudioInfoTests
|
||||
|
||||
var audio = new AudioInfo();
|
||||
|
||||
new AudioInfoHelper(ffmpegExe, logger).ParseFileNameInfo(file, audio);
|
||||
new AudioInfoHelper(ffmpegExe, ffprobe, logger).ParseFileNameInfo(file, audio);
|
||||
|
||||
Assert.AreEqual("Meat Loaf", audio.Artist);
|
||||
Assert.AreEqual("Bat out of Hell II- Back Into Hell…", audio.Album);
|
||||
@@ -100,7 +103,7 @@ public class AudioInfoTests
|
||||
int result = convert.Execute(args);
|
||||
Assert.AreEqual(1, result);
|
||||
|
||||
var audio = new AudioInfoHelper(ffmpegExe, args.Logger).Read(args.WorkingFile);
|
||||
var audio = new AudioInfoHelper(ffmpegExe, ffprobe, args.Logger).Read(args.WorkingFile);
|
||||
Assert.AreEqual(192 * 1024, audio.Bitrate);
|
||||
|
||||
var md = new Dictionary<string, object>();
|
||||
@@ -119,6 +122,46 @@ public class AudioInfoTests
|
||||
|
||||
string log = logger.ToString();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void AudioFormatInfoTest()
|
||||
{
|
||||
string ffmpegOutput = @"{
|
||||
""format"": {
|
||||
""filename"": ""Aqua - Aquarium - 03 - Barbie Girl.flac"",
|
||||
""nb_streams"": 1,
|
||||
""nb_programs"": 0,
|
||||
""format_name"": ""flac"",
|
||||
""format_long_name"": ""raw FLAC"",
|
||||
""start_time"": ""0.000000"",
|
||||
""duration"": ""197.906667"",
|
||||
""size"": ""25955920"",
|
||||
""bit_rate"": ""1049218"",
|
||||
""probe_score"": 100,
|
||||
""tags"": {
|
||||
""TITLE"": ""Barbie Girl"",
|
||||
""ARTIST"": ""Aqua"",
|
||||
""ALBUM"": ""Aquarium"",
|
||||
""track"": ""3"",
|
||||
""DATE"": ""1997"",
|
||||
""GENRE"": ""Eurodance"",
|
||||
""TOTALTRACKS"": ""11"",
|
||||
""disc"": ""1"",
|
||||
""TOTALDISCS"": ""1""
|
||||
}
|
||||
}
|
||||
}";
|
||||
|
||||
// Deserialize the JSON using System.Text.Json
|
||||
var result = FFprobeAudioInfo.Parse(ffmpegOutput);
|
||||
Assert.IsFalse(result.IsFailed);
|
||||
var audioFormatInfo = result.Value;
|
||||
|
||||
Assert.AreEqual(1049218, audioFormatInfo.Bitrate);
|
||||
Assert.AreEqual("Barbie Girl", audioFormatInfo.Tags?.Title);
|
||||
Assert.AreEqual("Aqua", audioFormatInfo.Tags?.Artist);
|
||||
Assert.AreEqual("3", audioFormatInfo.Tags?.Track);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,210 +1,208 @@
|
||||
#if(DEBUG)
|
||||
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using System.IO;
|
||||
using AudioNodes.Tests;
|
||||
|
||||
namespace FileFlows.AudioNodes.Tests
|
||||
namespace FileFlows.AudioNodes.Tests;
|
||||
|
||||
[TestClass]
|
||||
public class ConvertTests
|
||||
{
|
||||
using FileFlows.AudioNodes;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.IO;
|
||||
|
||||
[TestClass]
|
||||
public class ConvertTests
|
||||
[TestMethod]
|
||||
public void Convert_FlacToAac()
|
||||
{
|
||||
[TestMethod]
|
||||
public void Convert_FlacToAac()
|
||||
//const string file = @"/home/john/Music/unprocessed/Aqua - Aquarium - 03 - Barbie Girl.flac";
|
||||
const string file = "/home/john/Music/unprocessed/Christina Perri - Lovestrong. (2011) - 04 - Distance.mp3";
|
||||
|
||||
foreach (var codec in new[] { "MP3", "aac", "ogg"})
|
||||
{
|
||||
const string file = @"/home/john/Music/Aquarium (1997)/Aqua - Aquarium - 03 - Barbie Girl.flac";
|
||||
|
||||
foreach (var codec in new[] { "aac", "ogg", "MP3" })
|
||||
foreach (int quality in new[] { 0, 10 })
|
||||
{
|
||||
foreach (int quality in new[] { 0, 10 })
|
||||
var logger = new TestLogger();
|
||||
ConvertAudio node = new();
|
||||
node.Codec = codec;
|
||||
node.Bitrate = quality + 10;
|
||||
node.HighEfficiency = true;
|
||||
var args = new FileFlows.Plugin.NodeParameters(file, logger, false, string.Empty, new LocalFileService());
|
||||
args.GetToolPathActual = (string tool) =>
|
||||
{
|
||||
var logger = new TestLogger();
|
||||
ConvertAudio node = new();
|
||||
node.Codec = codec;
|
||||
node.Bitrate = quality + 10;
|
||||
node.HighEfficiency = true;
|
||||
var args = new FileFlows.Plugin.NodeParameters(file, logger, false, string.Empty, null);
|
||||
args.GetToolPathActual = (string tool) =>
|
||||
{
|
||||
if(tool.ToLowerInvariant().Contains("ffmpeg")) return @"/usr/bin/ffmpeg";
|
||||
return tool;
|
||||
};
|
||||
args.TempPath = @"/home/john/temp";
|
||||
new AudioFile().Execute(args); // need to read the Audio info and set it
|
||||
int output = node.Execute(args);
|
||||
if(tool.ToLowerInvariant().Contains("ffmpeg")) return @"/usr/local/bin/ffmpeg";
|
||||
if(tool.ToLowerInvariant().Contains("ffprobe")) return @"/usr/local/bin/ffprobe";
|
||||
return tool;
|
||||
};
|
||||
args.TempPath = @"/home/john/temp";
|
||||
var af = new AudioFile();
|
||||
Assert.IsTrue(af.PreExecute(args));
|
||||
af.Execute(args); // need to read the Audio info and set it
|
||||
Assert.IsTrue(node.PreExecute(args));
|
||||
int output = node.Execute(args);
|
||||
|
||||
var log = logger.ToString();
|
||||
Assert.AreEqual(1, output);
|
||||
var fi = new FileInfo(args.WorkingFile);
|
||||
File.Move(args.WorkingFile, Path.Combine(fi.DirectoryName, quality + fi.Extension), true);
|
||||
}
|
||||
var log = logger.ToString();
|
||||
Assert.AreEqual(1, output);
|
||||
var fi = new FileInfo(args.WorkingFile);
|
||||
File.Move(args.WorkingFile, Path.Combine(fi.DirectoryName, quality + fi.Extension), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Convert_FlacToMp3()
|
||||
{
|
||||
[TestMethod]
|
||||
public void Convert_FlacToMp3()
|
||||
{
|
||||
|
||||
const string file = @"D:\music\unprocessed\01-billy_joel-you_may_be_right.flac";
|
||||
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, null);;
|
||||
args.GetToolPathActual = (string tool) => @"C:\utils\ffmpeg\ffmpeg.exe";
|
||||
args.TempPath = @"D:\music\temp";
|
||||
new AudioFile().Execute(args); // need to read the Audio info and set it
|
||||
int output = node.Execute(args);
|
||||
ConvertToMP3 node = new();
|
||||
var args = new FileFlows.Plugin.NodeParameters(file, new TestLogger(), false, string.Empty, null);;
|
||||
args.GetToolPathActual = (string tool) => @"C:\utils\ffmpeg\ffmpeg.exe";
|
||||
args.TempPath = @"D:\music\temp";
|
||||
new AudioFile().Execute(args); // need to read the Audio info and set it
|
||||
int output = node.Execute(args);
|
||||
|
||||
Assert.AreEqual(1, output);
|
||||
}
|
||||
[TestMethod]
|
||||
public void Convert_Mp3ToWAV()
|
||||
{
|
||||
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";
|
||||
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, null);;
|
||||
args.GetToolPathActual = (string tool) => @"C:\utils\ffmpeg\ffmpeg.exe";
|
||||
args.TempPath = @"D:\music\temp";
|
||||
new AudioFile().Execute(args); // need to read the Audio info and set it
|
||||
int output = node.Execute(args);
|
||||
ConvertToWAV node = new();
|
||||
var args = new FileFlows.Plugin.NodeParameters(file, new TestLogger(), false, string.Empty, null);;
|
||||
args.GetToolPathActual = (string tool) => @"C:\utils\ffmpeg\ffmpeg.exe";
|
||||
args.TempPath = @"D:\music\temp";
|
||||
new AudioFile().Execute(args); // need to read the Audio info and set it
|
||||
int output = node.Execute(args);
|
||||
|
||||
Assert.AreEqual(1, output);
|
||||
}
|
||||
Assert.AreEqual(1, output);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Convert_Mp3ToOgg()
|
||||
{
|
||||
[TestMethod]
|
||||
public void Convert_Mp3ToOgg()
|
||||
{
|
||||
|
||||
const string file = @"D:\music\unprocessed\04-billy_joel-scenes_from_an_italian_restaurant-b2125758.mp3";
|
||||
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, null);;
|
||||
args.GetToolPathActual = (string tool) => @"C:\utils\ffmpeg\ffmpeg.exe";
|
||||
args.TempPath = @"D:\music\temp";
|
||||
new AudioFile().Execute(args); // need to read the Audio info and set it
|
||||
int output = node.Execute(args);
|
||||
ConvertToOGG node = new();
|
||||
var args = new FileFlows.Plugin.NodeParameters(file, new TestLogger(), false, string.Empty, null);;
|
||||
args.GetToolPathActual = (string tool) => @"C:\utils\ffmpeg\ffmpeg.exe";
|
||||
args.TempPath = @"D:\music\temp";
|
||||
new AudioFile().Execute(args); // need to read the Audio info and set it
|
||||
int output = node.Execute(args);
|
||||
|
||||
Assert.AreEqual(1, output);
|
||||
}
|
||||
Assert.AreEqual(1, output);
|
||||
}
|
||||
|
||||
|
||||
[TestMethod]
|
||||
public void Convert_AacToMp3()
|
||||
{
|
||||
[TestMethod]
|
||||
public void Convert_AacToMp3()
|
||||
{
|
||||
|
||||
const string file = @"D:\music\temp\37f315a0-4afc-4a72-a0b4-eb7eb681b9b3.aac";
|
||||
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, null);;
|
||||
args.GetToolPathActual = (string tool) => @"C:\utils\ffmpeg\ffmpeg.exe";
|
||||
args.TempPath = @"D:\music\temp";
|
||||
new AudioFile().Execute(args); // need to read the Audio info and set it
|
||||
int output = node.Execute(args);
|
||||
ConvertToMP3 node = new();
|
||||
var args = new FileFlows.Plugin.NodeParameters(file, new TestLogger(), false, string.Empty, null);;
|
||||
args.GetToolPathActual = (string tool) => @"C:\utils\ffmpeg\ffmpeg.exe";
|
||||
args.TempPath = @"D:\music\temp";
|
||||
new AudioFile().Execute(args); // need to read the Audio info and set it
|
||||
int output = node.Execute(args);
|
||||
|
||||
Assert.AreEqual(1, output);
|
||||
}
|
||||
Assert.AreEqual(1, output);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Convert_Mp3_AlreadyMp3()
|
||||
{
|
||||
[TestMethod]
|
||||
public void Convert_Mp3_AlreadyMp3()
|
||||
{
|
||||
|
||||
const string file = @"D:\videos\Audio\13-the_cranberries-why.mp3";
|
||||
const string file = @"D:\videos\Audio\13-the_cranberries-why.mp3";
|
||||
|
||||
ConvertAudio node = new();
|
||||
node.SkipIfCodecMatches = true;
|
||||
node.Codec = "mp3";
|
||||
ConvertAudio node = new();
|
||||
node.SkipIfCodecMatches = true;
|
||||
node.Codec = "mp3";
|
||||
|
||||
node.Bitrate = 192;
|
||||
var args = new FileFlows.Plugin.NodeParameters(file, new TestLogger(), false, string.Empty, null);;
|
||||
args.GetToolPathActual = (string tool) => @"C:\utils\ffmpeg\ffmpeg.exe";
|
||||
args.TempPath = @"D:\music\temp";
|
||||
new AudioFile().Execute(args); // need to read the Audio info and set it
|
||||
int output = node.Execute(args);
|
||||
node.Bitrate = 192;
|
||||
var args = new FileFlows.Plugin.NodeParameters(file, new TestLogger(), false, string.Empty, null);;
|
||||
args.GetToolPathActual = (string tool) => @"C:\utils\ffmpeg\ffmpeg.exe";
|
||||
args.TempPath = @"D:\music\temp";
|
||||
new AudioFile().Execute(args); // need to read the Audio info and set it
|
||||
int output = node.Execute(args);
|
||||
|
||||
Assert.AreEqual(2, output);
|
||||
}
|
||||
Assert.AreEqual(2, output);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Convert_VideoToMp3()
|
||||
{
|
||||
[TestMethod]
|
||||
public void Convert_VideoToMp3()
|
||||
{
|
||||
|
||||
const string file = @"D:\videos\testfiles\basic.mkv";
|
||||
const string file = @"D:\videos\testfiles\basic.mkv";
|
||||
|
||||
ConvertToMP3 node = new();
|
||||
var args = new FileFlows.Plugin.NodeParameters(file, new TestLogger(), false, string.Empty, null);;
|
||||
args.GetToolPathActual = (string tool) => @"C:\utils\ffmpeg\ffmpeg.exe";
|
||||
args.TempPath = @"D:\music\temp";
|
||||
//new AudioFile().Execute(args); // need to read the Audio info and set it
|
||||
node.PreExecute(args);
|
||||
int output = node.Execute(args);
|
||||
ConvertToMP3 node = new();
|
||||
var args = new FileFlows.Plugin.NodeParameters(file, new TestLogger(), false, string.Empty, null);;
|
||||
args.GetToolPathActual = (string tool) => @"C:\utils\ffmpeg\ffmpeg.exe";
|
||||
args.TempPath = @"D:\music\temp";
|
||||
//new AudioFile().Execute(args); // need to read the Audio info and set it
|
||||
node.PreExecute(args);
|
||||
int output = node.Execute(args);
|
||||
|
||||
Assert.AreEqual(1, output);
|
||||
}
|
||||
Assert.AreEqual(1, output);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Convert_VideoToAac()
|
||||
{
|
||||
[TestMethod]
|
||||
public void Convert_VideoToAac()
|
||||
{
|
||||
|
||||
const string file = @"D:\videos\testfiles\basic.mkv";
|
||||
const string file = @"D:\videos\testfiles\basic.mkv";
|
||||
|
||||
ConvertToAAC node = new();
|
||||
var args = new FileFlows.Plugin.NodeParameters(file, new TestLogger(), false, string.Empty, null);;
|
||||
args.GetToolPathActual = (string tool) => @"C:\utils\ffmpeg\ffmpeg.exe";
|
||||
args.TempPath = @"D:\music\temp";
|
||||
//new AudioFile().Execute(args); // need to read the Audio info and set it
|
||||
node.PreExecute(args);
|
||||
int output = node.Execute(args);
|
||||
ConvertToAAC node = new();
|
||||
var args = new FileFlows.Plugin.NodeParameters(file, new TestLogger(), false, string.Empty, null);;
|
||||
args.GetToolPathActual = (string tool) => @"C:\utils\ffmpeg\ffmpeg.exe";
|
||||
args.TempPath = @"D:\music\temp";
|
||||
//new AudioFile().Execute(args); // need to read the Audio info and set it
|
||||
node.PreExecute(args);
|
||||
int output = node.Execute(args);
|
||||
|
||||
Assert.AreEqual(1, output);
|
||||
}
|
||||
Assert.AreEqual(1, output);
|
||||
}
|
||||
|
||||
|
||||
[TestMethod]
|
||||
public void Convert_TwoPass()
|
||||
{
|
||||
[TestMethod]
|
||||
public void Convert_TwoPass()
|
||||
{
|
||||
|
||||
const string file = @"D:\music\flacs\01-billy_joel-you_may_be_right.flac";
|
||||
const string file = @"D:\music\flacs\01-billy_joel-you_may_be_right.flac";
|
||||
|
||||
ConvertToAAC node = new();
|
||||
var logger = new TestLogger();
|
||||
var args = new FileFlows.Plugin.NodeParameters(file, logger, false, string.Empty, null);
|
||||
args.GetToolPathActual = (string tool) => @"C:\utils\ffmpeg\ffmpeg.exe";
|
||||
args.TempPath = @"D:\music\temp";
|
||||
new AudioFile().Execute(args); // need to read the Audio info and set it
|
||||
node.Normalize = true;
|
||||
int output = node.Execute(args);
|
||||
ConvertToAAC node = new();
|
||||
var logger = new TestLogger();
|
||||
var args = new FileFlows.Plugin.NodeParameters(file, logger, false, string.Empty, null);
|
||||
args.GetToolPathActual = (string tool) => @"C:\utils\ffmpeg\ffmpeg.exe";
|
||||
args.TempPath = @"D:\music\temp";
|
||||
new AudioFile().Execute(args); // need to read the Audio info and set it
|
||||
node.Normalize = true;
|
||||
int output = node.Execute(args);
|
||||
|
||||
string log = logger.ToString();
|
||||
string log = logger.ToString();
|
||||
|
||||
Assert.AreEqual(1, output);
|
||||
}
|
||||
Assert.AreEqual(1, output);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Convert_TwoPass_VideoFile()
|
||||
{
|
||||
[TestMethod]
|
||||
public void Convert_TwoPass_VideoFile()
|
||||
{
|
||||
|
||||
const string file = @"D:\videos\testfiles\basic.mkv";
|
||||
const string file = @"D:\videos\testfiles\basic.mkv";
|
||||
|
||||
ConvertToAAC node = new();
|
||||
var logger = new TestLogger();
|
||||
var args = new FileFlows.Plugin.NodeParameters(file, logger, false, string.Empty, null);
|
||||
args.GetToolPathActual = (string tool) => @"C:\utils\ffmpeg\ffmpeg.exe";
|
||||
args.TempPath = @"D:\music\temp";
|
||||
new AudioFile().Execute(args); // need to read the Audio info and set it
|
||||
node.Normalize = true;
|
||||
int output = node.Execute(args);
|
||||
ConvertToAAC node = new();
|
||||
var logger = new TestLogger();
|
||||
var args = new FileFlows.Plugin.NodeParameters(file, logger, false, string.Empty, null);
|
||||
args.GetToolPathActual = (string tool) => @"C:\utils\ffmpeg\ffmpeg.exe";
|
||||
args.TempPath = @"D:\music\temp";
|
||||
new AudioFile().Execute(args); // need to read the Audio info and set it
|
||||
node.Normalize = true;
|
||||
int output = node.Execute(args);
|
||||
|
||||
string log = logger.ToString();
|
||||
string log = logger.ToString();
|
||||
|
||||
Assert.AreEqual(1, output);
|
||||
}
|
||||
Assert.AreEqual(1, output);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
483
AudioNodes/Tests/_LocalFileService.cs
Normal file
483
AudioNodes/Tests/_LocalFileService.cs
Normal file
@@ -0,0 +1,483 @@
|
||||
#if(DEBUG)
|
||||
using FileFlows.Plugin;
|
||||
using FileFlows.Plugin.Models;
|
||||
using FileFlows.Plugin.Services;
|
||||
using System.IO;
|
||||
using FileFlows.AudioNodes.Tests;
|
||||
|
||||
namespace AudioNodes.Tests;
|
||||
|
||||
public class LocalFileService : IFileService
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the path separator for the file system
|
||||
/// </summary>
|
||||
public char PathSeparator { get; init; } = Path.DirectorySeparatorChar;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the allowed paths the file service can access
|
||||
/// </summary>
|
||||
public string[] AllowedPaths { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a function for replacing variables in a string.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The function takes a string input, a boolean indicating whether to strip missing variables,
|
||||
/// and a boolean indicating whether to clean special characters.
|
||||
/// </remarks>
|
||||
public ReplaceVariablesDelegate ReplaceVariables { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the permissions to use for files
|
||||
/// </summary>
|
||||
public int? Permissions { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the owner:group to use for files
|
||||
/// </summary>
|
||||
public string OwnerGroup { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the logger used for logging
|
||||
/// </summary>
|
||||
public ILogger? Logger { get; set; }
|
||||
|
||||
public Result<string[]> GetFiles(string path, string searchPattern = "", bool recursive = false)
|
||||
{
|
||||
if (IsProtectedPath(ref path))
|
||||
return Result<string[]>.Fail("Cannot access protected path: " + path);
|
||||
try
|
||||
{
|
||||
return Directory.GetFiles(path, searchPattern ?? string.Empty,
|
||||
recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return new string[] { };
|
||||
}
|
||||
}
|
||||
|
||||
public Result<string[]> GetDirectories(string path)
|
||||
{
|
||||
if (IsProtectedPath(ref path))
|
||||
return Result<string[]>.Fail("Cannot access protected path: " + path);
|
||||
try
|
||||
{
|
||||
return Directory.GetDirectories(path);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return new string[] { };
|
||||
}
|
||||
}
|
||||
|
||||
public Result<bool> DirectoryExists(string path)
|
||||
{
|
||||
if (IsProtectedPath(ref path))
|
||||
return Result<bool>.Fail("Cannot access protected path: " + path);
|
||||
try
|
||||
{
|
||||
return Directory.Exists(path);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public Result<bool> DirectoryDelete(string path, bool recursive = false)
|
||||
{
|
||||
if (IsProtectedPath(ref path))
|
||||
return Result<bool>.Fail("Cannot access protected path: " + path);
|
||||
try
|
||||
{
|
||||
Directory.Delete(path, recursive);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Result<bool>.Fail(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public Result<bool> DirectoryMove(string path, string destination)
|
||||
{
|
||||
if (IsProtectedPath(ref path))
|
||||
return Result<bool>.Fail("Cannot access protected path: " + path);
|
||||
if (IsProtectedPath(ref destination))
|
||||
return Result<bool>.Fail("Cannot access protected path: " + destination);
|
||||
try
|
||||
{
|
||||
Directory.Move(path, destination);
|
||||
SetPermissions(destination);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Result<bool>.Fail(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public Result<bool> DirectoryCreate(string path)
|
||||
{
|
||||
if (IsProtectedPath(ref path))
|
||||
return Result<bool>.Fail("Cannot access protected path: " + path);
|
||||
try
|
||||
{
|
||||
var dirInfo = new DirectoryInfo(path);
|
||||
if (dirInfo.Exists == false)
|
||||
dirInfo.Create();
|
||||
SetPermissions(path);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Result<bool>.Fail(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public Result<bool> FileExists(string path)
|
||||
{
|
||||
if (IsProtectedPath(ref path))
|
||||
return Result<bool>.Fail("Cannot access protected path: " + path);
|
||||
try
|
||||
{
|
||||
return File.Exists(path);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public Result<FileInformation> FileInfo(string path)
|
||||
{
|
||||
if (IsProtectedPath(ref path))
|
||||
return Result<FileInformation>.Fail("Cannot access protected path: " + path);
|
||||
try
|
||||
{
|
||||
FileInfo fileInfo = new FileInfo(path);
|
||||
|
||||
return new FileInformation
|
||||
{
|
||||
CreationTime = fileInfo.CreationTime,
|
||||
CreationTimeUtc = fileInfo.CreationTimeUtc,
|
||||
LastWriteTime = fileInfo.LastWriteTime,
|
||||
LastWriteTimeUtc = fileInfo.LastWriteTimeUtc,
|
||||
Extension = fileInfo.Extension.TrimStart('.'),
|
||||
Name = fileInfo.Name,
|
||||
FullName = fileInfo.FullName,
|
||||
Length = fileInfo.Length,
|
||||
Directory = fileInfo.DirectoryName
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Result<FileInformation>.Fail(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public Result<bool> FileDelete(string path)
|
||||
{
|
||||
if (IsProtectedPath(ref path))
|
||||
return Result<bool>.Fail("Cannot access protected path: " + path);
|
||||
try
|
||||
{
|
||||
var fileInfo = new FileInfo(path);
|
||||
if(fileInfo.Exists)
|
||||
fileInfo.Delete();
|
||||
return true;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public Result<long> FileSize(string path)
|
||||
{
|
||||
if (IsProtectedPath(ref path))
|
||||
return Result<long>.Fail("Cannot access protected path: " + path);
|
||||
try
|
||||
{
|
||||
var fileInfo = new FileInfo(path);
|
||||
if (fileInfo.Exists == false)
|
||||
return Result<long>.Fail("File does not exist");
|
||||
return fileInfo.Length;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Result<long>.Fail(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public Result<DateTime> FileCreationTimeUtc(string path)
|
||||
{
|
||||
if (IsProtectedPath(ref path))
|
||||
return Result<DateTime>.Fail("Cannot access protected path: " + path);
|
||||
try
|
||||
{
|
||||
var fileInfo = new FileInfo(path);
|
||||
if (fileInfo.Exists == false)
|
||||
return Result<DateTime>.Fail("File does not exist");
|
||||
return fileInfo.CreationTimeUtc;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Result<DateTime>.Fail(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public Result<DateTime> FileLastWriteTimeUtc(string path)
|
||||
{
|
||||
if (IsProtectedPath(ref path))
|
||||
return Result<DateTime>.Fail("Cannot access protected path: " + path);
|
||||
try
|
||||
{
|
||||
var fileInfo = new FileInfo(path);
|
||||
if (fileInfo.Exists == false)
|
||||
return Result<DateTime>.Fail("File does not exist");
|
||||
return fileInfo.LastWriteTimeUtc;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Result<DateTime>.Fail(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public Result<bool> FileMove(string path, string destination, bool overwrite = true)
|
||||
{
|
||||
if (IsProtectedPath(ref path))
|
||||
return Result<bool>.Fail("Cannot access protected path: " + path);
|
||||
if (IsProtectedPath(ref destination))
|
||||
return Result<bool>.Fail("Cannot access protected path: " + destination);
|
||||
try
|
||||
{
|
||||
var fileInfo = new FileInfo(path);
|
||||
if (fileInfo.Exists == false)
|
||||
return Result<bool>.Fail("File does not exist");
|
||||
var destDir = new FileInfo(destination).Directory;
|
||||
if (destDir.Exists == false)
|
||||
{
|
||||
destDir.Create();
|
||||
SetPermissions(destDir.FullName);
|
||||
}
|
||||
|
||||
fileInfo.MoveTo(destination, overwrite);
|
||||
SetPermissions(destination);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Result<bool>.Fail(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public Result<bool> FileCopy(string path, string destination, bool overwrite = true)
|
||||
{
|
||||
if (IsProtectedPath(ref path))
|
||||
return Result<bool>.Fail("Cannot access protected path: " + path);
|
||||
if (IsProtectedPath(ref destination))
|
||||
return Result<bool>.Fail("Cannot access protected path: " + destination);
|
||||
try
|
||||
{
|
||||
var fileInfo = new FileInfo(path);
|
||||
if (fileInfo.Exists == false)
|
||||
return Result<bool>.Fail("File does not exist");
|
||||
|
||||
var destDir = new FileInfo(destination).Directory;
|
||||
if (destDir.Exists == false)
|
||||
{
|
||||
destDir.Create();
|
||||
SetPermissions(destDir.FullName);
|
||||
}
|
||||
|
||||
fileInfo.CopyTo(destination, overwrite);
|
||||
SetPermissions(destination);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Result<bool>.Fail(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public Result<bool> FileAppendAllText(string path, string text)
|
||||
{
|
||||
if (IsProtectedPath(ref path))
|
||||
return Result<bool>.Fail("Cannot access protected path: " + path);
|
||||
try
|
||||
{
|
||||
File.AppendAllText(path, text);
|
||||
SetPermissions(path);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Result<bool>.Fail(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public bool FileIsLocal(string path) => true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the local path
|
||||
/// </summary>
|
||||
/// <param name="path">the path</param>
|
||||
/// <returns>the local path to the file</returns>
|
||||
public Result<string> GetLocalPath(string path)
|
||||
=> Result<string>.Success(path);
|
||||
|
||||
public Result<bool> Touch(string path)
|
||||
{
|
||||
if (IsProtectedPath(ref path))
|
||||
return Result<bool>.Fail("Cannot access protected path: " + path);
|
||||
|
||||
if (DirectoryExists(path).Is(true))
|
||||
{
|
||||
try
|
||||
{
|
||||
Directory.SetLastWriteTimeUtc(path, DateTime.UtcNow);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Result<bool>.Fail("Failed to touch directory: " + ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (File.Exists(path))
|
||||
File.SetLastWriteTimeUtc(path, DateTime.UtcNow);
|
||||
else
|
||||
{
|
||||
File.Create(path);
|
||||
SetPermissions(path);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Result<bool>.Fail($"Failed to touch file: '{path}' => {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public Result<bool> SetCreationTimeUtc(string path, DateTime date)
|
||||
{
|
||||
if (IsProtectedPath(ref path))
|
||||
return Result<bool>.Fail("Cannot access protected path: " + path);
|
||||
try
|
||||
{
|
||||
if (!File.Exists(path))
|
||||
return Result<bool>.Fail("File not found.");
|
||||
|
||||
File.SetCreationTimeUtc(path, date);
|
||||
return Result<bool>.Success(true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Result<bool>.Fail($"Error setting creation time: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public Result<bool> SetLastWriteTimeUtc(string path, DateTime date)
|
||||
{
|
||||
if (IsProtectedPath(ref path))
|
||||
return Result<bool>.Fail("Cannot access protected path: " + path);
|
||||
try
|
||||
{
|
||||
if (!File.Exists(path))
|
||||
return Result<bool>.Fail("File not found.");
|
||||
|
||||
File.SetLastWriteTimeUtc(path, date);
|
||||
return Result<bool>.Success(true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Result<bool>.Fail($"Error setting last write time: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a path is accessible by the file server
|
||||
/// </summary>
|
||||
/// <param name="path">the path to check</param>
|
||||
/// <returns>true if accessible, otherwise false</returns>
|
||||
private bool IsProtectedPath(ref string path)
|
||||
{
|
||||
if (OperatingSystem.IsWindows())
|
||||
path = path.Replace("/", "\\");
|
||||
else
|
||||
path = path.Replace("\\", "/");
|
||||
|
||||
if(ReplaceVariables != null)
|
||||
path = ReplaceVariables(path, true);
|
||||
|
||||
if (FileHelper.IsSystemDirectory(path))
|
||||
return true; // a system directory, no access
|
||||
|
||||
if (AllowedPaths?.Any() != true)
|
||||
return false; // no allowed paths configured, allow all
|
||||
|
||||
if (OperatingSystem.IsWindows())
|
||||
path = path.ToLowerInvariant();
|
||||
|
||||
for(int i=0;i<AllowedPaths.Length;i++)
|
||||
{
|
||||
string p = OperatingSystem.IsWindows() ? AllowedPaths[i].ToLowerInvariant().TrimEnd('\\') : AllowedPaths[i].TrimEnd('/');
|
||||
if (path.StartsWith(p))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void SetPermissions(string path, int? permissions = null, Action<string> logMethod = null)
|
||||
{
|
||||
logMethod ??= (string message) => Logger?.ILog(message);
|
||||
|
||||
permissions = permissions != null && permissions > 0 ? permissions : Permissions;
|
||||
if (permissions == null || permissions < 1)
|
||||
permissions = 777;
|
||||
|
||||
|
||||
if ((File.Exists(path) == false && Directory.Exists(path) == false))
|
||||
{
|
||||
logMethod("SetPermissions: File doesnt existing, skipping");
|
||||
return;
|
||||
}
|
||||
|
||||
//StringLogger stringLogger = new StringLogger();
|
||||
var logger = new TestLogger();
|
||||
|
||||
bool isFile = new FileInfo(path).Exists;
|
||||
|
||||
FileHelper.SetPermissions(logger, path, file: isFile,
|
||||
permissions: permissions.Value.ToString("D3"));
|
||||
|
||||
FileHelper.ChangeOwner(logger, path, file: isFile, ownerGroup: OwnerGroup);
|
||||
|
||||
logMethod(logger.ToString());
|
||||
|
||||
return;
|
||||
|
||||
|
||||
if (OperatingSystem.IsLinux())
|
||||
{
|
||||
var filePermissions = FileHelper.ConvertLinuxPermissionsToUnixFileMode(permissions.Value);
|
||||
if (filePermissions == UnixFileMode.None)
|
||||
{
|
||||
logMethod("SetPermissions: Invalid file permissions: " + permissions.Value);
|
||||
return;
|
||||
}
|
||||
|
||||
File.SetUnixFileMode(path, filePermissions);
|
||||
logMethod($"SetPermissions: Permission [{filePermissions}] set on file: " + path);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
#endif
|
||||
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user