From 8860d2ef2d9916c77a8f7009296fdb3231d24449 Mon Sep 17 00:00:00 2001 From: Denis Nutiu Date: Sun, 23 Jan 2022 15:12:17 +0200 Subject: [PATCH] Implement working cli. --- ConsoleInterface/ConsoleInterface.csproj | 8 +- ConsoleInterface/Program.cs | 47 ++++++++--- Image/Cleaner.cs | 38 --------- Image/ICleaner.cs | 9 --- Image/TaskExecutor.cs | 53 ------------- ImageCore/FilesRetriever.cs | 27 +++++++ {Image => ImageCore}/ICompressor.cs | 0 ImageCore/IMetadataRemover.cs | 7 ++ {Image => ImageCore}/IOutputFormatter.cs | 0 .../ImageCore.csproj | 6 +- {Image => ImageCore}/LosslessCompressor.cs | 3 +- ImageCore/MetadataRemover.cs | 23 ++++++ ImageCore/NullCompressor.cs | 11 +++ .../OriginalFilenameOutputFormatter.cs | 10 ++- ImageCore/TaskExecutor.cs | 77 +++++++++++++++++++ ImageCore/TaskExecutorOptions.cs | 17 ++++ ImgMetadataRemover.sln | 2 +- 17 files changed, 221 insertions(+), 117 deletions(-) delete mode 100644 Image/Cleaner.cs delete mode 100644 Image/ICleaner.cs delete mode 100644 Image/TaskExecutor.cs create mode 100644 ImageCore/FilesRetriever.cs rename {Image => ImageCore}/ICompressor.cs (100%) create mode 100644 ImageCore/IMetadataRemover.cs rename {Image => ImageCore}/IOutputFormatter.cs (100%) rename Image/Image.csproj => ImageCore/ImageCore.csproj (88%) rename {Image => ImageCore}/LosslessCompressor.cs (80%) create mode 100644 ImageCore/MetadataRemover.cs create mode 100644 ImageCore/NullCompressor.cs rename {Image => ImageCore}/OriginalFilenameOutputFormatter.cs (66%) create mode 100644 ImageCore/TaskExecutor.cs create mode 100644 ImageCore/TaskExecutorOptions.cs diff --git a/ConsoleInterface/ConsoleInterface.csproj b/ConsoleInterface/ConsoleInterface.csproj index 0d1c7af..3d04f25 100644 --- a/ConsoleInterface/ConsoleInterface.csproj +++ b/ConsoleInterface/ConsoleInterface.csproj @@ -3,10 +3,16 @@ Exe netcoreapp3.1 + metadata_remover + true + true + win-x64 + true - + + diff --git a/ConsoleInterface/Program.cs b/ConsoleInterface/Program.cs index da529ea..de28a1c 100644 --- a/ConsoleInterface/Program.cs +++ b/ConsoleInterface/Program.cs @@ -1,23 +1,50 @@ -using Image; +using CommandLine; +using Image; using Microsoft.Extensions.Logging; +// ReSharper disable MemberCanBePrivate.Global +// ReSharper disable ClassNeverInstantiated.Global +// ReSharper disable UnusedAutoPropertyAccessor.Global + namespace ConsoleInterface { internal static class Program { private static void Main(string[] args) + { + Parser.Default.ParseArguments(args).WithParsed(RunOptions); + } + + private static void RunOptions(Options options) { var loggerFactory = LoggerFactory.Create(b => b.AddConsole()); - var outputFormatter = OriginalFilenameOutputFormatter.Create(@"C:\Users\nutiu\Downloads\Photos-001\clean"); - var executor = TaskExecutor.Create(); - executor.Logger = loggerFactory.CreateLogger("Executor"); - executor.ParallelCleanImages(new[] + TaskExecutor.Logger = loggerFactory.CreateLogger(nameof(TaskExecutor)); + FilesRetriever.Logger = loggerFactory.CreateLogger(nameof(FilesRetriever)); + OriginalFilenameOutputFormatter.Logger = + loggerFactory.CreateLogger(nameof(OriginalFilenameOutputFormatter)); + + var outputFormatter = OriginalFilenameOutputFormatter.Create(options.DestinationDirectory); + var executor = TaskExecutor.Create(new TaskExecutorOptions { - @"C:\Users\nutiu\Downloads\Photos-001\IMG_0138.HEIC", - @"C:\Users\nutiu\Downloads\Photos-001\IMG_0137.HEIC", - @"C:\Users\nutiu\Downloads\Photos-001\IMG_0140.HEIC", - @"C:\Users\nutiu\Downloads\Photos-001\12382975864_09e6e069e7_o.jpg" - }, outputFormatter); + EnableCompression = options.CompressFiles is true, + OutputFormatter = outputFormatter + }); + var filesRetriever = FilesRetriever.Create(); + + + executor.ParallelCleanImages(filesRetriever.GetFilenamesFromPath(options.SourceDirectory)); + } + + public class Options + { + [Option('c', "compress", Required = false, HelpText = "Compress images after cleaning.", Default = true)] + public bool? CompressFiles { get; set; } + + [Option('d', "dest", Required = false, HelpText = "The destination directory.", Default = "./cleaned")] + public string DestinationDirectory { get; set; } + + [Value(0, MetaName = "source", HelpText = "The source directory.", Default = ".")] + public string SourceDirectory { get; set; } } } } \ No newline at end of file diff --git a/Image/Cleaner.cs b/Image/Cleaner.cs deleted file mode 100644 index 2930e85..0000000 --- a/Image/Cleaner.cs +++ /dev/null @@ -1,38 +0,0 @@ -using ImageMagick; - -namespace Image -{ - public class Cleaner : ICleaner - { - private readonly ICompressor _compressor; - private readonly IMagickImage _magickImage; - - public Cleaner(IMagickImage magickImage, ICompressor compressor) - { - _magickImage = magickImage; - _compressor = compressor; - } - - public Cleaner(string fileName) : this(new MagickImage(fileName), new LosslessCompressor()) - { - } - - public void CleanImage(string newFileName) - { - _magickImage.RemoveProfile("exif"); - _magickImage.Write(newFileName); - _compressor.Compress(newFileName); - } - - public void RemoveExifData(string newFilename) - { - _magickImage.RemoveProfile("exif"); - _magickImage.Write(newFilename); - } - - public void OptimizeImage(string fileName) - { - _compressor.Compress(fileName); - } - } -} \ No newline at end of file diff --git a/Image/ICleaner.cs b/Image/ICleaner.cs deleted file mode 100644 index 327a135..0000000 --- a/Image/ICleaner.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Image -{ - public interface ICleaner - { - void CleanImage(string newFileName); - void OptimizeImage(string fileName); - public void RemoveExifData(string newFilename); - } -} \ No newline at end of file diff --git a/Image/TaskExecutor.cs b/Image/TaskExecutor.cs deleted file mode 100644 index 127ca17..0000000 --- a/Image/TaskExecutor.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; - -namespace Image -{ - public class TaskExecutor - { - public ILogger Logger = NullLogger.Instance; - - public static TaskExecutor Create() - { - return new TaskExecutor(); - } - - public bool CleanImage(string fileName, string newFilename) - { - try - { - ICleaner cleaner = new Cleaner(fileName); - cleaner.CleanImage(newFilename); - return true; - } - catch (Exception e) - { - Logger.LogError(e.ToString()); - return false; - } - } - - public void ParallelCleanImages(IEnumerable fileNames, IOutputFormatter outputFormatter) - { - Logger.LogInformation("Starting parallel image cleaning."); - var tasks = new List>(); - foreach (var fileName in fileNames) - { - var task = new Task(() => CleanImage(fileName, outputFormatter.FormatOutputPath(fileName))); - tasks.Add(task); - task.Start(); - } - - var result = Task.WhenAll(tasks); - result.Wait(); - - var successTasks = tasks.Count(t => t.IsCompletedSuccessfully && t.Result); - var errorTasks = tasks.Count() - successTasks; - Logger.LogInformation($"All tasks completed. Success: {successTasks}, Errors: {errorTasks}"); - } - } -} \ No newline at end of file diff --git a/ImageCore/FilesRetriever.cs b/ImageCore/FilesRetriever.cs new file mode 100644 index 0000000..c54f0bd --- /dev/null +++ b/ImageCore/FilesRetriever.cs @@ -0,0 +1,27 @@ +using System.Collections.Generic; +using System.IO; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; + +namespace Image +{ + public class FilesRetriever + { + public static ILogger Logger = NullLogger.Instance; + + private FilesRetriever() + { + } + + public static FilesRetriever Create() + { + return new FilesRetriever(); + } + + public IEnumerable GetFilenamesFromPath(string path) + { + Logger.LogInformation($"Getting files from {path}."); + return Directory.GetFiles(path, "*.*"); + } + } +} \ No newline at end of file diff --git a/Image/ICompressor.cs b/ImageCore/ICompressor.cs similarity index 100% rename from Image/ICompressor.cs rename to ImageCore/ICompressor.cs diff --git a/ImageCore/IMetadataRemover.cs b/ImageCore/IMetadataRemover.cs new file mode 100644 index 0000000..22b83d7 --- /dev/null +++ b/ImageCore/IMetadataRemover.cs @@ -0,0 +1,7 @@ +namespace Image +{ + public interface IMetadataRemover + { + void CleanImage(string newFileName); + } +} \ No newline at end of file diff --git a/Image/IOutputFormatter.cs b/ImageCore/IOutputFormatter.cs similarity index 100% rename from Image/IOutputFormatter.cs rename to ImageCore/IOutputFormatter.cs diff --git a/Image/Image.csproj b/ImageCore/ImageCore.csproj similarity index 88% rename from Image/Image.csproj rename to ImageCore/ImageCore.csproj index 0b78338..99bc5d3 100644 --- a/Image/Image.csproj +++ b/ImageCore/ImageCore.csproj @@ -6,9 +6,9 @@ - - - + + + diff --git a/Image/LosslessCompressor.cs b/ImageCore/LosslessCompressor.cs similarity index 80% rename from Image/LosslessCompressor.cs rename to ImageCore/LosslessCompressor.cs index 392c550..098429b 100644 --- a/Image/LosslessCompressor.cs +++ b/ImageCore/LosslessCompressor.cs @@ -4,13 +4,14 @@ namespace Image { public class LosslessCompressor : ICompressor { + public static readonly LosslessCompressor Instance = new LosslessCompressor(); private readonly ImageOptimizer _imageOptimizer; public LosslessCompressor() { _imageOptimizer = new ImageOptimizer(); } - + public void Compress(string fileName) { _imageOptimizer.LosslessCompress(fileName); diff --git a/ImageCore/MetadataRemover.cs b/ImageCore/MetadataRemover.cs new file mode 100644 index 0000000..4fe6733 --- /dev/null +++ b/ImageCore/MetadataRemover.cs @@ -0,0 +1,23 @@ +using ImageMagick; + +namespace Image +{ + public class MetadataRemover : IMetadataRemover + { + private readonly ICompressor _compressor; + private readonly IMagickImage _magickImage; + + public MetadataRemover(IMagickImage magickImage, ICompressor compressor) + { + _magickImage = magickImage; + _compressor = compressor; + } + + public void CleanImage(string newFileName) + { + _magickImage.RemoveProfile("exif"); + _magickImage.Write(newFileName); + _compressor.Compress(newFileName); + } + } +} \ No newline at end of file diff --git a/ImageCore/NullCompressor.cs b/ImageCore/NullCompressor.cs new file mode 100644 index 0000000..cdd7dd5 --- /dev/null +++ b/ImageCore/NullCompressor.cs @@ -0,0 +1,11 @@ +namespace Image +{ + public class NullCompressor : ICompressor + { + public static readonly NullCompressor Instance = new NullCompressor(); + + public void Compress(string fileName) + { + } + } +} \ No newline at end of file diff --git a/Image/OriginalFilenameOutputFormatter.cs b/ImageCore/OriginalFilenameOutputFormatter.cs similarity index 66% rename from Image/OriginalFilenameOutputFormatter.cs rename to ImageCore/OriginalFilenameOutputFormatter.cs index 3ec8671..0e01e1a 100644 --- a/Image/OriginalFilenameOutputFormatter.cs +++ b/ImageCore/OriginalFilenameOutputFormatter.cs @@ -1,14 +1,22 @@ using System.IO; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; namespace Image { public class OriginalFilenameOutputFormatter : IOutputFormatter { + public static ILogger Logger = NullLogger.Instance; private readonly string _rootDirectory; public OriginalFilenameOutputFormatter(string rootDirectory) { - if (!Directory.Exists(rootDirectory)) Directory.CreateDirectory(rootDirectory); + if (!Directory.Exists(rootDirectory)) + { + Logger.LogWarning("Output directory does not exists. Creating."); + Directory.CreateDirectory(rootDirectory); + } + _rootDirectory = rootDirectory; } diff --git a/ImageCore/TaskExecutor.cs b/ImageCore/TaskExecutor.cs new file mode 100644 index 0000000..2711ea2 --- /dev/null +++ b/ImageCore/TaskExecutor.cs @@ -0,0 +1,77 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using ImageMagick; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; + +namespace Image +{ + public class TaskExecutor + { + public static ILogger Logger = NullLogger.Instance; + private readonly TaskExecutorOptions _options; + + private TaskExecutor(TaskExecutorOptions options) + { + _options = options ?? throw new ArgumentException("Options cannot be null!"); + } + + public static TaskExecutor Create(TaskExecutorOptions options) + { + return new TaskExecutor(options); + } + + public bool CleanImage(string fileName, string newFilename) + { + try + { + ICompressor compressor = NullCompressor.Instance; + var imageMagick = new MagickImage(fileName); + if (_options.EnableCompression) + { + compressor = LosslessCompressor.Instance; + } + + Logger.LogDebug( + $"Cleaning {fileName}, compression {_options.EnableCompression}, outputFormatter {nameof(_options.OutputFormatter)}."); + IMetadataRemover metadataRemover = new MetadataRemover(imageMagick, compressor); + metadataRemover.CleanImage(newFilename); + return true; + } + catch (Exception e) + { + Logger.LogError(e.ToString()); + return false; + } + } + + public void ParallelCleanImages(IEnumerable fileNames) + { + Logger.LogInformation("Starting parallel image cleaning."); + var filenamesArray = fileNames as string[] ?? fileNames.ToArray(); + if (!filenamesArray.Any()) + { + Logger.LogWarning("Empty fileNames, nothing to do."); + return; + } + + var tasks = new List>(); + foreach (var fileName in filenamesArray) + { + var task = new Task(() => + CleanImage(fileName, _options.OutputFormatter.FormatOutputPath(fileName))); + tasks.Add(task); + task.Start(); + } + + var result = Task.WhenAll(tasks); + result.Wait(); + + var successTasks = tasks.Count(t => t.IsCompletedSuccessfully && t.Result); + var errorTasks = tasks.Count() - successTasks; + Logger.LogInformation($"All tasks completed. Success: {successTasks}, Errors: {errorTasks}"); + } + } +} \ No newline at end of file diff --git a/ImageCore/TaskExecutorOptions.cs b/ImageCore/TaskExecutorOptions.cs new file mode 100644 index 0000000..162e5b2 --- /dev/null +++ b/ImageCore/TaskExecutorOptions.cs @@ -0,0 +1,17 @@ +using System; + +namespace Image +{ + public class TaskExecutorOptions + { + private IOutputFormatter _outputFormatter; + + public IOutputFormatter OutputFormatter + { + get => _outputFormatter; + set => _outputFormatter = value ?? throw new ArgumentException("Output formatter cannot be null!"); + } + + public bool EnableCompression { get; set; } = true; + } +} \ No newline at end of file diff --git a/ImgMetadataRemover.sln b/ImgMetadataRemover.sln index 26bce1f..cab734a 100644 --- a/ImgMetadataRemover.sln +++ b/ImgMetadataRemover.sln @@ -1,6 +1,6 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Image", "Image\Image.csproj", "{10AD7910-C8AA-43ED-9231-EBE267AC270F}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImageCore", "ImageCore\ImageCore.csproj", "{10AD7910-C8AA-43ED-9231-EBE267AC270F}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConsoleInterface", "ConsoleInterface\ConsoleInterface.csproj", "{F04E0337-23D3-4209-9BDE-B798090A7A63}" EndProject