small changes

This commit is contained in:
Holger Börchers 2022-12-01 14:04:35 +01:00
parent 4157993abd
commit 99ab384ade
10 changed files with 145 additions and 221 deletions

View File

@ -4,32 +4,31 @@ using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using PhotoRenamer.Types;
namespace PhotoRenamer.Test
namespace PhotoRenamer.Test;
[TestClass]
public class FilesTest
{
[TestClass]
public class FilesTest
private readonly string[] _files;
public FilesTest()
{
private readonly string[] _files;
public FilesTest()
{
var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Assets");
_files = FilesHelper.FindSupportedFilesRecursively(path).ToArray();
}
[TestMethod, Priority(0)]
public void FindSupportedFiles()
{
Assert.AreEqual(2, _files.Length);
Assert.AreEqual("IMG_20120528_125912.jpg", Path.GetFileName(_files[0]));
Assert.AreEqual("IMG_20120526_170007.jpg", Path.GetFileName(_files[1]));
}
[TestMethod, Priority(1)]
public void GetMetaData()
{
var expected = new[] {new MediaFile("r", DateTime.Now), new MediaFile("r", DateTime.Now)};
// var actual = FilesHelper.GetMediaFiles(_files);
}
var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Assets");
_files = FilesHelper.FindSupportedFilesRecursively(path).ToArray();
}
}
[TestMethod, Priority(0)]
public void FindSupportedFiles()
{
Assert.AreEqual(2, _files.Length);
Assert.AreEqual("IMG_20120528_125912.jpg", Path.GetFileName(_files[0]));
Assert.AreEqual("IMG_20120526_170007.jpg", Path.GetFileName(_files[1]));
}
[TestMethod, Priority(1)]
public void GetMetaData()
{
var expected = new[] { new MediaFile("r", DateTime.Now), new MediaFile("r", DateTime.Now) };
// var actual = FilesHelper.GetMediaFiles(_files);
}
}

View File

