FF-1155 - audio nodes now copies metadata when converting

This commit is contained in:
John Andrews
2024-01-23 12:38:20 +13:00
parent 3ea1295e93
commit a54c10d6b7
13 changed files with 1109 additions and 187 deletions

View File

@@ -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);
}

View 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);
}
}

View File

@@ -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; }

View File

@@ -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);

View 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("\"", "\\\"");
}
}

View File

@@ -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);

View File

@@ -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.");

View File

@@ -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;

View File

@@ -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

View File

@@ -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);
}
}

View 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.