Implement working cli.

This commit is contained in:
Denis-Cosmin Nutiu 2022-01-23 15:12:17 +02:00
parent a3c0c567f4
commit 8860d2ef2d
17 changed files with 221 additions and 117 deletions

View file

@ -3,10 +3,16 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
<AssemblyName>metadata_remover</AssemblyName>
<PublishSingleFile>true</PublishSingleFile>
<SelfContained>true</SelfContained>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<PublishReadyToRun>true</PublishReadyToRun>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Image\Image.csproj"/>
<PackageReference Include="CommandLineParser" Version="2.8.0"/>
<ProjectReference Include="..\ImageCore\ImageCore.csproj"/>
<PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0"/>
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="6.0.0"/>
</ItemGroup>

View file

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

View file

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

View file

@ -1,9 +0,0 @@
namespace Image
{
public interface ICleaner
{
void CleanImage(string newFileName);
void OptimizeImage(string fileName);
public void RemoveExifData(string newFilename);
}
}

View file

@ -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<string> fileNames, IOutputFormatter outputFormatter)
{
Logger.LogInformation("Starting parallel image cleaning.");
var tasks = new List<Task<bool>>();
foreach (var fileName in fileNames)
{
var task = new Task<bool>(() => 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}");
}
}
}

View file

@ -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<string> GetFilenamesFromPath(string path)
{
Logger.LogInformation($"Getting files from {path}.");
return Directory.GetFiles(path, "*.*");
}
}
}

View file

@ -0,0 +1,7 @@
namespace Image
{
public interface IMetadataRemover
{
void CleanImage(string newFileName);
}
}

View file

@ -6,9 +6,9 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Magick.NET-Q16-AnyCPU" Version="8.6.1"/>
<PackageReference Include="Magick.NET.Core" Version="8.6.1"/>
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.0"/>
<PackageReference Include="Magick.NET-Q16-AnyCPU" Version="8.6.1" />
<PackageReference Include="Magick.NET.Core" Version="8.6.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.0" />
</ItemGroup>
</Project>

View file

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

View file

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

View file

@ -0,0 +1,11 @@
namespace Image
{
public class NullCompressor : ICompressor
{
public static readonly NullCompressor Instance = new NullCompressor();
public void Compress(string fileName)
{
}
}
}

View file

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

77
ImageCore/TaskExecutor.cs Normal file
View file

@ -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<string> 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<Task<bool>>();
foreach (var fileName in filenamesArray)
{
var task = new Task<bool>(() =>
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}");
}
}
}

View file

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

View file

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