@ -1,21 +1,21 @@
namespace PhotoRenamer
{
public static class FilesHelper
{
private static readonly string[] SupportedFileExtensions = {".jpg", ".cr2", ".mp4"};
namespace PhotoRenamer;
public static IEnumerable<string> FindSupportedFilesRecursively(string path)
public static class FilesHelper
{
private static readonly string[] SupportedFileExtensions = { ".jpg", ".cr2", ".mp4" };
public static IEnumerable<string> FindSupportedFilesRecursively(string path)
{
var files = Directory.EnumerateFiles(path, "*", SearchOption.AllDirectories);
foreach (var file in files)
{
var files = Directory.EnumerateFiles(path, "*", SearchOption.AllDirectories);
foreach (var file in files)
var fileExt = Path.GetExtension(file);
if (fileExt is null)
continue;
foreach (var supportedFileExtension in SupportedFileExtensions)
{
var fileExt = Path.GetExtension(file);
if (fileExt is null) continue;
foreach (var supportedFileExtension in SupportedFileExtensions)
{
if (fileExt.Equals(supportedFileExtension, StringComparison.InvariantCultureIgnoreCase))
yield return file;
}
if (fileExt.Equals(supportedFileExtension, StringComparison.InvariantCultureIgnoreCase))
yield return file;
}
}
}

View File

@ -1,76 +0,0 @@
namespace PhotoRenamer
{
public static class HttpClientExtensions
{
public static async Task DownloadAsync(
this HttpClient client,
string requestUri,
Stream destination,
IProgress<float>? progress = null,
CancellationToken cancellationToken = default
)
{
// Get the http headers first to examine the content length
using var response = await client.GetAsync(
requestUri,
HttpCompletionOption.ResponseHeadersRead
);
var contentLength = response.Content.Headers.ContentLength;
using var download = await response.Content.ReadAsStreamAsync(cancellationToken);
// Ignore progress reporting when no progress reporter was
// passed or when the content length is unknown
if (progress == null || !contentLength.HasValue)
{
await download.CopyToAsync(destination, cancellationToken);
return;
}
// Convert absolute progress (bytes downloaded) into relative progress (0% - 100%)
var relativeProgress = new Progress<long>(
totalBytes => progress.Report((float)totalBytes / contentLength.Value)
);
// Use extension method to report progress while downloading
await download.CopyToAsync(destination, 81920, relativeProgress, cancellationToken);
progress.Report(1);
}
public static async Task CopyToAsync(
this Stream source,
Stream destination,
int bufferSize,
IProgress<long>? progress = null,
CancellationToken cancellationToken = default
)
{
if (source == null)
throw new ArgumentNullException(nameof(source));
if (!source.CanRead)
throw new ArgumentException("Has to be readable", nameof(source));
if (destination == null)
throw new ArgumentNullException(nameof(destination));
if (!destination.CanWrite)
throw new ArgumentException("Has to be writable", nameof(destination));
if (bufferSize < 0)
throw new ArgumentOutOfRangeException(nameof(bufferSize));
var buffer = new byte[bufferSize];
long totalBytesRead = 0;
int bytesRead;
while (
(
bytesRead = await source
.ReadAsync(buffer, 0, buffer.Length, cancellationToken)
.ConfigureAwait(false)
) != 0
)
{
await destination
.WriteAsync(buffer, 0, bytesRead, cancellationToken)
.ConfigureAwait(false);
totalBytesRead += bytesRead;
progress?.Report(totalBytesRead);
}
}
}
}

View File

@ -7,7 +7,14 @@
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<PropertyGroup>
<PublishAot>true</PublishAot>
<UseSystemResourceKeys>true</UseSystemResourceKeys>
<InvariantGlobalization>true</InvariantGlobalization>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="PublishAotCompressed" Version="1.0.0" />
<PackageReference Include="MetadataExtractor" Version="2.7.2" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.CommandLine" Version="7.0.0" />

View File

@ -1,2 +0,0 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeInspection/CSharpLanguageProject/LanguageLevel/@EntryValue">CSharp90</s:String></wpf:ResourceDictionary>

View File

@ -1,34 +1,33 @@
using Microsoft.Extensions.Configuration;
using Serilog;
namespace PhotoRenamer
{
internal static class Program
{
private static int Main(string[] args)
{
var configuration = CreateHostBuilder(args).Build();
foreach (var (key, value) in configuration.AsEnumerable())
{
Console.WriteLine($"{{{key}: {value}}}");
}
Log.Logger = new LoggerConfiguration().WriteTo.Console().CreateLogger();
try
{
var renamer = new Renamer(configuration);
return renamer.Run();
}
catch (Exception e)
{
Log.Error(e, "Error executing program");
return -1;
}
}
namespace PhotoRenamer;
public static IConfigurationBuilder CreateHostBuilder(string[] args) => new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile(AppDomain.CurrentDomain.BaseDirectory + "\\appsettings.json", optional: true,
reloadOnChange: true)
.AddCommandLine(args);
internal static class Program
{
private static int Main(string[] args)
{
var configuration = CreateHostBuilder(args).Build();
foreach (var (key, value) in configuration.AsEnumerable())
{
Console.WriteLine($"{{{key}: {value}}}");
}
Log.Logger = new LoggerConfiguration().WriteTo.Console().CreateLogger();
try
{
var renamer = new Renamer(configuration);
return renamer.Run();
}
catch (Exception e)
{
Log.Error(e, "Error executing program");
return -1;
}
}
public static IConfigurationBuilder CreateHostBuilder(string[] args) =>
new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile(AppDomain.CurrentDomain.BaseDirectory + "\\appsettings.json", optional: true, reloadOnChange: true)
.AddCommandLine(args);
}

View File

@ -13,6 +13,7 @@ namespace PhotoRenamer
private readonly ProgressBarOptions _childOptions;
private readonly string _sourcePath;
private readonly string _targetPath;
private int _currentCount;
public Renamer(IConfiguration configuration)
{
@ -39,71 +40,49 @@ namespace PhotoRenamer
BackgroundColor = ConsoleColor.DarkGray,
BackgroundCharacter = '\u2593'
};
var i = 0;
_currentCount = 0;
using var progressBar = new ProgressBar(files.Length, "Copying files", options);
var po = new ParallelOptions { MaxDegreeOfParallelism = 4 };
Parallel.ForEach(
files,
po,
file =>
{
try
{
var directories = ImageMetadataReader.ReadMetadata(file);
var dateTime =
GetDateTimeFromExif(directories)
?? GetDateTimeFromMp4(directories)
?? GetDateTimeFromLastWrite(file);
var folder = CreateFolder(dateTime);
CopyFile(folder, file, progressBar);
}
catch (ImageProcessingException)
{
//silently ignore
}
finally
{
Interlocked.Increment(ref i);
progressBar.Tick(i);
}
}
);
var po = new ParallelOptions { MaxDegreeOfParallelism = 1 };
Parallel.ForEach(files, po, file => Body(file, progressBar));
return 0;
}
private void Body(string file, ProgressBar progressBar)
{
try
{
var directories = ImageMetadataReader.ReadMetadata(file);
var dateTime = GetDateTimeFromExif(directories) ?? GetDateTimeFromMp4(directories) ?? GetDateTimeFromLastWrite(file);
var folder = CreateFolder(dateTime);
CopyFile(folder, file, progressBar);
}
catch (ImageProcessingException)
{
//silently ignore
}
finally
{
Interlocked.Increment(ref _currentCount);
progressBar.Tick(_currentCount);
}
}
private static DateTime GetDateTimeFromLastWrite(string file)
{
var creationTime = File.GetLastWriteTime(file);
return creationTime;
}
private static DateTime? GetDateTimeFromExif(
IEnumerable<MetadataExtractor.Directory> directories
)
private static DateTime? GetDateTimeFromExif(IEnumerable<MetadataExtractor.Directory> directories)
{
DateTime dateTime = default;
return
directories
.OfType<ExifIfd0Directory>()
.FirstOrDefault()
?.TryGetDateTime(ExifDirectoryBase.TagDateTime, out dateTime) == true
? (DateTime?)dateTime
: null;
return directories.OfType<ExifIfd0Directory>().FirstOrDefault()?.TryGetDateTime(ExifDirectoryBase.TagDateTime, out var dateTime) == true ? dateTime : null;
}
private static DateTime? GetDateTimeFromMp4(
IEnumerable<MetadataExtractor.Directory> directories
)
private static DateTime? GetDateTimeFromMp4(IEnumerable<MetadataExtractor.Directory> directories)
{
DateTime dateTime = default;
return
directories
.OfType<QuickTimeMovieHeaderDirectory>()
.FirstOrDefault()
?.TryGetDateTime(QuickTimeMovieHeaderDirectory.TagCreated, out dateTime) == true
? (DateTime?)dateTime
: null;
return directories.OfType<QuickTimeMovieHeaderDirectory>().FirstOrDefault()?.TryGetDateTime(QuickTimeMovieHeaderDirectory.TagCreated, out var dateTime) == true ? dateTime : null;
}
private static void ResetTimes(FileSystemInfo destination, FileSystemInfo source)
@ -122,11 +101,13 @@ namespace PhotoRenamer
return;
using var child = progressBar.Spawn(100, destination.FullName, _childOptions);
using var client = new HttpClient();
using var fileStream = File.OpenWrite(destination.FullName);
var progress = new Progress<float>(o => progressBar.Tick((int)o * 100));
await client.DownloadAsync(source.FullName, fileStream, progress);
await using var sourceStream = File.Open(source.FullName, FileMode.Open, FileAccess.Read, FileShare.Read);
await using var targetStream = File.Open(destination.FullName, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite);
var progress = new Progress<long>(o => progressBar.Tick((int)o * 100));
await sourceStream.CopyToAsync(targetStream, 81920, progress);
ResetTimes(destination, source);
child.Tick(100);
@ -134,13 +115,12 @@ namespace PhotoRenamer
private string CreateFolder(DateTime dateTime)
{
var folder = Path.Combine(
_targetPath,
dateTime.Year.ToString(),
dateTime.Month.ToString("D2")
);
var folder = Path.Combine(_targetPath, dateTime.Year.ToString(), dateTime.Month.ToString("D2"));
if (!Directory.Exists(folder))
{
Directory.CreateDirectory(folder);
}
return folder;
}
}

View File

@ -0,0 +1,28 @@
namespace PhotoRenamer;
public static class StreamExtensions
{
public static async Task CopyToAsync(this Stream source, Stream destination, int bufferSize, IProgress<long>? progress = null, CancellationToken cancellationToken = default)
{
if (source == null)
throw new ArgumentNullException(nameof(source));
if (!source.CanRead)
throw new ArgumentException("Has to be readable", nameof(source));
if (destination == null)
throw new ArgumentNullException(nameof(destination));
if (!destination.CanWrite)
throw new ArgumentException("Has to be writable", nameof(destination));
if (bufferSize < 0)
throw new ArgumentOutOfRangeException(nameof(bufferSize));
var buffer = new byte[bufferSize];
long totalBytesRead = 0;
int bytesRead;
while ((bytesRead = await source.ReadAsync(buffer, cancellationToken).ConfigureAwait(false)) != 0)
{
await destination.WriteAsync(buffer.AsMemory(0, bytesRead), cancellationToken).ConfigureAwait(false);
totalBytesRead += bytesRead;
progress?.Report(totalBytesRead);
}
}
}

View File

@ -1,15 +1,4 @@
namespace PhotoRenamer.Types
{
public class MediaFile
{
public MediaFile(string path, DateTime creationDate)
{
Path = path;
CreationDate = creationDate;
}
public string Path { get; }
public DateTime CreationDate { get; }
}
}
public record MediaFile(string Path, DateTime CreationDate);
}

View File

@ -1,4 +1,4 @@
{
"Source": "C:\\Users\\Holger\\Desktop\\2013-08-01",
"Target": "C:\\Users\\Holger\\Desktop\\Oneplus5"
"Source": "D:\\Nextcloud2\\Upload",
"Target": "D:\\UploadX"
}