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 Microsoft.VisualStudio.TestTools.UnitTesting;
using PhotoRenamer.Types; using PhotoRenamer.Types;
namespace PhotoRenamer.Test namespace PhotoRenamer.Test;
[TestClass]
public class FilesTest
{ {
[TestClass] private readonly string[] _files;
public class FilesTest
public FilesTest()
{ {
private readonly string[] _files; var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Assets");
_files = FilesHelper.FindSupportedFilesRecursively(path).ToArray();
}
public FilesTest() [TestMethod, Priority(0)]
{ public void FindSupportedFiles()
var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Assets"); {
_files = FilesHelper.FindSupportedFilesRecursively(path).ToArray(); 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(0)] [TestMethod, Priority(1)]
public void FindSupportedFiles() public void GetMetaData()
{ {
Assert.AreEqual(2, _files.Length); var expected = new[] { new MediaFile("r", DateTime.Now), new MediaFile("r", DateTime.Now) };
Assert.AreEqual("IMG_20120528_125912.jpg", Path.GetFileName(_files[0])); // var actual = FilesHelper.GetMediaFiles(_files);
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 namespace PhotoRenamer;
{
public static class FilesHelper
{
private static readonly string[] SupportedFileExtensions = {".jpg", ".cr2", ".mp4"};
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); var fileExt = Path.GetExtension(file);
foreach (var file in files) if (fileExt is null)
continue;
foreach (var supportedFileExtension in SupportedFileExtensions)
{ {
var fileExt = Path.GetExtension(file); if (fileExt.Equals(supportedFileExtension, StringComparison.InvariantCultureIgnoreCase))
if (fileExt is null) continue; yield return file;
foreach (var supportedFileExtension in SupportedFileExtensions)
{
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> <ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup> </PropertyGroup>
<PropertyGroup>
<PublishAot>true</PublishAot>
<UseSystemResourceKeys>true</UseSystemResourceKeys>
<InvariantGlobalization>true</InvariantGlobalization>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="PublishAotCompressed" Version="1.0.0" />
<PackageReference Include="MetadataExtractor" Version="2.7.2" /> <PackageReference Include="MetadataExtractor" Version="2.7.2" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="7.0.0" /> <PackageReference Include="Microsoft.Extensions.Configuration" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.CommandLine" 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 Microsoft.Extensions.Configuration;
using Serilog; using Serilog;
namespace PhotoRenamer 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;
}
}
public static IConfigurationBuilder CreateHostBuilder(string[] args) => new ConfigurationBuilder() internal static class Program
.SetBasePath(Directory.GetCurrentDirectory()) {
.AddJsonFile(AppDomain.CurrentDomain.BaseDirectory + "\\appsettings.json", optional: true, private static int Main(string[] args)
reloadOnChange: true) {
.AddCommandLine(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 ProgressBarOptions _childOptions;
private readonly string _sourcePath; private readonly string _sourcePath;
private readonly string _targetPath; private readonly string _targetPath;
private int _currentCount;
public Renamer(IConfiguration configuration) public Renamer(IConfiguration configuration)
{ {
@ -39,71 +40,49 @@ namespace PhotoRenamer
BackgroundColor = ConsoleColor.DarkGray, BackgroundColor = ConsoleColor.DarkGray,
BackgroundCharacter = '\u2593' BackgroundCharacter = '\u2593'
}; };
var i = 0; _currentCount = 0;
using var progressBar = new ProgressBar(files.Length, "Copying files", options); using var progressBar = new ProgressBar(files.Length, "Copying files", options);
var po = new ParallelOptions { MaxDegreeOfParallelism = 4 }; var po = new ParallelOptions { MaxDegreeOfParallelism = 1 };
Parallel.ForEach(
files, Parallel.ForEach(files, po, file => Body(file, progressBar));
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);
}
}
);
return 0; 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) private static DateTime GetDateTimeFromLastWrite(string file)
{ {
var creationTime = File.GetLastWriteTime(file); var creationTime = File.GetLastWriteTime(file);
return creationTime; return creationTime;
} }
private static DateTime? GetDateTimeFromExif( private static DateTime? GetDateTimeFromExif(IEnumerable<MetadataExtractor.Directory> directories)
IEnumerable<MetadataExtractor.Directory> directories
)
{ {
DateTime dateTime = default; return directories.OfType<ExifIfd0Directory>().FirstOrDefault()?.TryGetDateTime(ExifDirectoryBase.TagDateTime, out var dateTime) == true ? dateTime : null;
return
directories
.OfType<ExifIfd0Directory>()
.FirstOrDefault()
?.TryGetDateTime(ExifDirectoryBase.TagDateTime, out dateTime) == true
? (DateTime?)dateTime
: null;
} }
private static DateTime? GetDateTimeFromMp4( private static DateTime? GetDateTimeFromMp4(IEnumerable<MetadataExtractor.Directory> directories)
IEnumerable<MetadataExtractor.Directory> directories
)
{ {
DateTime dateTime = default; return directories.OfType<QuickTimeMovieHeaderDirectory>().FirstOrDefault()?.TryGetDateTime(QuickTimeMovieHeaderDirectory.TagCreated, out var dateTime) == true ? dateTime : null;
return
directories
.OfType<QuickTimeMovieHeaderDirectory>()
.FirstOrDefault()
?.TryGetDateTime(QuickTimeMovieHeaderDirectory.TagCreated, out dateTime) == true
? (DateTime?)dateTime
: null;
} }
private static void ResetTimes(FileSystemInfo destination, FileSystemInfo source) private static void ResetTimes(FileSystemInfo destination, FileSystemInfo source)
@ -122,11 +101,13 @@ namespace PhotoRenamer
return; return;
using var child = progressBar.Spawn(100, destination.FullName, _childOptions); 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 using var sourceStream = File.Open(source.FullName, FileMode.Open, FileAccess.Read, FileShare.Read);
await client.DownloadAsync(source.FullName, fileStream, progress); 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); ResetTimes(destination, source);
child.Tick(100); child.Tick(100);
@ -134,13 +115,12 @@ namespace PhotoRenamer
private string CreateFolder(DateTime dateTime) private string CreateFolder(DateTime dateTime)
{ {
var folder = Path.Combine( var folder = Path.Combine(_targetPath, dateTime.Year.ToString(), dateTime.Month.ToString("D2"));
_targetPath,
dateTime.Year.ToString(),
dateTime.Month.ToString("D2")
);
if (!Directory.Exists(folder)) if (!Directory.Exists(folder))
{
Directory.CreateDirectory(folder); Directory.CreateDirectory(folder);
}
return 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 namespace PhotoRenamer.Types
{ {
public class MediaFile public record MediaFile(string Path, DateTime CreationDate);
{
public MediaFile(string path, DateTime creationDate)
{
Path = path;
CreationDate = creationDate;
}
public string Path { get; }
public DateTime CreationDate { get; }
}
} }

View File

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