diff --git a/Metadata/Models/A_Metadata.cs b/Metadata/Models/A_Metadata.cs index f3c9c96..9bb74dc 100644 --- a/Metadata/Models/A_Metadata.cs +++ b/Metadata/Models/A_Metadata.cs @@ -147,7 +147,7 @@ public class A_Metadata ReadOnlyCollection ffmpegFiles; DeterministicHashCode deterministicHashCode; FilePath filePath = IId.GetFilePath(renameConfiguration.MetadataConfiguration, file); - if (!renameConfiguration.SkipIdFiles || filePath.Id is null || !filePath.IsIdFormat && !filePath.IsPaddedIdFormat) + if (!renameConfiguration.SkipIdFiles || filePath.Id is null || !filePath.IsIntelligentIdFormat && filePath.SortOrder is not null) { (ffmpegFiles, ffmpegFilePath) = rename.ConvertAndGetFfmpegFiles(renameConfiguration, filePath); if (ffmpegFilePath is not null) diff --git a/Metadata/Models/Binder/MetadataConfiguration.cs b/Metadata/Models/Binder/MetadataConfiguration.cs index 57b0e4b..97b2b48 100644 --- a/Metadata/Models/Binder/MetadataConfiguration.cs +++ b/Metadata/Models/Binder/MetadataConfiguration.cs @@ -9,6 +9,7 @@ public class MetadataConfiguration public bool? ForceMetadataLastWriteTimeToCreationTime { get; set; } public string[]? IgnoreRulesKeyWords { get; set; } + public int? IntMinValueLength { get; set; } public int? Offset { get; set; } public bool? PropertiesChangedForMetadata { get; set; } @@ -38,6 +39,7 @@ public class MetadataConfiguration private static void Verify(MetadataConfiguration configuration) { + if (configuration.IntMinValueLength is null || configuration.IntMinValueLength != int.MinValue.ToString().Length) throw new NotSupportedException(nameof(configuration.IgnoreRulesKeyWords)); if (configuration.IgnoreRulesKeyWords is null || configuration.IgnoreRulesKeyWords.Length == 0) throw new NullReferenceException(nameof(configuration.IgnoreRulesKeyWords)); } @@ -46,6 +48,7 @@ public class MetadataConfiguration Shared.Models.MetadataConfiguration result; if (configuration is null) throw new NullReferenceException(nameof(configuration)); if (configuration.ForceMetadataLastWriteTimeToCreationTime is null) throw new NullReferenceException(nameof(configuration.ForceMetadataLastWriteTimeToCreationTime)); + if (configuration.IntMinValueLength is null) throw new NullReferenceException(nameof(configuration.IntMinValueLength)); if (configuration.IgnoreRulesKeyWords is null) throw new NullReferenceException(nameof(configuration.IgnoreRulesKeyWords)); if (configuration.Offset is null) throw new NullReferenceException(nameof(configuration.Offset)); if (configuration.PropertiesChangedForMetadata is null) throw new NullReferenceException(nameof(configuration.PropertiesChangedForMetadata)); @@ -53,6 +56,7 @@ public class MetadataConfiguration result = new(resultConfiguration, configuration.ForceMetadataLastWriteTimeToCreationTime.Value, configuration.IgnoreRulesKeyWords, + configuration.IntMinValueLength.Value, configuration.Offset.Value, configuration.PropertiesChangedForMetadata.Value); return result; diff --git a/Rename/Rename.cs b/Rename/Rename.cs index 416899d..555b514 100644 --- a/Rename/Rename.cs +++ b/Rename/Rename.cs @@ -54,8 +54,7 @@ public class Rename : IRename else { CommandTask commandTask = Cli.Wrap("ffmpeg.exe") - // .WithArguments(new[] { "-ss", "00:00:00", "-t", "00:00:00", "-i", files[i], "-qScale:v", "2", "-r", "0.01", $"{fileHolder.Name}-%4d.jpg" }) - .WithArguments(new[] { "-i", filePath.FullName, "-vFrames", "1", $"{filePath.Name}-%4d.jpg" }) + .WithArguments(new[] { "-i", filePath.FullName, "-vf", "select=eq(n\\,0)", "-q:v", "1", $"{filePath.Name}-%4d.jpg" }) .WithWorkingDirectory(filePath.DirectoryName) .ExecuteAsync(); commandTask.Task.Wait(); @@ -122,7 +121,7 @@ public class Rename : IRename { rename.Tick(); filePath = IId.GetFilePath(renameConfiguration.MetadataConfiguration, file); - if (renameConfiguration.SkipIdFiles && filePath.Id is not null && (filePath.IsIdFormat || filePath.IsPaddedIdFormat)) + if (renameConfiguration.SkipIdFiles && filePath.Id is not null && (filePath.IsIntelligentIdFormat || filePath.SortOrder is not null)) continue; (ffmpegFiles, ffmpegFilePath) = rename.ConvertAndGetFfmpegFiles(renameConfiguration, filePath); if (ffmpegFilePath is not null) @@ -173,13 +172,13 @@ public class Rename : IRename return results; } - private static void VerifyIntMinValueLength(ReadOnlyCollection exifDirectories, int intMinValueLength) + private static void VerifyIntMinValueLength(MetadataConfiguration metadataConfiguration, ReadOnlyCollection exifDirectories) { foreach ((DateTime _, ExifDirectory exifDirectory, string _, string _) in exifDirectories) { if (exifDirectory.Id is null) continue; - if (intMinValueLength < exifDirectory.Id.Value.ToString().Length) + if (metadataConfiguration.IntMinValueLength < exifDirectory.Id.Value.ToString().Length) throw new NotSupportedException(); } } @@ -189,11 +188,20 @@ public class Rename : IRename string? checkDirectory; if (fileHolder.DirectoryName is null) throw new NullReferenceException(nameof(fileHolder.DirectoryName)); - (int season, string seasonName) = IDate.GetSeason(record.DateTime.DayOfYear); - string maker = IMetadata.GetMaker(record.ExifDirectory.ExifDirectoryBase); - string splat = fileHolder.DirectoryName[^3..][1] == '!' ? fileHolder.DirectoryName[^3..] : string.Empty; + string directoryName; + string year = record.DateTime.Year.ToString(); + string checkDirectoryName = Path.GetFileName(fileHolder.DirectoryName); + if (!checkDirectoryName.Contains(year)) + throw new NotImplementedException(); + else + { + string maker = IMetadata.GetMaker(record.ExifDirectory.ExifDirectoryBase); + (int seasonValue, string seasonName) = IDate.GetSeason(record.DateTime.DayOfYear); + string splat = fileHolder.DirectoryName[^3..][1] == '!' ? fileHolder.DirectoryName[^3..] : string.Empty; + directoryName = $"{year}.{seasonValue} {seasonName} {maker.Split(' ')[0]}{splat}"; + } string rootDirectory = renameConfiguration.MoveFilesToRoot ? renameConfiguration.MetadataConfiguration.ResultConfiguration.RootDirectory : fileHolder.DirectoryName; - checkDirectory = Path.Combine(rootDirectory, "_ Destination _", $"{record.DateTime.Year}.{season} {seasonName} {maker.Split(' ')[0]}{splat}"); + checkDirectory = Path.Combine(rootDirectory, "_ Destination _", directoryName); return checkDirectory; } @@ -212,8 +220,7 @@ public class Rename : IRename List distinct = []; const string jpeg = ".jpeg"; string jsonFileSubDirectory; - int intMinValueLength = int.MinValue.ToString().Length; - VerifyIntMinValueLength(exifDirectories, intMinValueLength); + VerifyIntMinValueLength(renameConfiguration.MetadataConfiguration, exifDirectories); ResultConfiguration resultConfiguration = renameConfiguration.MetadataConfiguration.ResultConfiguration; ReadOnlyCollection records = new((from l in exifDirectories orderby l.DateTime select l).ToArray()); for (int i = 0; i < records.Count; i++) @@ -226,8 +233,8 @@ public class Rename : IRename continue; checkDirectory = GetCheckDirectory(renameConfiguration, record, fileHolder); checkFileExtension = fileHolder.ExtensionLowered == jpeg ? jpg : fileHolder.ExtensionLowered; + paddedId = IId.GetPaddedId(renameConfiguration.MetadataConfiguration, i, record.ExifDirectory.Id.Value); jsonFileSubDirectory = Path.GetDirectoryName(Path.GetDirectoryName(record.JsonFile)) ?? throw new Exception(); - paddedId = IId.GetPaddedId(intMinValueLength, renameConfiguration.MetadataConfiguration.Offset + i, record.ExifDirectory.Id.Value); checkFile = Path.Combine(checkDirectory, $"{paddedId}{checkFileExtension}"); if (checkFile == fileHolder.FullName) continue; diff --git a/Shared/Models/FilePath.cs b/Shared/Models/FilePath.cs index ef6d92a..ef43f1c 100644 --- a/Shared/Models/FilePath.cs +++ b/Shared/Models/FilePath.cs @@ -5,12 +5,13 @@ namespace View_by_Distance.Shared.Models; public record FilePath(string DirectoryName, string ExtensionLowered, + string FileNameFirstSegment, string FullName, int? Id, - bool IsIdFormat, - bool IsPaddedIdFormat, + bool IsIntelligentIdFormat, string Name, - string NameWithoutExtension) + string NameWithoutExtension, + int? SortOrder) { public override string ToString() diff --git a/Shared/Models/IntelligentIdRecord.cs b/Shared/Models/IntelligentIdRecord.cs new file mode 100644 index 0000000..35e7bb2 --- /dev/null +++ b/Shared/Models/IntelligentIdRecord.cs @@ -0,0 +1,24 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace View_by_Distance.Shared.Models; + +public record IntelligentIdRecord(int Key, + char GroupChar2, + char GroupChar1, + string Reverse) +{ + + public override string ToString() + { + string result = JsonSerializer.Serialize(this, IntelligentIdRecordSourceGenerationContext.Default.IntelligentIdRecord); + return result; + } + +} + +[JsonSourceGenerationOptions(WriteIndented = true)] +[JsonSerializable(typeof(IntelligentIdRecord))] +internal partial class IntelligentIdRecordSourceGenerationContext : JsonSerializerContext +{ +} \ No newline at end of file diff --git a/Shared/Models/MetadataConfiguration.cs b/Shared/Models/MetadataConfiguration.cs index 30c9407..1896087 100644 --- a/Shared/Models/MetadataConfiguration.cs +++ b/Shared/Models/MetadataConfiguration.cs @@ -6,6 +6,7 @@ namespace View_by_Distance.Shared.Models; public record MetadataConfiguration(ResultConfiguration ResultConfiguration, bool ForceMetadataLastWriteTimeToCreationTime, string[] IgnoreRulesKeyWords, + int IntMinValueLength, int Offset, bool PropertiesChangedForMetadata) { diff --git a/Shared/Models/Stateless/Id.cs b/Shared/Models/Stateless/Id.cs index 19b8da2..cb0fb61 100644 --- a/Shared/Models/Stateless/Id.cs +++ b/Shared/Models/Stateless/Id.cs @@ -1,3 +1,4 @@ +using System.Text; using View_by_Distance.Shared.Models.Stateless.Methods; namespace View_by_Distance.Shared.Models.Stateless; @@ -5,26 +6,59 @@ namespace View_by_Distance.Shared.Models.Stateless; internal abstract class Id { - internal static bool NameWithoutExtensionIsIdFormat(string fileNameWithoutExtension) + private static int GetId(MetadataConfiguration metadataConfiguration, string intelligentId) { - bool result; - int intMinValueLength = int.MinValue.ToString().Length; - if (fileNameWithoutExtension.Length < 5 || fileNameWithoutExtension.Length > intMinValueLength) - result = false; - else - { - bool skipOneAllAreNumbers = fileNameWithoutExtension[1..].All(l => char.IsNumber(l)); - result = (skipOneAllAreNumbers && fileNameWithoutExtension[0] == '-') || (skipOneAllAreNumbers && char.IsNumber(fileNameWithoutExtension[0])); - } + int result; + StringBuilder results = new(); + if (metadataConfiguration.IntMinValueLength < 4) + throw new NotSupportedException(); + for (int j = intelligentId.Length - 4; j > -1; j--) + _ = results.Append(intelligentId[j]); + _ = results.Append(intelligentId[^3]).Append(intelligentId[^2]); + result = int.Parse(results.ToString()); + if (intelligentId[^1] is '1' or '2') + result *= -1; + else if (intelligentId[^1] is not '9' and not '8') + throw new NotSupportedException(); return result; } - internal static FilePath GetFilePath(FilePath filePath, string file) + private static IntelligentIdRecord GetIntelligentIdRecord(MetadataConfiguration metadataConfiguration, long id, bool ignore) { - FilePath result; - string fileName = Path.GetFileName(file); - string fileExtensionLowered = Path.GetExtension(file).ToLower(); - result = new(filePath.DirectoryName, fileExtensionLowered, file, filePath.Id, filePath.IsIdFormat, filePath.IsPaddedIdFormat, fileName, filePath.NameWithoutExtension); + IntelligentIdRecord result; + StringBuilder stringBuilder = new(); + if (metadataConfiguration.IntMinValueLength < 4) + throw new NotSupportedException(); + int key; + string value; + if (id > -1) + { + key = ignore ? 8 : 9; + value = id.ToString().PadLeft(metadataConfiguration.IntMinValueLength, '0'); + } + else + { + key = ignore ? 2 : 1; + value = id.ToString()[1..].PadLeft(metadataConfiguration.IntMinValueLength, '0'); + } + for (int j = value.Length - 3; j > -1; j--) + _ = stringBuilder.Append(value[j]); + result = new(key, value[^2], value[^1], stringBuilder.ToString()); + return result; + } + + private static string GetIntelligentId(IntelligentIdRecord intelligentId) => + $"{intelligentId.Reverse}{intelligentId.GroupChar2}{intelligentId.GroupChar1}{intelligentId.Key}"; + + internal static string GetPaddedId(MetadataConfiguration metadataConfiguration, int index, int id) + { + string result; + IntelligentIdRecord intelligentIdRecord = GetIntelligentIdRecord(metadataConfiguration, id, ignore: false); + string intelligentId = GetIntelligentId(intelligentIdRecord); + int check = GetId(metadataConfiguration, intelligentId); + if (check != id) + throw new NotSupportedException(); + result = $"{metadataConfiguration.Offset + index}{intelligentId}"; return result; } @@ -32,44 +66,30 @@ internal abstract class Id { FilePath result; int? id; - short? multiplier; - char negativeMarker; - int absoluteValueOfId; + int? sortOder; string fileName = Path.GetFileName(file); + string[] segments = Path.GetFileName(fileName).Split('.'); string fileExtensionLowered = Path.GetExtension(file).ToLower(); string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(file); string fileDirectoryName = Path.GetDirectoryName(file) ?? throw new NullReferenceException(); - short sortOrderOnlyLengthIndex = IId.GetSortOrderOnlyLengthIndex(metadataConfiguration.Offset); - bool nameWithoutExtensionIsIdFormat = IId.NameWithoutExtensionIsIdFormat(fileNameWithoutExtension); - bool nameWithoutExtensionIsPaddedIdFormat = IId.NameWithoutExtensionIsPaddedIdFormat(fileNameWithoutExtension, sortOrderOnlyLengthIndex); - if (!nameWithoutExtensionIsIdFormat && !nameWithoutExtensionIsPaddedIdFormat) - id = null; - else if (nameWithoutExtensionIsIdFormat) + short sortOrderOnlyLengthIndex = IId.GetSortOrderOnlyLengthIndex(metadataConfiguration); + string fileNameFirstSegment = segments[0]; + bool fileNameFirstSegmentIsIntelligentIdFormat = IId.NameWithoutExtensionIsIntelligentIdFormat(metadataConfiguration, fileNameFirstSegment); + bool fileNameFirstSegmentIsPaddedIntelligentIdFormat = IId.NameWithoutExtensionIsPaddedIntelligentIdFormat(metadataConfiguration, sortOrderOnlyLengthIndex, fileNameFirstSegment); + if (!fileNameFirstSegmentIsIntelligentIdFormat && !fileNameFirstSegmentIsPaddedIntelligentIdFormat) + (id, sortOder) = (null, null); + else if (fileNameFirstSegmentIsIntelligentIdFormat) + (id, sortOder) = (GetId(metadataConfiguration, fileNameFirstSegment), null); + else if (fileNameFirstSegmentIsPaddedIntelligentIdFormat) { - if (!int.TryParse(fileNameWithoutExtension, out absoluteValueOfId)) - id = null; + if (!int.TryParse(fileNameFirstSegment[..sortOrderOnlyLengthIndex], out int absoluteValueOfSortOrder)) + (id, sortOder) = (null, null); else - id = absoluteValueOfId; + (id, sortOder) = (GetId(metadataConfiguration, fileNameFirstSegment[sortOrderOnlyLengthIndex..]), absoluteValueOfSortOrder); } else - { - negativeMarker = fileNameWithoutExtension[sortOrderOnlyLengthIndex - 2]; - if (negativeMarker == '7') - multiplier = 1; - else if (negativeMarker == '3') - multiplier = -1; - else - multiplier = null; - if (!int.TryParse(fileNameWithoutExtension[sortOrderOnlyLengthIndex..], out absoluteValueOfId)) - id = null; - else - { - id = absoluteValueOfId * multiplier; - if (id is null || !fileNameWithoutExtension.EndsWith(id.Value.ToString()[1..])) - id = null; - } - } - result = new(fileDirectoryName, fileExtensionLowered, file, id, nameWithoutExtensionIsIdFormat, nameWithoutExtensionIsPaddedIdFormat, fileName, fileNameWithoutExtension); + throw new NotSupportedException(); + result = new(fileDirectoryName, fileExtensionLowered, fileNameFirstSegment, file, id, fileNameFirstSegmentIsIntelligentIdFormat, fileName, fileNameWithoutExtension, sortOder); return result; } diff --git a/Shared/Models/Stateless/Methods/IId.cs b/Shared/Models/Stateless/Methods/IId.cs index 507cdfd..80dc2b3 100644 --- a/Shared/Models/Stateless/Methods/IId.cs +++ b/Shared/Models/Stateless/Methods/IId.cs @@ -3,38 +3,32 @@ namespace View_by_Distance.Shared.Models.Stateless.Methods; public interface IId { - bool TestStatic_NameWithoutExtensionIsIdFormat(string fileNameWithoutExtension) => - NameWithoutExtensionIsIdFormat(fileNameWithoutExtension); - static bool NameWithoutExtensionIsIdFormat(string fileNameWithoutExtension) => - Id.NameWithoutExtensionIsIdFormat(fileNameWithoutExtension); + string TestStatic_GetIntelligentId(IntelligentIdRecord intelligentId) => + GetIntelligentId(intelligentId); + static string GetIntelligentId(IntelligentIdRecord intelligentId) => + $"{intelligentId.Reverse}{intelligentId.GroupChar2}{intelligentId.GroupChar1}{intelligentId.Key}"; - bool TestStatic_NameWithoutExtensionIsIdFormat(FileHolder fileHolder) => - NameWithoutExtensionIsIdFormat(fileHolder); - static bool NameWithoutExtensionIsIdFormat(FileHolder fileHolder) => - NameWithoutExtensionIsIdFormat(fileHolder.NameWithoutExtension); + string TestStatic_GetPaddedId(MetadataConfiguration metadataConfiguration, int index, int id) => + GetPaddedId(metadataConfiguration, index, id); + static string GetPaddedId(MetadataConfiguration metadataConfiguration, int index, int id) => + Id.GetPaddedId(metadataConfiguration, index, id); - string TestStatic_GetPaddedId(int intMinValueLength, int index, int id) => - GetPaddedId(intMinValueLength, index, id); - static string GetPaddedId(int intMinValueLength, int index, int id) => - id > -1 ? $"{index}070{id.ToString().PadLeft(intMinValueLength, '0')}" : $"{index}030{id.ToString()[1..].PadLeft(intMinValueLength, '0')}"; + bool TestStatic_NameWithoutExtensionIsIntelligentIdFormat(MetadataConfiguration metadataConfiguration, string fileNameFirstSegment) => + NameWithoutExtensionIsIntelligentIdFormat(metadataConfiguration, fileNameFirstSegment); + static bool NameWithoutExtensionIsIntelligentIdFormat(MetadataConfiguration metadataConfiguration, string fileNameFirstSegment) => + fileNameFirstSegment.Length - 1 == metadataConfiguration.IntMinValueLength && fileNameFirstSegment[^1] is '1' or '2' or '8' or '9' && fileNameFirstSegment.All(char.IsNumber); - bool TestStatic_NameWithoutExtensionIsPaddedIdFormat(string fileNameWithoutExtension, short sortOrderOnlyLengthIndex) => - NameWithoutExtensionIsPaddedIdFormat(fileNameWithoutExtension, sortOrderOnlyLengthIndex); - static bool NameWithoutExtensionIsPaddedIdFormat(string fileNameWithoutExtension, short sortOrderOnlyLengthIndex) => - fileNameWithoutExtension.Length > sortOrderOnlyLengthIndex - && fileNameWithoutExtension[sortOrderOnlyLengthIndex] == '0' - && fileNameWithoutExtension[sortOrderOnlyLengthIndex - 3] == '0' - && fileNameWithoutExtension.All(l => char.IsNumber(l)); + bool TestStatic_NameWithoutExtensionIsPaddedIntelligentIdFormat(MetadataConfiguration metadataConfiguration, short sortOrderOnlyLengthIndex, string fileNameFirstSegment) => + NameWithoutExtensionIsPaddedIntelligentIdFormat(metadataConfiguration, sortOrderOnlyLengthIndex, fileNameFirstSegment); + static bool NameWithoutExtensionIsPaddedIntelligentIdFormat(MetadataConfiguration metadataConfiguration, short sortOrderOnlyLengthIndex, string fileNameFirstSegment) => + fileNameFirstSegment.Length == metadataConfiguration.IntMinValueLength + sortOrderOnlyLengthIndex + 1 + && fileNameFirstSegment[^1] is '1' or '2' or '8' or '9' + && fileNameFirstSegment.All(char.IsNumber); - short TestStatic_GetSortOrderOnlyLengthIndex(int offset) => - GetSortOrderOnlyLengthIndex(offset); - static short GetSortOrderOnlyLengthIndex(int offset) => - (short)(offset.ToString().Length + 3); - - FilePath TestStatic_GetFilePath(FilePath filePath, string file) => - GetFilePath(filePath, file); - static FilePath GetFilePath(FilePath filePath, string file) => - Id.GetFilePath(filePath, file); + short TestStatic_GetSortOrderOnlyLengthIndex(MetadataConfiguration metadataConfiguration) => + GetSortOrderOnlyLengthIndex(metadataConfiguration); + static short GetSortOrderOnlyLengthIndex(MetadataConfiguration metadataConfiguration) => + (short)metadataConfiguration.Offset.ToString().Length; FilePath TestStatic_GetFilePath(MetadataConfiguration metadataConfiguration, string file) => GetFilePath(metadataConfiguration, file);