diff --git a/Metadata/Models/A_Metadata.cs b/Metadata/Models/A_Metadata.cs index 453364d..623ed0b 100644 --- a/Metadata/Models/A_Metadata.cs +++ b/Metadata/Models/A_Metadata.cs @@ -1,4 +1,5 @@ using MetadataExtractor; +using System.Collections.ObjectModel; using System.Text.Json; using View_by_Distance.Metadata.Models.Stateless; using View_by_Distance.Metadata.Models.Stateless.Methods; @@ -30,11 +31,17 @@ public class A_Metadata _FileGroups = IPath.GetKeyValuePairs(aAConfiguration, bResultsFullGroupDirectory, [aAConfiguration.ResultSingleton]); } - public ExifDirectory GetMetadataCollection(IMetadataConfiguration metadataConfiguration, FilePath filePath, DeterministicHashCode deterministicHashCode) + public FileInfo GetFileInfo(FilePath filePath) + { + FileInfo result; + (_, int directoryIndex) = IPath.GetDirectoryNameAndIndex(_AAConfiguration.ResultAllInOneSubdirectoryLength, filePath.Name); + result = new(Path.Combine(_FileGroups[_AAConfiguration.ResultSingleton][directoryIndex], $"{filePath.NameWithoutExtension}{filePath.ExtensionLowered}.json")); + return result; + } + + public ExifDirectory GetMetadataCollection(IMetadataConfiguration metadataConfiguration, FilePath filePath, FileInfo fileInfo, DeterministicHashCode deterministicHashCode) { ExifDirectory? results; - (_, int directoryIndex) = IPath.GetDirectoryNameAndIndex(_AAConfiguration.ResultAllInOneSubdirectoryLength, filePath.Name); - FileInfo fileInfo = new(Path.Combine(_FileGroups[_AAConfiguration.ResultSingleton][directoryIndex], $"{filePath.NameWithoutExtension}{filePath.ExtensionLowered}.json")); if (_ForceMetadataLastWriteTimeToCreationTime && !fileInfo.Exists && File.Exists(Path.ChangeExtension(fileInfo.FullName, ".delete"))) { File.Move(Path.ChangeExtension(fileInfo.FullName, ".delete"), fileInfo.FullName); @@ -71,9 +78,8 @@ public class A_Metadata try { size = Dimensions.GetDimensions(filePath.FullName); } catch (Exception) { size = null; } - IReadOnlyList directories = ImageMetadataReader.ReadMetadata(filePath.FullName); - results = Exif.Covert(filePath, deterministicHashCode, fileInfo, size, directories); + results = Exif.Covert(filePath, deterministicHashCode, size, directories); string json = JsonSerializer.Serialize(results, ExifDirectorySourceGenerationContext.Default.ExifDirectory); if (IPath.WriteAllText(fileInfo.FullName, json, updateDateWhenMatches: false, compareBeforeWrite: true, updateToWhenMatches: null) && _ForceMetadataLastWriteTimeToCreationTime) { @@ -84,26 +90,31 @@ public class A_Metadata return results; } - public static Action GetResultCollection(IRename rename, IMetadataConfiguration metadataConfiguration, A_Metadata metadata, List exifDirectories, Action tick) + public static Action SetExifDirectoryCollection(IRename rename, IMetadataConfiguration metadataConfiguration, A_Metadata metadata, List<(string, FileInfo, ExifDirectory)> exifDirectories, Action tick) { return file => { tick.Invoke(); + FileInfo fileInfo; + FilePath? ffmpegFilePath; + ExifDirectory exifDirectory; + ReadOnlyCollection ffmpegFiles; + DeterministicHashCode deterministicHashCode; FilePath filePath = IId.GetFilePath(metadataConfiguration, file); if (filePath.ExtensionLowered is not ".paddedId" and not ".lsv") { if (filePath.Id is null || (!filePath.IsIdFormat && !filePath.IsPaddedIdFormat)) { - string[]? ffmpegFiles = rename.ConvertAndGetFfmpegFiles(filePath); - filePath = ffmpegFiles is null || ffmpegFiles.Length < 0 ? filePath : IId.GetFilePath(filePath, ffmpegFiles[0]); - DeterministicHashCode deterministicHashCode = filePath.Id is not null ? deterministicHashCode = new(null, filePath.Id, null) : deterministicHashCode = rename.GetDeterministicHashCode(filePath); - if (ffmpegFiles is not null) - { - foreach (string ffmpegFile in ffmpegFiles) - File.Delete(ffmpegFile); - } + (ffmpegFiles, ffmpegFilePath) = rename.ConvertAndGetFfmpegFiles(filePath); + if (ffmpegFilePath is not null) + filePath = ffmpegFilePath; + fileInfo = metadata.GetFileInfo(filePath); + deterministicHashCode = filePath.Id is not null ? deterministicHashCode = new(null, filePath.Id, null) : deterministicHashCode = rename.GetDeterministicHashCode(filePath); + exifDirectory = metadata.GetMetadataCollection(metadataConfiguration, filePath, fileInfo, deterministicHashCode); lock (exifDirectories) - exifDirectories.Add(metadata.GetMetadataCollection(metadataConfiguration, filePath, deterministicHashCode)); + exifDirectories.Add(new(file, fileInfo, exifDirectory)); + foreach (string ffmpegFile in ffmpegFiles) + File.Delete(ffmpegFile); } } }; diff --git a/Metadata/Models/Binder/Configuration.cs b/Metadata/Models/Binder/Configuration.cs index 01ae522..32a8376 100644 --- a/Metadata/Models/Binder/Configuration.cs +++ b/Metadata/Models/Binder/Configuration.cs @@ -81,15 +81,7 @@ public class Configuration public static MetadataConfiguration Get(IConfigurationRoot configurationRoot) { MetadataConfiguration result; -#if Linux - string environmentName = "Linux"; -#elif OSX - string environmentName = "OSX"; -#elif Windows - string environmentName = "Windows"; -#endif - string section = string.Concat(environmentName, ":", nameof(Configuration)); - IConfigurationSection configurationSection = configurationRoot.GetSection(section); + IConfigurationSection configurationSection = configurationRoot.GetSection(nameof(Configuration)); #pragma warning disable IL3050, IL2026 Configuration? configuration = configurationSection.Get(); #pragma warning restore IL3050, IL2026 diff --git a/Metadata/Models/MetadataConfiguration copy.cs b/Metadata/Models/MetadataConfiguration copy.cs deleted file mode 100644 index 2e39fde..0000000 --- a/Metadata/Models/MetadataConfiguration copy.cs +++ /dev/null @@ -1,127 +0,0 @@ -// using System.Text.Json; -// using System.Text.Json.Serialization; - -// namespace View_by_Distance.Metadata.Models; - -// public class ZMetadataConfiguration : Shared.Models.Properties.IAAConfiguration -// { - -// protected string _RootDirectory; - -// public string RootDirectory => _RootDirectory; - -// public string DateGroup { init; get; } -// public string FileNameDirectorySeparator { init; get; } -// public bool ForcePropertyLastWriteTimeToCreationTime { init; get; } -// public string[] IgnoreExtensions { init; get; } -// public string[] IgnoreRulesKeyWords { init; get; } -// public int MaxImagesInDirectoryForTopLevelFirstPass { init; get; } -// public string? ModelName { init; get; } -// public int? NumberOfJitters { init; get; } -// public int? NumberOfTimesToUpsample { init; get; } -// public int Offset { init; get; } -// public string Pattern { init; get; } -// public string PersonBirthdayFormat { init; get; } -// public bool PopulatePropertyId { init; get; } -// public string? PredictorModelName { init; get; } -// public bool PropertiesChangedForProperty { init; get; } -// public string[] PropertyContentCollectionFiles { init; get; } -// public string ResultAllInOne { init; get; } -// public int ResultAllInOneSubdirectoryLength { init; get; } -// public string ResultCollection { init; get; } -// public string ResultContent { init; get; } -// public string ResultSingleton { init; get; } -// public string[] ValidImageFormatExtensions { init; get; } - -// [JsonConstructor] -// public MetadataConfiguration(string dateGroup, -// string fileNameDirectorySeparator, -// bool forcePropertyLastWriteTimeToCreationTime, -// string[] ignoreExtensions, -// string[] ignoreRulesKeyWords, -// int maxImagesInDirectoryForTopLevelFirstPass, -// string? modelName, -// int? numberOfJitters, -// int? numberOfTimesToUpsample, -// int offset, -// string pattern, -// string personBirthdayFormat, -// bool populatePropertyId, -// string? predictorModelName, -// bool propertiesChangedForProperty, -// string[] propertyContentCollectionFiles, -// string resultAllInOne, -// int resultAllInOneSubdirectoryLength, -// string resultCollection, -// string resultContent, -// string resultSingleton, -// string rootDirectory, -// string[] validImageFormatExtensions) -// { -// DateGroup = dateGroup; -// FileNameDirectorySeparator = fileNameDirectorySeparator; -// ForcePropertyLastWriteTimeToCreationTime = forcePropertyLastWriteTimeToCreationTime; -// IgnoreExtensions = ignoreExtensions; -// IgnoreRulesKeyWords = ignoreRulesKeyWords; -// MaxImagesInDirectoryForTopLevelFirstPass = maxImagesInDirectoryForTopLevelFirstPass; -// ModelName = modelName; -// NumberOfJitters = numberOfJitters; -// NumberOfTimesToUpsample = numberOfTimesToUpsample; -// Offset = offset; -// Pattern = pattern; -// PersonBirthdayFormat = personBirthdayFormat; -// PredictorModelName = predictorModelName; -// PopulatePropertyId = populatePropertyId; -// PropertiesChangedForProperty = propertiesChangedForProperty; -// PropertyContentCollectionFiles = propertyContentCollectionFiles; -// ResultAllInOne = resultAllInOne; -// ResultAllInOneSubdirectoryLength = resultAllInOneSubdirectoryLength; -// ResultCollection = resultCollection; -// ResultContent = resultContent; -// ResultSingleton = resultSingleton; -// _RootDirectory = rootDirectory; -// ValidImageFormatExtensions = validImageFormatExtensions; -// } - -// public override string ToString() -// { -// string result = JsonSerializer.Serialize(this, MetadataConfigurationSourceGenerationContext.Default.MetadataConfiguration); -// return result; -// } - -// public void ChangeRootDirectory(string rootDirectory) => -// _RootDirectory = Path.GetFullPath(rootDirectory); - -// public static void Verify(MetadataConfiguration propertyConfiguration, bool requireExist) -// { -// if (propertyConfiguration is null) -// throw new NullReferenceException(nameof(propertyConfiguration)); -// if (propertyConfiguration.IgnoreExtensions is null || propertyConfiguration.IgnoreExtensions.Length == 0) -// throw new NullReferenceException(nameof(propertyConfiguration.IgnoreExtensions)); -// if (propertyConfiguration.IgnoreRulesKeyWords is null || propertyConfiguration.IgnoreRulesKeyWords.Length == 0) -// throw new NullReferenceException(nameof(propertyConfiguration.IgnoreRulesKeyWords)); -// if (propertyConfiguration.PropertyContentCollectionFiles is null) -// throw new NullReferenceException(nameof(propertyConfiguration.PropertyContentCollectionFiles)); -// if (propertyConfiguration.ValidImageFormatExtensions is null || propertyConfiguration.ValidImageFormatExtensions.Length == 0) -// throw new NullReferenceException(nameof(propertyConfiguration.ValidImageFormatExtensions)); -// if (propertyConfiguration is null) -// throw new NullReferenceException(nameof(propertyConfiguration)); -// if (string.IsNullOrEmpty(propertyConfiguration.DateGroup)) -// throw new NullReferenceException(nameof(propertyConfiguration.DateGroup)); -// if (string.IsNullOrEmpty(propertyConfiguration.FileNameDirectorySeparator)) -// throw new NullReferenceException(nameof(propertyConfiguration.FileNameDirectorySeparator)); -// if (string.IsNullOrEmpty(propertyConfiguration.Pattern)) -// throw new NullReferenceException(nameof(propertyConfiguration.Pattern)); -// if (string.IsNullOrEmpty(propertyConfiguration.RootDirectory) || (requireExist && !Directory.Exists(propertyConfiguration.RootDirectory))) -// throw new NullReferenceException(nameof(propertyConfiguration.RootDirectory)); -// if (propertyConfiguration.RootDirectory != Path.GetFullPath(propertyConfiguration.RootDirectory)) -// throw new Exception(); -// } - -// } - -// [JsonSourceGenerationOptions(WriteIndented = true)] -// [JsonSerializable(typeof(MetadataConfiguration))] -// internal partial class MetadataConfigurationSourceGenerationContext : JsonSerializerContext -// { -// } \ No newline at end of file diff --git a/Metadata/Models/Stateless/Dimensions.cs b/Metadata/Models/Stateless/Dimensions.cs index c1d3ca1..1daf27a 100644 --- a/Metadata/Models/Stateless/Dimensions.cs +++ b/Metadata/Models/Stateless/Dimensions.cs @@ -122,7 +122,7 @@ internal static class Dimensions /// The path of the image to get the dimensions of. /// The dimensions of the specified image. /// The image was of an unrecognized format. - public static Size GetDimensions(BinaryReader binaryReader) + internal static Size GetDimensions(BinaryReader binaryReader) { int maxMagicBytesLength = _ImageFormatDecoders.Keys.OrderByDescending(x => x.Length).First().Length; @@ -146,11 +146,11 @@ internal static class Dimensions /// /// Gets the dimensions of an image. - /// + ///internal /// The path of the image to get the dimensions of. /// The dimensions of the specified image. /// The image was of an unrecognized format. - public static Size GetDimensions(string path) + internal static Size GetDimensions(string path) { using BinaryReader binaryReader = new(File.OpenRead(path)); try diff --git a/Metadata/Models/Stateless/Exif.cs b/Metadata/Models/Stateless/Exif.cs index a01855c..681f67a 100644 --- a/Metadata/Models/Stateless/Exif.cs +++ b/Metadata/Models/Stateless/Exif.cs @@ -322,7 +322,7 @@ internal abstract class Exif return result; } - internal static Shared.Models.ExifDirectory Covert(Shared.Models.FilePath filePath, Shared.Models.DeterministicHashCode deterministicHashCode, FileInfo fileInfo, System.Drawing.Size? size, IReadOnlyList directories) + internal static Shared.Models.ExifDirectory Covert(Shared.Models.FilePath filePath, Shared.Models.DeterministicHashCode deterministicHashCode, System.Drawing.Size? size, IReadOnlyList directories) { Shared.Models.ExifDirectory results; Shared.Models.AviDirectory aviDirectory = GetAviDirectory(directories); @@ -338,14 +338,13 @@ internal abstract class Exif Shared.Models.QuickTimeTrackHeaderDirectory quickTimeTrackHeaderDirectory = GetQuickTimeTrackHeaderDirectoryDirectory(directories); results = new(aviDirectory, exifDirectoryBase, - filePath.FullName, fileMetadataDirectory, gifHeaderDirectory, gpsDirectory, size?.Height, deterministicHashCode.Id ?? filePath.Id, - fileInfo.FullName, jpegDirectory, + filePath.Name, photoshopDirectory, pngDirectory, quickTimeMovieHeaderDirectory, diff --git a/Rename/Models/AppSettings.cs b/Rename/Models/AppSettings.cs index 1c0d3c4..60b95a8 100644 --- a/Rename/Models/AppSettings.cs +++ b/Rename/Models/AppSettings.cs @@ -4,11 +4,7 @@ using System.Text.Json.Serialization; namespace View_by_Distance.Rename.Models; public record AppSettings(string Company, - string DefaultUnknownDirectoryName, - bool ForceIdName, - int MaxDegreeOfParallelism, - int MaxMinutesDelta, - bool RenameUndo) + int MaxDegreeOfParallelism) { public override string ToString() diff --git a/Rename/Models/Binder/AppSettings.cs b/Rename/Models/Binder/AppSettings.cs index 32cdafc..c18b5cb 100644 --- a/Rename/Models/Binder/AppSettings.cs +++ b/Rename/Models/Binder/AppSettings.cs @@ -8,11 +8,7 @@ public class AppSettings { public string? Company { get; set; } - public string? DefaultUnknownDirectoryName { get; set; } - public bool? ForceIdName { get; set; } public int? MaxDegreeOfParallelism { get; set; } - public int? MaxMinutesDelta { get; set; } - public bool? RenameUndo { get; set; } public override string ToString() { @@ -24,19 +20,10 @@ public class AppSettings { Models.AppSettings result; if (appSettings?.Company is null) throw new NullReferenceException(nameof(appSettings.Company)); - if (appSettings?.DefaultUnknownDirectoryName is null) throw new NullReferenceException(nameof(appSettings.DefaultUnknownDirectoryName)); - if (appSettings?.ForceIdName is null) throw new NullReferenceException(nameof(appSettings.ForceIdName)); if (appSettings?.MaxDegreeOfParallelism is null) throw new NullReferenceException(nameof(appSettings.MaxDegreeOfParallelism)); - if (appSettings?.MaxMinutesDelta is null) throw new NullReferenceException(nameof(appSettings.MaxMinutesDelta)); - if (appSettings?.RenameUndo is null) throw new NullReferenceException(nameof(appSettings.RenameUndo)); result = new( appSettings.Company, - appSettings.DefaultUnknownDirectoryName, - appSettings.ForceIdName.Value, - appSettings.MaxDegreeOfParallelism.Value, - appSettings.MaxMinutesDelta.Value, - appSettings.RenameUndo.Value - ); + appSettings.MaxDegreeOfParallelism.Value); return result; } diff --git a/Rename/Models/Binder/Configuration.cs b/Rename/Models/Binder/Configuration.cs index 6062601..e4e2057 100644 --- a/Rename/Models/Binder/Configuration.cs +++ b/Rename/Models/Binder/Configuration.cs @@ -37,15 +37,7 @@ public class Configuration public static Models.Configuration Get(IConfigurationRoot configurationRoot, Metadata.Models.MetadataConfiguration metadataConfiguration) { Models.Configuration result; -#if Linux - string environmentName = "Linux"; -#elif OSX - string environmentName = "OSX"; -#elif Windows - string environmentName = "Windows"; -#endif - string section = string.Concat(environmentName, ":", nameof(Configuration)); - IConfigurationSection configurationSection = configurationRoot.GetSection(section); + IConfigurationSection configurationSection = configurationRoot.GetSection(nameof(Configuration)); #pragma warning disable IL3050, IL2026 Configuration? configuration = configurationSection.Get(); #pragma warning restore IL3050, IL2026 diff --git a/Rename/Rename.cs b/Rename/Rename.cs index 441c7ae..9b90b31 100644 --- a/Rename/Rename.cs +++ b/Rename/Rename.cs @@ -2,6 +2,7 @@ using CliWrap; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using ShellProgressBar; +using System.Collections.ObjectModel; using System.Drawing; using System.Drawing.Imaging; using System.Runtime.InteropServices; @@ -15,6 +16,9 @@ namespace View_by_Distance.Rename; public class Rename : IRename { + private record ToDo(string? Directory, FileHolder FileHolder, string File, bool JsonFile); + private record Record(DateTime DateTime, ExifDirectory ExifDirectory, string File, string JsonFile); + private readonly AppSettings _AppSettings; private readonly Configuration _Configuration; private readonly IConfigurationRoot _ConfigurationRoot; @@ -39,10 +43,12 @@ public class Rename : IRename logger?.LogInformation("{RootDirectory}", directoryInfo.FullName); MetadataConfiguration.Verify(metadataConfiguration, requireExist: false); Verify(); - List linesB = RenameFilesInDirectories(logger, ticks, directoryInfo); - if (linesB.Count != 0) + ReadOnlyCollection exifDirectories = GetExifDirectoryCollection(directoryInfo); + ReadOnlyCollection toDoCollection = GetToDoCollection(logger, ticks, exifDirectories); + ReadOnlyCollection lines = RenameFilesInDirectories(toDoCollection); + if (lines.Count != 0) { - File.WriteAllLines($"D:/Tmp/Phares/{DateTime.Now.Ticks}.tsv", linesB); + File.WriteAllLines($"D:/Tmp/Phares/{DateTime.Now.Ticks}.tsv", lines); _ = IPath.DeleteEmptyDirectories(directoryInfo.FullName); } } @@ -59,53 +65,15 @@ public class Rename : IRename throw new NullReferenceException(nameof(_MetadataConfiguration)); } - private bool GetRunToDoCollectionFirst(long ticks, DirectoryInfo directoryInfo) + (ReadOnlyCollection, FilePath?) IRename.ConvertAndGetFfmpegFiles(FilePath filePath) { - bool result = false; - string[] directories; - string seasonDirectory; - DateTime dateTime = new(ticks); - (int season, string seasonName) = IDate.GetSeason(dateTime.DayOfYear); - string eDistanceContentDirectory = IResult.GetResultsDateGroupDirectory(_MetadataConfiguration, nameof(A_Metadata), _MetadataConfiguration.ResultContent); - FileSystemInfo fileSystemInfo = new DirectoryInfo(eDistanceContentDirectory); - string[] checkDirectories = - [ - Path.Combine(directoryInfo.FullName, "Ancestry"), - Path.Combine(directoryInfo.FullName, "Facebook"), - Path.Combine(directoryInfo.FullName, "LinkedIn"), - directoryInfo.FullName, - ]; - foreach (string checkDirectory in checkDirectories) - { - if (checkDirectory == directoryInfo.FullName) - seasonDirectory = Path.Combine(checkDirectory, $"{dateTime.Year}.{season} {seasonName}"); - else - seasonDirectory = Path.Combine(checkDirectory, $"{dateTime.Year}.{season} {seasonName} {Path.GetFileName(checkDirectory)}"); - if (!Directory.Exists(seasonDirectory)) - _ = Directory.CreateDirectory(seasonDirectory); - if (result) - continue; - directories = Directory.GetDirectories(checkDirectory, "*", SearchOption.TopDirectoryOnly); - foreach (string directory in directories) - { - if (new DirectoryInfo(directory).LastWriteTime > fileSystemInfo.LastWriteTime) - { - result = true; - break; - } - } - } - return result; - } - - string[]? IRename.ConvertAndGetFfmpegFiles(FilePath filePath) - { - string[]? results; + List results = []; + FilePath? result; bool isIgnoreExtension; bool isValidImageFormatExtension = _MetadataConfiguration.ValidImageFormatExtensions.Contains(filePath.ExtensionLowered); isIgnoreExtension = isValidImageFormatExtension && _MetadataConfiguration.IgnoreExtensions.Contains(filePath.ExtensionLowered); if (!isIgnoreExtension && isValidImageFormatExtension) - results = null; + result = null; else { CommandTask commandTask = Cli.Wrap("ffmpeg.exe") @@ -114,19 +82,20 @@ public class Rename : IRename .WithWorkingDirectory(filePath.DirectoryName) .ExecuteAsync(); commandTask.Task.Wait(); - results = Directory.GetFiles(filePath.DirectoryName, $"{filePath.Name}-*.jpg", SearchOption.TopDirectoryOnly); - if (results.Length == 0) + results.AddRange(Directory.GetFiles(filePath.DirectoryName, $"{filePath.Name}-*.jpg", SearchOption.TopDirectoryOnly)); + if (results.Count == 0) throw new Exception(); - if (!filePath.Name.EndsWith("-0001.jpg")) + result = IId.GetFilePath(_MetadataConfiguration, results[0]); + if (!result.Name.EndsWith("-0001.jpg")) throw new Exception(); - isValidImageFormatExtension = _MetadataConfiguration.ValidImageFormatExtensions.Contains(filePath.ExtensionLowered); - isIgnoreExtension = isValidImageFormatExtension && _MetadataConfiguration.IgnoreExtensions.Contains(filePath.ExtensionLowered); + isValidImageFormatExtension = _MetadataConfiguration.ValidImageFormatExtensions.Contains(result.ExtensionLowered); + isIgnoreExtension = isValidImageFormatExtension && _MetadataConfiguration.IgnoreExtensions.Contains(result.ExtensionLowered); if (isIgnoreExtension || !isValidImageFormatExtension) throw new Exception(); - if (filePath.DirectoryName is null) - throw new NullReferenceException(nameof(filePath.DirectoryName)); + if (result.DirectoryName is null) + throw new NullReferenceException(nameof(result.DirectoryName)); } - return results; + return new(new(results), result); } #pragma warning disable CA1416 @@ -164,53 +133,163 @@ public class Rename : IRename #pragma warning restore CA1416 - private void GetResultCollection(IRename rename, List exifDirectories, IEnumerable files, A_Metadata metadata) + private void GetExifDirectoryCollection(IRename rename, List<(string, FileInfo, ExifDirectory)> exifDirectories, IEnumerable files, A_Metadata metadata) { - string[]? ffmpegFiles; + FileInfo fileInfo; + FilePath filePath; + FilePath? ffmpegFilePath; + ExifDirectory exifDirectory; + ReadOnlyCollection ffmpegFiles; DeterministicHashCode deterministicHashCode; foreach (string file in files) { - FilePath filePath = IId.GetFilePath(_MetadataConfiguration, file); + filePath = IId.GetFilePath(_MetadataConfiguration, file); if (filePath.ExtensionLowered is ".paddedId" or ".lsv") continue; if (files.Contains($"{filePath.FullName}.paddedId")) continue; if (filePath.Id is not null && (filePath.IsIdFormat || filePath.IsPaddedIdFormat)) continue; - ffmpegFiles = rename.ConvertAndGetFfmpegFiles(filePath); - filePath = ffmpegFiles is null || ffmpegFiles.Length < 0 ? filePath : IId.GetFilePath(filePath, ffmpegFiles[0]); + (ffmpegFiles, ffmpegFilePath) = rename.ConvertAndGetFfmpegFiles(filePath); + if (ffmpegFilePath is not null) + filePath = ffmpegFilePath; + fileInfo = metadata.GetFileInfo(filePath); if (filePath.Id is not null) deterministicHashCode = new(null, filePath.Id, null); else deterministicHashCode = rename.GetDeterministicHashCode(filePath); - if (ffmpegFiles is not null) - { - foreach (string ffmpegFile in ffmpegFiles) - File.Delete(ffmpegFile); - } - exifDirectories.Add(metadata.GetMetadataCollection(_MetadataConfiguration, filePath, deterministicHashCode)); + exifDirectory = metadata.GetMetadataCollection(_MetadataConfiguration, filePath, fileInfo, deterministicHashCode); + exifDirectories.Add(new(file, fileInfo, exifDirectory)); + foreach (string ffmpegFile in ffmpegFiles) + File.Delete(ffmpegFile); } } - private List RenameFilesInDirectories(ILogger? logger, long ticks, DirectoryInfo directoryInfo) + private static ReadOnlyCollection GetExifDirectoryCollection(List<(string, FileInfo, ExifDirectory)> exifDirectories) { - List old = []; + List results = []; + DateTime? dateTime; + foreach ((string file, FileInfo fileInfo, ExifDirectory exifDirectory) in exifDirectories) + { + dateTime = IDate.GetDateTimeOriginal(exifDirectory); + dateTime ??= IDate.GetMinimum(exifDirectory); + results.Add(new(dateTime.Value, exifDirectory, file, fileInfo.FullName)); + } + return new(results); + } + + private ReadOnlyCollection GetExifDirectoryCollection(DirectoryInfo directoryInfo) + { + ReadOnlyCollection results; IRename rename = this; - List exifDirectories = []; - bool runToDoCollectionFirst = GetRunToDoCollectionFirst(ticks, directoryInfo); + List<(string, FileInfo, ExifDirectory)> exifDirectories = []; + int appSettingsMaxDegreeOfParallelism = _AppSettings.MaxDegreeOfParallelism; IEnumerable files = Directory.EnumerateFiles(directoryInfo.FullName, "*", SearchOption.AllDirectories); A_Metadata metadata = new(_MetadataConfiguration, _Configuration.ForceMetadataLastWriteTimeToCreationTime, _Configuration.PropertiesChangedForMetadata); - if (runToDoCollectionFirst) - GetResultCollection(rename, exifDirectories, files, metadata); + if (appSettingsMaxDegreeOfParallelism == 1) + GetExifDirectoryCollection(rename, exifDirectories, files, metadata); else { - ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = _AppSettings.MaxDegreeOfParallelism }; + ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = appSettingsMaxDegreeOfParallelism }; ProgressBar progressBar = new(123000, "EnumerateFiles load", new ProgressBarOptions() { ProgressCharacter = '─', ProgressBarOnBottom = true, DisableBottomPercentage = true }); - files.AsParallel().ForAll(A_Metadata.GetResultCollection(rename, _MetadataConfiguration, metadata, exifDirectories, () => progressBar.Tick())); + files.AsParallel().ForAll(A_Metadata.SetExifDirectoryCollection(rename, _MetadataConfiguration, metadata, exifDirectories, () => progressBar.Tick())); if (progressBar.CurrentTick != exifDirectories.Count) throw new NotSupportedException(); } - return old; + results = GetExifDirectoryCollection(exifDirectories); + return results; + } + + private static void VerifyIntMinValueLength(ReadOnlyCollection exifDirectories, int intMinValueLength) + { + foreach ((DateTime _, ExifDirectory exifDirectory, string _, string _) in exifDirectories) + { + if (exifDirectory.Id is null) + continue; + if (intMinValueLength < exifDirectory.Id.Value.ToString().Length) + throw new NotSupportedException(); + } + } + + private ReadOnlyCollection GetToDoCollection(ILogger? logger, long ticks, ReadOnlyCollection exifDirectories) + { + List results = []; + int season; + Record record; + string paddedId; + string checkFile; + string seasonName; + FileHolder fileHolder; + string? seasonDirectory; + string jsonFileDirectory; + const string jpg = ".jpg"; + string checkFileExtension; + List distinct = []; + const string jpeg = ".jpeg"; + int intMinValueLength = int.MinValue.ToString().Length; + VerifyIntMinValueLength(exifDirectories, intMinValueLength); + ReadOnlyCollection records = new((from l in exifDirectories orderby l.DateTime select l).ToArray()); + for (int i = 0; i < records.Count; i++) + { + record = records[i]; + if (record.ExifDirectory.Id is null) + continue; + fileHolder = new(record.File); + if (fileHolder.DirectoryName is null) + continue; + (season, seasonName) = IDate.GetSeason(record.DateTime.DayOfYear); + jsonFileDirectory = Path.GetDirectoryName(record.JsonFile) ?? throw new Exception(); + checkFileExtension = fileHolder.ExtensionLowered == jpeg ? jpg : fileHolder.ExtensionLowered; + seasonDirectory = Path.Combine(fileHolder.DirectoryName, $"{record.DateTime.Year}.{season} {seasonName}"); + paddedId = IId.GetPaddedId(intMinValueLength, _MetadataConfiguration.Offset + i, record.ExifDirectory.Id.Value); + checkFile = Path.Combine(seasonDirectory, $"{paddedId}{checkFileExtension}"); + if (checkFile == fileHolder.FullName) + continue; + if (File.Exists(checkFile)) + { + checkFile = string.Concat(checkFile, ".del"); + if (File.Exists(checkFile)) + continue; + } + results.Add(new(null, new(record.JsonFile), Path.Combine(jsonFileDirectory, $"{record.ExifDirectory.Id.Value}{checkFileExtension}.json"), JsonFile: true)); + if (distinct.Contains(checkFile)) + continue; + distinct.Add(checkFile); + results.Add(new(seasonDirectory, fileHolder, checkFile, JsonFile: false)); + } + return new(results); + } + + private static void VerifyDirectories(ReadOnlyCollection toDoCollection) + { + List distinct = []; + foreach (ToDo toDo in toDoCollection) + { + if (toDo.Directory is null || distinct.Contains(toDo.Directory)) + continue; + if (!Directory.Exists(toDo.Directory)) + _ = Directory.CreateDirectory(toDo.Directory); + distinct.Add(toDo.Directory); + } + } + + private ReadOnlyCollection RenameFilesInDirectories(ReadOnlyCollection toDoCollection) + { + List results = []; + VerifyDirectories(toDoCollection); + foreach (ToDo toDo in toDoCollection) + { + if (toDo.JsonFile) + File.Move(toDo.FileHolder.FullName, toDo.File); + else if (toDo.Directory is null) + throw new NotSupportedException(); + else + { + File.Move(toDo.FileHolder.FullName, toDo.File); + results.Add($"{toDo.FileHolder.FullName}\t{toDo.File}"); + } + } + return new(results); } } \ No newline at end of file diff --git a/Shared/Models/ExifDirectory.cs b/Shared/Models/ExifDirectory.cs index 802523b..1b7dce1 100644 --- a/Shared/Models/ExifDirectory.cs +++ b/Shared/Models/ExifDirectory.cs @@ -5,14 +5,13 @@ namespace View_by_Distance.Shared.Models; public record ExifDirectory(AviDirectory AviDirectory, ExifDirectoryBase ExifDirectoryBase, - string File, FileMetadataDirectory FileMetadataDirectory, GifHeaderDirectory GifHeaderDirectory, GpsDirectory GpsDirectory, int? Height, int? Id, - string JsonFile, JpegDirectory JpegDirectory, + string OriginalFileName, PhotoshopDirectory PhotoshopDirectory, PngDirectory PngDirectory, QuickTimeMovieHeaderDirectory QuickTimeMovieHeaderDirectory, diff --git a/Shared/Models/Stateless/Methods/IDate.cs b/Shared/Models/Stateless/Methods/IDate.cs index 3b5ffda..0a7d5e6 100644 --- a/Shared/Models/Stateless/Methods/IDate.cs +++ b/Shared/Models/Stateless/Methods/IDate.cs @@ -13,4 +13,14 @@ public interface IDate static (int Season, string seasonName) GetSeason(int dayOfYear) => XDate.GetSeason(dayOfYear); + DateTime? TestStatic_GetDateTimeOriginal(ExifDirectory exifDirectory) => + GetDateTimeOriginal(exifDirectory); + static DateTime? GetDateTimeOriginal(ExifDirectory exifDirectory) => + XDate.GetDateTimeOriginal(exifDirectory); + + DateTime TestStatic_GetMinimum(ExifDirectory exifDirectory) => + GetMinimum(exifDirectory); + static DateTime GetMinimum(ExifDirectory exifDirectory) => + XDate.GetMinimum(exifDirectory); + } \ No newline at end of file diff --git a/Shared/Models/Stateless/Methods/IRename.cs b/Shared/Models/Stateless/Methods/IRename.cs index 0562cd6..28afa5f 100644 --- a/Shared/Models/Stateless/Methods/IRename.cs +++ b/Shared/Models/Stateless/Methods/IRename.cs @@ -1,9 +1,11 @@ +using System.Collections.ObjectModel; + namespace View_by_Distance.Shared.Models.Stateless.Methods; public interface IRename { - string[]? ConvertAndGetFfmpegFiles(FilePath filePath); + (ReadOnlyCollection, FilePath?) ConvertAndGetFfmpegFiles(FilePath filePath); DeterministicHashCode GetDeterministicHashCode(FilePath filePath); } \ No newline at end of file diff --git a/Shared/Models/Stateless/XDate.cs b/Shared/Models/Stateless/XDate.cs index 37e4919..bfd9cfa 100644 --- a/Shared/Models/Stateless/XDate.cs +++ b/Shared/Models/Stateless/XDate.cs @@ -1,3 +1,6 @@ +using System.Globalization; +using System.Text; + namespace View_by_Distance.Shared.Models.Stateless; internal abstract class XDate @@ -47,4 +50,100 @@ internal abstract class XDate return new(result, results); } + internal static DateTime? GetDateTimeOriginal(ExifDirectory exifDirectory) + { + DateTime? result; + List results = []; + if (exifDirectory.ExifDirectoryBase.DateTimeOriginal is not null) + results.Add(exifDirectory.ExifDirectoryBase.DateTimeOriginal.Value); + if (exifDirectory.AviDirectory.DateTimeOriginal is not null) + results.Add(exifDirectory.AviDirectory.DateTimeOriginal.Value); + if (exifDirectory.QuickTimeMovieHeaderDirectory.Created is not null) + results.Add(exifDirectory.QuickTimeMovieHeaderDirectory.Created.Value); + if (exifDirectory.QuickTimeTrackHeaderDirectory.Created is not null) + results.Add(exifDirectory.QuickTimeTrackHeaderDirectory.Created.Value); + result = results.Count == 0 ? null : results.Min(); + return result; + } + + private static DateTime? GetDateTimeFromName(string fileNameWithoutExtension) + { + DateTime? result = null; + int length; + string format; + string fullFormat; + StringBuilder value = new(); + const string ticksExample = "##################"; + string[][] dateFormats = + [ + [string.Empty, "yyyyMMdd_HHmmss", string.Empty], + [string.Empty, "yyyyMMddHHmmssfff", string.Empty], + [string.Empty, "yyyyMMdd_", ticksExample], + [string.Empty, "yyyy-MM-dd_", ticksExample], + [string.Empty, "yyyy-MM-dd.", ticksExample], + // [string.Empty, "yyyy-MM-dd.", $"{ticksExample}.{fileHolder.Length}"], + [string.Empty, "yyyy-MM-dd HH.mm.ss", string.Empty], + [string.Empty, "yyyyMMdd_HHmmss", "_LLS"], + [string.Empty, "yyyyMMdd_HHmmss", "_HDR"], + ["WIN_", "yyyyMMdd_HH_mm_ss", "_Pro"], + ["IMG_", "yyyyMMdd_HHmmss", string.Empty], + ["IMG#####-", "yyyyMMdd-HHmm", string.Empty], + ["CameraZOOM-", "yyyyMMddHHmmss", string.Empty], + ["VideoCapture_", "yyyyMMdd-HHmmss ", string.Empty] + ]; + foreach (string[] dateFormat in dateFormats) + { + _ = value.Clear(); + if (dateFormat.Length != 3) + throw new Exception(); + fullFormat = string.Join(string.Empty, dateFormat); + if (fileNameWithoutExtension.Length != fullFormat.Length) + continue; + format = dateFormat[1]; + length = dateFormat[0].Length + dateFormat[1].Length; + for (int i = dateFormat[0].Length; i < length; i++) + _ = value.Append(fileNameWithoutExtension[i]); + if (value.Length != format.Length) + continue; + if (DateTime.TryParseExact(value.ToString(), format, CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTime checkDateTime)) + { + if (fileNameWithoutExtension.Length < ticksExample.Length || !long.TryParse(fileNameWithoutExtension[^ticksExample.Length..], out long ticks)) + result = checkDateTime; + else + result = new DateTime(ticks); + break; + } + } + return result; + } + + internal static DateTime GetMinimum(ExifDirectory exifDirectory) + { + DateTime result; + List results = []; + if (exifDirectory.ExifDirectoryBase.DateTimeOriginal is not null) + results.Add(exifDirectory.ExifDirectoryBase.DateTimeOriginal.Value); + if (exifDirectory.AviDirectory.DateTimeOriginal is not null) + results.Add(exifDirectory.AviDirectory.DateTimeOriginal.Value); + if (exifDirectory.QuickTimeMovieHeaderDirectory.Created is not null) + results.Add(exifDirectory.QuickTimeMovieHeaderDirectory.Created.Value); + if (exifDirectory.QuickTimeTrackHeaderDirectory.Created is not null) + results.Add(exifDirectory.QuickTimeTrackHeaderDirectory.Created.Value); + if (results.Count == 0) + { + string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(exifDirectory.OriginalFileName); + DateTime? dateTime = GetDateTimeFromName(fileNameWithoutExtension); + if (dateTime is not null) + results.Add(dateTime.Value); + if (exifDirectory.ExifDirectoryBase.DateTime is not null) + results.Add(exifDirectory.ExifDirectoryBase.DateTime.Value); + if (exifDirectory.ExifDirectoryBase.DateTimeDigitized is not null) + results.Add(exifDirectory.ExifDirectoryBase.DateTimeDigitized.Value); + } + if (results.Count == 0 && exifDirectory.FileMetadataDirectory.FileModifiedDate is not null) + results.Add(exifDirectory.FileMetadataDirectory.FileModifiedDate.Value); + result = results.Count == 0 ? DateTime.MinValue : results.Min(); + return result; + } + } \ No newline at end of file